Handle backpressure from CoalescerNode

Now do proper sourcegen for the tlCoal edge that's coming out
of the coalescer manager node.  This also prevents inflight table from
being full.

This means we move setting source ID of coalReq to outside the
coalescer, because sourceGen needs looking into response bits as well,
which is easier to do outside coalescer at the toplevel.

FIXME: coalescer unit test is still broken.
This commit is contained in:
Hansung Kim
2023-05-12 01:05:23 -07:00
parent 7e6ebb9e35
commit e02e4ca500

View File

@@ -93,7 +93,7 @@ object defaultConfig extends CoalescerConfig(
wordSizeInBytes = 4, wordSizeInBytes = 4,
// when attaching to SoC, 16 source IDs are not enough due to longer latency // when attaching to SoC, 16 source IDs are not enough due to longer latency
numOldSrcIds = 16, numOldSrcIds = 16,
numNewSrcIds = 4, numNewSrcIds = 8,
respQueueDepth = 4, respQueueDepth = 4,
coalLogSizes = Seq(3), coalLogSizes = Seq(3),
sizeEnum = DefaultInFlightTableSizeEnum, sizeEnum = DefaultInFlightTableSizeEnum,
@@ -663,16 +663,12 @@ class MultiCoalescer(
) )
} }
val sourceGen = Module( val coalesceValid = chosenValid
new RoundRobinSourceGenerator(log2Ceil(config.numNewSrcIds))
)
sourceGen.io.gen := io.coalReq.fire // use up a source ID only when request is created
sourceGen.io.reclaim.valid := false.B // not used
sourceGen.io.reclaim.bits := DontCare // not used
val coalesceValid = chosenValid && sourceGen.io.id.valid // setting source is deferred, because in order to do proper source ID
// generation we also have to look at the responses coming back, which
io.coalReq.bits.source := sourceGen.io.id.bits // is easier to do at the toplevel.
io.coalReq.bits.source := DontCare
io.coalReq.bits.mask := mask.asUInt io.coalReq.bits.mask := mask.asUInt
io.coalReq.bits.data := data.asUInt io.coalReq.bits.data := data.asUInt
io.coalReq.bits.size := chosenSize io.coalReq.bits.size := chosenSize
@@ -732,6 +728,8 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig)
reqQueues.io.coalescable := coalescer.io.coalescable reqQueues.io.coalescable := coalescer.io.coalescable
reqQueues.io.invalidate := coalescer.io.invalidate reqQueues.io.invalidate := coalescer.io.invalidate
val uncoalescer = Module(new Uncoalescer(config, nonCoalReqT, coalReqT))
// =========================================================================== // ===========================================================================
// Request flow // Request flow
// =========================================================================== // ===========================================================================
@@ -761,8 +759,10 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig)
val deq = reqQueues.io.queue.deq(lane) val deq = reqQueues.io.queue.deq(lane)
enq.valid := tlIn.a.valid enq.valid := tlIn.a.valid
enq.bits := req enq.bits := req
// TODO: deq.ready should respect downstream arbiter // Only allow dequeue when uncoalescer is ready to record the current
deq.ready := true.B // queue entries
// TODO: deq.ready should also respect downstream arbiter
deq.ready := uncoalescer.io.coalReq.ready
// Stall upstream core or memtrace driver when shiftqueue is not ready // Stall upstream core or memtrace driver when shiftqueue is not ready
tlIn.a.ready := enq.ready tlIn.a.ready := enq.ready
tlOut.a.valid := deq.valid tlOut.a.valid := deq.valid
@@ -831,7 +831,7 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig)
// queues. // queues.
// The maximum number of requests from a single lane that can go into a // The maximum number of requests from a single lane that can go into a
// coalesced request. Upper bound is min(DEPTH, 2**sourceWidth). // coalesced request.
val numPerLaneReqs = config.queueDepth val numPerLaneReqs = config.queueDepth
// FIXME: no need to contain maxCoalLogSize data // FIXME: no need to contain maxCoalLogSize data
@@ -911,12 +911,19 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig)
dontTouch(tlOut.d) dontTouch(tlOut.d)
} }
val uncoalescer = Module(
new Uncoalescer(config, nonCoalReqT, coalReqT)
)
// connect coalesced request that is newly generated and being recorded in // connect coalesced request that is newly generated and being recorded in
// the uncoalescer // the uncoalescer
uncoalescer.io.coalReq <> coalescer.io.coalReq uncoalescer.io.coalReq <> coalescer.io.coalReq
// We can't simply use coalescer.io.coalReq.valid here.
// coalescer.io.coalReq.valid tells us when there exists a valid coalescing
// combination, but not when we can actually fire that to downstream, because
// we can still be blocked by source ID clashes due to backpressure.
// So, we have to overwrite just the valid bit with the final valid that
// indicates when we can send this request out.
// NOTE(hansung): this feels slightly awkward. Maybe doing sourcegen inside
// the coalescer so that it gives the final call is better, but that may be
// too much IO for the coalescer.
uncoalescer.io.coalReq.valid := coalReqValid
uncoalescer.io.invalidate := coalescer.io.invalidate uncoalescer.io.invalidate := coalescer.io.invalidate
val reqQueueHeads = reqQueues.io.queue.deq.map(_.bits) val reqQueueHeads = reqQueues.io.queue.deq.map(_.bits)
uncoalescer.io.windowElts := reqQueues.io.elts uncoalescer.io.windowElts := reqQueues.io.elts
@@ -984,9 +991,10 @@ class Uncoalescer(
) )
}) })
// Uncoalescer has to be always ready to accept and record new coalesced // If inflight table is full, we cannot accept new requests to record them.
// requests, so that it doesn't stall the coalescer. // This might happen when we sent out many requests and exhausted all source
io.coalReq.ready := true.B // IDs, but they haven't come back yet.
io.coalReq.ready := inflightTable.io.enq.ready
// Construct a new entry for the inflight table using generated coalesced request // Construct a new entry for the inflight table using generated coalesced request
def generateInflightTableEntry: InflightCoalReqTableEntry = { def generateInflightTableEntry: InflightCoalReqTableEntry = {
@@ -1136,7 +1144,6 @@ class InflightCoalReqTable(config: CoalescerConfig) extends Module {
val full = Wire(Bool()) val full = Wire(Bool())
full := (0 until entries).map(table(_).valid).reduce(_ && _) full := (0 until entries).map(table(_).valid).reduce(_ && _)
assert(!full, "inflight table is full and blocking coalescer")
dontTouch(full) dontTouch(full)
// Enqueue logic // Enqueue logic
@@ -1156,9 +1163,8 @@ class InflightCoalReqTable(config: CoalescerConfig) extends Module {
// Lookup logic // Lookup logic
io.lookup.valid := table(io.lookupSourceId).valid io.lookup.valid := table(io.lookupSourceId).valid
io.lookup.bits := table(io.lookupSourceId).bits io.lookup.bits := table(io.lookupSourceId).bits
val lookupFire = io.lookup.ready && io.lookup.valid
// Dequeue as soon as lookup succeeds // Dequeue as soon as lookup succeeds
when(lookupFire) { when(io.lookup.fire) {
table(io.lookupSourceId).valid := false.B table(io.lookupSourceId).valid := false.B
} }