/////////////////////////////////////////// // plic.sv // // Written: bbracker@hmc.edu 18 January 2021 // Modified: // // Purpose: Platform-Level Interrupt Controller // Based on RISC-V spec (https://github.com/riscv/riscv-plic-spec/blob/master/riscv-plic.adoc) // With clarifications from ROA's existing implementation (https://roalogic.github.io/plic/docs/AHB-Lite_PLIC_Datasheet.pdf) // Supports only 1 target core and only a global threshold. // // *** Big questions: // Do we detect requests as level-triggered or edge-trigged? // If edge-triggered, do we want to allow 1 source to be able to make a number of repeated requests? // // A component of the Wally configurable RISC-V project. // // Copyright (C) 2021 Harvey Mudd College & Oklahoma State University // // MIT LICENSE // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS // BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE // OR OTHER DEALINGS IN THE SOFTWARE. //////////////////////////////////////////////////////////////////////////////////////////////// `include "wally-config.vh" `define N `PLIC_NUM_SRC // number of interrupt sources // does not include source 0, which does not connect to anything according to spec // up to 63 sources supported; *** in the future, allow up to 1023 sources `define C 2 // number of conexts // hardcoded to 2 contexts for now; *** later upgrade to arbitrary (up to 15872) contexts module plic ( input logic HCLK, HRESETn, input logic HSELPLIC, input logic [27:0] HADDR, // *** could factor out entryd into HADDRd at the level of uncore input logic HWRITE, input logic HREADY, input logic [1:0] HTRANS, input logic [`XLEN-1:0] HWDATA, input logic UARTIntr,GPIOIntr, output logic [`XLEN-1:0] HREADPLIC, output logic HRESPPLIC, HREADYPLIC, output logic MExtIntM, SExtIntM); logic memwrite, memread, initTrans; logic [23:0] entry, entryd; logic [31:0] Din, Dout; // context-independent signals logic [`N:1] requests; logic [`N:1][2:0] intPriority; logic [`N:1] intInProgress, intPending, nextIntPending; // context-dependent signals logic [`C-1:0][2:0] intThreshold; logic [`C-1:0][`N:1] intEn; logic [`C-1:0][5:0] intClaim; // ID's are 6 bits if we stay within 63 sources logic [`C-1:0][7:1][`N:1] irqMatrix; logic [`C-1:0][7:1] priorities_with_irqs; logic [`C-1:0][7:1] max_priority_with_irqs; logic [`C-1:0][`N:1] irqs_at_max_priority; logic [`C-1:0][7:1] threshMask; // ======= // AHB I/O // ======= assign entry = {HADDR[23:2],2'b0}; assign initTrans = HREADY & HSELPLIC & (HTRANS != 2'b00); assign memread = initTrans & ~HWRITE; // entryd and memwrite are delayed by a cycle because AHB controller waits a cycle before outputting write data flopr #(1) memwriteflop(HCLK, ~HRESETn, initTrans & HWRITE, memwrite); flopr #(24) entrydflop(HCLK, ~HRESETn, entry, entryd); assign HRESPPLIC = 0; // OK assign HREADYPLIC = 1'b1; // PLIC never takes >1 cycle to respond // account for subword read/write circuitry // -- Note PLIC registers are 32 bits no matter what; access them with LW SW. if (`XLEN == 64) begin assign Din = entryd[2] ? HWDATA[63:32] : HWDATA[31:0]; assign HREADPLIC = entryd[2] ? {Dout,32'b0} : {32'b0,Dout}; end else begin // 32-bit assign HREADPLIC = Dout; assign Din = HWDATA[31:0]; end // ================== // Register Interface // ================== always @(posedge HCLK,negedge HRESETn) begin // resetting if (~HRESETn) begin intPriority <= #1 {`N{3'b0}}; intEn <= #1 {2{`N'b0}}; intThreshold <= #1 {2{3'b0}}; intInProgress <= #1 `N'b0; // writing end else begin if (memwrite) casez(entryd) 24'h0000??: intPriority[entryd[7:2]] <= #1 Din[2:0]; `ifdef PLIC_NUM_SRC_LT_32 // *** switch to a generate for loop so as to deprecate PLIC_NUM_SRC_LT_32 and allow up to 1023 sources 24'h002000: intEn[0][`N:1] <= #1 Din[`N:1]; 24'h002080: intEn[1][`N:1] <= #1 Din[`N:1]; `endif `ifndef PLIC_NUM_SRC_LT_32 24'h002000: intEn[0][31:1] <= #1 Din[31:1]; 24'h002004: intEn[0][`N:32] <= #1 Din[31:0]; 24'h002080: intEn[1][31:1] <= #1 Din[31:1]; 24'h002084: intEn[1][`N:32] <= #1 Din[31:0]; `endif 24'h200000: intThreshold[0] <= #1 Din[2:0]; 24'h200004: intInProgress <= #1 intInProgress & ~(`N'b1 << (Din[5:0]-1)); // lower "InProgress" to signify completion 24'h201000: intThreshold[1] <= #1 Din[2:0]; 24'h201004: intInProgress <= #1 intInProgress & ~(`N'b1 << (Din[5:0]-1)); // lower "InProgress" to signify completion endcase // reading if (memread) casez(entry) 24'h0000??: Dout <= #1 {29'b0,intPriority[entry[7:2]]}; `ifdef PLIC_NUM_SRC_LT_32 24'h001000: Dout <= #1 {{(31-`N){1'b0}},intPending,1'b0}; 24'h002000: Dout <= #1 {{(31-`N){1'b0}},intEn[0],1'b0}; 24'h002080: Dout <= #1 {{(31-`N){1'b0}},intEn[1],1'b0}; `endif `ifndef PLIC_NUM_SRC_LT_32 24'h001000: Dout <= #1 {intPending[31:1],1'b0}; 24'h001004: Dout <= #1 {{(63-`N){1'b0}},intPending[`N:32]}; 24'h002000: Dout <= #1 {intEn[0][31:1],1'b0}; 24'h002004: Dout <= #1 {{(63-`N){1'b0}},intEn[0][`N:32]}; 24'h002080: Dout <= #1 {intEn[0][31:1],1'b0}; 24'h002084: Dout <= #1 {{(63-`N){1'b0}},intEn[1][`N:32]}; `endif 24'h200000: Dout <= #1 {29'b0,intThreshold[0]}; 24'h200004: begin Dout <= #1 {26'b0,intClaim[0]}; intInProgress <= #1 intInProgress | (`N'b1 << (intClaim[0]-1)); // claimed requests are currently in progress of being serviced until they are completed end 24'h201000: Dout <= #1 {29'b0,intThreshold[1]}; 24'h201004: begin Dout <= #1 {26'b0,intClaim[1]}; intInProgress <= #1 intInProgress | (`N'b1 << (intClaim[1]-1)); // claimed requests are currently in progress of being serviced until they are completed end default: Dout <= #1 32'h0; // invalid access endcase else Dout <= #1 32'h0; end end // connect sources to requests always_comb begin requests = `N'b0; `ifdef PLIC_GPIO_ID requests[`PLIC_GPIO_ID] = GPIOIntr; `endif `ifdef PLIC_UART_ID requests[`PLIC_UART_ID] = UARTIntr; `endif end // pending interrupt requests assign nextIntPending = (intPending | // existing pending requests (requests & ~intInProgress)) & // assert new requests (if they aren't already being serviced) ~({`N{((entry == 24'h200004) & memread)}} << (intClaim[0]-1)) & // deassert requests that just completed ~({`N{((entry == 24'h201004) & memread)}} << (intClaim[1]-1)); flopr #(`N) intPendingFlop(HCLK,~HRESETn,nextIntPending,intPending); // context-dependent signals genvar ctx; for (ctx=0; ctx<`C; ctx++) begin // request matrix // priority level (rows) X source ID (columns) // // irqMatrix[ctx][pri][src] is high if source // has priority level and has an "active" interrupt request // ("active" meaning it is enabled in context and is pending) genvar src, pri; for (pri=1; pri<=7; pri++) begin for (src=1; src<=`N; src++) begin assign irqMatrix[ctx][pri][src] = (intPriority[src]==pri) & intPending[src] & intEn[ctx][src]; end end // which prority levels have one or more active requests? assign priorities_with_irqs[ctx][7:1] = { |irqMatrix[ctx][7], |irqMatrix[ctx][6], |irqMatrix[ctx][5], |irqMatrix[ctx][4], |irqMatrix[ctx][3], |irqMatrix[ctx][2], |irqMatrix[ctx][1] }; // get the highest priority level that has active requests assign max_priority_with_irqs[ctx][7:1] = { priorities_with_irqs[ctx][7], priorities_with_irqs[ctx][6] & ~|priorities_with_irqs[ctx][7], priorities_with_irqs[ctx][5] & ~|priorities_with_irqs[ctx][7:6], priorities_with_irqs[ctx][4] & ~|priorities_with_irqs[ctx][7:5], priorities_with_irqs[ctx][3] & ~|priorities_with_irqs[ctx][7:4], priorities_with_irqs[ctx][2] & ~|priorities_with_irqs[ctx][7:3], priorities_with_irqs[ctx][1] & ~|priorities_with_irqs[ctx][7:2] }; // of the sources at the highest priority level that has active requests, // which sources have active requests? assign irqs_at_max_priority[ctx][`N:1] = ({`N{max_priority_with_irqs[ctx][7]}} & irqMatrix[ctx][7]) | ({`N{max_priority_with_irqs[ctx][6]}} & irqMatrix[ctx][6]) | ({`N{max_priority_with_irqs[ctx][5]}} & irqMatrix[ctx][5]) | ({`N{max_priority_with_irqs[ctx][4]}} & irqMatrix[ctx][4]) | ({`N{max_priority_with_irqs[ctx][3]}} & irqMatrix[ctx][3]) | ({`N{max_priority_with_irqs[ctx][2]}} & irqMatrix[ctx][2]) | ({`N{max_priority_with_irqs[ctx][1]}} & irqMatrix[ctx][1]); // of the sources at the highest priority level that has active requests, // choose the source with the lowest source ID to be the most urgent // and set intClaim to the source ID of the most urgent active request integer k; always_comb begin intClaim[ctx] = 6'b0; for (k=`N; k>0; k--) begin if (irqs_at_max_priority[ctx][k]) intClaim[ctx] = k[5:0]; end end // create threshold mask always_comb begin threshMask[ctx][7] = (intThreshold[ctx] != 7); threshMask[ctx][6] = (intThreshold[ctx] != 6) & threshMask[ctx][7]; threshMask[ctx][5] = (intThreshold[ctx] != 5) & threshMask[ctx][6]; threshMask[ctx][4] = (intThreshold[ctx] != 4) & threshMask[ctx][5]; threshMask[ctx][3] = (intThreshold[ctx] != 3) & threshMask[ctx][4]; threshMask[ctx][2] = (intThreshold[ctx] != 2) & threshMask[ctx][3]; threshMask[ctx][1] = (intThreshold[ctx] != 1) & threshMask[ctx][2]; end end // is the max priority > threshold? // *** would it be any better to first priority encode maxPriority into binary and then ">" with threshold? assign MExtIntM = |(threshMask[0] & priorities_with_irqs[0]); assign SExtIntM = |(threshMask[1] & priorities_with_irqs[1]); endmodule