move gemmini connections and smem from tile to cluster

This commit is contained in:
Richard Yan
2024-03-26 17:02:32 -07:00
parent eaef07385c
commit 45c2cf415a
2 changed files with 348 additions and 69 deletions

View File

@@ -4,15 +4,15 @@
package radiance.tile
import chisel3._
import chisel3.experimental.SourceInfo
import chisel3.util._
import org.chipsalliance.cde.config.Parameters
import freechips.rocketchip.diplomacy._
import freechips.rocketchip.prci.ClockSinkParameters
import freechips.rocketchip.regmapper.RegField
import freechips.rocketchip.subsystem._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.diplomacy.{LazyModule, AddressSet, SimpleDevice, ClockCrossingType}
import freechips.rocketchip.regmapper.RegField
import freechips.rocketchip.prci.ClockSinkParameters
import freechips.rocketchip.util.BundleField
import gemmini._
import org.chipsalliance.cde.config.Parameters
case class RadianceClusterParams(
val clusterId: Int,
@@ -39,19 +39,177 @@ class RadianceCluster (
// Instantiate cluster-local shared memory scratchpad
//
// Instantiate the same number of banks as there are lanes.
val numLsuLanes = 4 // FIXME: hardcoded
// val numLsuLanes = 4 // FIXME: hardcoded
val wordSize = 4
val smemBanks = Seq.tabulate(numLsuLanes) { bankId =>
// Banked-by-word (4 bytes)
// base for bank 1: ff...000000|01|00
// mask for bank 1; 00...111111|00|11
val base = 0xff000000L | (bankId * wordSize)
val mask = 0x00001fffL ^ ((numLsuLanes - 1) * wordSize)
LazyModule(new TLRAM(AddressSet(base, mask), beatBytes = wordSize))
}
smemBanks.foreach(_.node := clbus.outwardNode)
val numCores = leafTiles.size
val gemminis = leafTiles.values.filter(_.isInstanceOf[GemminiTile]).asInstanceOf[Iterable[GemminiTile]]
require(gemminis.size == 1, "there should be one and only one gemmini per cluster")
val gemmini = gemminis.head.gemmini
val gemminiTile = gemminis.head
// val gemminiConfig = thisClusterParams.gemminiConfig.get // TODO: handle None gracefully
val gemminiConfig = gemmini.config
val max_write_width_bytes = gemminiConfig.dma_buswidth / 8
val radianceTiles = leafTiles.values.filter(_.isInstanceOf[RadianceTile]).asInstanceOf[Iterable[RadianceTile]]
val numCores = leafTiles.size - gemminis.size
// **************************************
// ______ _________ ___
// / __/ |/ / __/ |/ /
// _\ \/ /|_/ / _// /|_/ /
// /___/_/ /_/___/_/ /_/
//
// **************************************
// TODO: parametrize bank configuration
// TODO: move rw split node to separate file
// TODO: stride by word
val unified_mem_read_node = TLIdentityNode()
val unified_mem_write_node = TLIdentityNode()
val spad_data_len = gemminiConfig.sp_width / 8
val acc_data_len = gemminiConfig.sp_width / gemminiConfig.inputType.getWidth * gemminiConfig.accType.getWidth / 8
val max_data_len = spad_data_len // max acc_data_len
val smem_base = gemminiConfig.tl_ext_mem_base
val smem_depth = gemminiConfig.sp_bank_entries * spad_data_len / max_data_len
val smem_width = max_data_len
val smem_banks = gemminiConfig.sp_banks
val smem_subbanks = 1
unified_mem_read_node :=* TLWidthWidget(spad_data_len) :=* gemmini.spad_read_nodes
// unified_mem_read_node :=* TLWidthWidget(acc_data_len) :=* acc_read_nodes
unified_mem_write_node :=* TLWidthWidget(spad_data_len) :=* gemmini.spad_write_nodes
// unified_mem_write_node :=* TLWidthWidget(acc_data_len) :=* acc_write_nodes
unified_mem_write_node := gemmini.spad.spad_writer.node // this is the dma write node
// this node accepts both read and write requests,
// splits & arbitrates them into one client node per type of operation
// it keeps the read and write channels fully separate to allow parallel processing
val unified_mem_node = TLNexusNode(
clientFn = { seq =>
val in_mapping = TLXbar.mapInputIds(seq)
val read_src_range = IdRange(in_mapping.map(_.start).min, in_mapping.map(_.end).max)
assert((read_src_range.start == 0) && isPow2(read_src_range.end))
val write_src_range = read_src_range.shift(read_src_range.size)
val visibilities = seq.flatMap(_.masters.flatMap(_.visibility))
val vis_min = visibilities.map(_.base).min
val vis_max = visibilities.map{ x => x.base + x.mask }.max
val vis_mask = vis_max - vis_min
require(isPow2(vis_mask + 1) || vis_mask == -1)
println(f"combined visibilities of unified memory node clients: ${vis_min}, ${vis_mask}")
seq(0).v1copy(
echoFields = BundleField.union(seq.flatMap(_.echoFields)),
requestFields = BundleField.union(seq.flatMap(_.requestFields)),
responseKeys = seq.flatMap(_.responseKeys).distinct,
minLatency = seq.map(_.minLatency).min,
clients = Seq(
TLMasterParameters.v1(
name = "unified_mem_read_client",
sourceId = read_src_range,
visibility = Seq(AddressSet(vis_min, vis_mask)),
supportsProbe = TransferSizes.mincover(seq.map(_.anyEmitClaims.get)),
supportsGet = TransferSizes.mincover(seq.map(_.anyEmitClaims.get)),
supportsPutFull = TransferSizes.none,
supportsPutPartial = TransferSizes.none
),
TLMasterParameters.v1(
name = "unified_mem_write_client",
sourceId = write_src_range,
visibility = Seq(AddressSet(vis_min, vis_mask)),
supportsProbe = TransferSizes.mincover(
seq.map(_.anyEmitClaims.putFull) ++seq.map(_.anyEmitClaims.putPartial)),
supportsGet = TransferSizes.none,
supportsPutFull = TransferSizes.mincover(seq.map(_.anyEmitClaims.putFull)),
supportsPutPartial = TransferSizes.mincover(seq.map(_.anyEmitClaims.putPartial))
)
)
)
},
managerFn = { seq =>
// val fifoIdFactory = TLXbar.relabeler()
seq(0).v1copy(
responseFields = BundleField.union(seq.flatMap(_.responseFields)),
requestKeys = seq.flatMap(_.requestKeys).distinct,
minLatency = seq.map(_.minLatency).min,
endSinkId = TLXbar.mapOutputIds(seq).map(_.end).max,
managers = Seq(TLSlaveParameters.v2(
name = Some(f"unified_mem_manager"),
address = Seq(AddressSet(gemmini.spad_base, smem_depth * smem_width * smem_banks - 1)),
supports = TLMasterToSlaveTransferSizes(
get = TransferSizes(1, smem_width),
putFull = TransferSizes(1, smem_width),
putPartial = TransferSizes(1, smem_width)),
fifoId = Some(0)
))
)
}
)
unified_mem_read_node := TLWidthWidget(spad_data_len) := unified_mem_node
unified_mem_write_node := TLWidthWidget(spad_data_len) := unified_mem_node
val stride_by_word = false
// collection of read and write managers for each sram (sub)bank
val smem_bank_mgrs : Seq[Seq[TLManagerNode]] = if (stride_by_word) {
assert(false, "TODO under construction")
// assert((config.sp_capacity match { case CapacityInKilobytes(kb) => kb * 1024}) ==
// gemmini.config.sp_bank_entries * spad_data_len / max_data_len * gemmini.config.sp_banks * max_data_len)
(0 until gemminiConfig.sp_banks).map { bank =>
LazyModule(new TLRAM(
address = AddressSet(max_data_len * bank,
((gemminiConfig.sp_bank_entries * spad_data_len / max_data_len - 1) * gemminiConfig.sp_banks + bank)
* max_data_len + (max_data_len - 1)),
beatBytes = max_data_len
))
}.map(x => Seq(x.node))
} else {
require(isPow2(smem_banks))
(0 until smem_banks).map { bank =>
Seq(TLManagerNode(Seq(TLSlavePortParameters.v1(
managers = Seq(TLSlaveParameters.v2(
name = Some(f"sp_bank${bank}_read_mgr"),
address = Seq(AddressSet(smem_base + (smem_depth * smem_width * bank),
smem_depth * smem_width - 1)),
supports = TLMasterToSlaveTransferSizes(
get = TransferSizes(1, smem_width)),
fifoId = Some(0)
)),
beatBytes = smem_width
))),
TLManagerNode(Seq(TLSlavePortParameters.v1(
managers = Seq(TLSlaveParameters.v2(
name = Some(f"sp_bank${bank}_write_mgr"),
address = Seq(AddressSet(smem_base + (smem_depth * smem_width * bank),
smem_depth * smem_width - 1)),
supports = TLMasterToSlaveTransferSizes(
putFull = TransferSizes(1, smem_width),
putPartial = TransferSizes(1, smem_width)),
fifoId = Some(0)
)),
beatBytes = smem_width
))))
}
}
val smem_r_xbar = TLXbar()
val smem_w_xbar = TLXbar()
smem_r_xbar :=* unified_mem_read_node
smem_w_xbar :=* unified_mem_write_node
smem_bank_mgrs.foreach { mem =>
require(mem.length == 2)
mem.head := smem_r_xbar
mem.last := TLFragmenter(spad_data_len, max_write_width_bytes) := smem_w_xbar
}
// connect tile smem nodes to xbar, and xbar to banks
// val smem_xbar = TLXbar()
unified_mem_node :=* TLWidthWidget(4) :=* clbus.outwardNode
gemminiTile.slaveNode :=* TLWidthWidget(4) :=* clbus.outwardNode
// printf and perf counter buffer FIXME: make configurable
TLRAM(AddressSet(x"ff004000", numCores * 0x200 - 1)) := TLFragmenter(4, 4) := clbus.outwardNode
// Diplomacy sink nodes for cluster-wide barrier sync signal
val barrierSlaveNode = BarrierSlaveNode(numCores)
@@ -65,7 +223,7 @@ class RadianceCluster (
// Tie corresponding smem ports from every tile into a single port using
// Xbars so that the number of ports going into the sharedmem do not scale
// with the number of tiles.
leafTiles.foreach { case (id, tile: RadianceTile) =>
radianceTiles.foreach { tile =>
// (perSmemPortXbars zip tile.smemNodes).foreach {
// case (xbar, node) => xbar.node := node
// }
@@ -78,7 +236,7 @@ class RadianceCluster (
val regDevice = new SimpleDevice("radiance-cluster-barrier-reg",
Seq(s"radiance-cluster-barrier-reg${clusterId}"))
val regNode = TLRegisterNode(
address = Seq(AddressSet(0xff003f00L, 0xff)),
address = Seq(AddressSet(0xff004f00L, 0xff)),
device = regDevice,
beatBytes = wordSize,
concurrency = 1)
@@ -92,11 +250,8 @@ class RadianceCluster (
}
class RadianceClusterModuleImp(outer: RadianceCluster) extends ClusterModuleImp(outer) {
outer.leafTiles.foreach { case (id, tile: RadianceTile) =>
// println(s"======= RadianceCluster: tile.smemXbar.node.edge = ${tile.smemXbar.node.out.size}")
println(s"======= RadianceCluster: clbus inward edges = ${outer.clbus.inwardNode.inward.inputs.length}")
println(s"======= RadianceCluster: clbus name = ${outer.clbus.busName}")
}
println(s"======= RadianceCluster: clbus inward edges = ${outer.clbus.inwardNode.inward.inputs.length}")
println(s"======= RadianceCluster: clbus name = ${outer.clbus.busName}")
val numBarriers = 4 // FIXME: hardcoded
@@ -146,5 +301,164 @@ class RadianceClusterModuleImp(outer: RadianceCluster) extends ClusterModuleImp(
0x38 -> Seq(RegField(32, perCoreSyncedRegs(3)(1))),
)
// TODO: remove Pipeline dependency of gemmini
def makeSmemBanks: Unit = {
outer.smem_bank_mgrs.foreach { case Seq(r, w) =>
val mem_depth = outer.smem_depth
val mem_width = outer.smem_width
val mem = TwoPortSyncMem(
n = mem_depth,
t = UInt((mem_width * 8).W),
mask_len = mem_width // byte level mask
)
val (r_node, r_edge) = r.in.head
val (w_node, w_edge) = w.in.head
// READ
mem.io.ren := r_node.a.fire
mem.io.raddr := (r_node.a.bits.address ^ outer.smem_base.U) >> log2Ceil(mem_width).U
val data_pipe_in = Wire(DecoupledIO(mem.io.rdata.cloneType))
data_pipe_in.valid := RegNext(mem.io.ren)
data_pipe_in.bits := mem.io.rdata
val metadata_pipe_in = Wire(DecoupledIO(new Bundle {
val source = r_node.a.bits.source.cloneType
val size = r_node.a.bits.size.cloneType
}))
metadata_pipe_in.valid := mem.io.ren
metadata_pipe_in.bits.source := r_node.a.bits.source
metadata_pipe_in.bits.size := r_node.a.bits.size
val sram_read_backup_reg = RegInit(0.U.asTypeOf(Valid(mem.io.rdata.cloneType)))
val data_pipe_inst = Module(new Pipeline(data_pipe_in.bits.cloneType, 1)())
data_pipe_inst.io.in <> data_pipe_in
val data_pipe = data_pipe_inst.io.out
val metadata_pipe = Pipeline(metadata_pipe_in, 2)
assert((data_pipe.valid || sram_read_backup_reg.valid) === metadata_pipe.valid)
// data pipe is filled, but D is not ready and SRAM read came back
when (data_pipe.valid && !r_node.d.ready && data_pipe_in.valid) {
assert(!data_pipe_in.ready) // we should fill backup reg only if data pipe is not enqueueing
assert(!sram_read_backup_reg.valid) // backup reg should be empty
assert(!metadata_pipe_in.ready) // metadata should be filled previous cycle
sram_read_backup_reg.valid := true.B
sram_read_backup_reg.bits := mem.io.rdata
}.otherwise {
assert(data_pipe_in.ready || !data_pipe_in.valid) // do not skip any response
}
assert(metadata_pipe_in.fire || !mem.io.ren) // when requesting sram, metadata needs to be ready
assert(r_node.d.fire === metadata_pipe.fire) // metadata dequeues iff D fires
// when D becomes ready, and data pipe has emptied, time for backup to empty
when (r_node.d.ready && sram_read_backup_reg.valid && !data_pipe.valid) {
sram_read_backup_reg.valid := false.B
}
assert(!(sram_read_backup_reg.valid && data_pipe.valid && data_pipe_in.fire)) // must empty backup before filling data pipe
assert(data_pipe_in.valid === data_pipe_in.fire)
r_node.d.bits := r_edge.AccessAck(
Mux(r_node.d.valid, metadata_pipe.bits.source, 0.U),
Mux(r_node.d.valid, metadata_pipe.bits.size, 0.U),
Mux(!data_pipe.valid, sram_read_backup_reg.bits, data_pipe.bits))
r_node.d.valid := data_pipe.valid || sram_read_backup_reg.valid
// r node A is not ready only if D is not ready and both slots filled
r_node.a.ready := r_node.d.ready && !(data_pipe.valid && sram_read_backup_reg.valid)
data_pipe.ready := r_node.d.ready
metadata_pipe.ready := r_node.d.ready
// WRITE
mem.io.wen := w_node.a.fire
mem.io.waddr := (w_node.a.bits.address ^ outer.smem_base.U) >> log2Ceil(mem_width).U
mem.io.wdata := w_node.a.bits.data
mem.io.mask := w_node.a.bits.mask.asBools
w_node.a.ready := w_node.d.ready// && (mem.io.waddr =/= mem.io.raddr)
w_node.d.valid := w_node.a.valid
w_node.d.bits := w_edge.AccessAck(w_node.a.bits)
}
}
def connectUnifiedMemNode: Unit = {
val u_out = outer.unified_mem_node.out
val u_in = outer.unified_mem_node.in
assert(u_out.length == 2)
println(f"gemmini unified memory node has ${u_in.length} incoming client(s)")
val r_out = u_out.head
val w_out = u_out.last
val in_src = TLXbar.mapInputIds(u_in.map(_._2.client))
val in_src_size = in_src.map(_.end).max
assert(isPow2(in_src_size)) // should be checked already, but just to be sure
// arbitrate all reads into one read while assigning source prefix, same for write
val a_arbiter_in = (u_in zip in_src).map { case ((in_node, _), src_range) =>
val in_r: DecoupledIO[TLBundleA] =
WireDefault(0.U.asTypeOf(Decoupled(new TLBundleA(in_node.a.bits.params.copy(
sourceBits = log2Up(in_src_size) + 1
)))))
val in_w: DecoupledIO[TLBundleA] = WireDefault(0.U.asTypeOf(in_r.cloneType))
val req_is_read = in_node.a.bits.opcode === TLMessages.Get
(Seq(in_r.bits.user, in_r.bits.address, in_r.bits.opcode, in_r.bits.size,
in_r.bits.mask, in_r.bits.param, in_r.bits.data)
zip Seq(in_node.a.bits.user, in_node.a.bits.address, in_node.a.bits.opcode, in_node.a.bits.size,
in_node.a.bits.mask, in_node.a.bits.param, in_node.a.bits.data))
.foreach { case (x, y) => x := y }
in_r.bits.source := in_node.a.bits.source | src_range.start.U | Mux(req_is_read, 0.U, in_src_size.U)
in_w.bits := in_r.bits
in_r.valid := in_node.a.valid && req_is_read
in_w.valid := in_node.a.valid && !req_is_read
in_node.a.ready := Mux(req_is_read, in_r.ready, in_w.ready)
(in_r, in_w)
}
// we cannot use round robin because it might reorder requests, even from the same client
val (a_arbiter_in_r_nodes, a_arbiter_in_w_nodes) = a_arbiter_in.unzip
TLArbiter.lowest(r_out._2, r_out._1.a, a_arbiter_in_r_nodes:_*)
TLArbiter.lowest(w_out._2, w_out._1.a, a_arbiter_in_w_nodes:_*)
def trim(id: UInt, size: Int): UInt = if (size <= 1) 0.U else id(log2Ceil(size)-1, 0) // from Xbar
// for each unified mem node client, arbitrate read/write responses on d channel
(u_in zip in_src).zipWithIndex.foreach { case (((in_node, in_edge), src_range), i) =>
// assign d channel back based on source, invalid if source prefix mismatch
val resp = Seq(r_out._1.d, w_out._1.d)
val source_match = resp.zipWithIndex.map { case (r, i) =>
(r.bits.source(r.bits.source.getWidth - 1) === i.U(1.W)) && // MSB indicates read(0)/write(1)
src_range.contains(trim(r.bits.source, in_src_size))
}
val d_arbiter_in = resp.map(r => WireDefault(
0.U.asTypeOf(Decoupled(new TLBundleD(r.bits.params.copy(
sourceBits = in_node.d.bits.source.getWidth,
sizeBits = in_node.d.bits.size.getWidth
))))
))
(d_arbiter_in lazyZip resp lazyZip source_match).foreach { case (arb_in, r, sm) =>
(Seq(arb_in.bits.user, arb_in.bits.opcode, arb_in.bits.data, arb_in.bits.param,
arb_in.bits.sink, arb_in.bits.denied, arb_in.bits.corrupt)
zip Seq(r.bits.user, r.bits.opcode, r.bits.data, r.bits.param,
r.bits.sink, r.bits.denied, r.bits.corrupt))
.foreach { case (x, y) => x := y }
arb_in.bits.source := trim(r.bits.source, 1 << in_node.d.bits.source.getWidth) // we can trim b/c isPow2(prefix)
arb_in.bits.size := trim(r.bits.size, 1 << in_node.d.bits.size.getWidth) // FIXME: check truncation
arb_in.valid := r.valid && sm
r.ready := arb_in.ready
}
TLArbiter.robin(in_edge, in_node.d, d_arbiter_in:_*)
}
}
makeSmemBanks
connectUnifiedMemNode
println(s"======== barrierSlaveNode: ${outer.barrierSlaveNode.in(0)._2.barrierIdBits}")
}

View File

@@ -5,19 +5,17 @@ package radiance.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.HierarchicalElementCrossingParamsLike
import freechips.rocketchip.util._
import freechips.rocketchip.prci.ClockSinkParameters
import freechips.rocketchip.regmapper.RegField
import freechips.rocketchip.rocket._
import freechips.rocketchip.subsystem.HierarchicalElementCrossingParamsLike
import freechips.rocketchip.tile._
import freechips.rocketchip.tilelink._
import freechips.rocketchip.util._
import org.chipsalliance.cde.config._
import radiance.memory._
import gemmini.{CapacityInKilobytes, Gemmini, GemminiCustomConfigs, GemminiFPConfigs}
import radiance.subsystem.{GPUMemParams, GPUMemory}
case class RadianceTileParams(
@@ -57,14 +55,14 @@ case class RadianceTileParams(
// though. TODO see how BOOM does that
case class VortexCoreParams(
bootFreqHz: BigInt = 0,
useVM: Boolean = true,
useVM: Boolean = false,
useUser: Boolean = false,
useSupervisor: Boolean = false,
useHypervisor: Boolean = false,
useDebug: Boolean = true,
useAtomics: Boolean = true,
useAtomics: Boolean = false,
useAtomicsOnlyForIO: Boolean = false,
useCompressed: Boolean = true,
useCompressed: Boolean = false,
useRVE: Boolean = false,
useConditionalZero: Boolean = false,
nLocalInterrupts: Int = 0,
@@ -89,8 +87,8 @@ case class VortexCoreParams(
clockGate: Boolean = false,
mvendorid: Int = 0, // 0 means non-commercial implementation
mimpid: Int = 0x20181004, // release date in BCD
mulDiv: Option[MulDivParams] = Some(MulDivParams()),
fpu: Option[FPUParams] = Some(FPUParams()),
mulDiv: Option[MulDivParams] = None,
fpu: Option[FPUParams] = None,
debugROB: Boolean = false, // if enabled, uses a C++ debug ROB to generate trace-with-wdata
haveCease: Boolean = true, // non-standard CEASE instruction
haveSimTimeout: Boolean = true // add plusarg for simulation timeout
@@ -344,39 +342,6 @@ class RadianceTile private (
tlMasterXbar.node :=* AddressOrNode(base) :=* dcacheNode
}
// ROCC
// TODO: parametrize
val gemmini = LazyModule(new Gemmini(GemminiFPConfigs.FP32DefaultConfig.copy(
has_training_convs = false,
has_max_pool = false,
use_tl_ext_mem = true,
tl_ext_mem_base = x"ff000000",
sp_singleported = false,
spad_read_delay = 4,
use_shared_ext_mem = true,
acc_sub_banks = 1,
has_normalizations = false,
meshRows = 8,
meshColumns = 8,
dma_buswidth = 256,
tile_latency = 0,
sp_capacity = CapacityInKilobytes(16),
acc_capacity = CapacityInKilobytes(8),
)))
val roccs: Seq[LazyRoCC] = Seq(gemmini)
tlMasterXbar.node :=* AddressOrNode(base) :=* gemmini.atlNode
tlOtherMastersNode :=* AddressOrNode(base) :=* gemmini.tlNode
// MMIO
// gemmini.stlNode :=* TLWidthWidget(4) :=* smemXbar.node
// sharedmem access
//
// FIXME: gemmini spad has 16B data width; core smem interface has 4B. Need
// to consolidate by either coalescing, or changing gemmini spad to
// strided-by-word
// gemmini.unified_mem_node :=* TLWidthWidget(4) :=* smemXbar.node
// TLRAM(AddressSet(x"ff004000", 0xfff)) := TLFragmenter(4, 4) := smemXbar.node
/* below are copied from rocket */
val tile_master_blocker =