🧭集成指南(Integration Guide)
本指南介绍如何将ChiselAIA集成到RISC-V系统中。
This guide introduces the integration process of ChiselAIA into a RISC-V system.
概览(Overview)
集成涉及2个Scala文件,共4个Scala类:
APLIC.scala
:APLICParams
:用于配置APLIC实例的参数类APLIC
:APLIC模块的核心逻辑- 每个系统需要一个实例:
TLAPLIC
:对APLIC
模块的Tilelink协议包装AXI4APLIC
:对APLIC
模块的AXI4协议包装
IMSIC.scala
:IMSICParams
:用于配置IMSIC实例的参数类IMSIC
:IMSIC模块的核心逻辑- 每个处理器核心需要一个实例:
TLIMSIC
:对IMSIC
模块的Tilelink协议包装AXI4IMSIC
:对IMSIC
模块的AXI4协议包装
Integration involves 2 scala files, including 4 scala classes:
APLIC.scala
:APLICParams
: Parameter classes for configuring APLIC instance.APLIC
: The main logic of APLIC module.- Requiring one instance per system:
TLAPLIC
: TheAPLIC
module wrapped by Tilelink protocol,AXI4APLIC
: TheAPLIC
module wrapped by AXI4 protocol.
IMSIC.scala
:IMSICParams
: Parameter classes for configuring IMSIC instances.IMSIC
: The main logic of IMSIC module.- Requiring one instance per hart:
TLIMSIC
: TheIMSIC
module wrapped by Tilelink protocol,AXI4IMSIC
: TheIMSIC
module wrapped by AXI4 protocol.
参数(Parameters)
本节概述了APLIC和IMSIC的可配置参数。
虽然提供了默认值,但我们强烈建议根据具体的集成需求,自定义带有👉标记的参数。
其他参数要么是派生的,要么是硬编码的(详情参见Params.scala
)。
This section outlines the configurable parameters for APLIC and IMSIC.
While defaul values are provided,
we strongly recommend customizing parameters marked with 👉 to suit your specific integration needs.
Other parameters are either derived or hard-coded, (see Params.scala
for details).
命名约定:
Num
后缀:某实体的数量,Width
后缀:某实体的位宽(通常是log2(实体数量)
),Addr
后缀:某实体的地址。
Naming conventions:
Num
suffix: Number of the items.Width
suffix: Bit width of an item (typicallylog2(number of the item)
).Addr
suffix: Address of an item.
IMSICParams
package aia
import chisel3._
import chisel3.IO
import chisel3.util._
import freechips.rocketchip.amba.axi4._
import freechips.rocketchip.amba.axi4.AXI4Xbar
import freechips.rocketchip.devices.tilelink._
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.prci.ClockSinkDomain
import freechips.rocketchip.regmapper._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util._
import org.chipsalliance.cde.config.Parameters
import utility._
// RegMap that supports Default and Valid
object RegMapDV {
def Unwritable = null
def apply(addr: Int, reg: UInt, wfn: UInt => UInt = (x => x)) = (addr, (reg, wfn))
def generate(
default: UInt,
mapping: Map[Int, (UInt, UInt => UInt)],
raddr: UInt,
rdata: UInt,
rvalid: Bool,
waddr: UInt,
wen: Bool,
wdata: UInt,
wmask: UInt
): Unit = {
val chiselMapping = mapping.map { case (a, (r, w)) => (a.U, r, w) }
val rdata_valid = WireDefault(0.U((rdata.getWidth + 1).W))
rdata_valid := LookupTreeDefault(
raddr,
Cat(default, false.B),
chiselMapping.map { case (a, r, w) => (a, Cat(r, true.B)) }
)
rdata := rdata_valid(rdata.getWidth, 1)
rvalid := rdata_valid(0)
chiselMapping.map { case (a, r, w) =>
if (w != null) when(wen && waddr === a)(r := w(MaskData(r, wdata, wmask)))
}
}
def generate(
default: UInt,
mapping: Map[Int, (UInt, UInt => UInt)],
addr: UInt,
rdata: UInt,
rvalid: Bool,
wen: Bool,
wdata: UInt,
wmask: UInt
): Unit = generate(default, mapping, addr, rdata, rvalid, addr, wen, wdata, wmask)
}
// Based on Xiangshan NewCSR
object OpType extends ChiselEnum {
val ILLEGAL = Value(0.U)
val CSRRW = Value(1.U)
val CSRRS = Value(2.U)
val CSRRC = Value(3.U)
}
object PrivType extends ChiselEnum {
val U = Value(0.U)
val S = Value(1.U)
val M = Value(3.U)
}
class MSITransBundle(params: IMSICParams) extends Bundle {
val vld_req = Input(Bool()) // request from axireg
val data = Input(UInt(params.MSI_INFO_WIDTH.W))
val vld_ack = Output(Bool()) // ack for axireg from imsic. which indicates imsic can work actively.
}
class ForCVMBundle extends Bundle {
val cmode = Input(Bool()) // add port: cpu mode is tee or ree
val notice_pending =
Output(Bool()) // add port: interrupt pending of ree when cmode is tee,else interrupt pending of tee.
}
class AddrBundle(params: IMSICParams) extends Bundle {
val valid = Bool() // 表示 addr 是否有效
val bits = new Bundle {
val addr = UInt(params.iselectWidth.W) // 存储实际地址值
val virt = Bool()
val priv = PrivType()
}
}
class CSRToIMSICBundle(params: IMSICParams) extends Bundle {
val addr = new AddrBundle(params)
val vgein = UInt(params.vgeinWidth.W)
val wdata = ValidIO(new Bundle {
val op = OpType()
val data = UInt(params.xlen.W)
})
val claims = Vec(params.privNum, Bool())
}
class IMSICToCSRBundle(params: IMSICParams) extends Bundle {
val rdata = ValidIO(UInt(params.xlen.W))
val illegal = Bool()
val pendings = UInt(params.intFilesNum.W)
val topeis = Vec(params.privNum, UInt(32.W))
}
case class IMSICParams(
// MC IMSIC中断源数量的对数,默认值9表示IMSIC支持最多512(2^9)个中断源
// MC (Logarithm of number of interrupt sources to IMSIC.
// MC The default 9 means IMSIC support at most 512 (2^9) interrupt sources):
// MC{visible}
imsicIntSrcWidth: Int = 9,
// MC 👉 本IMSIC的机器态中断文件的地址(Address of machine-level interrupt files for this IMSIC):
mAddr: Long = 0x00000L,
// MC 👉 本IMSIC的监管态和客户态中断文件的地址(Addr for supervisor-level and guest-level interrupt files for this IMSIC):
sgAddr: Long = 0x10000L,
// MC 👉 客户中断文件的数量(Number of guest interrupt files):
geilen: Int = 5,
// MC vgein信号的位宽(The width of the vgein signal):
vgeinWidth: Int = 6,
// MC iselect信号的位宽(The width of iselect signal):
iselectWidth: Int = 12,
EnableImsicAsyncBridge: Boolean = false,
HasTEEIMSIC: Boolean = false
// MC{hide}
) {
lazy val xlen: Int = 64 // currently only support xlen = 64
lazy val xlenWidth = log2Ceil(xlen)
require(
imsicIntSrcWidth <= 11,
f"imsicIntSrcWidth=${imsicIntSrcWidth}, must not greater than log2(2048)=11, as there are at most 2048 eip/eie bits"
)
lazy val privNum: Int = 3 // number of privilege modes: machine, supervisor, virtualized supervisor
lazy val intFilesNum: Int = 2 + geilen // number of interrupt files, m, s, vs0, vs1, ...
lazy val eixNum: Int = pow2(imsicIntSrcWidth).toInt / xlen // number of eip/eie registers
lazy val intFileMemWidth: Int = 12 // interrupt file memory region width: 12-bit width => 4KB size
require(vgeinWidth >= log2Ceil(geilen))
require(
iselectWidth >= 8,
f"iselectWidth=${iselectWidth} needs to be able to cover addr [0x70, 0xFF], that is from CSR eidelivery to CSR eie63"
)
lazy val INTP_FILE_WIDTH = log2Ceil(intFilesNum)
lazy val MSI_INFO_WIDTH = imsicIntSrcWidth + INTP_FILE_WIDTH
}
class IMSIC(
params: IMSICParams,
beatBytes: Int = 4
)(implicit p: Parameters) extends Module {
println(f"IMSICParams.geilen: ${params.geilen}%d")
class IMSICGateWay extends Module {
// === io port define ===
val msiio = IO(new MSITransBundle(params))
val msi_data_o = IO(Output(UInt(params.imsicIntSrcWidth.W)))
val msi_valid_o = IO(Output(UInt(params.intFilesNum.W)))
// === main body ===
val msi_in = Wire(UInt(params.MSI_INFO_WIDTH.W))
msi_in := msiio.data
val msi_vld_req_cpu = WireInit(false.B)
when(params.EnableImsicAsyncBridge.B) {
msi_vld_req_cpu := AsyncResetSynchronizerShiftReg(msiio.vld_req, 3, 0)
}.otherwise {
msi_vld_req_cpu := msiio.vld_req
}
val msi_vld_ack_cpu = RegNext(msi_vld_req_cpu)
// generate the msi_vld_ack,to handle with the input msi request.
msiio.vld_ack := msi_vld_ack_cpu
val msi_vld_ris_cpu = msi_vld_req_cpu & (~msi_vld_ack_cpu) // rising of msi_vld_req
val msi_data_catch = RegInit(0.U(params.imsicIntSrcWidth.W))
val msi_intf_valids = RegInit(0.U(params.intFilesNum.W))
msi_data_o := msi_data_catch(params.imsicIntSrcWidth - 1, 0)
msi_valid_o := msi_intf_valids // multi-bis switch vector
when(msi_vld_ris_cpu) {
msi_data_catch := msi_in(params.imsicIntSrcWidth - 1, 0)
msi_intf_valids := 1.U << msi_in(params.MSI_INFO_WIDTH - 1,params.imsicIntSrcWidth)
}.otherwise {
msi_intf_valids := 0.U
}
}
class IntFile extends Module {
override def desiredName = "IntFile"
val fromCSR = IO(Input(new Bundle {
val seteipnum = ValidIO(UInt(params.imsicIntSrcWidth.W))
val addr = ValidIO(UInt(params.iselectWidth.W))
val wdata = ValidIO(new Bundle {
val op = OpType()
val data = UInt(params.xlen.W)
})
val claim = Bool()
}))
val toCSR = IO(Output(new Bundle {
val rdata = ValidIO(UInt(params.xlen.W))
val illegal = Bool()
val pending = Bool()
val topei = UInt(params.imsicIntSrcWidth.W)
}))
/// indirect CSRs
val eidelivery = RegInit(0.U(params.xlen.W))
val eithreshold = RegInit(0.U(params.xlen.W))
val eips = RegInit(VecInit.fill(params.eixNum)(0.U(params.xlen.W)))
val eies = RegInit(VecInit.fill(params.eixNum)(0.U(params.xlen.W)))
val illegal_wdata_op = WireDefault(false.B)
locally { // scope for xiselect CSR reg map
val wdata = WireDefault(0.U(params.xlen.W))
val wmask = WireDefault(0.U(params.xlen.W))
when(fromCSR.wdata.valid) {
switch(fromCSR.wdata.bits.op) {
is(OpType.ILLEGAL) {
illegal_wdata_op := true.B
}
is(OpType.CSRRW) {
wdata := fromCSR.wdata.bits.data
wmask := Fill(params.xlen, 1.U)
}
is(OpType.CSRRS) {
wdata := Fill(params.xlen, 1.U)
wmask := fromCSR.wdata.bits.data
}
is(OpType.CSRRC) {
wdata := 0.U
wmask := fromCSR.wdata.bits.data
}
}
}
val validAddresses = Seq(
0x70, 0x72, 0x80, 0xc0
) ++ (0 until eips.length).map(i => 0x82 + i * 2
).toSeq ++ (0 until eies.length).map(i => 0xc2 + i * 2
).toSeq ++ (0 until 16).map(i => 0x30 + i).toSeq
val isValidAddress = VecInit(validAddresses.map(_.U === fromCSR.addr.bits)).asUInt.orR
def bit0ReadOnlyZero(x: UInt): UInt = x & ~1.U(x.getWidth.W)
def fixEIDelivery(x: UInt): UInt = x & 1.U
RegMapDV.generate(
0.U,
Map(
RegMapDV(0x70, eidelivery, fixEIDelivery),
RegMapDV(0x72, eithreshold),
RegMapDV(0x80, eips(0), bit0ReadOnlyZero),
RegMapDV(0xc0, eies(0), bit0ReadOnlyZero)
) ++ eips.drop(1).zipWithIndex.map { case (eip: UInt, i: Int) =>
RegMapDV(0x82 + i * 2, eip)
} ++ eies.drop(1).zipWithIndex.map { case (eie: UInt, i: Int) =>
RegMapDV(0xc2 + i * 2, eie)
},
/*raddr*/ fromCSR.addr.bits,
/*rdata*/ toCSR.rdata.bits,
/*rdata*/ toCSR.rdata.valid,
/*waddr*/ fromCSR.addr.bits,
/*wen */ fromCSR.wdata.valid,
/*wdata*/ wdata,
/*wmask*/ wmask
)
val illegal_csr = WireDefault(false.B)
when(fromCSR.addr.bits >= 0x00.U && fromCSR.addr.bits <= 0xFF.U &&
!isValidAddress) {
illegal_csr := true.B
}
toCSR.illegal := (fromCSR.addr.valid | fromCSR.wdata.valid) & (
~toCSR.rdata.valid | illegal_wdata_op | illegal_csr
)
} // end of scope for xiselect CSR reg map
locally {
val index = fromCSR.seteipnum.bits(params.imsicIntSrcWidth - 1, params.xlenWidth)
val offset = fromCSR.seteipnum.bits(params.xlenWidth - 1, 0)
when(fromCSR.seteipnum.valid) {
// set eips bit
eips(index) := eips(index) | UIntToOH(offset)
}
}
locally { // scope for xtopei
// The ":+ true.B" trick explain:
// Append true.B to handle the cornor case, where all bits in eip and eie are disabled.
// If do not append true.B, then we need to check whether the eip & eie are empty,
// otherwise, the returned topei will become the max index, that is 2^intSrcWidth-1
// Noted: the support max interrupt sources number = 2^intSrcWidth
// [0, 2^intSrcWidth-1] :+ 2^intSrcWidth
val eipBools = Cat(eips.reverse).asBools :+ true.B
val eieBools = Cat(eies.reverse).asBools :+ true.B
def xtopei_filter(xeidelivery: UInt, xeithreshold: UInt, xtopei: UInt): UInt = {
val tmp_xtopei = Mux(xeidelivery(params.xlen - 1, 1) === 0.U, Mux(xeidelivery(0), xtopei, 0.U) , 0.U)
// {
// all interrupts are enabled, when eithreshold == 1;
// interrupts, when i < eithreshold, are enabled;
// } <=> interrupts, when i <= (eithreshold -1), are enabled
Mux(tmp_xtopei <= (xeithreshold - 1.U), tmp_xtopei, 0.U)
}
toCSR.topei := xtopei_filter(
eidelivery,
eithreshold,
ParallelPriorityMux(
(eipBools zip eieBools).zipWithIndex.map {
case ((p: Bool, e: Bool), i: Int) => (p & e, i.U)
}
)
)
} // end of scope for xtopei
toCSR.pending := toCSR.topei =/= 0.U
when(fromCSR.claim) {
val index = toCSR.topei(params.imsicIntSrcWidth - 1, params.xlenWidth)
val offset = toCSR.topei(params.xlenWidth - 1, 0)
// clear the pending bit indexed by xtopei in xeip
eips(index) := eips(index) & ~UIntToOH(offset)
}
}
val toCSR = IO(Output(new IMSICToCSRBundle(params)))
val fromCSR = IO(Input(new CSRToIMSICBundle(params)))
val msiio = IO(new MSITransBundle(params))
private val illegal_priv = WireDefault(false.B)
private val intFilesSelOH = WireDefault(0.U(params.intFilesNum.W))
locally {
when (fromCSR.addr.valid)
{
val pv = Cat(fromCSR.addr.bits.priv.asUInt, fromCSR.addr.bits.virt)
when(pv === Cat(PrivType.M.asUInt, false.B))(intFilesSelOH := UIntToOH(0.U))
.elsewhen(pv === Cat(PrivType.S.asUInt, false.B))(intFilesSelOH := UIntToOH(1.U))
.elsewhen(pv === Cat(PrivType.S.asUInt, true.B))(intFilesSelOH := UIntToOH(1.U + fromCSR.vgein))
.otherwise(illegal_priv := true.B)
}
}
private val topeis_forEachIntFiles = Wire(Vec(params.intFilesNum, UInt(params.imsicIntSrcWidth.W)))
private val illegals_forEachIntFiles = Wire(Vec(params.intFilesNum, Bool()))
// instance and connect IMSICGateWay.
val imsicGateWay = Module(new IMSICGateWay)
imsicGateWay.msiio <> msiio
val pendings = Wire(Vec(params.intFilesNum,Bool()))
val vec_rdata = Wire(Vec(params.intFilesNum, ValidIO(UInt(params.xlen.W))))
Seq(1, 1 + params.geilen).zipWithIndex.map {
case (intFilesNum: Int, i: Int) => {
// j: index for S intFile: S, G1, G2, ...
val maps = (0 until intFilesNum).map { j =>
val flati = i + j
val pi = if (flati > 2) 2 else flati // index for privileges: M, S, VS.
def sel_addr(old: AddrBundle): AddrBundle = {
val new_ = Wire(new AddrBundle(params))
new_.valid := old.valid & intFilesSelOH(flati)
new_.bits.addr := old.bits.addr
new_.bits.virt := old.bits.virt
new_.bits.priv := old.bits.priv
new_
}
def sel_wdata[T <: Data](old: Valid[T]): Valid[T] = {
val new_ = Wire(Valid(chiselTypeOf(old.bits)))
new_.bits := old.bits
new_.valid := old.valid & intFilesSelOH(flati)
new_
}
val intFile = Module(new IntFile)
val toCSR_rdata = RegNext(intFile.toCSR.rdata)
intFile.fromCSR.seteipnum.bits := imsicGateWay.msi_data_o
intFile.fromCSR.seteipnum.valid := imsicGateWay.msi_valid_o(flati)
intFile.fromCSR.addr.valid := sel_addr(fromCSR.addr).valid
intFile.fromCSR.addr.bits := sel_addr(fromCSR.addr).bits.addr
intFile.fromCSR.wdata := sel_wdata(fromCSR.wdata)
intFile.fromCSR.claim := fromCSR.claims(pi)
vec_rdata(flati) := toCSR_rdata
pendings(flati) := intFile.toCSR.pending
topeis_forEachIntFiles(flati) := intFile.toCSR.topei
illegals_forEachIntFiles(flati) := intFile.toCSR.illegal
}
}
}
toCSR.rdata.valid := vec_rdata.map(_.valid).reduce(_|_)
toCSR.rdata.bits := vec_rdata.map(_.bits).reduce(_|_)
toCSR.pendings := (pendings.zipWithIndex.map{case (p,i) => p << i.U}).reduce(_ | _) //vector -> multi-bit
locally {
// Format of *topei:
// * bits 26:16 Interrupt identity
// * bits 10:0 Interrupt priority (same as identity)
// * All other bit positions are zeros.
// For detailed explainations of these memory region arguments,
// please refer to the manual *The RISC-V Advanced Interrupt Architeture*: 3.9. Top external interrupt CSRs
def wrap(topei: UInt): UInt = {
val zeros = 0.U((16 - params.imsicIntSrcWidth).W)
Cat(zeros, topei, zeros, topei)
}
toCSR.topeis(0) := wrap(topeis_forEachIntFiles(0)) // m
toCSR.topeis(1) := wrap(topeis_forEachIntFiles(1)) // s
toCSR.topeis(2) := wrap(ParallelMux(
UIntToOH(fromCSR.vgein - 1.U, params.geilen).asBools,
topeis_forEachIntFiles.drop(2)
)) // vs
// UIntToOH(0,geilen=5) -> 00001
// vgein = 1 - 对应第一个vs - 如果drop(2),那么就应该是 00001
//
}
val illegal_fromCSR_num = WireDefault(false.B)
when(fromCSR.addr.bits.virt === true.B && fromCSR.vgein === 0.U) { illegal_fromCSR_num := true.B }
toCSR.illegal := (fromCSR.addr.valid | fromCSR.wdata.valid) & Seq(
illegals_forEachIntFiles.reduce(_ | _),
fromCSR.vgein >= params.geilen.asUInt + 1.U,
illegal_fromCSR_num,
illegal_priv
).reduce(_ | _)
}
//define IMSIC_WRAP: instance one imsic when HasCVMExtention is supported, else instance two imsic modules.
class IMSIC_WRAP(
params: IMSICParams,
beatBytes: Int = 4
)(implicit p: Parameters) extends Module {
// define the ports
val toCSR = IO(Output(new IMSICToCSRBundle(params)))
val fromCSR = IO(Input(new CSRToIMSICBundle(params)))
val msiio = IO(new MSITransBundle(params))
// define additional ports when HasCVMExtention is supported.
val sec = if (params.HasTEEIMSIC) Some(IO(new ForCVMBundle()))
else None // include cmode input port,and o_notice_pending output port.
val teemsiio = if (params.HasTEEIMSIC) Some(IO(new MSITransBundle(params))) else None
// instance module,and body logic
private val imsic = Module(new IMSIC(params, beatBytes))
imsic.fromCSR := fromCSR
toCSR := imsic.toCSR
imsic.msiio <> msiio
// define additional logic for sec extention
// .foreach logic only happens when sec is not none.
sec.foreach { secIO =>
// get the sec.mode, connect sec.o_notice_pending to top.
val cmode = Wire(Bool())
val notice_pending = Wire(Bool())
cmode := secIO.cmode
secIO.notice_pending := notice_pending
// instance tee imsic module.
val teeimsic = Module(new IMSIC(params, beatBytes))
teemsiio.foreach(teemsiio => teeimsic.msiio <> teemsiio)
toCSR.rdata := Mux(cmode, teeimsic.toCSR.rdata, imsic.toCSR.rdata) // toCSR needs to the selected depending cmode.
toCSR.illegal := Mux(cmode, teeimsic.toCSR.illegal, imsic.toCSR.illegal)
val s_pendings = Mux(cmode, teeimsic.toCSR.pendings(params.intFilesNum-1,1), imsic.toCSR.pendings(params.intFilesNum-1,1))
val m_pendings = imsic.toCSR.pendings(0) // machine mode only from imsic.
toCSR.pendings := Cat(s_pendings,m_pendings)
// toCSR.pendings := VecInit((0 until params.intFilesNum).map(i => pendings(i))) // uint->vector
toCSR.topeis := Mux(cmode, teeimsic.toCSR.topeis, imsic.toCSR.topeis)
toCSR.topeis(0) := imsic.toCSR.topeis(0) // machine mode only from imsic.
// to get the o_notice_pending, excluding the machine interrupt
// val s_orpend_ree = imsic.toCSR.pendings.slice(1, params.intFilesNum) // extract the | of vector(1,N-1)
// val s_orpend_tee = teeimsic.toCSR.pendings.slice(1, params.intFilesNum)
// notice_pending := Mux(cmode, s_orpend_ree.reduce(_ | _), s_orpend_tee.reduce(_ | _))
val s_orpend_ree = imsic.toCSR.pendings(params.intFilesNum-1,1) // extract the | of vector(1,N-1)
val s_orpend_tee = teeimsic.toCSR.pendings(params.intFilesNum-1,1) //bit(params.intFilesNum-1:1)
notice_pending := Mux(cmode, s_orpend_ree.orR, s_orpend_tee.orR)
teeimsic.fromCSR := fromCSR
teeimsic.fromCSR.addr.valid := cmode & fromCSR.addr.valid // cmode=1,controls tee csr access to interrupt file indirectly
teeimsic.fromCSR.wdata.valid := cmode & fromCSR.wdata.valid
teeimsic.fromCSR.claims(0) := false.B // machine interrupts are inactive for tee imsic.
for (i <- 1 until params.privNum) {
teeimsic.fromCSR.claims(i) := cmode & fromCSR.claims(i)
}
imsic.fromCSR.addr.valid := (cmode === false.B) & fromCSR.addr.valid // cmode=1,controls tee csr access to interrupt file indirectly
imsic.fromCSR.wdata.valid := (cmode === false.B) & fromCSR.wdata.valid
imsic.fromCSR.claims(0) := fromCSR.claims(0) // machine interrupts are inactive for tee imsic.
for (i <- 1 until params.privNum) {
imsic.fromCSR.claims(i) := (cmode === false.B) & fromCSR.claims(i)
}
}
}
//generate TLIMSIC top module:including TLRegIMSIC_WRAP and IMSIC_WRAP
class TLIMSIC(
params: IMSICParams,
beatBytes: Int = 4
// asyncQueueParams: AsyncQueueParams
)(implicit p: Parameters) extends LazyModule with HasIMSICParameters {
val axireg = LazyModule(new TLRegIMSIC_WRAP(IMSICParams(HasTEEIMSIC = GHasTEEIMSIC), beatBytes))
lazy val module = new Imp
class Imp extends LazyModuleImp(this) {
val toCSR = IO(Output(new IMSICToCSRBundle(params)))
val fromCSR = IO(Input(new CSRToIMSICBundle(params)))
private val imsic = Module(new IMSIC_WRAP(IMSICParams(HasTEEIMSIC = GHasTEEIMSIC), beatBytes))
toCSR := imsic.toCSR
imsic.fromCSR := fromCSR
axireg.module.msiio <> imsic.msiio // msi_req/msi_ack interconnect
// define additional ports for cvm extention
val io_sec = if (GHasTEEIMSIC) Some(IO(new ForCVMBundle()))
else None // include cmode input port,and o_notice_pending output port.
/* code on when imsic has two clock domains.*/
// --- define soc_clock for imsic bus logic ***//
val soc_clock = IO(Input(Clock()))
val soc_reset = IO(Input(Reset()))
axireg.module.clock := soc_clock
axireg.module.reset := soc_reset
imsic.clock := clock
imsic.reset := reset
axireg.module.msiio <> imsic.msiio // msi_req/msi_ack interconnect
// code will be compiled only when io_sec is not None.
io_sec.foreach(iosec => imsic.sec.foreach(imsicsec => imsicsec <> iosec))
// code will be compiled only when tee_axireg is not None.
axireg.module.teemsiio.foreach(tee_msi_trans => imsic.teemsiio.foreach(teemsiio => tee_msi_trans <> teemsiio))
}
}
class AXI4IMSIC(
params: IMSICParams,
beatBytes: Int = 4
)(implicit p: Parameters) extends LazyModule with HasIMSICParameters {
val axireg = LazyModule(new AXIRegIMSIC_WRAP(IMSICParams(HasTEEIMSIC = GHasTEEIMSIC), beatBytes))
lazy val module = new Imp
class Imp extends LazyModuleImp(this) {
val toCSR = IO(Output(new IMSICToCSRBundle(params)))
val fromCSR = IO(Input(new CSRToIMSICBundle(params)))
private val imsic = Module(new IMSIC_WRAP(IMSICParams(HasTEEIMSIC = GHasTEEIMSIC), beatBytes))
toCSR := imsic.toCSR
imsic.fromCSR := fromCSR
axireg.module.msiio <> imsic.msiio // msi_req/msi_ack interconnect
// define additional ports for cvm extention
val io_sec = if (GHasTEEIMSIC) Some(IO(new ForCVMBundle()))
else None // include cmode input port,and o_notice_pending output port.
/* code on when imsic has two clock domains.*/
// --- define soc_clock for imsic bus logic ***//
val soc_clock = IO(Input(Clock()))
val soc_reset = IO(Input(Reset()))
axireg.module.clock := soc_clock
axireg.module.reset := soc_reset
imsic.clock := clock
imsic.reset := reset
// code will be compiled only when io_sec is not None.
io_sec.foreach(iosec => imsic.sec.foreach(imsicsec => imsicsec <> iosec))
// code will be compiled only when tee_axireg is not None.
axireg.module.teemsiio.foreach(tee_msi_trans => imsic.teemsiio.foreach(teemsiio => tee_msi_trans <> teemsiio))
}
}
// code below is for SEC IMSIC spec
//generate TLRegIMSIC_WRAP for IMSIC, when HasCVMExtention is supported, IMSIC is instantiated by two times,else only one
class TLRegIMSIC_WRAP(
params: IMSICParams,
beatBytes: Int = 4
)(implicit p: Parameters) extends LazyModule {
val axireg = LazyModule(new TLRegIMSIC(params, beatBytes)(Parameters.empty))
val tee_axireg =
if (params.HasTEEIMSIC) Some(LazyModule(new TLRegIMSIC(params, beatBytes)(Parameters.empty))) else None
lazy val module = new TLRegIMSICImp(this)
class TLRegIMSICImp(outer: LazyModule) extends LazyModuleImp(outer) {
val msiio = IO(Flipped(new MSITransBundle(params)))
msiio <> axireg.module.msiio
val teemsiio = if (params.HasTEEIMSIC) Some(IO(Flipped(new MSITransBundle(params))))
else None // backpressure signal for axi4bus, from imsic working on cpu clock
// code below will be compiled only when teeio is not none.
teemsiio.foreach(teemsiio => tee_axireg.foreach(tee_axireg => teemsiio <> tee_axireg.module.msiio))
}
}
//generate AXIRegIMSIC_WRAP for IMSIC, when HasCVMExtention is supported, IMSIC is instantiated by two times,else only one
class AXIRegIMSIC_WRAP(
params: IMSICParams,
beatBytes: Int = 4
)(implicit p: Parameters) extends LazyModule {
val axireg = LazyModule(new AXIRegIMSIC(params, beatBytes)(Parameters.empty))
// val tee_axireg = if (params.HasTEEIMSIC) Some(LazyModule(new AXIRegIMSIC(IMSICParams(teemode = true), beatBytes)(Parameters.empty))) else None
val tee_axireg =
if (params.HasTEEIMSIC) Some(LazyModule(new AXIRegIMSIC(params, beatBytes)(Parameters.empty))) else None
lazy val module = new AXIRegIMSICImp(this)
class AXIRegIMSICImp(outer: LazyModule) extends LazyModuleImp(outer) {
val msiio = IO(Flipped(new MSITransBundle(params))) // backpressure signal for axi4bus, from imsic working on cpu clock
msiio <> axireg.module.msiio
val teemsiio = if (params.HasTEEIMSIC) Some(IO(Flipped(new MSITransBundle(params))))
else None // backpressure signal for axi4bus, from imsic working on cpu clock
// code below will be compiled only when teeio is not none.
teemsiio.foreach(teemsiio => tee_axireg.foreach(tee_axireg => teemsiio <> tee_axireg.module.msiio))
}
}
class TLRegIMSIC(
params: IMSICParams,
beatBytes: Int = 4,
seperateBus: Boolean = false
)(implicit p: Parameters) extends LazyModule {
val fromMem = Seq.fill(if (seperateBus) 2 else 1)(TLXbar())
// val fromMem = LazyModule(new TLXbar).node
private val intfileFromMems = Seq(
AddressSet(params.mAddr, pow2(params.intFileMemWidth) - 1),
AddressSet(params.sgAddr, pow2(params.intFileMemWidth) * pow2(log2Ceil(1 + params.geilen)) - 1)
).zipWithIndex.map { case (addrset, i) =>
val intfileFromMem = TLRegMapperNode(
address = Seq(addrset),
beatBytes = beatBytes
)
intfileFromMem := (if (seperateBus) fromMem(i) else fromMem.head)
intfileFromMem
}
lazy val module = new TLRegIMSICImp(this)
class TLRegIMSICImp(outer: LazyModule) extends LazyModuleImp(outer) {
val msiio = IO(Flipped(new MSITransBundle(params))) // backpressure signal for axi4bus, from imsic working on cpu clock
private val reggen = Module(new RegGen(params, beatBytes))
// ---- instance sync fifo ----//
// --- fifo wdata: {vector_valid,setipnum}, fifo wren: |vector_valid---//
val FifoDataWidth = params.MSI_INFO_WIDTH
val fifo_wdata = Wire(Valid(UInt(FifoDataWidth.W)))
// depth:8, data width: FifoDataWidth
private val fifo_sync = Module(new Queue(UInt(FifoDataWidth.W), 8))
// define about fifo write
fifo_wdata.bits := reggen.io.seteipnum
fifo_wdata.valid := reggen.io.valid
fifo_sync.io.enq.valid := fifo_wdata.valid
fifo_sync.io.enq.bits := fifo_wdata.bits
// fifo rd,controlled by msi_vld_ack from imsic working on csr clock.
// msi_vld_ack_soc: sync result with soc clock
val msi_vld_ack_soc = WireInit(false.B)
val msi_vld_ack_cpu = msiio.vld_ack
val msi_vld_req = RegInit(false.B)
when(params.EnableImsicAsyncBridge.B) {
msi_vld_ack_soc := AsyncResetSynchronizerShiftReg(msi_vld_ack_cpu, 3, 0)
}.otherwise {
msi_vld_ack_soc := msi_vld_ack_cpu
}
fifo_sync.io.deq.ready := ~msi_vld_req
// generate the msi_vld_req: high if ~empty,low when msi_vld_ack_soc
msiio.vld_req := msi_vld_req
val msi_vld_ack_soc_1f = RegNext(msi_vld_ack_soc)
val msi_vld_ack_soc_ris = msi_vld_ack_soc & (~msi_vld_ack_soc_1f)
// val fifo_empty = ~fifo_sync.io.deq.valid
// msi_vld_req : high when fifo empty is false, low when ack is high. and io.deq.valid := ~empty
when(fifo_sync.io.deq.valid === true.B) {
msi_vld_req := true.B
}.elsewhen(msi_vld_ack_soc_ris) {
msi_vld_req := false.B
}.otherwise {
msi_vld_req := msi_vld_req
}
// get the msi interrupt ID info
val msi_id_data = RegInit(0.U(params.MSI_INFO_WIDTH.W))
val rdata_vld = fifo_sync.io.deq.fire // assign to fifo rdata
when(rdata_vld) { // fire: io.deq.valid & io.deq.ready
msi_id_data := fifo_sync.io.deq.bits(params.MSI_INFO_WIDTH - 1, 0)
}.otherwise {
msi_id_data := msi_id_data
}
// port connect: io.valid is interrupt file index info.
msiio.data := msi_id_data
val backpress = fifo_sync.io.enq.ready
(intfileFromMems zip reggen.regmapIOs).map {
case (intfileFromMem, regmapIO) => intfileFromMem.regmap(regmapIO._1, regmapIO._2, backpress)
}
}
}
//generate axi42reg for IMSIC
class AXIRegIMSIC(
params: IMSICParams,
beatBytes: Int = 4,
seperateBus: Boolean = false
)(implicit p: Parameters) extends LazyModule {
val fromMem = Seq.fill(if (seperateBus) 2 else 1)(AXI4Xbar())
val axi4tolite = Seq.fill(if (seperateBus) 2 else 1)(LazyModule(new AXI4ToLite()(Parameters.empty)))
fromMem zip axi4tolite.map(_.node) foreach (x => x._1 := x._2)
private val intfileFromMems = Seq(
AddressSet(params.mAddr, pow2(params.intFileMemWidth) - 1),
AddressSet(params.sgAddr, pow2(params.intFileMemWidth) * pow2(log2Ceil(1 + params.geilen)) - 1)
).zipWithIndex.map { case (addrset, i) =>
val intfileFromMem = AXI4RegMapperNode(
address = addrset,
beatBytes = beatBytes
)
intfileFromMem := (if (seperateBus) fromMem(i) else fromMem.head)
intfileFromMem
}
lazy val module = new AXIRegIMSICImp(this)
class AXIRegIMSICImp(outer: LazyModule) extends LazyModuleImp(outer) {
val msiio = IO(Flipped(new MSITransBundle(params))) // backpressure signal for axi4bus, from imsic working on cpu clock
private val reggen = Module(new RegGen(params, beatBytes))
// ---- instance sync fifo ----//
// --- fifo wdata: {vector_valid,setipnum}, fifo wren: |vector_valid---//
val FifoDataWidth = params.MSI_INFO_WIDTH
val fifo_wdata = Wire(Valid(UInt(FifoDataWidth.W)))
// depth:8, data width: FifoDataWidth
private val fifo_sync = Module(new Queue(UInt(FifoDataWidth.W), 8))
// define about fifo write
fifo_wdata.bits := reggen.io.seteipnum
fifo_wdata.valid := reggen.io.valid
fifo_sync.io.enq.valid := fifo_wdata.valid
fifo_sync.io.enq.bits := fifo_wdata.bits
// fifo rd,controlled by msi_vld_ack from imsic working on csr clock.
// msi_vld_ack_soc: sync result with soc clock
val msi_vld_ack_soc = WireInit(false.B)
val msi_vld_ack_cpu = msiio.vld_ack
val msi_vld_req = RegInit(false.B)
when(params.EnableImsicAsyncBridge.B) {
msi_vld_ack_soc := AsyncResetSynchronizerShiftReg(msi_vld_ack_cpu, 3, 0)
}.otherwise {
msi_vld_ack_soc := msi_vld_ack_cpu
}
fifo_sync.io.deq.ready := ~msi_vld_req
// generate the msi_vld_req: high if ~empty,low when msi_vld_ack_soc
msiio.vld_req := msi_vld_req
val msi_vld_ack_soc_1f = RegNext(msi_vld_ack_soc)
val msi_vld_ack_soc_ris = msi_vld_ack_soc & (~msi_vld_ack_soc_1f)
// val fifo_empty = ~fifo_sync.io.deq.valid
// msi_vld_req : high when fifo empty is false, low when ack is high. and io.deq.valid := ~empty
when(fifo_sync.io.deq.valid === true.B) {
msi_vld_req := true.B
}.elsewhen(msi_vld_ack_soc_ris) {
msi_vld_req := false.B
}.otherwise {
msi_vld_req := msi_vld_req
}
// get the msi interrupt ID info
val msi_id_data = RegInit(0.U(params.MSI_INFO_WIDTH.W))
val rdata_vld = fifo_sync.io.deq.fire // assign to fifo rdata
when(rdata_vld) { // fire: io.deq.valid & io.deq.ready
msi_id_data := fifo_sync.io.deq.bits(params.MSI_INFO_WIDTH - 1, 0)
}.otherwise {
msi_id_data := msi_id_data
}
// port connect: io.valid is interrupt file index info.
msiio.data := msi_id_data
val backpress = fifo_sync.io.enq.ready
(intfileFromMems zip reggen.regmapIOs).map {
case (intfileFromMem, regmapIO) => intfileFromMem.regmap(regmapIO._1, regmapIO._2, backpress)
}
}
}
//integrated for async clock domain,kmh,zhaohong
class RegGen(
params: IMSICParams,
beatBytes: Int = 4
) extends Module {
val regmapIOs = Seq(
params.intFileMemWidth,
params.intFileMemWidth + log2Ceil(1 + params.geilen)
).map { width =>
val regmapParams = RegMapperParams(width - log2Up(beatBytes), beatBytes)
(IO(Flipped(Decoupled(new RegMapperInput(regmapParams)))), IO(Decoupled(new RegMapperOutput(regmapParams))))
}
// define the output reg: seteipnum is the MSI id,vld[],valid flag for interrupt file domains: m,s,vs1~vsgeilen
val io = IO(Output(new Bundle {
val seteipnum = UInt(params.MSI_INFO_WIDTH.W)
val valid = Bool()
}))
val valids = WireInit(VecInit(Seq.fill(params.intFilesNum)(false.B)))
val seteipnums = WireInit(VecInit(Seq.fill(params.intFilesNum)(0.U(params.imsicIntSrcWidth.W))))
val outseteipnum = RegInit(0.U(params.MSI_INFO_WIDTH.W))
val outvalids = RegInit(VecInit(Seq.fill(params.intFilesNum)(false.B)))
(regmapIOs zip Seq(1, 1 + params.geilen)).zipWithIndex.map { // seq[0]: m interrupt file, seq[1]: s&vs interrupt file
case ((regmapIO: (DecoupledIO[RegMapperInput], DecoupledIO[RegMapperOutput]), intFilesNum: Int), i: Int) =>
{
// j: index is 0 for m file for seq[0],index is 0~params.geilen for S intFile for seq[1]: S, G1, G2, ...
val maps = (0 until intFilesNum).map { j =>
val flati = i + j // seq[0]:0+0=0;seq[1]:(0~geilen)+1
val seteipnum = WireInit(0.U.asTypeOf(Valid(UInt(params.imsicIntSrcWidth.W)))); /*for debug*/
dontTouch(seteipnum)
valids(flati) := seteipnum.valid
seteipnums(flati) := seteipnum.bits
j * pow2(params.intFileMemWidth).toInt -> Seq(RegField(
32,
0.U,
RegWriteFn { (valid, data) =>
when(valid) { seteipnum.bits := data(params.imsicIntSrcWidth - 1, 0); seteipnum.valid := true.B }; true.B
}
))
}
regmapIO._2 <> RegMapper(beatBytes, 1, true, regmapIO._1, maps: _*)
}
for (i <- 0 until params.intFilesNum) {
when(valids(i)) {
outseteipnum := Cat(i.U, seteipnums(i))
}
}
outvalids := valids
io.seteipnum := outseteipnum
io.valid := outvalids.reduce(_ | _)
}
}```
### `APLICParams`
APLIC接收的中断源数量的对数。
默认值7表示APLIC支持最多128(2^7)个中断源。
**注意**:`aplicIntSrcWidth`必须小于`imsicIntSrcWidth`,
因为APLIC的中断源将被转换为MSI,
而APLIC转换成的MSI是IMSIC中断源的子集。
(Logarithm of number of interrupt sources to APLIC:
The default 7 means APLIC support at most 128 (2^7) interrupt sources.
**Note**: `aplicIntSrcWidth` must be **less than** `imsicIntSrcWidth`,
as APLIC interrupt sources are converted to MSIs,
which are a subset of IMSIC's interrupt sources):
```scala
aplicIntSrcWidth: Int = 7,
imsicIntSrcWidth: Int = 9,
👉 APLIC域的基地址(Base address of APLIC domains):
baseAddr: Long = 0x19960000L,
注意:下述中括号内的变量与AIA规范中的一致(第3.6节:用于多个中断文件的内存区域排列)。
Note: The following variables in bracket align with the AIA specification (Section 3.6: Memory Region Arrangement for Multiple Interrupt Files).
👉 每个组的成员数量(Number of members per group)[\(h_{max}\)]:
membersNum : Int = 2 ,
👉 所有IMSIC的机器态中断文件的基地址(Base address of machine-level interrupt files for all IMSICs)[\(A\)]:
mBaseAddr : Long = 0x61000000L ,
👉 所有IMSIC的监管态和客户态中断文件的基地址(Base addr for supervisor-level and guest-level interrupt files for all IMSICs)[\(B\)]:
sgBaseAddr : Long = 0x82900000L ,
👉 组的数量(Number of groups )[\(g_{max}\)]:
groupsNum : Int = 1 ,
👉 客户中断文件的数量(Number of guest interrupt files):
geilen : Int = 5 ,
实例化(Instantiation)
-
APLICParams
和IMSICParams
:- 每个类一个实例,
- 根据参数部分的说明,实例化参数。
-
TLAPLIC
/AXI4APLIC
:- 单个实例,
- 参数
params
:接收APLICParams
的实例,
-
TLIMSIC
/AXI4IMSIC
:- 每个核心一个实例,
- 参数
params
:接收IMSICParams
的实例,
-
APLICParams
andIMSICParams
:- Single instance each,
- Instantiation parameters according to Parameters section.
-
TLAPLIC
/AXI4APLIC
:- Single instance,
- Parameter
params
: receiving theAPLICParams
's instance,
-
TLIMSIC
/AXI4IMSIC
:- One instance per hart,
- Parameter
params
: receiving theIMSICParams
's instance,
关于hartIndex(About hartIndex)
根据AIA规范: AIA的hart编号 可能与RISC-V特权架构分配给hart的唯一 hart标识符(“hart ID”)无关。 在ChiselAIA中,hartIndex编码为groupID拼接上memberID。
According to the AIA specification:
The AIA's hart index may or
may not have any relationship to the unique
hart identifier ("hart ID")
that the RISC-V Privileged Architecture assigns to the hart.
In ChiselAIA, the hartIndex is encoded as a concatenation of groupID
and memberID
:
示例(Examples)
简单的4核系统(A Simple 4-Hart System)
对于一个简单的未分组系统,设置groupsNum=1,则可以将hart ID复用作为AIA的`hartIndex:
For a simple ungrouped system, set groupsNum=1 to allow reuse of hart ID as AIA's hartIndex
:
val imsic_params = IMSICParams()
val aplic_params = APLICParams(groupsNum=1, membersNum=4)
val imsics = (0 until 4).map( i => {
val imsic = LazyModule(new TLIMSIC(imsic_params)(Parameters.empty))
val aplic = LazyModule(new TLAPLIC(aplic_params)(Parameters.empty))
分组的4核系统(A Grouped 4-Hart System)
在src/main/scala/Example.AIA
和src/main/scala/Example-axi.scala
中,我们提供了一个如何实例化APLIC核IMSIC的示例
(我们的单元测试也是基于该示例)。
以Tilelink为例,我们接下来展示一些关键的代码:
We provide an example of instantiating the APLIC and IMSIC, in src/main/scala/Example.AIA
and src/main/scala/Example-axi.scala
(Furthermore, we will use this example to conduct unit tests.).
Take Tilelink as an example, we provide key lines of code below:
val imsic_params = IMSICParams()
val aplic_params = APLICParams(groupsNum=2, membersNum=2)
val imsics = (0 until 4).map( i => {
val imsic = LazyModule(new TLIMSIC(imsic_params)(Parameters.empty))
val aplic = LazyModule(new TLAPLIC(aplic_params)(Parameters.empty))
此配置创建了一个2位的hartIndex
,高位表示 groupID,低位表示 memberID。
有关详细的IO连接,请参考下图和src/main/scala/Example.AIA
。
This configuration creates a 2-bit hartIndex
where the higher bit represents groupID
and the lower bit represents memberID
.
For detailed IO connections, refer to the following figure and src/main/scala/Example.AIA
.