7 Commits

Author SHA1 Message Date
Zhongdi LUO
a978855cef feat: execute blackwell fp16 bwgmma on systolic array 2026-07-16 00:19:31 +00:00
Zhongdi LUO
035534a486 chore: update vortex synthesis rtl 2026-07-13 00:03:13 +00:00
Zhongdi LUO
cdbf07ab9d feat: pipeline scalar softmax and blackwell mma issue 2026-07-12 02:00:14 +00:00
Zhongdi LUO
3e8976490d feat: add fp16 scalar tmem softmax and split tmem 2026-07-10 13:03:12 +00:00
Zhongdi LUO
007350fd5a feat: include vortex fexp RTL 2026-07-02 07:25:32 +00:00
Zhongdi LUO
47d6585896 Wire scalar TMEM through Radiance tile 2026-06-24 06:25:10 +00:00
Zhongdi LUO
f88085331e Save pre-TMEM-bank Radiance changes 2026-06-21 08:20:21 +00:00
6 changed files with 536 additions and 159 deletions

View File

@@ -0,0 +1,129 @@
// See LICENSE.SiFive for license details.
// See LICENSE.Berkeley for license details.
package radiance.core
import chisel3._
import chisel3.util._
import freechips.rocketchip.tile
/** An 8x8 output-stationary FP16 systolic array for Blackwell BWGMMA.
*
* Each PE contains an FP16 multiplier and an FP32 accumulator. A operands
* move left-to-right and B operands move top-to-bottom.
*/
class BlackwellFP16SystolicArray(val kDim: Int = 32)
extends Module with tile.HasFPUParameters {
private val arrayDim = 8
private val wavefrontCycles = kDim + 2 * (arrayDim - 1)
require(kDim > 0)
val fLen = 32
val minFLen = 16
def xLen = 32
private val tIn = tile.FType.H
private val tOut = tile.FType.S
private val recOutWidth = tOut.exp + tOut.sig + 1
private val stepWidth = log2Ceil(wavefrontCycles)
private val kIndexWidth = math.max(1, log2Ceil(kDim))
val io = IO(new Bundle {
val start = Input(Bool())
val ready = Output(Bool())
val busy = Output(Bool())
val done = Output(Bool())
val a = Input(Vec(arrayDim, Vec(kDim, UInt(16.W))))
val b = Input(Vec(kDim, Vec(arrayDim, UInt(16.W))))
val c = Input(Vec(arrayDim, Vec(arrayDim, UInt(32.W))))
val result = Output(Vec(arrayDim, Vec(arrayDim, UInt(32.W))))
})
val busyReg = RegInit(false.B)
val doneReg = RegInit(false.B)
val stepReg = RegInit(0.U(stepWidth.W))
val aPipe = RegInit(VecInit(Seq.fill(arrayDim)(VecInit(Seq.fill(arrayDim)(0.U(16.W))))))
val bPipe = RegInit(VecInit(Seq.fill(arrayDim)(VecInit(Seq.fill(arrayDim)(0.U(16.W))))))
val accum = Reg(Vec(arrayDim, Vec(arrayDim, UInt(recOutWidth.W))))
val aFlow = Wire(Vec(arrayDim, Vec(arrayDim, UInt(16.W))))
val bFlow = Wire(Vec(arrayDim, Vec(arrayDim, UInt(16.W))))
for (row <- 0 until arrayDim) {
val active = stepReg >= row.U && stepReg < (row + kDim).U
val kIndex = (stepReg - row.U)(kIndexWidth - 1, 0)
aFlow(row)(0) := Mux(active, io.a(row)(kIndex), 0.U)
for (col <- 1 until arrayDim) {
aFlow(row)(col) := aPipe(row)(col - 1)
}
}
for (col <- 0 until arrayDim) {
val active = stepReg >= col.U && stepReg < (col + kDim).U
val kIndex = (stepReg - col.U)(kIndexWidth - 1, 0)
bFlow(0)(col) := Mux(active, io.b(kIndex)(col), 0.U)
for (row <- 1 until arrayDim) {
bFlow(row)(col) := bPipe(row - 1)(col)
}
}
val sums = Seq.tabulate(arrayDim, arrayDim) { (row, col) =>
val aRec = unbox(recode(aFlow(row)(col), H), H, Some(tIn))
val bRec = unbox(recode(bFlow(row)(col), H), H, Some(tIn))
val multiplier = Module(new hardfloat.MulFullRawFN(tIn.exp, tIn.sig))
multiplier.io.a := hardfloat.rawFloatFromRecFN(tIn.exp, tIn.sig, aRec)
multiplier.io.b := hardfloat.rawFloatFromRecFN(tIn.exp, tIn.sig, bRec)
val product = Module(new hardfloat.RoundAnyRawFNToRecFN(
multiplier.io.rawOut.expWidth,
multiplier.io.rawOut.sigWidth,
tOut.exp,
tOut.sig,
0))
product.io.invalidExc := multiplier.io.invalidExc
product.io.infiniteExc := false.B
product.io.in := multiplier.io.rawOut
product.io.roundingMode := hardfloat.consts.round_near_even
product.io.detectTininess := hardfloat.consts.tininess_afterRounding
val adder = Module(new hardfloat.AddRecFN(tOut.exp, tOut.sig))
adder.io.subOp := 0.U
adder.io.a := accum(row)(col)
adder.io.b := product.io.out
adder.io.roundingMode := hardfloat.consts.round_near_even
adder.io.detectTininess := hardfloat.consts.tininess_afterRounding
adder.io.out
}
io.ready := !busyReg
io.busy := busyReg
io.done := doneReg
for (row <- 0 until arrayDim; col <- 0 until arrayDim) {
io.result(row)(col) := ieee(box(accum(row)(col), S))
}
doneReg := false.B
when(io.start && !busyReg) {
busyReg := true.B
stepReg := 0.U
for (row <- 0 until arrayDim; col <- 0 until arrayDim) {
aPipe(row)(col) := 0.U
bPipe(row)(col) := 0.U
accum(row)(col) := unbox(recode(io.c(row)(col), S), S, Some(tOut))
}
}.elsewhen(busyReg) {
aPipe := aFlow
bPipe := bFlow
for (row <- 0 until arrayDim; col <- 0 until arrayDim) {
accum(row)(col) := sums(row)(col)
}
when(stepReg === (wavefrontCycles - 1).U) {
busyReg := false.B
doneReg := true.B
}.otherwise {
stepReg := stepReg + 1.U
}
}
}

