Skip to content

Commit 6cccec5

Browse files
committed
Make duplicate-bank regfile follow collector semantics
Although its design is simpler, duplicate-bank regfile still requires staging of the operands until downstream EX is ready. This is best handled by implementing flops-based collector banks for DuplicatedCollector and having it conform to the latency-insensitive readReq/Resp/Data protocol that is designed to work well with the reservation station. In this new protocol, operand reads from the collector module always becomes combinational, making it easier to align EX issue fire with operand data access. WIP: This breaks noILP = true.
1 parent a1217d1 commit 6cccec5

5 files changed

Lines changed: 145 additions & 81 deletions

File tree

src/main/scala/radiance/muon/Backend.scala

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,21 +79,21 @@ class Backend(
7979
collector.io.readReq.valid := collector.io.readReq.bits.anyEnabled()
8080
if (noILP) {
8181
// on noILP, manage collector entirely after issue
82-
(haves lazyZip regs lazyZip collector.io.readData.regs lazyZip collector.io.readReq.bits.regs)
83-
.foreach { case (has, reg, readData, collReq) =>
84-
val pReg = issued.bits.uop.inst(reg)
85-
collReq.enable := issued.valid && issued.bits.uop.inst.b(has)
86-
collReq.pReg := pReg
87-
readData.enable := issued.valid && issued.bits.uop.inst.b(has)
88-
readData.pReg.get := pReg
89-
readData.collEntry := DontCare
90-
}
91-
collector.io.readReq.bits.rsEntryId := DontCare
82+
// (haves lazyZip regs lazyZip collector.io.readData.resp lazyZip collector.io.readReq.bits.regs)
83+
// .foreach { case (has, reg, readData, collReq) =>
84+
// val pReg = issued.bits.uop.inst(reg)
85+
// collReq.enable := issued.valid && issued.bits.uop.inst.b(has)
86+
// collReq.pReg := pReg
87+
// readData.enable := issued.valid && issued.bits.uop.inst.b(has)
88+
// readData.pReg.get := pReg
89+
// readData.collEntry := DontCare
90+
// }
91+
// collector.io.readReq.bits.rsEntryId := DontCare
9292

9393
reservStation.io.collector.readReq.ready := false.B
9494
reservStation.io.collector.readResp.ports.foreach(_.valid := false.B)
9595
reservStation.io.collector.readResp.ports.foreach(_.bits := DontCare)
96-
reservStation.io.collector.readData.regs.foreach(_.data := DontCare)
96+
// reservStation.io.collector.readData.resp.foreach(_.data := DontCare)
9797
} else {
9898
// RS manages collector
9999
collector.io.readReq <> reservStation.io.collector.readReq
@@ -105,8 +105,11 @@ class Backend(
105105
val executeIn = WireInit(0.U.asTypeOf(fuInT(hasRs1 = true, hasRs2 = true, hasRs3 = true)))
106106
val operands = Seq(executeIn.rs1Data, executeIn.rs2Data, executeIn.rs3Data).map(_.get)
107107
collector.io.readResp.ports.foreach(_.ready := true.B)
108-
(operands zip collector.io.readData.regs).foreach { case (opnd, port) =>
109-
opnd := port.data
108+
(operands zip collector.io.readData.resp.bits).foreach { case (opnd, port) =>
109+
// value-gate to reduce switching
110+
opnd := Mux(port.enable,
111+
port.data,
112+
VecInit.fill(numLanes)(0.U.asTypeOf(regDataT)))
110113
}
111114

112115
// -------

src/main/scala/radiance/muon/Collector.scala

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,16 @@ object CollectorResponse {
5252
class CollectorOperandRead(implicit p: Parameters) extends CoreBundle()(p) {
5353
val collEntryWidth = log2Up(muonParams.numCollectorEntries)
5454
val hasPReg = !muonParams.useCollector
55-
val regs = Vec(Isa.maxNumRegs, new Bundle {
56-
val enable = Input(Bool())
57-
val pReg = Option.when(hasPReg)(Input(pRegT))
58-
val collEntry = Input(UInt(collEntryWidth.W))
59-
val data = Output(Vec(numLanes, regDataT))
55+
/** RS will keep track of collEntry and send a req with it */
56+
val req = Flipped(Valid(new Bundle {
57+
val collEntry = UInt(collEntryWidth.W)
58+
}))
59+
val resp = Decoupled(Vec(Isa.maxNumRegs, new Bundle {
60+
val enable = Bool()
61+
val pReg = Option.when(hasPReg)(pRegT)
62+
val data = Vec(numLanes, regDataT)
6063
// TODO: tmask
61-
})
64+
}))
6265
}
6366

6467
/** Simple operand collector with duplicated register files for rs1/2/3.
@@ -78,13 +81,13 @@ class DuplicatedCollector(implicit p: Parameters) extends CoreModule()(p) {
7881
val writeReq = CollectorRequest(1, isWrite = true)
7982
val writeResp = CollectorResponse(1, isWrite = true)
8083
/** Data read port for the operands readily collected & stored in the
81-
* flip-flop banks. Combinational-read. */
84+
* flip-flop banks. */
8285
val readData = new CollectorOperandRead
8386
})
8487

8588
def vecRegDataT = Vec(numLanes, regDataT)
8689
val vecZeros = 0.U.asTypeOf(vecRegDataT)
87-
val rfBanks = Seq.fill(3)(Seq.fill(muonParams.numRegBanks)(SRAM(
90+
val rfBanks = Seq.fill(Isa.maxNumRegs)(Seq.fill(muonParams.numRegBanks)(SRAM(
8891
size = muonParams.numPhysRegs / muonParams.numRegBanks,
8992
tpe = vecRegDataT,
9093
numReadPorts = 1,
@@ -100,36 +103,66 @@ class DuplicatedCollector(implicit p: Parameters) extends CoreModule()(p) {
100103
if (bankIdWidth == 0) 0.U else r(regWidth - 1, bankAddrWidth)
101104
}
102105

103-
// read response port
104-
// duplicated collectors always answer readResp 1-cycle after since no
105-
// conflicts to worry about
106-
io.readReq.ready := true.B
107-
(io.readReq.bits.regs lazyZip io.readResp.ports).foreach { case (reqPort, respPort) =>
108-
val opEn = io.readReq.fire && reqPort.enable
109-
respPort.valid := RegNext(opEn, false.B)
110-
respPort.bits.collEntry := 0.U // fixed for DuplicatedCollector
111-
}
112-
113-
// read data port
114-
val dataEnables = io.readData.regs.map(_.enable)
115-
val dataPRegs = io.readData.regs.map(_.pReg.get)
116-
(dataEnables lazyZip dataPRegs lazyZip rfBanks lazyZip io.readData.regs)
117-
.foreach { case (en, pReg, banks, readDataOp) =>
106+
// flip-flop banks that stage collected registers until EX consumes them
107+
val collBanks = Seq.fill(Isa.maxNumRegs)(Module(
108+
new Queue(gen = vecRegDataT, entries = 1, pipe = true)
109+
))
110+
111+
// read requests
112+
val readEns = io.readReq.bits.regs.map(_.enable)
113+
val readPRegs = io.readReq.bits.regs.map(_.pReg)
114+
(readEns lazyZip readPRegs lazyZip rfBanks lazyZip collBanks)
115+
.foreach { case (en, pReg, banks, collBank) =>
116+
val bankEn = io.readReq.fire && en && (pReg =/= 0.U)
118117
val bankPorts = VecInit(banks.map(_.readPorts.head))
119118
val bankId = regBankId(pReg)
120119
val nextBankId = RegNext(bankId, 0.U)
120+
val nextEn = RegNext(bankEn, false.B)
121121
val nextPReg = RegNext(pReg, 0.U)
122122

123123
// request
124124
bankPorts.foreach(_.enable := false.B)
125-
bankPorts(bankId).enable := en && (pReg =/= 0.U)
125+
bankPorts(bankId).enable := bankEn
126126
bankPorts.foreach(_.address := regBankAddr(pReg))
127127

128128
val bankOut = Mux(nextPReg =/= 0.U,
129-
VecInit(bankPorts.map(_.data))(nextBankId),
130-
vecZeros)
131-
readDataOp.data := bankOut
129+
VecInit(bankPorts.map(_.data))(nextBankId),
130+
vecZeros)
131+
collBank.io.enq.valid := nextEn
132+
collBank.io.enq.bits := bankOut
132133
}
134+
// consider backpressure from collector banks
135+
val allCollBanksReady = collBanks.map(_.io.enq.ready).reduce(_ && _)
136+
io.readReq.ready := allCollBanksReady
137+
138+
// read response
139+
// duplicated collector always answers readResp 1-cycle later
140+
(io.readReq.bits.regs lazyZip io.readResp.ports).foreach { case (reqPort, respPort) =>
141+
val opEn = io.readReq.fire && reqPort.enable
142+
respPort.valid := RegNext(opEn, false.B)
143+
respPort.bits.collEntry := 0.U // fixed for DuplicatedCollector
144+
}
145+
146+
// // readData
147+
// val dataEnables = io.readData.regs.map(_.enable)
148+
// val dataPRegs = io.readData.regs.map(_.pReg.get)
149+
// (dataEnables lazyZip dataPRegs lazyZip rfBanks lazyZip io.readData.regs)
150+
// .foreach { case (en, pReg, banks, readDataOp) =>
151+
// val bankPorts = VecInit(banks.map(_.readPorts.head))
152+
// val bankId = regBankId(pReg)
153+
// val nextBankId = RegNext(bankId, 0.U)
154+
// val nextPReg = RegNext(pReg, 0.U)
155+
156+
// // request
157+
// bankPorts.foreach(_.enable := false.B)
158+
// bankPorts(bankId).enable := en && (pReg =/= 0.U)
159+
// bankPorts.foreach(_.address := regBankAddr(pReg))
160+
161+
// val bankOut = Mux(nextPReg =/= 0.U,
162+
// VecInit(bankPorts.map(_.data))(nextBankId),
163+
// vecZeros)
164+
// readDataOp.data := bankOut
165+
// }
133166

134167
// write port
135168
require(io.writeReq.bits.regs.length == 1, "collector: only single-writeback per cycle supported")
@@ -150,6 +183,16 @@ class DuplicatedCollector(implicit p: Parameters) extends CoreModule()(p) {
150183

151184
io.writeResp.ports.head.valid := RegNext(writeEnable)
152185
io.writeResp.ports.head.bits.collEntry := 0.U // duplicated collector has no collector entry
186+
187+
// operand serve (readData)
188+
io.readData.resp.valid := collBanks.map(_.io.deq.valid).reduce(_ || _)
189+
(io.readData.resp.bits zip collBanks).foreach { case (opnd, collBank) =>
190+
opnd.enable := collBank.io.deq.valid
191+
opnd.data := collBank.io.deq.bits
192+
assert(opnd.pReg.isEmpty)
193+
// dequeue. Note: should be fire to consider EX back-pressure
194+
collBank.io.deq.ready := io.readData.resp.fire
195+
}
153196
}
154197

155198
class CollectorAllocTableEntry(implicit p: Parameters) extends CoreBundle()(p) {

src/main/scala/radiance/muon/MuonCore.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import freechips.rocketchip.resources.BigIntHexContext
66
import freechips.rocketchip.rocket.ALU
77
import freechips.rocketchip.util.ParameterizedBundle
88
import org.chipsalliance.cde.config.{Field, Parameters}
9-
import org.chipsalliance.diplomacy.lazymodule.LazyModule
109
import radiance.cluster.CacheFlushBundle
1110
import radiance.muon.backend.RegWriteback
1211
import radiance.muon.backend.fp.FPPipeParams
@@ -35,9 +34,9 @@ case class MuonCoreParams(
3534
noILP: Boolean = false, // fallback to single-in-flight instruction issue
3635
// logic ("bypass")
3736
// collector
38-
useCollector: Boolean = false, // if true, use a bank-conflict-avoiding
39-
// operand collector; if false, use a simple
40-
// rs1/2/3-duplicated register file
37+
useCollector: Boolean = true, // if true, use a bank-conflict-avoiding
38+
// operand collector; if false, use a simple
39+
// rs1/2/3-duplicated register file
4140
numRegBanks: Int = 1, // when useCollector true
4241
numCollectorEntries: Int = 1, // when useCollector true
4342
// execute

src/main/scala/radiance/muon/ReservationStation.scala

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class ReservationStation(implicit p: Parameters) extends CoreModule()(p) {
6969
def collFiredTable(row: Int): Vec[Bool] = collFiredTable(row.U)
7070
// where the operand lives in the collector banks
7171
val collPtrTable = Mem(numEntries, Vec(Isa.maxNumRegs, UInt(collEntryWidth.W)))
72-
val collPriorityTable = Wire(Vec(numEntries, Bool()))
72+
val collAllReadyTable = Wire(Vec(numEntries, Bool()))
7373

7474
(0 until numEntries).map { i =>
7575
val uop = instTable(i).uop
@@ -119,55 +119,69 @@ class ReservationStation(implicit p: Parameters) extends CoreModule()(p) {
119119
.map { case (r, (b, cf)) => !r && !b && !cf})
120120
val needCollect = valid && needCollectOps.reduce(_ || _)
121121
// is this uop RAW-cleared and only waiting for collection?
122-
val priority = needCollect && (needCollectOps === VecInit(opReadys.map(!_)))
123-
(needCollect, needCollectOps, priority)
122+
val needCollectAllReady = needCollect && (needCollectOps === VecInit(opReadys.map(!_)))
123+
(needCollect, needCollectOps, needCollectAllReady)
124124
}
125125
// select a single entry for collection
126126
// TODO: @perf: currently a simple priority encoder; might introduce fairness
127127
// problem
128128
val collBitvec = WireDefault(VecInit(needCollects.map(_._1)))
129-
collPriorityTable := VecInit(needCollects.map(_._3))
129+
collAllReadyTable := VecInit(needCollects.map(_._3))
130130
dontTouch(collBitvec)
131-
dontTouch(collPriorityTable)
132-
133-
// Prioritize rows that has no RAW-busy ops, and only needs collection as the
134-
// last step before issue. Otherwise, rows with partial ops can take up
135-
// valuable space in the collector banks.
131+
dontTouch(collAllReadyTable)
132+
133+
// don't allow early-firing collector requests for partial operands of an
134+
// instruction. Partial collects may result in a deadlock with insufficient
135+
// collector entries where the younger instruction requesting a partial
136+
// collect blocks collection of an older instruction, but has RAW hazard to
137+
// that older instruction.
138+
val allowPartialCollect = false
139+
// If allowPartialCollect == true, prioritize rows that has no RAW-busy ops
140+
// (no partial-collects), and only needs collection as the last step before
141+
// issue. Otherwise, rows with partial ops can take up valuable space in the
142+
// collector banks.
136143
//
137-
// NOTE: It's debatable whether this logic should be in the collector or not.
138-
// But for that, we need some kind of bookkeeping in the collector for the
144+
// NOTE: Arguably all of this should be in the collector module. But for
145+
// that, we need some kind of bookkeeping in the collector for the
139146
// partial-collect uops, which is what the collector banks are meant for,
140147
// which are expensive.
141-
val anyPriority = collPriorityTable.reduce(_ || _)
142-
val firstPriorityRow = PriorityEncoder(collPriorityTable)
148+
val allReadyExists = collAllReadyTable.reduce(_ || _)
149+
val firstAllReadyRow = PriorityEncoder(collAllReadyTable)
143150
val firstNeedRow = PriorityEncoder(collBitvec)
144-
val collRow = Mux(anyPriority, firstPriorityRow, firstNeedRow)
151+
val collRow = Mux(!allowPartialCollect.B || allReadyExists,
152+
firstAllReadyRow, firstNeedRow)
145153
dontTouch(collRow)
146154

147155
val collOpNeed = VecInit(needCollects.map(_._2))(collRow)
156+
val collValid = (if (allowPartialCollect) {
157+
allReadyExists
158+
} else {
159+
collOpNeed.reduce(_ || _)
160+
})
148161
val collUop = instTable(collRow).uop
149162
val collPC = WireDefault(collUop.pc)
150163
dontTouch(collPC)
151164
val collRegs = Seq(collUop.inst.rs1, collUop.inst.rs2, collUop.inst.rs3)
152165

153-
// this is clunky, but Mem does not support partial-field updates
154-
val newCollPtr = WireDefault(collPtrTable(collRow))
155166
assert(collOpNeed.length == io.collector.readReq.bits.regs.length)
156167
assert(collRegs.length == io.collector.readReq.bits.regs.length)
168+
169+
io.collector.readReq.valid := collValid
170+
// this is clunky, but Mem does not support partial-field updates
171+
val newCollPtr = WireDefault(collPtrTable(collRow))
157172
(collOpNeed lazyZip collRegs lazyZip io.collector.readReq.bits.regs)
158173
.zipWithIndex.foreach { case ((need, pReg, collPort), rsi) =>
159174
assert(collPort.data.isEmpty)
160-
collPort.enable := need
175+
collPort.enable := collValid && need
161176
collPort.pReg := Mux(need, pReg, 0.U)
162177
// TODO: currently assumes DuplicatedCollector with only 1 entry
163178
newCollPtr(rsi) := 0.U
164179
}
165180
io.collector.readReq.bits.rsEntryId := collRow
166-
io.collector.readReq.valid := io.collector.readReq.bits.anyEnabled()
167181
when (io.collector.readReq.fire) {
168-
val fired = (collFiredTable(collRow) zip io.collector.readReq.bits.regs.map(_.enable))
182+
val newFired = (collFiredTable(collRow) zip io.collector.readReq.bits.regs.map(_.enable))
169183
.map { case (a,b) => a || b }
170-
collFiredTable(collRow) := fired
184+
collFiredTable(collRow) := newFired
171185
collPtrTable(collRow) := newCollPtr
172186

173187
printf(cf"RS: collector request fired at row:${collRow}, pc:${collUop.pc}%x\n")
@@ -271,25 +285,30 @@ class ReservationStation(implicit p: Parameters) extends CoreModule()(p) {
271285
(issueScheduler.io.in zip eligibles).foreach { case (s, e) => s <> e }
272286
val issueScheduled = issueScheduler.io.out
273287

274-
// drive the operands port upon issue via collector's combinational readData
288+
// drive collector's operand serve port upon issue, for use in EX
275289
val issuedId = WireDefault(issueScheduler.io.out.bits.entryId)
276-
io.collector.readData.regs.zipWithIndex.foreach { case (port, rsi) =>
277-
port.enable := issueScheduled.fire && hasOpTable(issuedId)(rsi)
278-
port.collEntry := collPtrTable(issuedId)(rsi)
279-
port.pReg match {
280-
// for no-collector config, collEntry pointer is not used; use pRegs to
281-
// actually drive SRAMs
282-
case Some(pReg) => pReg := rsTable(issuedId)(rsi)
283-
case None => assert(useCollector,
284-
"collector data port has unnecessary pReg field instantiated when useCollector == true")
285-
}
286-
// port.data input is not used
287-
}
290+
io.collector.readData.req.valid := issueScheduled.valid
291+
io.collector.readData.req.bits.collEntry := 0.U // fixed for DuplicatedCollector
292+
io.collector.readData.resp.ready := issueScheduled.fire
293+
assert(useCollector, "FIXME: !useCollector is broken currently")
294+
// io.collector.readData.req.bits.zipWithIndex.foreach { case (port, rsi) =>
295+
// port.enable := issueScheduled.fire && hasOpTable(issuedId)(rsi)
296+
// port.pReg match {
297+
// // for no-collector config, collEntry pointer is not used; use pRegs to
298+
// // actually drive SRAMs
299+
// // FIXME: remove
300+
// case Some(pReg) => pReg := rsTable(issuedId)(rsi)
301+
// case None => assert(useCollector,
302+
// "collector data port has unnecessary pReg field instantiated when useCollector == true")
303+
// }
304+
// // port.data input is not used
305+
// }
288306
dontTouch(issuedId)
289307

290308
// if not using collector, RS only directly uses the readData port and never
291309
// sends readReq / gets readResp back, so we need to signal scoreboard
292310
// explicitly at issue time
311+
// FIXME: Hacky; handle this altogether in Collector
293312
if (!useCollector) {
294313
io.scb.updateColl.enable := issueScheduled.fire
295314
io.scb.updateColl.reads.foreach(_ := 0.U.asTypeOf(new ScoreboardRegUpdate))
@@ -309,8 +328,8 @@ class ReservationStation(implicit p: Parameters) extends CoreModule()(p) {
309328
// implication due to sequential reads unlike collector buffers with
310329
// combinational reads. In that case, we need to align the issue timing with
311330
// the SRAM round-trip.
312-
val collectorSequentialRead = !useCollector
313-
if (collectorSequentialRead) {
331+
val operandOneCycleLate = !useCollector
332+
if (operandOneCycleLate) {
314333
val queue = Module(
315334
new Queue(gen = chiselTypeOf(io.issue.bits), entries = 1, pipe = true)
316335
)
@@ -404,7 +423,7 @@ class ReservationStation(implicit p: Parameters) extends CoreModule()(p) {
404423
cf"hasOp:${hasOpTable(i)(0)}${hasOpTable(i)(1)}${hasOpTable(i)(2)} | " +
405424
cf"opReady:${opReadyTable(i)(0)}${opReadyTable(i)(1)}${opReadyTable(i)(2)} | " +
406425
cf"busy:${busyTable(i)(0)}${busyTable(i)(1)}${busyTable(i)(2)} | " +
407-
cf"collPriority:${collPriorityTable(i)} | " +
426+
cf"collPriority:${collAllReadyTable(i)} | " +
408427
cf"collFired:${collFiredTable(i)(0)}${collFiredTable(i)(1)}${collFiredTable(i)(2)}" +
409428
cf"\n")
410429
}

src/main/scala/radiance/unittest/Configs.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class MuonCoreNoILPTestConfig extends Config(
3939
new WithSIMTConfig(numWarps = 8, numLanes = 16, numLsuLanes = 16, numSMEMInFlights = 4) ++
4040
new BaseSubsystemConfig)
4141

42-
class MuonCoreTestNoDiffConfig extends Config(
42+
class MuonCoreNoDiffTestConfig extends Config(
4343
new WithMuonUnitTestHarness(new MuonCoreTest()(_)) ++
4444
new WithMuonCores(1, standalone = true, difftest = false) ++
4545
new WithSIMTConfig(numWarps = 8, numLanes = 16, numLsuLanes = 16, numSMEMInFlights = 4) ++

0 commit comments

Comments
 (0)