Files
radiance/src/main/scala/tilelink/Coalescing.scala
2023-03-09 22:56:34 -08:00

303 lines
9.9 KiB
Scala

// See LICENSE.SiFive for license details.
package freechips.rocketchip.tilelink
import chisel3._
import chisel3.util._
import freechips.rocketchip.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.devices.tilelink.TLTestRAM
import freechips.rocketchip.util.ShiftQueue
import freechips.rocketchip.unittest._
class CoalRegEntry(val sourceWidth: Int, val addressWidth: Int) extends Bundle {
val source = UInt(sourceWidth.W)
val address = UInt(addressWidth.W)
val data = UInt(64.W /* FIXME hardcoded */ )
}
class CoalescingUnit(numLanes: Int = 1)(implicit p: Parameters)
extends LazyModule {
// val beatBytes = 8
// val seqParam = Seq(
// TLSlaveParameters.v1(
// address = Seq(AddressSet(0x0000, 0xffffff)),
// // resources = device.reg,
// regionType = RegionType.UNCACHED,
// executable = true,
// supportsArithmetic = TransferSizes(1, beatBytes),
// supportsLogical = TransferSizes(1, beatBytes),
// supportsGet = TransferSizes(1, beatBytes),
// supportsPutFull = TransferSizes(1, beatBytes),
// supportsPutPartial = TransferSizes(1, beatBytes),
// supportsHint = TransferSizes(1, beatBytes),
// fifoId = Some(0)
// )
// )
// Identity node that captures the incoming TL requests and passes them
// through the other end, dropping coalesced requests. This is what the
// upstream node will connect to.
val node = TLIdentityNode()
// Master node that actually generates coalesced requests.
// This and the IdentityNode will be the two outward-facing nodes that the
// downstream, either L1 or the system bus, will connect to.
protected val clientParam = Seq(
TLMasterParameters.v1(
name = "CoalescerNode",
sourceId = IdRange(0, 0xffff)
// visibility = Seq(AddressSet(0x0000, 0xffffff))
)
)
protected val coalescerNode = TLClientNode(
Seq(TLMasterPortParameters.v1(clientParam))
)
// Connect master node as the first of the N+1-th inward edges of the
// IdentityNode
node :=* coalescerNode
lazy val module = new Impl
class Impl extends LazyModuleImp(this) {
// Per-lane FIFO that buffers incoming requests
val sourceWidth = node.in(0)._1.params.sourceBits
val addressWidth = node.in(0)._1.params.addressBits
val coalRegEntry = new CoalRegEntry(sourceWidth, addressWidth)
val fifos = Seq.tabulate(numLanes) { _ =>
Module(
new ShiftQueue(coalRegEntry, 4 /* FIXME hardcoded */ )
)
}
println(s"============= node edges: ${node.in.length}")
// Override IdentityNode implementation so that we wire node output to the
// FIFO output, instead of directly passing through node input.
// See IdentityNode definition in `diplomacy/Nodes.scala`.
(node.in zip node.out).zipWithIndex.foreach {
case (((_, edgeIn), _), 0) =>
// No need to do anything on the edge from coalescerNode
assert(
edgeIn.master.masters(0).name == "CoalescerNode",
"First edge is not connected to the coalescer master node"
)
case (((tlIn, _), (tlOut, edgeOut)), i) =>
val fifo = fifos(i - 1)
val newReq = Wire(coalRegEntry)
newReq.source := tlIn.a.bits.source
newReq.address := tlIn.a.bits.address
newReq.data := tlIn.a.bits.data
fifo.io.enq.valid := tlIn.a.valid
fifo.io.enq.bits := newReq
// FIXME: deq.ready should respect the ready state of the downstream
// module, e.g. Xbar or NoC.
fifo.io.deq.ready := true.B
val head = fifo.io.deq.bits
tlOut.a.valid := fifo.io.deq.valid
// FIXME: generate Get or Put according to read/write
val (legal, bits) = edgeOut.Get(
fromSource = head.source,
// `toAddress` should be aligned to 2**lgSize
toAddress = head.address,
// 64 bits = 8 bytes = 2**(3) bytes
lgSize = 0.U
// data = (i + 100).U
// data = tlIn.a.bits.data + 0xFF.U
)
assert(legal, "unhandled illegal TL req gen")
tlOut.a.bits := bits
tlIn.d <> tlOut.d
dontTouch(tlIn.a)
dontTouch(tlOut.a)
dontTouch(tlOut.d)
}
val (tlCoal, edgeCoal) = coalescerNode.out(0)
// FIXME: currently generating bogus coalesced requests
tlCoal.a.valid := true.B
val (legal, bits) = edgeCoal.Get(
fromSource = 0.U,
// `toAddress` should be aligned to 2**lgSize
toAddress = 0xabcd00.U,
// 64 bits = 8 bytes = 2**(3) bytes
lgSize = 3.U
)
assert(legal, "unhandled illegal TL req gen")
tlCoal.a.bits := bits
val coalRespValid = Wire(Bool())
coalRespValid := tlCoal.a.valid
val coalRespData = Wire(UInt(tlCoal.params.dataBits.W))
coalRespData := tlCoal.d.bits.data
dontTouch(coalRespValid)
dontTouch(coalRespData)
}
}
class MemTraceDriver(numLanes: Int = 1)(implicit p: Parameters)
extends LazyModule {
// Create N client nodes together
val laneNodes = Seq.tabulate(numLanes) { i =>
val clientParam = Seq(
TLMasterParameters.v1(
name = "MemTraceDriver" + i.toString,
sourceId = IdRange(0, 0xffff)
// visibility = Seq(AddressSet(0x0000, 0xffffff))
)
)
TLClientNode(Seq(TLMasterPortParameters.v1(clientParam)))
}
// Combine N outgoing client node into 1 idenity node for diplomatic
// connection.
val node = TLIdentityNode()
laneNodes.foreach { l => node := l }
lazy val module = new MemTraceDriverImp(this, numLanes)
}
class TraceReq extends Bundle {
val valid = Bool()
val address = UInt(64.W)
val is_store = Bool()
val mask = UInt(8.W)
val data = UInt(64.W)
}
class MemTraceDriverImp(outer: MemTraceDriver, numLanes: Int)
extends LazyModuleImp(outer)
with UnitTestModule {
val sim = Module(
new SimMemTrace(filename = "vecadd.core1.thread4.trace", numLanes)
)
sim.io.clock := clock
sim.io.reset := reset.asBool
sim.io.trace_read.ready := true.B
// Split output of SimMemTrace, which is flattened across all lanes,
// back to each lane's.
// Maybe this part can be improved, since now we are still mannually shifting everything
val laneReqs = Wire(Vec(numLanes, new TraceReq))
laneReqs.zipWithIndex.foreach { case (req, i) =>
req.valid := (sim.io.trace_read.valid >> i)
req.address := (sim.io.trace_read.address >> (64 * i))
req.is_store := (sim.io.trace_read.is_store >> i)
req.mask := (sim.io.trace_read.store_mask >> (8 * i))
req.data := (sim.io.trace_read.data >> (64 * i))
}
// To prevent collision of sourceId with a current in-flight message,
// just use a counter that increments indefinitely as the sourceId of new
// messages.
val sourceIdCounter = Reg(UInt(64.W))
sourceIdCounter := sourceIdCounter + 1.U
// Connect each lane to its respective TL node.
(outer.laneNodes zip laneReqs).foreach { case (node, req) =>
val (tlOut, edge) = node.out(0)
tlOut.a.valid := req.valid
val (plegal, pbits) = edge.Put(
fromSource = sourceIdCounter,
toAddress = req.address,
// Memory trace addresses are not aligned in word addresses (e.g.
// read of size 1 at 0x1007) so leave lgSize to 0.
// TODO: We need to build an issue logic that aligns addresses at
// word boundaries and uses masks.
// NOTE: this is in byte size, not bits
lgSize = 0.U,
data = req.data
)
val (glegal, gbits) = edge.Get(
fromSource = sourceIdCounter,
toAddress = req.address,
lgSize = 0.U
)
val legal = Mux(req.is_store, plegal, glegal)
val bits = Mux(req.is_store, pbits, gbits)
assert(legal, "unhandled illegal TL req gen")
tlOut.a.bits := bits
// tl_out.a.bits.mask := 0xf.U
dontTouch(tlOut.a)
tlOut.d.ready := true.B
}
io.finished := sim.io.trace_read.finished
// Clock Counter, for debugging purpose
val clkcount = RegInit(0.U(64.W))
clkcount := clkcount + 1.U
dontTouch(clkcount)
}
class SimMemTrace(val filename: String, numLanes: Int)
extends BlackBox(
Map("FILENAME" -> filename, "NUM_LANES" -> numLanes)
)
with HasBlackBoxResource {
val io = IO(new Bundle {
val clock = Input(Clock())
val reset = Input(Bool())
// These names have to match declarations in the Verilog code, eg.
// trace_read_address.
val trace_read = new Bundle {
val ready = Input(Bool())
val valid = Output(UInt(numLanes.W))
// Chisel can't interface with Verilog 2D port, so flatten all lanes into
// single wide 1D array.
// TODO: assumes 64-bit address.
val address = Output(UInt((64 * numLanes).W))
val is_store = Output(UInt(numLanes.W))
val store_mask = Output(UInt((8 * numLanes).W))
val data = Output(UInt((64 * numLanes).W))
val finished = Output(Bool())
}
})
addResource("/vsrc/SimMemTrace.v")
addResource("/csrc/SimMemTrace.cc")
addResource("/csrc/SimMemTrace.h")
}
class CoalConnectTrace(implicit p: Parameters) extends LazyModule {
// TODO: use parameters for numLanes
val numLanes = 4
val coal = LazyModule(new CoalescingUnit(numLanes))
val driver = LazyModule(new MemTraceDriver(numLanes))
coal.node :=* driver.node
// Use TLTestRAM as bogus downstream TL manager nodes
// TODO: swap this out with a memtrace logger
val rams = Seq.tabulate(numLanes + 1) { _ =>
LazyModule(
// TODO: properly propagate beatBytes?
new TLRAM(address = AddressSet(0x0000, 0xffffff), beatBytes = 8)
)
}
// Connect all (N+1) outputs of coal to separate TestRAM modules
rams.foreach { r => r.node := coal.node }
lazy val module = new Impl
class Impl extends LazyModuleImp(this) with UnitTestModule {
driver.module.io.start := io.start
io.finished := driver.module.io.finished
}
}
class CoalescingUnitTest(timeout: Int = 500000)(implicit p: Parameters)
extends UnitTest(timeout) {
val dut = Module(LazyModule(new CoalConnectTrace).module)
dut.io.start := io.start
io.finished := dut.io.finished
}