View File

@@ -6,6 +6,29 @@ package radiance.core
import chisel3._ import chisel3._
import chisel3.util._ import chisel3.util._
object TensorCoreBlackwellFP16Packing {
def halfWord(x: UInt, idx: Int): UInt = {
x((idx + 1) * 16 - 1, idx * 16)
}
def selectA(operandA: UInt, k: Int, elemM: UInt, numLanes: Int): UInt = {
if (numLanes == 4) {
Mux(elemM.asBool, halfWord(operandA, 8 + k), halfWord(operandA, k))
} else {
MuxLookup(elemM, halfWord(operandA, k))(Seq(
0.U -> halfWord(operandA, k),
1.U -> halfWord(operandA, 8 + k),
2.U -> halfWord(operandA, 16 + k),
3.U -> halfWord(operandA, 24 + k)
))
}
}
def selectB(operandB: UInt, k: Int, elemN: UInt): UInt = {
Mux(elemN.asBool, halfWord(operandB, 8 + k), halfWord(operandB, k))
}
}
class TensorCoreBlackwell( class TensorCoreBlackwell(
val numWarps: Int, val numWarps: Int,
val numLanes: Int, val numLanes: Int,
@@ -37,6 +60,13 @@ class TensorCoreBlackwell(
val numBFragsPerGroup = numSubsteps * numBFragsPerSubstep val numBFragsPerGroup = numSubsteps * numBFragsPerSubstep
val numBFragsPerSet = numBGroups * numBFragsPerGroup val numBFragsPerSet = numBGroups * numBFragsPerGroup
val numCFrags = numBGroups * numMGroups * numSubsteps val numCFrags = numBGroups * numMGroups * numSubsteps
val systolicDim = 8
val systolicK = 32
val numTilesPerDim = 16 / systolicDim
val numMGroupsPerTile = systolicDim / mElemsPerFrag
val numCFragsPerTile = systolicDim * systolicDim / numLanes
val totalAFrags = numSets * numAFragsPerSet
val totalBFrags = numSets * numBFragsPerSet
object Ops { object Ops {
val bwgmma :: bwgmmaWait :: tcgen05Cp :: tcgen05CpWait :: tcgen05Ld :: tcgen05St :: tcgen05Cb :: Nil = Enum(7) val bwgmma :: bwgmmaWait :: tcgen05Cp :: tcgen05CpWait :: tcgen05Ld :: tcgen05St :: tcgen05Cb :: Nil = Enum(7)
@@ -106,7 +136,7 @@ class TensorCoreBlackwell(
object State extends ChiselEnum { object State extends ChiselEnum {
val idle, bwLoadAReq, bwLoadAResp, bwLoadBReq, bwLoadBResp, val idle, bwLoadAReq, bwLoadAResp, bwLoadBReq, bwLoadBResp,
bwReadCReq, bwReadCResp, bwCompute, bwDpuResp, bwWriteCReq, bwReadCReq, bwReadCResp, bwArrayStart, bwArrayRun, bwWriteCReq,
bwWriteCWait, bwDone, cpRead, cpWrite, ldReq, stReq, stWrite, waitWb, bwWriteCWait, bwDone, cpRead, cpWrite, ldReq, stReq, stWrite, waitWb,
cbRead, cbCapture, cbWrite = Value cbRead, cbCapture, cbWrite = Value
} }
@@ -120,17 +150,16 @@ class TensorCoreBlackwell(
val addrCReg = RegInit(0.U(addressWidth.W)) val addrCReg = RegInit(0.U(addressWidth.W))
val sourceCounter = RegInit(0.U(sourceWidth.W)) val sourceCounter = RegInit(0.U(sourceWidth.W))
val setReg = RegInit(0.U(log2Ceil(numSets).W)) val aIndexReg = RegInit(0.U(log2Ceil(totalAFrags).W))
val aIndexReg = RegInit(0.U(log2Ceil(numAFragsPerSet).W)) val bIndexReg = RegInit(0.U(log2Ceil(totalBFrags).W))
val bGroupReg = RegInit(0.U(log2Ceil(numBGroups).W)) val tileMReg = RegInit(0.U(log2Ceil(numTilesPerDim).W))
val bIndexReg = RegInit(0.U(log2Ceil(numBFragsPerGroup).W)) val tileNReg = RegInit(0.U(log2Ceil(numTilesPerDim).W))
val mGroupReg = RegInit(0.U(log2Ceil(numMGroups).W)) val cTileFragReg = RegInit(0.U(log2Ceil(numCFragsPerTile).W))
val substepReg = RegInit(0.U(1.W))
val elemReg = RegInit(0.U(log2Ceil(numLanes).W))
val waitCounter = RegInit(0.U(3.W)) val waitCounter = RegInit(0.U(3.W))
val aBuf = Reg(Vec(numAFragsPerSet, UInt(memWidth.W))) val aBuf = Reg(Vec(totalAFrags, UInt(memWidth.W)))
val bBuf = Reg(Vec(numBFragsPerGroup, UInt(memWidth.W))) val bBuf = Reg(Vec(totalBFrags, UInt(memWidth.W)))
val cTile = Reg(Vec(systolicDim, Vec(systolicDim, UInt(laneWidth.W))))
val cDataReg = Reg(UInt(memWidth.W)) val cDataReg = Reg(UInt(memWidth.W))
val mmaDataReg = Reg(Vec(numLanes, UInt(laneWidth.W))) val mmaDataReg = Reg(Vec(numLanes, UInt(laneWidth.W)))
@@ -142,11 +171,15 @@ class TensorCoreBlackwell(
base + (fragIndex << fragOffsetBits).asUInt base + (fragIndex << fragOffsetBits).asUInt
} }
val aFragIndex = (setReg * numAFragsPerSet.U) + aIndexReg val localSubstep = cTileFragReg(0)
val bFragIndex = val localMGroup = (cTileFragReg >> 1)(log2Ceil(numMGroupsPerTile) - 1, 0)
(setReg * numBFragsPerSet.U) + (bGroupReg * numBFragsPerGroup.U) + bIndexReg val localBGroup = cTileFragReg >> log2Ceil(numMGroupsPerTile * numSubsteps)
val cMGroup = (tileMReg * numMGroupsPerTile.U) + localMGroup
val cBGroup = (tileNReg * 2.U) + localBGroup
val cFragIndex = val cFragIndex =
(((bGroupReg * numMGroups.U) + mGroupReg) * numSubsteps.U) + substepReg (((cBGroup * numMGroups.U) + cMGroup) * numSubsteps.U) + localSubstep
val aFragIndex = aIndexReg
val bFragIndex = bIndexReg
val aReqAddress = byteAddress(addrAReg, aFragIndex) val aReqAddress = byteAddress(addrAReg, aFragIndex)
val bReqAddress = byteAddress(addrBReg, bFragIndex) val bReqAddress = byteAddress(addrBReg, bFragIndex)
val cReqAddress = byteAddress(addrCReg, cFragIndex) val cReqAddress = byteAddress(addrCReg, cFragIndex)
@@ -187,45 +220,47 @@ class TensorCoreBlackwell(
io.respB.ready := false.B io.respB.ready := false.B
io.initiate.ready := state === State.idle && !wbValid io.initiate.ready := state === State.idle && !wbValid
val operandA = Cat(aBuf((mGroupReg << 1) + 1.U), aBuf(mGroupReg << 1)) val systolic = Module(new BlackwellFP16SystolicArray(systolicK))
val operandB = systolic.io.start := state === State.bwArrayStart
if (numLanes == 4) { systolic.io.c := cTile
Cat(bBuf((substepReg << 1) + 1.U), bBuf(substepReg << 1))
} else {
bBuf(substepReg)
}
val cWords = cDataReg.asTypeOf(Vec(numLanes, UInt(laneWidth.W)))
val dpuInValid = WireDefault(false.B)
val dpu = Module(new TensorDotProductUnit(
dim = 8,
half = true
))
private def halfWord(x: UInt, idx: Int): UInt = { // Preserve the software-visible FP16 fragment packing while presenting
x((idx + 1) * 16 - 1, idx * 16) // logical 8x32 and 32x8 operands to the systolic array.
for (row <- 0 until systolicDim; k <- 0 until systolicK) {
val set = k / 8
val kInSet = k % 8
val logicalM = (tileMReg << 3) + row.U
val mGroup = logicalM >> log2Ceil(mElemsPerFrag)
val elemM = logicalM(log2Ceil(mElemsPerFrag) - 1, 0)
val aIndex = set.U * numAFragsPerSet.U + (mGroup << 1)
val operandA = Cat(aBuf(aIndex + 1.U), aBuf(aIndex))
systolic.io.a(row)(k) := TensorCoreBlackwellFP16Packing.selectA(
operandA, kInSet, elemM, numLanes)
} }
val elemM = if (numLanes == 4) elemReg(0, 0) else elemReg(1, 0) for (k <- 0 until systolicK; col <- 0 until systolicDim) {
val elemN = if (numLanes == 4) elemReg(1) else elemReg(2) val set = k / 8
dpu.io.in.valid := dpuInValid val kInSet = k % 8
for (k <- 0 until 8) { val logicalN = (tileNReg << 3) + col.U
dpu.io.in.bits.a(k) := ( val bGroup = logicalN >> 2
if (numLanes == 4) { val substep = logicalN(1)
Mux(elemM.asBool, halfWord(operandA, 8 + k), halfWord(operandA, k)) val elemN = logicalN(0)
} else { val bIndex = set.U * numBFragsPerSet.U +
MuxLookup(elemM, halfWord(operandA, k))(Seq( bGroup * numBFragsPerGroup.U + substep * numBFragsPerSubstep.U
0.U -> halfWord(operandA, k), val operandB =
1.U -> halfWord(operandA, 8 + k), if (numLanes == 4) Cat(bBuf(bIndex + 1.U), bBuf(bIndex)) else bBuf(bIndex)
2.U -> halfWord(operandA, 16 + k), systolic.io.b(k)(col) := TensorCoreBlackwellFP16Packing.selectB(
3.U -> halfWord(operandA, 24 + k) operandB, kInSet, elemN)
)) }
}
) val mmaWords = Wire(Vec(numLanes, UInt(laneWidth.W)))
dpu.io.in.bits.b(k) := Mux(elemN.asBool, halfWord(operandB, 8 + k), halfWord(operandB, k)) for (lane <- 0 until numLanes) {
val elemM = lane % mElemsPerFrag
val elemN = lane / mElemsPerFrag
val row = localMGroup * mElemsPerFrag.U + elemM.U
val col = localBGroup * 4.U + localSubstep * 2.U + elemN.U
mmaWords(lane) := systolic.io.result(row)(col)
} }
dpu.io.in.bits.c := cWords(elemReg)
dpu.io.stall := false.B
val dpuValid = dpu.io.out.valid
when(io.writeback.fire) { when(io.writeback.fire) {
wbValid := false.B wbValid := false.B
@@ -238,13 +273,11 @@ class TensorCoreBlackwell(
addrAReg := io.initiate.bits.addressA addrAReg := io.initiate.bits.addressA
addrBReg := io.initiate.bits.addressB addrBReg := io.initiate.bits.addressB
addrCReg := io.initiate.bits.addressC addrCReg := io.initiate.bits.addressC
setReg := 0.U
aIndexReg := 0.U aIndexReg := 0.U
bGroupReg := 0.U
bIndexReg := 0.U bIndexReg := 0.U
mGroupReg := 0.U tileMReg := 0.U
substepReg := 0.U tileNReg := 0.U
elemReg := 0.U cTileFragReg := 0.U
switch(io.initiate.bits.op) { switch(io.initiate.bits.op) {
is(Ops.bwgmma) { state := State.bwLoadAReq } is(Ops.bwgmma) { state := State.bwLoadAReq }
is(Ops.tcgen05Cp) { state := State.cpRead } is(Ops.tcgen05Cp) { state := State.cpRead }
@@ -266,8 +299,7 @@ class TensorCoreBlackwell(
when(state === State.bwLoadAResp) { when(state === State.bwLoadAResp) {
aBuf(aIndexReg) := io.tmemC.aRdata aBuf(aIndexReg) := io.tmemC.aRdata
when(aIndexReg === (numAFragsPerSet - 1).U) { when(aIndexReg === (totalAFrags - 1).U) {
bGroupReg := 0.U
bIndexReg := 0.U bIndexReg := 0.U
state := State.bwLoadBReq state := State.bwLoadBReq
}.otherwise { }.otherwise {
@@ -292,9 +324,10 @@ class TensorCoreBlackwell(
io.respB.ready := true.B io.respB.ready := true.B
when(io.respB.fire) { when(io.respB.fire) {
bBuf(bIndexReg) := io.respB.bits.data bBuf(bIndexReg) := io.respB.bits.data
when(bIndexReg === (numBFragsPerGroup - 1).U) { when(bIndexReg === (totalBFrags - 1).U) {
mGroupReg := 0.U tileMReg := 0.U
substepReg := 0.U tileNReg := 0.U
cTileFragReg := 0.U
state := State.bwReadCReq state := State.bwReadCReq
}.otherwise { }.otherwise {
bIndexReg := bIndexReg + 1.U bIndexReg := bIndexReg + 1.U
@@ -312,58 +345,57 @@ class TensorCoreBlackwell(
} }
when(state === State.bwReadCResp) { when(state === State.bwReadCResp) {
cDataReg := io.tmemC.cRdata val cWords = io.tmemC.cRdata.asTypeOf(Vec(numLanes, UInt(laneWidth.W)))
elemReg := 0.U for (lane <- 0 until numLanes) {
state := State.bwCompute val elemM = lane % mElemsPerFrag
val elemN = lane / mElemsPerFrag
val row = localMGroup * mElemsPerFrag.U + elemM.U
val col = localBGroup * 4.U + localSubstep * 2.U + elemN.U
cTile(row)(col) := cWords(lane)
}
when(cTileFragReg === (numCFragsPerTile - 1).U) {
state := State.bwArrayStart
}.otherwise {
cTileFragReg := cTileFragReg + 1.U
state := State.bwReadCReq
}
} }
when(state === State.bwCompute) { when(state === State.bwArrayStart) {
dpuInValid := true.B when(systolic.io.ready) {
state := State.bwDpuResp state := State.bwArrayRun
}
} }
when(state === State.bwDpuResp) { when(state === State.bwArrayRun) {
when(dpuValid) { when(systolic.io.done) {
mmaDataReg(elemReg) := dpu.io.out.bits.data cTileFragReg := 0.U
when(elemReg === (numLanes - 1).U) { state := State.bwWriteCReq
state := State.bwWriteCReq
}.otherwise {
elemReg := elemReg + 1.U
state := State.bwCompute
}
} }
} }
when(state === State.bwWriteCReq) { when(state === State.bwWriteCReq) {
io.tmemC.cWen := true.B io.tmemC.cWen := true.B
io.tmemC.cWaddr := tmemCBase + cFragIndex io.tmemC.cWaddr := tmemCBase + cFragIndex
io.tmemC.cWdata := mmaDataReg.asUInt io.tmemC.cWdata := mmaWords.asUInt
io.tmemC.cMask := Fill(maskWidth, 1.U(1.W)) io.tmemC.cMask := Fill(maskWidth, 1.U(1.W))
when(io.tmemC.cWready) { when(io.tmemC.cWready) {
when(substepReg === 0.U) { mmaDataReg := mmaWords
substepReg := 1.U when(cTileFragReg =/= (numCFragsPerTile - 1).U) {
state := State.bwReadCReq cTileFragReg := cTileFragReg + 1.U
}.elsewhen(mGroupReg =/= (numMGroups - 1).U) {
substepReg := 0.U
mGroupReg := mGroupReg + 1.U
state := State.bwReadCReq
}.elsewhen(bGroupReg =/= (numBGroups - 1).U) {
substepReg := 0.U
mGroupReg := 0.U
bGroupReg := bGroupReg + 1.U
bIndexReg := 0.U
state := State.bwLoadBReq
}.elsewhen(setReg =/= (numSets - 1).U) {
substepReg := 0.U
mGroupReg := 0.U
bGroupReg := 0.U
bIndexReg := 0.U
setReg := setReg + 1.U
aIndexReg := 0.U
state := State.bwLoadAReq
}.otherwise { }.otherwise {
waitCounter := 7.U cTileFragReg := 0.U
state := State.bwWriteCWait when(tileNReg =/= (numTilesPerDim - 1).U) {
tileNReg := tileNReg + 1.U
state := State.bwReadCReq
}.elsewhen(tileMReg =/= (numTilesPerDim - 1).U) {
tileMReg := tileMReg + 1.U
tileNReg := 0.U
state := State.bwReadCReq
}.otherwise {
waitCounter := 7.U
state := State.bwWriteCWait
}
} }
} }
} }

View File

@@ -851,6 +851,9 @@ class RadianceTileModuleImp(outer: RadianceTile)
core.io.tc_tmem_C_rready := DontCare core.io.tc_tmem_C_rready := DontCare
core.io.tc_tmem_C_rdata := DontCare core.io.tc_tmem_C_rdata := DontCare
core.io.tc_tmem_C_wready := DontCare core.io.tc_tmem_C_wready := DontCare
core.io.sc_tmem_rready := DontCare
core.io.sc_tmem_rdata := DontCare
core.io.sc_tmem_wready := DontCare
} }
def connectTensorBlackwell = { def connectTensorBlackwell = {
@@ -885,59 +888,225 @@ class RadianceTileModuleImp(outer: RadianceTile)
tcDData.foreach(_ := 0.U) tcDData.foreach(_ := 0.U)
tcDTag.foreach(_ := 0.U) tcDTag.foreach(_ := 0.U)
// TMEM matrix: one shared 2R1W SRAM. read0 is operand A, read1 is C. // TMEM keeps the ISA-visible address space unified while storing the
// Each warp owns 2KB: A tile and C tile are 1KB each. The row count // A and C halves in separate 1R1W arrays. This avoids duplicating each
// scales with the physical fragment width (16B for 4 lanes, 32B for 8). // bank for two read ports, and still allows common A-read/C-read pairs
// to proceed in parallel because they normally hit different arrays.
val tmemBytesPerWarp = 2048 val tmemBytesPerWarp = 2048
val tmemDepth = outer.numWarps * (tmemBytesPerWarp / outer.tcSmemSize) val tmemFragsPerWarp = tmemBytesPerWarp / outer.tcSmemSize
val tmem = Module(new radiance.memory.TwoReadOneWriteSyncMem( val tmemFragsPerTile = tmemFragsPerWarp / 2
tmemDepth, UInt((outer.tcSmemSize * 8).W))) val tmemLogicalDepth = outer.numWarps * tmemFragsPerWarp
val tmemArrayDepth = outer.numWarps * tmemFragsPerTile
val tmemBanks = 4
val tmemBankBits = log2Ceil(tmemBanks)
val tmemFragAddrBits = log2Ceil(tmemFragsPerWarp)
val tmemTileAddrBits = log2Ceil(tmemFragsPerTile)
val tmemWarpAddrBits = log2Ceil(outer.numWarps)
val tmemPhysAddrBits = log2Ceil(tmemArrayDepth)
val tmemBankDepth = tmemArrayDepth / tmemBanks
require(isPow2(tmemBanks))
require(isPow2(tmemFragsPerWarp))
require(tmemFragsPerWarp == tmemFragsPerTile * 2)
require(tmemLogicalDepth <= (1 << tmemAddrBits))
require(tmemArrayDepth % tmemBanks == 0)
require(tmemPhysAddrBits > tmemBankBits)
val tmemA = Seq.fill(tmemBanks) {
Module(new radiance.memory.TwoPortSyncMem(
tmemBankDepth, UInt((outer.tcSmemSize * 8).W)))
}
val tmemC = Seq.fill(tmemBanks) {
Module(new radiance.memory.TwoPortSyncMem(
tmemBankDepth, UInt((outer.tcSmemSize * 8).W)))
}
val aReadArb = Module(new RRArbiter(UInt(tmemAddrBits.W), nTC)) class TmemReadReq extends Bundle {
val cReadArb = Module(new RRArbiter(UInt(tmemAddrBits.W), nTC)) val addr = UInt(tmemAddrBits.W)
val src = UInt(2.W)
val tc = UInt(log2Ceil(nTC max 2).W)
}
class TmemWriteReq extends Bundle { class TmemWriteReq extends Bundle {
val addr = UInt(tmemAddrBits.W) val addr = UInt(tmemAddrBits.W)
val data = UInt(tmemDataBits.W) val data = UInt(tmemDataBits.W)
val mask = UInt(tmemMaskBits.W) val mask = UInt(tmemMaskBits.W)
} val src = UInt(1.W)
val cWriteArb = Module(new RRArbiter(new TmemWriteReq, nTC)) val tc = UInt(log2Ceil(nTC max 2).W)
(0 until nTC).foreach { tc =>
aReadArb.io.in(tc).valid := core.io.tc_tmem_A_ren(tc)
aReadArb.io.in(tc).bits := slice(core.io.tc_tmem_A_raddr, tmemAddrBits, tc)
cReadArb.io.in(tc).valid := core.io.tc_tmem_C_ren(tc)
cReadArb.io.in(tc).bits := slice(core.io.tc_tmem_C_raddr, tmemAddrBits, tc)
cWriteArb.io.in(tc).valid := core.io.tc_tmem_C_wen(tc)
cWriteArb.io.in(tc).bits.addr := slice(core.io.tc_tmem_C_waddr, tmemAddrBits, tc)
cWriteArb.io.in(tc).bits.data := slice(core.io.tc_tmem_C_wdata, tmemDataBits, tc)
cWriteArb.io.in(tc).bits.mask := slice(core.io.tc_tmem_C_mask, tmemMaskBits, tc)
} }
aReadArb.io.out.ready := true.B def tmemIsC(addr: UInt): Bool = addr(tmemTileAddrBits)
cReadArb.io.out.ready := true.B def tmemPhysAddr(addr: UInt): UInt = {
cWriteArb.io.out.ready := true.B val tileOffset = addr(tmemTileAddrBits - 1, 0)
if (tmemWarpAddrBits == 0) {
tileOffset
} else {
Cat(addr(tmemFragAddrBits + tmemWarpAddrBits - 1, tmemFragAddrBits), tileOffset)
}
}
def bank(addr: UInt): UInt = addr(tmemBankBits - 1, 0)
def row(addr: UInt): UInt = addr(tmemPhysAddrBits - 1, tmemBankBits)
tmem.io.ren0 := aReadArb.io.out.fire val aReady = Wire(Vec(nTC, Bool()))
tmem.io.raddr0 := aReadArb.io.out.bits val cReady = Wire(Vec(nTC, Bool()))
tmem.io.ren1 := cReadArb.io.out.fire val wReady = Wire(Vec(nTC, Bool()))
tmem.io.raddr1 := cReadArb.io.out.bits val scReadReady = Wire(Bool())
tmem.io.wen := cWriteArb.io.out.fire val scWriteReady = Wire(Bool())
tmem.io.waddr := cWriteArb.io.out.bits.addr aReady.foreach(_ := false.B)
tmem.io.wdata := cWriteArb.io.out.bits.data cReady.foreach(_ := false.B)
tmem.io.mask := cWriteArb.io.out.bits.mask wReady.foreach(_ := false.B)
scReadReady := false.B
scWriteReady := false.B
val aReadGrant = RegNext(Mux(aReadArb.io.out.fire, UIntToOH(aReadArb.io.chosen, nTC), 0.U(nTC.W))) val aReadGrant = Wire(Vec(tmemBanks, new TmemReadReq))
val cReadGrant = RegNext(Mux(cReadArb.io.out.fire, UIntToOH(cReadArb.io.chosen, nTC), 0.U(nTC.W))) val cReadGrant = Wire(Vec(tmemBanks, new TmemReadReq))
core.io.tc_tmem_A_rready := VecInit(aReadArb.io.in.map(_.fire)).asUInt val aReadValid = Wire(Vec(tmemBanks, Bool()))
core.io.tc_tmem_C_rready := VecInit(cReadArb.io.in.map(_.fire)).asUInt val cReadValid = Wire(Vec(tmemBanks, Bool()))
core.io.tc_tmem_C_wready := VecInit(cWriteArb.io.in.map(_.fire)).asUInt val aWriteGrant = Wire(Vec(tmemBanks, new TmemWriteReq))
val cWriteGrant = Wire(Vec(tmemBanks, new TmemWriteReq))
val aWriteValid = Wire(Vec(tmemBanks, Bool()))
val cWriteValid = Wire(Vec(tmemBanks, Bool()))
aReadGrant.foreach(_ := 0.U.asTypeOf(new TmemReadReq))
cReadGrant.foreach(_ := 0.U.asTypeOf(new TmemReadReq))
aReadValid.foreach(_ := false.B)
cReadValid.foreach(_ := false.B)
aWriteGrant.foreach(_ := 0.U.asTypeOf(new TmemWriteReq))
cWriteGrant.foreach(_ := 0.U.asTypeOf(new TmemWriteReq))
aWriteValid.foreach(_ := false.B)
cWriteValid.foreach(_ := false.B)
(0 until tmemBanks).foreach { b =>
val readRequests = (0 until nTC).flatMap { tc =>
val aAddr = slice(core.io.tc_tmem_A_raddr, tmemAddrBits, tc)
val cAddr = slice(core.io.tc_tmem_C_raddr, tmemAddrBits, tc)
Seq(
(core.io.tc_tmem_A_ren(tc).asBool, aAddr, 0.U(2.W), tc.U),
(core.io.tc_tmem_C_ren(tc).asBool, cAddr, 1.U(2.W), tc.U)
)
} ++ Seq(
(core.io.sc_tmem_ren.asBool, core.io.sc_tmem_raddr, 2.U(2.W), 0.U)
)
var aReadUsed = false.B
var cReadUsed = false.B
readRequests.foreach { case (valid, addr, src, tc) =>
val physAddr = tmemPhysAddr(addr)
val isC = tmemIsC(addr)
val aGrant = valid && !isC && bank(physAddr) === b.U && !aReadUsed
val cGrant = valid && isC && bank(physAddr) === b.U && !cReadUsed
when(aGrant) {
aReadGrant(b).addr := physAddr
aReadGrant(b).src := src
aReadGrant(b).tc := tc
}
when(cGrant) {
cReadGrant(b).addr := physAddr
cReadGrant(b).src := src
cReadGrant(b).tc := tc
}
aReadUsed = aReadUsed || aGrant
cReadUsed = cReadUsed || cGrant
when(aGrant || cGrant) {
when(src === 0.U) { aReady(tc) := true.B }
when(src === 1.U) { cReady(tc) := true.B }
when(src === 2.U) { scReadReady := true.B }
}
}
aReadValid(b) := aReadUsed
cReadValid(b) := cReadUsed
var aWriteUsed = false.B
var cWriteUsed = false.B
(0 until nTC).foreach { tc =>
val addr = slice(core.io.tc_tmem_C_waddr, tmemAddrBits, tc)
val physAddr = tmemPhysAddr(addr)
val isC = tmemIsC(addr)
val valid = core.io.tc_tmem_C_wen(tc).asBool && bank(physAddr) === b.U
val aGrant = valid && !isC && !aWriteUsed
val cGrant = valid && isC && !cWriteUsed
when(aGrant) {
aWriteValid(b) := true.B
aWriteGrant(b).addr := physAddr
aWriteGrant(b).data := slice(core.io.tc_tmem_C_wdata, tmemDataBits, tc)
aWriteGrant(b).mask := slice(core.io.tc_tmem_C_mask, tmemMaskBits, tc)
aWriteGrant(b).src := 0.U
aWriteGrant(b).tc := tc.U
wReady(tc) := true.B
}
when(cGrant) {
cWriteValid(b) := true.B
cWriteGrant(b).addr := physAddr
cWriteGrant(b).data := slice(core.io.tc_tmem_C_wdata, tmemDataBits, tc)
cWriteGrant(b).mask := slice(core.io.tc_tmem_C_mask, tmemMaskBits, tc)
cWriteGrant(b).src := 0.U
cWriteGrant(b).tc := tc.U
wReady(tc) := true.B
}
aWriteUsed = aWriteUsed || aGrant
cWriteUsed = cWriteUsed || cGrant
}
val scWPhysAddr = tmemPhysAddr(core.io.sc_tmem_waddr)
val scWIsC = tmemIsC(core.io.sc_tmem_waddr)
val scWValid = core.io.sc_tmem_wen.asBool && bank(scWPhysAddr) === b.U
val scWAGrant = scWValid && !scWIsC && !aWriteUsed
val scWCGrant = scWValid && scWIsC && !cWriteUsed
when(scWAGrant) {
aWriteValid(b) := true.B
aWriteGrant(b).addr := scWPhysAddr
aWriteGrant(b).data := core.io.sc_tmem_wdata
aWriteGrant(b).mask := core.io.sc_tmem_mask
aWriteGrant(b).src := 1.U
aWriteGrant(b).tc := 0.U
scWriteReady := true.B
}
when(scWCGrant) {
cWriteValid(b) := true.B
cWriteGrant(b).addr := scWPhysAddr
cWriteGrant(b).data := core.io.sc_tmem_wdata
cWriteGrant(b).mask := core.io.sc_tmem_mask
cWriteGrant(b).src := 1.U
cWriteGrant(b).tc := 0.U
scWriteReady := true.B
}
tmemA(b).io.ren := aReadValid(b)
tmemA(b).io.raddr := row(aReadGrant(b).addr)
tmemA(b).io.wen := aWriteValid(b)
tmemA(b).io.waddr := row(aWriteGrant(b).addr)
tmemA(b).io.wdata := aWriteGrant(b).data
tmemA(b).io.mask := aWriteGrant(b).mask
tmemC(b).io.ren := cReadValid(b)
tmemC(b).io.raddr := row(cReadGrant(b).addr)
tmemC(b).io.wen := cWriteValid(b)
tmemC(b).io.waddr := row(cWriteGrant(b).addr)
tmemC(b).io.wdata := cWriteGrant(b).data
tmemC(b).io.mask := cWriteGrant(b).mask
}
val aReadGrantReg = RegNext(aReadGrant)
val cReadGrantReg = RegNext(cReadGrant)
val aReadValidReg = RegNext(aReadValid)
val cReadValidReg = RegNext(cReadValid)
core.io.tc_tmem_A_rready := aReady.asUInt
core.io.tc_tmem_C_rready := cReady.asUInt
core.io.tc_tmem_C_wready := wReady.asUInt
core.io.sc_tmem_rready := scReadReady.asUInt
core.io.sc_tmem_wready := scWriteReady.asUInt
core.io.tc_tmem_A_rdata := VecInit((0 until nTC).map { tc => core.io.tc_tmem_A_rdata := VecInit((0 until nTC).map { tc =>
Mux(aReadGrant(tc), tmem.io.rdata0, 0.U(tmemDataBits.W)) VecInit((0 until tmemBanks).map { b =>
Mux(aReadValidReg(b) && aReadGrantReg(b).src === 0.U && aReadGrantReg(b).tc === tc.U, tmemA(b).io.rdata,
Mux(cReadValidReg(b) && cReadGrantReg(b).src === 0.U && cReadGrantReg(b).tc === tc.U, tmemC(b).io.rdata, 0.U(tmemDataBits.W)))
}).reduce(_ | _)
}).asUInt }).asUInt
core.io.tc_tmem_C_rdata := VecInit((0 until nTC).map { tc => core.io.tc_tmem_C_rdata := VecInit((0 until nTC).map { tc =>
Mux(cReadGrant(tc), tmem.io.rdata1, 0.U(tmemDataBits.W)) VecInit((0 until tmemBanks).map { b =>
Mux(aReadValidReg(b) && aReadGrantReg(b).src === 1.U && aReadGrantReg(b).tc === tc.U, tmemA(b).io.rdata,
Mux(cReadValidReg(b) && cReadGrantReg(b).src === 1.U && cReadGrantReg(b).tc === tc.U, tmemC(b).io.rdata, 0.U(tmemDataBits.W)))
}).reduce(_ | _)
}).asUInt }).asUInt
core.io.sc_tmem_rdata := VecInit((0 until tmemBanks).map { b =>
Mux(aReadValidReg(b) && aReadGrantReg(b).src === 2.U, tmemA(b).io.rdata,
Mux(cReadValidReg(b) && cReadGrantReg(b).src === 2.U, tmemC(b).io.rdata, 0.U(tmemDataBits.W)))
}).reduce(_ | _)
// port 2: SMEM B, one TL client per tensor core. RadianceSharedMem arbitrates them. // port 2: SMEM B, one TL client per tensor core. RadianceSharedMem arbitrates them.
(0 until nTC).foreach { tc => (0 until nTC).foreach { tc =>
@@ -1025,6 +1194,9 @@ class RadianceTileModuleImp(outer: RadianceTile)
core.io.tc_tmem_C_rready := DontCare core.io.tc_tmem_C_rready := DontCare
core.io.tc_tmem_C_rdata := DontCare core.io.tc_tmem_C_rdata := DontCare
core.io.tc_tmem_C_wready := DontCare core.io.tc_tmem_C_wready := DontCare
core.io.sc_tmem_rready := DontCare
core.io.sc_tmem_rdata := DontCare
core.io.sc_tmem_wready := DontCare
} }
} }
@@ -1197,18 +1369,6 @@ class VortexTLAdapter(
val outResp = chiselTypeOf(outTL._1.d) val outResp = chiselTypeOf(outTL._1.d)
}) })
val (bundle, edge) = outTL val (bundle, edge) = outTL
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 // io passthrough logic
// TLBundleA <> VortexBundleA // TLBundleA <> VortexBundleA
io.outReq.valid := io.inReq.valid io.outReq.valid := io.inReq.valid
@@ -1217,29 +1377,70 @@ class VortexTLAdapter(
io.outReq.bits.size := io.inReq.bits.size io.outReq.bits.size := io.inReq.bits.size
io.outReq.bits.source := io.inReq.bits.source io.outReq.bits.source := io.inReq.bits.source
io.outReq.bits.address := io.inReq.bits.address io.outReq.bits.address := io.inReq.bits.address
// Get requires contiguous mask; only copy core's potentially-partial mask val outMaskWidth = io.outReq.bits.mask.getWidth
// when writing val inMaskWidth = io.inReq.bits.mask.getWidth
val outDataWidth = io.outReq.bits.data.getWidth
val inDataWidth = io.inReq.bits.data.getWidth
val byteOffset = io.inReq.bits.address(log2Ceil(outMaskWidth) - 1, 0)
val responseOffsetWidth = log2Ceil(outMaskWidth)
val responseSourceWidth = inReqT.source.getWidth
val sourceGen = Module(
new SourceGenerator(
newSourceWidth,
Some(UInt((responseSourceWidth + responseOffsetWidth).W)),
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 := Cat(byteOffset, io.inReq.bits.source)
val alignedMask = Wire(UInt(outMaskWidth.W))
val alignedData = Wire(UInt(outDataWidth.W))
if (outMaskWidth == inMaskWidth) {
alignedMask := io.inReq.bits.mask
} else {
val paddedMask = Wire(UInt(outMaskWidth.W))
paddedMask := io.inReq.bits.mask
alignedMask := (paddedMask << byteOffset)(outMaskWidth - 1, 0)
}
if (outDataWidth == inDataWidth) {
alignedData := io.inReq.bits.data
} else {
val paddedData = Wire(UInt(outDataWidth.W))
paddedData := io.inReq.bits.data
alignedData := (paddedData << (byteOffset << 3))(outDataWidth - 1, 0)
}
// PutFull requires the TL-canonical full mask for address+size; PutPartial
// can carry the core-provided byte mask.
io.outReq.bits.mask := Mux( io.outReq.bits.mask := Mux(
edge.hasData(io.outReq.bits), io.outReq.bits.opcode === TLMessages.PutPartialData,
io.inReq.bits.mask, alignedMask,
// generate TL-correct mask
edge.mask(io.inReq.bits.address, io.inReq.bits.size) edge.mask(io.inReq.bits.address, io.inReq.bits.size)
) )
io.outReq.bits.data := io.inReq.bits.data io.outReq.bits.data := alignedData
io.outReq.bits.corrupt := 0.U io.outReq.bits.corrupt := 0.U
io.inReq.ready := io.outReq.ready io.inReq.ready := io.outReq.ready
// VortexBundleD <> TLBundleD // VortexBundleD <> TLBundleD
io.inResp.valid := io.outResp.valid io.inResp.valid := io.outResp.valid
io.inResp.bits.opcode := io.outResp.bits.opcode io.inResp.bits.opcode := io.outResp.bits.opcode
io.inResp.bits.size := io.outResp.bits.size io.inResp.bits.size := io.outResp.bits.size
io.inResp.bits.source := io.outResp.bits.source val responseMeta = sourceGen.io.peek.asUInt
io.inResp.bits.data := io.outResp.bits.data val responseSource = responseMeta(responseSourceWidth - 1, 0)
val responseByteOffset =
responseMeta(responseSourceWidth + responseOffsetWidth - 1, responseSourceWidth)
io.inResp.bits.source := responseSource
if (outDataWidth == inDataWidth) {
io.inResp.bits.data := io.outResp.bits.data
} else {
io.inResp.bits.data := (io.outResp.bits.data >> (responseByteOffset << 3))(inDataWidth - 1, 0)
}
io.outResp.ready := io.inResp.ready io.outResp.ready := io.inResp.ready
// "man-in-the-middle" // "man-in-the-middle"
io.inReq.ready := io.outReq.ready && sourceGen.io.id.valid io.inReq.ready := io.outReq.ready && sourceGen.io.id.valid
io.outReq.valid := io.inReq.valid && sourceGen.io.id.valid io.outReq.valid := io.inReq.valid && sourceGen.io.id.valid
io.outReq.bits.source := 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
} }

View File

@@ -120,6 +120,15 @@ class VortexBundle(tile: RadianceTile)(implicit p: Parameters) extends CoreBundl
val tc_tmem_C_waddr = Output(UInt((numTensorCores * 9).W)) val tc_tmem_C_waddr = Output(UInt((numTensorCores * 9).W))
val tc_tmem_C_wdata = Output(UInt((numTensorCores * numLanes * 32).W)) val tc_tmem_C_wdata = Output(UInt((numTensorCores * numLanes * 32).W))
val tc_tmem_C_mask = Output(UInt((numTensorCores * numLanes * 4).W)) val tc_tmem_C_mask = Output(UInt((numTensorCores * numLanes * 4).W))
val sc_tmem_ren = Output(UInt(1.W))
val sc_tmem_rready = Input(UInt(1.W))
val sc_tmem_raddr = Output(UInt(9.W))
val sc_tmem_rdata = Input(UInt((numLanes * 32).W))
val sc_tmem_wen = Output(UInt(1.W))
val sc_tmem_wready = Input(UInt(1.W))
val sc_tmem_waddr = Output(UInt(9.W))
val sc_tmem_wdata = Output(UInt((numLanes * 32).W))
val sc_tmem_mask = Output(UInt((numLanes * 4).W))
// FIXME: hardcoded // FIXME: hardcoded
val barrierIdBits = tile.barrierMasterNode.out(0)._2.barrierIdBits val barrierIdBits = tile.barrierMasterNode.out(0)._2.barrierIdBits
@@ -204,6 +213,7 @@ class Vortex(tile: RadianceTile)(implicit p: Parameters)
addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch_unit.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch_unit.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch_unit_sane.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_dispatch_unit_sane.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_tmem_softmax_unit.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_execute.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_execute.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_fetch.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_fetch.sv")
addResource("/vsrc/vortex/hw/rtl/core/VX_gather_unit.sv") addResource("/vsrc/vortex/hw/rtl/core/VX_gather_unit.sv")
@@ -351,6 +361,7 @@ class Vortex(tile: RadianceTile)(implicit p: Parameters)
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_div.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_div.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_dpi.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_dpi.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_dsp.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_dsp.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_exp.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_fma.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_fma.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_ncomp.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_ncomp.sv")
addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_rounding.sv") addResource("/vsrc/vortex/hw/rtl/fpu/VX_fpu_rounding.sv")

View File

@@ -283,6 +283,7 @@ class TensorCoreBlackwellTest extends AnyFlatSpec with ChiselScalatestTester {
var pendingB = Option.empty[(BigInt, BigInt)] var pendingB = Option.empty[(BigInt, BigInt)]
var sawWriteback = false var sawWriteback = false
var cycles = 0
for (_ <- 0 until 20000 if !sawWriteback) { for (_ <- 0 until 20000 if !sawWriteback) {
// Drive TMEM reads/writes // Drive TMEM reads/writes
@@ -306,11 +307,14 @@ class TensorCoreBlackwellTest extends AnyFlatSpec with ChiselScalatestTester {
} else None } else None
c.clock.step() c.clock.step()
cycles += 1
pendingB = nextB pendingB = nextB
} }
} }
assert(sawWriteback, "BWGMMA did not complete") assert(sawWriteback, "BWGMMA did not complete")
assert(cycles < 5000,
s"BWGMMA took $cycles cycles; fragment elements are not issuing back-to-back")
c.io.writeback.bits.wid.expect(1.U) c.io.writeback.bits.wid.expect(1.U)
// Verify all 32 C frags in TMEM // Verify all 32 C frags in TMEM
for (i <- 0 until 32) { for (i <- 0 until 32) {