Files
Toooba/src_Core/PLIC/PLIC.bsv
2020-12-21 13:37:01 +00:00

624 lines
22 KiB
Plaintext

// Copyright (c) 2019-2020 Bluespec, Inc. All Rights Reserved
//
//-
// RVFI_DII + CHERI modifications:
// Copyright (c) 2020 Alexandre Joannou
// Copyright (c) 2020 Jonathan Woodruff
// All rights reserved.
//
// This software was developed by SRI International and the University of
// Cambridge Computer Laboratory (Department of Computer Science and
// Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
// DARPA SSITH research programme.
//
// This work was supported by NCSC programme grant 4212611/RFA 15971 ("SafeBet").
//-
package PLIC;
// ================================================================
// This package implements a PLIC (Platform-Level Interrupt Controller)
// conforming to the RISC-V PLIC standard.
// It is parameterized for:
// - # of sources
// - # of targets
// - # of priorities
//
// ================================================================
// Bluespec lib imports
import ConfigReg :: *;
import Vector :: *;
import FIFOF :: *;
import ClientServer :: *;
import Assert :: *;
// ----------------
// BSV additional libs
import Cur_Cycle :: *;
import GetPut_Aux :: *;
import Semi_FIFOF :: *;
import SourceSink :: *;
import AXI4 :: *;
// ================================================================
// Project imports
import Fabric_Defs :: *; // for Wd_SId_2x3, Wd_Addr, Wd_Data...
// ================================================================
// Change bitwidth without requiring < or > constraints.
function Bit #(m) changeWidth (Bit #(n) x);
Bit #(TAdd #(m, n)) y = zeroExtend (x);
Bit #(m) z = y [valueOf (m)-1 : 0];
return z;
endfunction
// ================================================================
// Maximum supported sources, targets, ...
typedef 10 T_wd_source_id; // Max 1024 sources (source 0 is reserved for 'no interrupt')
typedef 5 T_wd_target_id; // Max 32 targets
// ================================================================
// Interfaces
// ----------------
// Individual source interface
interface PLIC_Source_IFC;
(* always_ready, always_enabled *)
method Action m_interrupt_req (Bool set_not_clear);
endinterface
// ----------------
// Individual target interface
interface PLIC_Target_IFC;
(* always_ready *)
method Bool m_eip; // external interrupt pending
endinterface
// ----------------
// PLIC interface
interface PLIC_IFC #(numeric type t_n_external_sources,
numeric type t_n_targets,
numeric type t_max_priority);
// Debugging
method Action set_verbosity (Bit #(4) verbosity);
method Action show_PLIC_state;
// Reset
interface Server #(Bit #(0), Bit #(0)) server_reset;
// set_addr_map should be called after this module's reset
method Action set_addr_map (Bit #(64) addr_base, Bit #(64) addr_lim);
// Memory-mapped access
interface AXI4_Slave #( Wd_SId_2x3, Wd_Addr, Wd_Data, 0, 0, 0, 0, 0)
axi4_slave;
// sources
interface Vector #(t_n_external_sources, PLIC_Source_IFC) v_sources;
// targets EIPs (External Interrupt Pending)
interface Vector #(t_n_targets, PLIC_Target_IFC) v_targets;
endinterface
// ================================================================
// PLIC module implementation
module mkPLIC (PLIC_IFC #(t_n_external_sources, t_n_targets, t_max_priority))
provisos (Add #(1, t_n_external_sources, t_n_sources), // source 0 is reserved for 'no source'
Add #(_any_0, TLog #(t_n_sources), T_wd_source_id),
Add #(_any_1, TLog #(t_n_targets), T_wd_target_id),
Log #(TAdd #(t_max_priority, 1), t_wd_priority));
// 0 = quiet; 1 = show PLIC transactions; 2 = also show AXI4 transactions
Reg #(Bit #(4)) cfg_verbosity <- mkConfigReg (0);
// Source_Ids and Priorities are read and written over the memory interface
// and should fit within the data bus width, currently 64 bits.
staticAssert ((valueOf (TLog #(t_n_sources)) <= 64), "PLIC: t_n_sources parameter too large");
staticAssert ((valueOf (TLog #(TAdd #(t_max_priority, 1))) <= 64), "PLIC: t_max_priority parameter too large");
Integer n_sources = valueOf (t_n_sources);
Integer n_targets = valueOf (t_n_targets);
// ----------------
// Soft reset requests and responses
FIFOF #(Bit #(0)) f_reset_reqs <- mkFIFOF;
FIFOF #(Bit #(0)) f_reset_rsps <- mkFIFOF;
// ----------------
// Memory-mapped access
// Base and limit addrs for this memory-mapped block.
Reg #(Bit #(64)) rg_addr_base <- mkRegU;
Reg #(Bit #(64)) rg_addr_lim <- mkRegU;
// Connector to AXI4 fabric
let slavePortShim <- mkAXI4ShimFF;
// ----------------
// Per-interrupt source state
// Interrupt pending from source
Vector #(t_n_sources, Reg #(Bool)) vrg_source_ip <- replicateM (mkConfigReg (False));
// Interrupt claimed and being serviced by a hart
Vector #(t_n_sources, Reg #(Bool)) vrg_source_busy <- replicateM (mkReg (False));
// Priority for this source
Vector #(t_n_sources, Reg #(Bit #(t_wd_priority))) vrg_source_prio <- replicateM (mkReg (0));
// ----------------
// Per-target hart context state
// Threshold: interrupts at or below threshold should be masked out for target
Vector #(t_n_targets, Reg #(Bit #(t_wd_priority))) vrg_target_threshold <- replicateM (mkReg ('1));
// ----------------
// Per-target, per-source state
// Interrupt enables from source to target
Vector #(t_n_targets,
Vector #(t_n_sources, Reg #(Bool))) vvrg_ie <- replicateM (replicateM (mkReg (False)));
// ================================================================
// Compute outputs for each target (combinational)
function Tuple2 #(Bit #(t_wd_priority), Bit #(TLog #(t_n_sources)))
fn_target_max_prio_and_max_id (Bit #(T_wd_target_id) target_id);
Bit #(t_wd_priority) max_prio = 0;
Bit #(TLog #(t_n_sources)) max_id = 0;
// Note: source_ids begin at 1, not 0.
for (Integer source_id = 1; source_id < n_sources; source_id = source_id + 1)
if ( vrg_source_ip [source_id]
&& (vrg_source_prio [source_id] > max_prio)
&& (vvrg_ie [target_id][source_id])) begin
max_id = fromInteger (source_id);
max_prio = vrg_source_prio [source_id];
end
// Assert: if any interrupt is pending (max_id > 0), then prio > 0
return tuple2 (max_prio, max_id);
endfunction
function Action fa_show_PLIC_state;
action
$display ("----------------");
$write ("Src IPs :");
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1)
$write (" %0d", pack (vrg_source_ip [source_id]));
$display ("");
$write ("Src Prios:");
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1)
$write (" %0d", vrg_source_prio [source_id]);
$display ("");
$write ("Src busy :");
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1)
$write (" %0d", pack (vrg_source_busy [source_id]));
$display ("");
for (Integer target_id = 0; target_id < n_targets; target_id = target_id + 1) begin
$write ("T %0d IEs :", target_id);
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1)
$write (" %0d", vvrg_ie [target_id][source_id]);
match { .max_prio, .max_id } = fn_target_max_prio_and_max_id (fromInteger (target_id));
$display (" MaxPri %0d, Thresh %0d, MaxId %0d",
max_prio, vrg_target_threshold [target_id], max_id);
end
endaction
endfunction
// ================================================================
// Soft reset
rule rl_reset;
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_reset", cur_cycle);
let x <- pop (f_reset_reqs);
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1) begin
vrg_source_ip [source_id] <= False;
vrg_source_busy [source_id] <= False;
vrg_source_prio [source_id] <= 0;
end
for (Integer target_id = 0; target_id < n_targets; target_id = target_id + 1) begin
// Mask all interrupts with highest threshold
vrg_target_threshold [target_id] <= '1;
end
for (Integer target_id = 0; target_id < n_targets; target_id = target_id + 1)
for (Integer source_id = 0; source_id < n_sources; source_id = source_id + 1)
vvrg_ie [target_id][source_id] <= False;
slavePortShim.clear;
f_reset_rsps.enq (?);
endrule
// ================================================================
// Bus interface for reading/writing control/status regs
// Relative-address map is same as 'SiFive U54-MC Core Complex Manual v1p0'.
// Accesses are 4-bytes wide, even though bus may be 64b wide.
// ----------------------------------------------------------------
// Handle memory-mapped read requests
rule rl_process_rd_req (! f_reset_reqs.notEmpty);
let rda <- get(slavePortShim.master.ar);
if (cfg_verbosity > 1) begin
$display ("%0d: PLIC.rl_process_rd_req:", cur_cycle);
$display (" ", fshow (rda));
end
let addr_offset = rda.araddr - rg_addr_base;
Bit #(64) rdata = 0;
AXI4_Resp rresp = OKAY;
if (rda.araddr < rg_addr_base) begin
// Technically this should not happen: the fabric should
// never have delivered such an addr to this IP.
$display ("%0d: ERROR: PLIC.rl_process_rd_req: unrecognized addr", cur_cycle);
$display (" ", fshow (rda));
rresp = DECERR;
end
// Source Priority
else if (addr_offset < 'h1000) begin
Bit #(T_wd_source_id) source_id = truncate (addr_offset [11:2]);
if ((0 < source_id) && (source_id <= fromInteger (n_sources - 1))) begin
rdata = changeWidth (vrg_source_prio [source_id]);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_rd_req: reading Source Priority: source %0d = 0x%0h",
cur_cycle, source_id, rdata);
end
else
rresp = SLVERR;
end
// Source IPs (interrupt pending).
// Return 32 consecutive IP bits starting with addr.
else if (('h1000 <= addr_offset) && (addr_offset < 'h2000)) begin
Bit #(T_wd_source_id) source_id_base = truncate ({ addr_offset [11:0], 5'h0 });
function Bool fn_ip_source_id (Integer source_id_offset);
let source_id = source_id_base + fromInteger (source_id_offset);
Bool ip_source_id = ( (source_id <= fromInteger (n_sources - 1))
? vrg_source_ip [source_id]
: False);
return ip_source_id;
endfunction
if (source_id_base <= fromInteger (n_sources - 1)) begin
Bit #(32) v_ip = pack (genWith (fn_ip_source_id));
rdata = changeWidth (v_ip);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_rd_req: reading Intr Pending 32 bits from source %0d = 0x%0h",
cur_cycle, source_id_base, rdata);
end
else
rresp = SLVERR;
end
// Source IEs (interrupt enables) for a target
// Return 32 consecutive IE bits starting with addr.
// Target 0 addrs: 2000-207F, Target 1 addrs: 2080-20FF, ...
else if (('h2000 <= addr_offset) && (addr_offset < 'h3000)) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [11:7]);
Bit #(T_wd_source_id) source_id_base = truncate ({ addr_offset [6:0], 5'h0 });
function Bool fn_ie_source_id (Integer source_id_offset);
let source_id = fromInteger (source_id_offset) + source_id_base;
return ( (source_id <= fromInteger (n_sources - 1))
? vvrg_ie [target_id][source_id]
: False);
endfunction
if ( (source_id_base <= fromInteger (n_sources - 1))
&& (target_id <= fromInteger (n_targets - 1))) begin
Bit #(32) v_ie = pack (genWith (fn_ie_source_id));
rdata = changeWidth (v_ie);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_rd_req: reading Intr Enable 32 bits from source %0d = 0x%0h",
cur_cycle, source_id_base, rdata);
end
else
rresp = SLVERR;
end
// Target threshold
else if ((addr_offset [31:0] & 32'hFFFF_0FFF) == 32'h0020_0000) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [20:12]);
if (target_id <= fromInteger (n_targets - 1)) begin
rdata = changeWidth (vrg_target_threshold [target_id]);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_rd_req: reading Threshold for target %0d = 0x%0h",
cur_cycle, target_id, rdata);
end
else
rresp = SLVERR;
end
// Interrupt service claim by target
else if ((addr_offset [31:0] & 32'hFFFF_0FFF) == 32'h0020_0004) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [20:12]);
match { .max_prio, .max_id } = fn_target_max_prio_and_max_id (target_id);
Bool eip = (max_prio > vrg_target_threshold [target_id]);
if (target_id <= fromInteger (n_targets - 1)) begin
if (max_id != 0) begin
vrg_source_ip [max_id] <= False;
vrg_source_busy [max_id] <= True;
rdata = changeWidth (max_id);
end
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_rd_req: reading Claim for target %0d = 0x%0h",
cur_cycle, target_id, rdata);
end
else
rresp = SLVERR;
end
else
rresp = SLVERR;
if (rresp != OKAY) begin
$display ("%0d: ERROR: PLIC.rl_process_rd_req: unrecognized addr", cur_cycle);
$display (" ", fshow (rda));
end
if ((valueOf (Wd_Data) == 64) && ((addr_offset & 'h7) == 'h4))
rdata = { rdata [31:0], 32'h0 };
// Send read-response to bus
Fabric_Data x = truncate (rdata);
let rdr = AXI4_RFlit {rid: rda.arid,
rdata: x,
rresp: rresp,
rlast: True,
ruser: rda.aruser}; // XXX This requires that Wd_AR_User == Wd_R_User
slavePortShim.master.r.put(rdr);
if (cfg_verbosity > 1) begin
$display ("%0d: PLIC.rl_process_rd_req", cur_cycle);
$display (" ", fshow (rda));
$display (" ", fshow (rdr));
end
endrule: rl_process_rd_req
// ----------------------------------------------------------------
// Handle memory-mapped write requests
(* descending_urgency = "rl_process_rd_req, rl_process_wr_req" *) // Ad hoc order
rule rl_process_wr_req (! f_reset_reqs.notEmpty);
let wra <- get(slavePortShim.master.aw);
let wrd <- get(slavePortShim.master.w);
if (cfg_verbosity > 1) begin
$display ("%0d: PLIC.rl_process_wr_req", cur_cycle);
$display (" ", fshow (wra));
$display (" ", fshow (wrd));
end
let addr_offset = wra.awaddr - rg_addr_base;
let wdata32 = (((valueOf (Wd_Data) == 64) && ((addr_offset & 'h7) == 'h4))
? wrd.wdata [63:32]
: wrd.wdata [31:0]);
let bresp = OKAY;
if (wra.awaddr < rg_addr_base) begin
// Technically this should not happen: the fabric should
// never have delivered such an addr to this IP.
$display ("%0d: ERROR: PLIC.rl_process_wr_req: unrecognized addr", cur_cycle);
$display (" ", fshow (wra));
$display (" ", fshow (wrd));
bresp = DECERR;
end
// Source priority
else if (addr_offset < 'h1000) begin
Bit #(T_wd_source_id) source_id = truncate (addr_offset [11:2]);
if ((0 < source_id) && (source_id <= fromInteger (n_sources - 1))) begin
vrg_source_prio [source_id] <= changeWidth (wdata32);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: writing Source Priority: source %0d = 0x%0h",
cur_cycle, source_id, wdata32);
end
else begin
// Note: write to source_id 0 is error; should it just be ignored?
bresp = SLVERR;
end
end
// Source IPs (interrupt pending).
// Read-only, so ignore write; just check that addr ok.
else if (('h1000 <= addr_offset) && (addr_offset < 'h2000)) begin
Bit #(T_wd_source_id) source_id_base = truncate ({ addr_offset [11:0], 5'h0 });
if (source_id_base <= fromInteger (n_sources - 1)) begin
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: Ignoring write to Read-only Intr Pending 32 bits from source %0d",
cur_cycle, source_id_base);
end
else
bresp = SLVERR;
end
// Source IEs (interrupt enables) for a target
// Write 32 consecutive IE bits starting with addr.
// Target 0 addrs: 2000-207F, Target 1 addrs: 2080-20FF, ...
else if (('h2000 <= addr_offset) && (addr_offset < 'h3000)) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [11:7]);
Bit #(T_wd_source_id) source_id_base = truncate ({ addr_offset [6:0], 5'h0 });
if ( (source_id_base <= fromInteger (n_sources - 1))
&& (target_id <= fromInteger (n_targets - 1))) begin
for (Bit #(T_wd_source_id) k = 0; k < 32; k = k + 1) begin
Bit #(T_wd_source_id) source_id = source_id_base + k;
if (source_id <= fromInteger (n_sources - 1))
vvrg_ie [target_id][source_id] <= unpack (wdata32 [k]);
end
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: writing Intr Enable 32 bits for target %0d from source %0d = 0x%0h",
cur_cycle, target_id, source_id_base, wdata32);
end
else
bresp = SLVERR;
end
// Target threshold
else if ((addr_offset [31:0] & 32'hFFFF_0FFF) == 32'h0020_0000) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [20:12]);
if (target_id <= fromInteger (n_targets - 1)) begin
vrg_target_threshold [target_id] <= changeWidth (wdata32);
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: writing threshold for target %0d = 0x%0h",
cur_cycle, target_id, wdata32);
end
else
bresp = SLVERR;
end
// Interrupt service completion by target
// Write data is the source ID to complete. Disabled sources are ignored.
else if ((addr_offset [31:0] & 32'hFFFF_0FFF) == 32'h0020_0004) begin
Bit #(T_wd_target_id) target_id = truncate (addr_offset [20:12]);
Bit #(T_wd_source_id) source_id = truncate (wdata32);
if (target_id <= fromInteger (n_targets - 1)) begin
if ( (source_id <= fromInteger (n_sources))
&& vvrg_ie [target_id][source_id]) begin
vrg_source_busy [source_id] <= False;
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: writing completion for target %0d for source 0x%0h",
cur_cycle, target_id, source_id);
end
else begin
if (cfg_verbosity > 0)
$display ("%0d: PLIC.rl_process_wr_req: ignoring completion for target %0d for source 0x%0h",
cur_cycle, target_id, source_id);
end
end
else
bresp = SLVERR;
end
else
bresp = SLVERR;
if (bresp != OKAY) begin
$display ("%0d: ERROR: PLIC.rl_process_wr_req: unrecognized addr", cur_cycle);
$display (" ", fshow (wra));
$display (" ", fshow (wrd));
end
// Send write-response to bus
let wrr = AXI4_BFlit {bid: wra.awid,
bresp: bresp,
buser: wra.awuser}; // XXX This requires that Wd_AW_User == Wd_B_User
slavePortShim.master.b.put(wrr);
if (cfg_verbosity > 1) begin
$display ("%0d: PLIC.AXI4.rl_process_wr_req", cur_cycle);
$display (" ", fshow (wra));
$display (" ", fshow (wrd));
$display (" ", fshow (wrr));
end
endrule: rl_process_wr_req
// ================================================================
// Creator of each source interface
function PLIC_Source_IFC fn_mk_PLIC_Source_IFC (Integer source_id);
return interface PLIC_Source_IFC;
method Action m_interrupt_req (Bool set_not_clear);
action
if (! vrg_source_busy [source_id + 1]) begin
vrg_source_ip [source_id + 1] <= set_not_clear;
if ((cfg_verbosity > 0) && (vrg_source_ip [source_id + 1] != set_not_clear))
$display ("%0d: %m.m_interrupt_req: changing vrg_source_ip [%0d] to %0d",
cur_cycle, source_id + 1, pack (set_not_clear));
end
endaction
endmethod
endinterface;
endfunction
// ================================================================
// Creator of each target interface
function PLIC_Target_IFC fn_mk_PLIC_Target_IFC (Integer target_id);
return interface PLIC_Target_IFC;
method Bool m_eip; // external interrupt pending
match { .max_prio, .max_id } = fn_target_max_prio_and_max_id (fromInteger (target_id));
Bool eip = (max_prio > vrg_target_threshold [target_id]);
return eip;
endmethod
endinterface;
endfunction
// ================================================================
// INTERFACE
// Debugging
method Action set_verbosity (Bit #(4) verbosity);
cfg_verbosity <= verbosity;
endmethod
method Action show_PLIC_state;
fa_show_PLIC_state;
endmethod
// Reset
interface server_reset = toGPServer (f_reset_reqs, f_reset_rsps);
// set_addr_map should be called after this module's reset
method Action set_addr_map (Bit #(64) addr_base, Bit #(64) addr_lim);
if (addr_base [1:0] != 0)
$display ("%0d: WARNING: PLIC.set_addr_map: addr_base 0x%0h is not 4-Byte-aligned",
cur_cycle, addr_base);
if (addr_lim [1:0] != 0)
$display ("%0d: WARNING: PLIC.set_addr_map: addr_lim 0x%0h is not 4-Byte-aligned",
cur_cycle, addr_lim);
rg_addr_base <= addr_base;
rg_addr_lim <= addr_lim;
if (cfg_verbosity > 0)
$display ("%0d: PLIC.set_addr_map: base 0x%0h limit 0x%0h", cur_cycle, addr_base, addr_lim);
endmethod
// Memory-mapped access
interface axi4_slave = slavePortShim.slave;
// sources
interface v_sources = genWith (fn_mk_PLIC_Source_IFC);
// targets
interface v_targets = genWith (fn_mk_PLIC_Target_IFC);
endmodule
// ================================================================
endpackage