Files
radiance/src/main/scala/tile/VortexTile.scala
Hansung Kim eb9772b750 Decouple Vortex dmem bundle from TL
Previously VortexBundle was being instantiated using the parameters of
the TileLink bundle from VortexTile.  This results in tight coupling
between Vortex interface parameters and downstream TileLink parameters.
This change adds a standalone Bundle used by the VortexCore wrapper
and is independently instantiated from the TL params, i.e. different
source widths.  Ideally we want to move away from using TL-like
structures for VortexBundle and handling adapter logic completely
outside the core blackbox.
2023-10-16 17:42:17 -07:00

442 lines
16 KiB
Scala

// See LICENSE.SiFive for license details.
// See LICENSE.Berkeley for license details.
package tile
import chisel3._
import chisel3.util._
import org.chipsalliance.cde.config._
import freechips.rocketchip.devices.tilelink._
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.interrupts._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.rocket._
import freechips.rocketchip.subsystem.TileCrossingParamsLike
import freechips.rocketchip.util._
import freechips.rocketchip.prci.ClockSinkParameters
import freechips.rocketchip.regmapper.RegField
import freechips.rocketchip.tile._
import rocket.Vortex
case class RocketTileBoundaryBufferParams(force: Boolean = false)
case class VortexTileParams(
core: RocketCoreParams = RocketCoreParams(),
useVxCache: Boolean = false,
icache: Option[ICacheParams] = None /* Some(ICacheParams()) */,
dcache: Option[DCacheParams] = None /* Some(DCacheParams()) */,
btb: Option[BTBParams] = None, // Some(BTBParams()),
dataScratchpadBytes: Int = 0,
name: Option[String] = Some("vortex_tile"),
hartId: Int = 0,
beuAddr: Option[BigInt] = None,
blockerCtrlAddr: Option[BigInt] = None,
clockSinkParams: ClockSinkParameters = ClockSinkParameters(),
boundaryBuffers: Option[RocketTileBoundaryBufferParams] = None
) extends InstantiableTileParams[VortexTile] {
// require(icache.isDefined)
// require(dcache.isDefined)
def instantiate(crossing: TileCrossingParamsLike, lookup: LookupByHartIdImpl)(
implicit p: Parameters
): VortexTile = {
new VortexTile(this, crossing, lookup)
}
}
class VortexBundleA extends Bundle {
val opcode = UInt(3.W) // FIXME: hardcoded
val size = UInt(4.W) // FIXME: hardcoded
val source = UInt(10.W) // FIXME: hardcoded
val address = UInt(32.W) // FIXME: hardcoded
val mask = UInt(4.W) // FIXME: hardcoded
val data = UInt(32.W) // FIXME: hardcoded
}
class VortexBundleD extends Bundle {
val opcode = UInt(3.W) // FIXME: hardcoded
val size = UInt(4.W) // FIXME: hardcoded
val source = UInt(10.W) // FIXME: hardcoded
val data = UInt(32.W) // FIXME: hardcoded
}
class VortexTile private (
val vortexParams: VortexTileParams,
crossing: ClockCrossingType,
lookup: LookupByHartIdImpl,
q: Parameters
) extends BaseTile(vortexParams, crossing, lookup, q)
with SinksExternalInterrupts
with SourcesExternalNotifications {
// Private constructor ensures altered LazyModule.p is used implicitly
def this(
params: VortexTileParams,
crossing: TileCrossingParamsLike,
lookup: LookupByHartIdImpl
)(implicit p: Parameters) =
this(params, crossing.crossingType, lookup, p)
val intOutwardNode = IntIdentityNode()
val slaveNode = TLIdentityNode()
val masterNode = visibilityNode
val regDevice = new SimpleDevice("vortex-reg", Seq("vortex-reg"))
val regNode = TLRegisterNode(
address = Seq(AddressSet(0x7c000000, 0xfff)),
device = regDevice,
beatBytes = 4,
concurrency = 1)
regNode := tlSlaveXbar.node
// val dmemDevice = new SimpleDevice("dtim", Seq("sifive,dtim0"))
/*val dmemNode = TLManagerNode(Seq(TLSlavePortParameters.v1(
Seq(TLSlaveParameters.v1(
address = AddressSet.misaligned(tileParams.dcache.get.scratch.getOrElse(0),
tileParams.dcache.get.nSets * tileParams.dcache.get.blockBytes),
resources = dmemDevice.reg("mem"),
regionType = RegionType.IDEMPOTENT,
executable = true,
supportsArithmetic = /*if (usingAtomics) TransferSizes(4, coreDataBytes) else*/ TransferSizes.none,
supportsLogical = /*if (usingAtomics) TransferSizes(4, coreDataBytes) else*/ TransferSizes.none,
supportsPutPartial = TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsPutFull = TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsGet = TransferSizes(1, lazyCoreParamsView.coreDataBytes),
fifoId = Some(0))), // requests handled in FIFO order
beatBytes = lazyCoreParamsView.coreDataBytes,
minLatency = 1)))*/
val numLanes = 4 // FIXME: hardcoded
val sourceWidth = 1 // TODO: use Parameters for this
val imemNodes = Seq.tabulate(1) { i =>
TLClientNode(
Seq(
TLMasterPortParameters.v1(
clients = Seq(
TLMasterParameters.v1(
sourceId = IdRange(0, 1 << 10), // TODO: magic numbers
name = s"Vortex Core ${vortexParams.hartId} I-Mem $i",
requestFifo = true,
supportsProbe =
TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsGet = TransferSizes(1, lazyCoreParamsView.coreDataBytes)
)
)
)
)
)
}
val dmemNodes = Seq.tabulate(numLanes) { i =>
TLClientNode(
Seq(
TLMasterPortParameters.v1(
clients = Seq(
TLMasterParameters.v1(
sourceId = IdRange(0, 1 << sourceWidth),
name = s"Vortex Core ${vortexParams.hartId} D-Mem Lane $i",
requestFifo = true,
supportsProbe =
TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsGet = TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsPutFull =
TransferSizes(1, lazyCoreParamsView.coreDataBytes),
supportsPutPartial =
TransferSizes(1, lazyCoreParamsView.coreDataBytes)
)
)
)
)
)
}
val memNode = TLClientNode(
Seq(
TLMasterPortParameters.v1(
clients = Seq(
TLMasterParameters.v1(
sourceId = IdRange(0, 1 << 15), // TODO magic numbers
name = s"Vortex Core ${vortexParams.hartId} Mem Interface",
requestFifo = true,
supportsProbe = TransferSizes(16, 16),
supportsGet = TransferSizes(16, 16),
supportsPutFull = TransferSizes(16, 16),
supportsPutPartial = TransferSizes(16, 16)
)
)
)
)
)
if (vortexParams.useVxCache) {
tlMasterXbar.node := TLWidthWidget(16) := memNode
} else {
imemNodes.foreach { tlMasterXbar.node := _ }
dmemNodes.foreach { tlMasterXbar.node := _ }
}
/* below are copied from rocket */
val bus_error_unit = vortexParams.beuAddr map { a =>
val beu =
LazyModule(new BusErrorUnit(new L1BusErrors, BusErrorUnitParams(a)))
intOutwardNode := beu.intNode
connectTLSlave(beu.node, xBytes)
beu
}
val tile_master_blocker =
tileParams.blockerCtrlAddr
.map(
BasicBusBlockerParams(_, xBytes, masterPortBeatBytes, deadlock = true)
)
.map(bp => LazyModule(new BasicBusBlocker(bp)))
tile_master_blocker.foreach(lm => connectTLSlave(lm.controlNode, xBytes))
// TODO: this doesn't block other masters, e.g. RoCCs
tlOtherMastersNode := tile_master_blocker.map {
_.node := tlMasterXbar.node
} getOrElse { tlMasterXbar.node }
masterNode :=* tlOtherMastersNode
DisableMonitors { implicit p => tlSlaveXbar.node :*= slaveNode }
val dtimProperty =
Nil // Seq(dmemDevice.asProperty).flatMap(p => Map("sifive,dtim" -> p))
val itimProperty =
Nil // frontend.icache.itimProperty.toSeq.flatMap(p => Map("sifive,itim" -> p))
val beuProperty = bus_error_unit
.map(d => Map("sifive,buserror" -> d.device.asProperty))
.getOrElse(Nil)
val cpuDevice: SimpleDevice =
new SimpleDevice("cpu", Seq("sifive,vortex0", "riscv")) {
override def parent = Some(ResourceAnchors.cpus)
override def describe(resources: ResourceBindings): Description = {
val Description(name, mapping) = super.describe(resources)
Description(
name,
mapping ++ cpuProperties ++ nextLevelCacheProperty
++ tileProperties ++ dtimProperty ++ itimProperty ++ beuProperty
)
}
}
ResourceBinding {
Resource(cpuDevice, "reg").bind(ResourceAddress(staticIdForMetadataUseOnly))
}
override lazy val module = new VortexTileModuleImp(this)
override def makeMasterBoundaryBuffers(
crossing: ClockCrossingType
)(implicit p: Parameters) = (vortexParams.boundaryBuffers, crossing) match {
case (Some(RocketTileBoundaryBufferParams(true)), _) => TLBuffer()
case (Some(RocketTileBoundaryBufferParams(false)), _: RationalCrossing) =>
TLBuffer(
BufferParams.none,
BufferParams.flow,
BufferParams.none,
BufferParams.flow,
BufferParams(1)
)
case _ => TLBuffer(BufferParams.none)
}
override def makeSlaveBoundaryBuffers(
crossing: ClockCrossingType
)(implicit p: Parameters) = (vortexParams.boundaryBuffers, crossing) match {
case (Some(RocketTileBoundaryBufferParams(true)), _) => TLBuffer()
case (Some(RocketTileBoundaryBufferParams(false)), _: RationalCrossing) =>
TLBuffer(
BufferParams.flow,
BufferParams.none,
BufferParams.none,
BufferParams.none,
BufferParams.none
)
case _ => TLBuffer(BufferParams.none)
}
}
class VortexTileModuleImp(outer: VortexTile) extends BaseTileModuleImp(outer) {
Annotated.params(this, outer.vortexParams)
val core = Module(new Vortex(outer)(outer.p))
core.io.clock := clock
core.io.reset := reset
// reset vector is connected in the Frontend to s2_pc
core.io.reset_vector := DontCare
outer.regNode.regmap(
0x00 -> Seq(RegField.r(32, core.io.cease))
)
// Report when the tile has ceased to retire instructions; for now the only cause is clock gating
outer.reportCease(outer.vortexParams.core.clockGate.option(core.io.cease))
outer.reportWFI(Some(core.io.wfi))
outer.decodeCoreInterrupts(core.io.interrupts) // Decode the interrupt vector
outer.bus_error_unit.foreach { beu =>
core.io.interrupts.buserror.get := beu.module.io.interrupt
}
core.io.interrupts.nmi.foreach { nmi => nmi := outer.nmiSinkNode.bundle }
// Pass through various external constants and reports that were bundle-bridged into the tile
// outer.traceSourceNode.bundle <> core.io.trace
core.io.traceStall := outer.traceAuxSinkNode.bundle.stall
// outer.bpwatchSourceNode.bundle <> core.io.bpwatch
// Copypasted from Rocket; not necessary for Vortex as hartId is set via Verilog parameter
// core.io.hartid := outer.hartIdSinkNode.bundle
// require(core.io.hartid.getWidth >= outer.hartIdSinkNode.bundle.getWidth,
// s"core hartid wire (${core.io.hartid.getWidth}b) truncates external hartid wire (${outer.hartIdSinkNode.bundle.getWidth}b)")
if (outer.vortexParams.useVxCache) {
println(s"width of a channel data ${core.io.mem.get.a.bits.data.getWidth}")
println(s"width of d channel data ${core.io.mem.get.d.bits.data.getWidth}")
core.io.mem.get.a <> outer.memNode.out.head._1.a
core.io.mem.get.d <> outer.memNode.out.head._1.d
} else {
(core.io.imem.get zip outer.imemNodes).foreach { case (coreMem, tileNode) =>
coreMem.d <> tileNode.out.head._1.d
tileNode.out.head._1.a <> coreMem.a
}
// Since the individual per-lane TL requests might come back out-of-sync between
// the lanes, but Vortex core expects the lane requests to be synced,
// we need to selectively fire responses that have the same source, and
// delay others. Below is the logic that implements this.
// choose one source out of the arriving per-lane TL D channels
val arb = Module(
new RRArbiter(core.io.dmem.get.head.d.bits.source.cloneType, outer.numLanes)
)
val dmemTLBundles = outer.dmemNodes.map(_.out.head._1)
arb.io.out.ready := true.B
(arb.io.in zip dmemTLBundles).foreach { case (arbIn, tlBundle) =>
arbIn.valid := tlBundle.d.valid
arbIn.bits := tlBundle.d.bits.source
}
val matchingSources = Wire(UInt(outer.numLanes.W))
matchingSources := dmemTLBundles
.map(b => (b.d.bits.source === arb.io.out.bits) && arb.io.out.valid)
.asUInt
// connection: VortexBundle <--> sourceGen <--> dmemNodes
val sourceGens = Seq.tabulate(outer.numLanes) { _ =>
Module(new VortexTLAdapter(
outer.sourceWidth,
new VortexBundleA(),
new VortexBundleD(),
chiselTypeOf(dmemTLBundles.head.a.bits),
chiselTypeOf(dmemTLBundles.head.d.bits),
))
}
(core.io.dmem.get zip sourceGens) foreach { case (coreMem, sourceGen) =>
sourceGen.io.inReq <> coreMem.a
coreMem.d <> sourceGen.io.inResp
}
(sourceGens zip dmemTLBundles) foreach { case (sourceGen, tlBundle) =>
tlBundle.a <> sourceGen.io.outReq
}
// using the chosen source id,
// - lie to core that response is not valid if source doesn't match picked
// - lie to downstream that core is not ready if source doesn't match picked
(sourceGens zip dmemTLBundles).zipWithIndex.foreach {
case ((sourceGen, tlBundle), i) =>
sourceGen.io.outResp.bits := tlBundle.d.bits
sourceGen.io.outResp.valid := tlBundle.d.valid && matchingSources(i)
tlBundle.d.ready := sourceGen.io.outResp.ready && matchingSources(i)
}
// (core.io.dmem.get zip outer.dmemNodes).foreach { case (coreMem, tileNode) =>
// tileNode.out.head._1.a <> coreMem.a
// }
}
// core.io.fpu := DontCare
// TODO eliminate this redundancy
// val h = dcachePorts.size
// val c = core.dcacheArbPorts
// val o = outer.nDCachePorts
// require(h == c, s"port list size was $h, core expected $c")
// require(h == o, s"port list size was $h, outer counted $o")
// TODO figure out how to move the below into their respective mix-ins
// dcacheArb.io.requestor <> dcachePorts.toSeq
}
// TODO: Currently in/out are assumed to be the same TL bundle with the same
// sourceWidth; this needs to be more flexible.
//
// Some @copypaste from CoalescerSourceGen.
class VortexTLAdapter(
newSourceWidth: Int,
inReqT: VortexBundleA,
inRespT: VortexBundleD,
outReqT: TLBundleA,
outRespT: TLBundleD
) extends Module {
val io = IO(new Bundle {
// in/out means upstream/downstream
// TODO: change inReq/inResp to VortexBundle
val inReq = Flipped(Decoupled(inReqT))
val outReq = Decoupled(outReqT)
val inResp = Decoupled(inRespT)
val outResp = Flipped(Decoupled(outRespT))
})
val sourceGen = Module(new SourceGenerator(
newSourceWidth,
Some(inReqT.source),
ignoreInUse = false
))
sourceGen.io.gen := io.outReq.fire // use up a source ID only when request is created
sourceGen.io.reclaim.valid := io.outResp.fire
sourceGen.io.reclaim.bits := io.outResp.bits.source
sourceGen.io.meta := io.inReq.bits.source
// io passthrough logic
// TLBundleA <> VortexBundleA
io.outReq.valid := io.inReq.valid
io.outReq.bits.opcode := io.inReq.bits.opcode
io.outReq.bits.param := 0.U
io.outReq.bits.size := io.inReq.bits.size
io.outReq.bits.source := io.inReq.bits.source
io.outReq.bits.address := io.inReq.bits.address
io.outReq.bits.mask := io.inReq.bits.mask
io.outReq.bits.data := io.inReq.bits.data
io.outReq.bits.corrupt := 0.U
io.inReq.ready := io.outReq.ready
// VortexBundleD <> TLBundleD
io.inResp.valid := io.outResp.valid
io.inResp.bits.opcode := io.outResp.bits.opcode
io.inResp.bits.size := io.outResp.bits.size
io.inResp.bits.source := io.outResp.bits.source
io.inResp.bits.data := io.outResp.bits.data
io.outResp.ready := io.inResp.ready
// "man-in-the-middle"
io.inReq.ready := io.outReq.ready && sourceGen.io.id.valid
io.outReq.valid := io.inReq.valid && sourceGen.io.id.valid
// FIXME: Fill is a hack; just change downstream to the right sourceWidth
// io.outReq.bits.source := Fill(newSourceWidth, sourceGen.io.id.bits)
io.outReq.bits.source := sourceGen.io.id.bits
// translate upstream response back to its old sourceId
io.inResp.bits.source := sourceGen.io.peek
}
// FIXME: unsure this is necessary
trait HasFpuOpt { this: RocketTileModuleImp =>
val fpuOpt =
outer.tileParams.core.fpu.map(params => Module(new FPU(params)(outer.p)))
}