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.
442 lines
16 KiB
Scala
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)))
|
|
}
|