2021-03-22 14:14:21 +00:00
///////////////////////////////////////////
// plic.sv
//
// Written: bbracker@hmc.edu 18 January 2021
// Modified:
//
// Purpose: Platform-Level Interrupt Controller
2021-04-04 10:40:53 +00:00
// 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.
2021-03-22 14:14:21 +00:00
//
2021-04-04 10:40:53 +00:00
// *** 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?
// Should PLIC also output SEIP or just MEIP?
// MEIP is the same as ExtIntM, right?
//
2021-03-22 14:14:21 +00:00
// A component of the Wally configurable RISC-V project.
//
// Copyright (C) 2021 Harvey Mudd College & Oklahoma State University
//
// 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 "
module plic (
input logic HCLK , HRESETn ,
input logic HSELPLIC ,
input logic [ 27 : 0 ] HADDR ,
input logic HWRITE ,
2021-04-04 10:40:53 +00:00
input logic HREADY ,
input logic [ 1 : 0 ] HTRANS ,
2021-03-22 14:14:21 +00:00
input logic [ `XLEN - 1 : 0 ] HWDATA ,
2021-04-04 10:40:53 +00:00
input logic UARTIntr ,
2021-03-22 14:14:21 +00:00
output logic [ `XLEN - 1 : 0 ] HREADPLIC ,
2021-04-04 10:40:53 +00:00
output logic HRESPPLIC , HREADYPLIC ,
output logic ExtIntM ) ;
// N in config should not exceed 63; does not inlcude source 0, which does not connect to anything according to spec
localparam N = `PLIC_NUM_SRC ;
2021-03-22 14:14:21 +00:00
2021-04-04 10:40:53 +00:00
logic memread , memwrite , initTrans ;
logic [ 27 : 0 ] entry , A ;
logic [ N: 1 ] requests ;
logic [ 2 : 0 ] intPriority [ N: 1 ] ;
2021-03-22 14:14:21 +00:00
logic [ 2 : 0 ] intThreshold ;
2021-04-04 10:40:53 +00:00
logic [ N: 1 ] intPending , nextIntPending , intEn , intInProgress ;
logic [ 5 : 0 ] intClaim ; // ID's are 6 bits if we stay within 63 sources
logic [ N: 1 ] pendingArray [ 7 : 1 ] ;
logic [ 7 : 1 ] pendingPGrouped ;
logic [ 7 : 1 ] pendingMaxP ;
logic [ N: 1 ] pendingRequestsAtMaxP ;
logic [ 7 : 1 ] threshMask ;
2021-03-22 14:14:21 +00:00
// AHB I/O
2021-04-04 10:40:53 +00:00
assign initTrans = HREADY & HSELPLIC & ( HTRANS ! = 2 'b00 ) ;
flopenrc # ( 1 ) memreadreg ( HCLK , ~ HRESETn , memread & HREADY , ( memread & HREADY ) | initTrans , HSELPLIC & ~ HWRITE , memread ) ;
flopenrc # ( 1 ) memwritereg ( HCLK , ~ HRESETn , memwrite & HREADY , ( memwrite & HREADY ) | initTrans , HSELPLIC & HWRITE , memwrite ) ;
flopenr # ( 28 ) haddrreg ( HCLK , ~ HRESETn , initTrans , HADDR , A ) ;
2021-03-22 14:14:21 +00:00
assign HRESPPLIC = 0 ; // OK
assign HREADYPLIC = 1 'b1 ; // will need to be modified if PLIC ever needs more than 1 cycle to do something
2021-04-04 10:40:53 +00:00
2021-03-22 14:14:21 +00:00
// word aligned reads
2021-04-04 10:40:53 +00:00
assign # 2 entry = { A [ 27 : 2 ] , 2 'b00 } ;
// register access
genvar i ;
2021-03-22 14:14:21 +00:00
generate
2021-04-04 10:40:53 +00:00
// priority registers
for ( i = 1 ; i < = N ; i = i + 1 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn )
intPriority [ i ] < = 3 'b0 ;
else if ( entry = = 28 'hc000000 + 4 * i ) // *** make sure this does not synthesize into N 28-bit equality comparators
if ( memwrite ) intPriority [ i ] < = # 1 HWDATA [ 2 : 0 ] ;
else HREADPLIC < = # 1 { { ( `XLEN - 3 ) { 1 'b0 } } , intPriority [ i ] } ;
// pending and enable registers
if ( N < 32 & & `XLEN = = 32 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn )
intEn < = { N { 1 'b0 } } ;
else
case ( entry )
28 'hc001000 : HREADPLIC < = # 1 { { ( 31 - N ) { 1 'b0 } } , intPending [ N: 1 ] , 1 'b0 } ;
28 'hc002000 : if ( memwrite ) intEn [ N: 1 ] < = # 1 HWDATA [ N: 1 ] ;
else HREADPLIC < = # 1 { { ( 31 - N ) { 1 'b0 } } , intEn [ N: 1 ] , 1 'b0 } ;
endcase
else if ( N > = 32 & & `XLEN = = 32 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn )
intEn < = { N { 1 'b0 } } ;
else
case ( entry )
28 'hc001000 : HREADPLIC < = # 1 { intPending [ 31 : 1 ] , 1 'b0 } ;
28 'hc001004 : HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intPending [ N: 32 ] } ;
28 'hc002000 : if ( memwrite ) intEn [ 31 : 1 ] < = # 1 HWDATA [ 31 : 1 ] ;
else HREADPLIC < = # 1 { intEn [ 31 : 1 ] , 1 'b0 } ;
28 'hc002004 : if ( memwrite ) intEn [ N: 32 ] < = # 1 HWDATA [ 31 : 0 ] ;
else HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intEn [ N: 32 ] } ;
endcase
else if ( N < 32 & & `XLEN = = 64 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn )
intEn < = { N { 1 'b0 } } ;
else
case ( entry )
28 'hc001000 : HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intPending [ N: 1 ] , 1 'b0 } ;
28 'hc002000 : if ( memwrite ) intEn [ N: 1 ] < = # 1 HWDATA [ N: 1 ] ;
else HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intEn [ N: 1 ] , 1 'b0 } ;
endcase
else if ( N > = 32 & & `XLEN = = 64 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn )
intEn < = { N { 1 'b0 } } ;
else
case ( entry )
28 'hc001000 : HREADPLIC < = # 1 { 32 'b0 , intPending [ 31 : 1 ] , 1 'b0 } ;
28 'hc001004 : HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intPending [ N: 32 ] , 32 'b0 } ; // rearranged so that you can access it with lw (when addr%8 = 4, subwordwrite thinks we are looking at the upper half of a 64bit word); *** is this reasonable? Why does SWW work like that anyways?; if we don't mind 32 and 64 bit versions having different memory maps, that might clean things up, but it might also be a departure of spec
28 'hc002000 : if ( memwrite ) intEn [ 31 : 1 ] < = # 1 HWDATA [ 31 : 1 ] ;
else HREADPLIC < = # 1 { intEn [ 31 : 1 ] , 1 'b0 } ;
28 'hc002004 : if ( memwrite ) intEn [ N: 32 ] < = # 1 HWDATA [ 63 : 32 ] ;
else HREADPLIC < = # 1 { { ( 63 - N ) { 1 'b0 } } , intEn [ N: 32 ] , 32 'b0 } ;
endcase
// threshold and claim/complete registers
if ( `XLEN = = 32 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn ) begin
intThreshold < = 3 'b0 ;
intInProgress < = { N { 1 'b0 } } ;
end else
case ( HADDR )
28 'hc200000 : if ( memwrite ) intThreshold [ 2 : 0 ] < = # 1 HWDATA [ 2 : 0 ] ;
else HREADPLIC < = # 1 { { 29 { 1 'b0 } } , intThreshold [ 2 : 0 ] } ;
28 'hc200004 : if ( memwrite ) intInProgress < = # 1 intInProgress & ~ ( 1 'b1 < < ( HWDATA [ 5 : 0 ] - 1 ) ) ; // lower "InProgress" to signify completion
else begin
HREADPLIC < = # 1 { { 26 { 1 'b0 } } , intClaim } ;
intInProgress < = # 1 intInProgress | ( 1 'b1 < < ( intClaim - 1 ) ) ; // claimed requests are currently in progress of being serviced until they are completed
end
endcase
else if ( `XLEN = = 64 )
always @ ( posedge HCLK , negedge HRESETn )
if ( ~ HRESETn ) begin
intThreshold < = 3 'b0 ;
intInProgress < = { N { 1 'b0 } } ;
end else
case ( HADDR )
28 'hc200000 : if ( memwrite ) intThreshold [ 2 : 0 ] < = # 1 HWDATA [ 2 : 0 ] ;
else HREADPLIC < = # 1 { { 61 { 1 'b0 } } , intThreshold [ 2 : 0 ] } ;
28 'hc200004 : if ( memwrite ) intInProgress < = # 1 intInProgress & ~ ( 1 'b1 < < ( HWDATA [ 5 : 0 ] - 1 ) ) ; // lower "InProgress" to signify completion
else begin
HREADPLIC < = # 1 { { 26 { 1 'b0 } } , intClaim , 32 'b0 } ;
intInProgress < = # 1 intInProgress | ( 1 'b1 < < ( intClaim - 1 ) ) ; // claimed requests are currently in progress of being serviced until they are completed
end
endcase
2021-03-22 14:14:21 +00:00
endgenerate
2021-04-04 10:40:53 +00:00
// connect sources to requests
`ifdef PLIC_UART_ID
assign requests [ `PLIC_UART_ID ] = UARTIntr ;
`endif
// or temporarily connect them to nothing
assign requests [ 3 : 1 ] = 3 'b0 ;
// pending updates
// *** verify that this matches the expectations of the things that make requests (in terms of timing, edge-triggered vs level-triggered)
assign nextIntPending = ( intPending | ( requests & ~ intInProgress ) ) // requests should raise intPending except when their service routine is already in progress
& ~ ( ( entry = = 28 'hc200004 ) < < ( intClaim - 1 ) ) ; // clear pending bit when claim register is read
flopr # ( N ) intPendingFlop ( HCLK , ~ HRESETn , nextIntPending , intPending ) ;
// pending array - indexed by priority_lvl x source_ID
2021-03-22 14:14:21 +00:00
generate
2021-04-04 10:40:53 +00:00
for ( i = 1 ; i < = N ; i = i + 1 ) begin
// *** make sure that this synthesizes into N decoders, not 7*N 3-bit equality comparators (right?)
assign pendingArray [ 7 ] [ i ] = ( intPriority [ i ] = = 7 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 6 ] [ i ] = ( intPriority [ i ] = = 6 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 5 ] [ i ] = ( intPriority [ i ] = = 5 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 4 ] [ i ] = ( intPriority [ i ] = = 4 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 3 ] [ i ] = ( intPriority [ i ] = = 3 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 2 ] [ i ] = ( intPriority [ i ] = = 2 ) & intEn [ i ] & intPending [ i ] ;
assign pendingArray [ 1 ] [ i ] = ( intPriority [ i ] = = 1 ) & intEn [ i ] & intPending [ i ] ;
2021-03-22 14:14:21 +00:00
end
endgenerate
2021-04-04 10:40:53 +00:00
// pending array, except grouped by priority
assign pendingPGrouped [ 7 : 1 ] = { | pendingArray [ 7 ] ,
| pendingArray [ 6 ] ,
| pendingArray [ 5 ] ,
| pendingArray [ 4 ] ,
| pendingArray [ 3 ] ,
| pendingArray [ 2 ] ,
| pendingArray [ 1 ] } ;
// pendingPGrouped, except only topmost priority is active
assign pendingMaxP [ 7 : 1 ] = { pendingPGrouped [ 7 ] ,
pendingPGrouped [ 6 ] & ~ | pendingPGrouped [ 7 ] ,
pendingPGrouped [ 5 ] & ~ | pendingPGrouped [ 7 : 6 ] ,
pendingPGrouped [ 4 ] & ~ | pendingPGrouped [ 7 : 5 ] ,
pendingPGrouped [ 3 ] & ~ | pendingPGrouped [ 7 : 4 ] ,
pendingPGrouped [ 2 ] & ~ | pendingPGrouped [ 7 : 3 ] ,
pendingPGrouped [ 1 ] & ~ | pendingPGrouped [ 7 : 2 ] } ;
// select the pending requests at that priority
assign pendingRequestsAtMaxP [ N: 1 ] = ( { N { pendingMaxP [ 7 ] } } & pendingArray [ 7 ] )
| ( { N { pendingMaxP [ 6 ] } } & pendingArray [ 6 ] )
| ( { N { pendingMaxP [ 5 ] } } & pendingArray [ 5 ] )
| ( { N { pendingMaxP [ 4 ] } } & pendingArray [ 4 ] )
| ( { N { pendingMaxP [ 3 ] } } & pendingArray [ 3 ] )
| ( { N { pendingMaxP [ 2 ] } } & pendingArray [ 2 ] )
| ( { N { pendingMaxP [ 1 ] } } & pendingArray [ 1 ] ) ;
// find the lowest ID amongst active interrupts at the highest priority
integer j ;
// *** verify that this synthesizes to a reasonable priority encoder and that j doesn't actually exist in hardware
always_comb begin
intClaim = 6 'b0 ;
for ( j = N ; j > 0 ; j = j - 1 ) begin
if ( pendingRequestsAtMaxP [ j ] ) intClaim = j ;
end
end
// create threshold mask
// *** I think this commented out version would be nice, but linter complains about circular logic
//assign threshMask[7:1] = {~(7==intThreshold),
// ~(6==intThreshold) & threshMask[7],
// ~(5==intThreshold) & threshMask[6],
// ~(4==intThreshold) & threshMask[5],
// ~(3==intThreshold) & threshMask[4],
// ~(2==intThreshold) & threshMask[3],
// ~(1==intThreshold) & threshMask[2]};
// *** verify that this alternate version does not synthesize to 7 separate comparators
assign threshMask [ 7 : 1 ] = { ( 7 > intThreshold ) ,
( 6 > intThreshold ) ,
( 5 > intThreshold ) ,
( 4 > intThreshold ) ,
( 3 > intThreshold ) ,
( 2 > intThreshold ) ,
( 1 > intThreshold ) } ;
// is the max priority > threshold?
// *** currently we decode threshold into threshMask and bitwise &, then reductive | ; would it be any better to binary encode maxPriority and ">" with threshold?
assign ExtIntM = | ( threshMask & pendingPGrouped ) ;
2021-03-22 14:14:21 +00:00
endmodule