From e02e4ca500e51b28902fb9dc2be4b1cf583805c7 Mon Sep 17 00:00:00 2001 From: Hansung Kim Date: Fri, 12 May 2023 01:05:23 -0700 Subject: [PATCH] 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. --- src/main/scala/tilelink/Coalescing.scala | 50 +++++++++++++----------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/main/scala/tilelink/Coalescing.scala b/src/main/scala/tilelink/Coalescing.scala index d73c501..b71675b 100644 --- a/src/main/scala/tilelink/Coalescing.scala +++ b/src/main/scala/tilelink/Coalescing.scala @@ -93,7 +93,7 @@ object defaultConfig extends CoalescerConfig( wordSizeInBytes = 4, // when attaching to SoC, 16 source IDs are not enough due to longer latency numOldSrcIds = 16, - numNewSrcIds = 4, + numNewSrcIds = 8, respQueueDepth = 4, coalLogSizes = Seq(3), sizeEnum = DefaultInFlightTableSizeEnum, @@ -663,16 +663,12 @@ class MultiCoalescer( ) } - val sourceGen = Module( - 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 - val coalesceValid = chosenValid && sourceGen.io.id.valid - - io.coalReq.bits.source := sourceGen.io.id.bits + // setting source is deferred, because in order to do proper source ID + // generation we also have to look at the responses coming back, which + // is easier to do at the toplevel. + io.coalReq.bits.source := DontCare io.coalReq.bits.mask := mask.asUInt io.coalReq.bits.data := data.asUInt io.coalReq.bits.size := chosenSize @@ -732,6 +728,8 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig) reqQueues.io.coalescable := coalescer.io.coalescable reqQueues.io.invalidate := coalescer.io.invalidate + val uncoalescer = Module(new Uncoalescer(config, nonCoalReqT, coalReqT)) + // =========================================================================== // Request flow // =========================================================================== @@ -761,8 +759,10 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig) val deq = reqQueues.io.queue.deq(lane) enq.valid := tlIn.a.valid enq.bits := req - // TODO: deq.ready should respect downstream arbiter - deq.ready := true.B + // Only allow dequeue when uncoalescer is ready to record the current + // 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 tlIn.a.ready := enq.ready tlOut.a.valid := deq.valid @@ -831,7 +831,7 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig) // queues. // 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 // FIXME: no need to contain maxCoalLogSize data @@ -911,12 +911,19 @@ class CoalescingUnitImp(outer: CoalescingUnit, config: CoalescerConfig) dontTouch(tlOut.d) } - val uncoalescer = Module( - new Uncoalescer(config, nonCoalReqT, coalReqT) - ) // connect coalesced request that is newly generated and being recorded in // the uncoalescer 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 val reqQueueHeads = reqQueues.io.queue.deq.map(_.bits) uncoalescer.io.windowElts := reqQueues.io.elts @@ -984,9 +991,10 @@ class Uncoalescer( ) }) - // Uncoalescer has to be always ready to accept and record new coalesced - // requests, so that it doesn't stall the coalescer. - io.coalReq.ready := true.B + // If inflight table is full, we cannot accept new requests to record them. + // This might happen when we sent out many requests and exhausted all source + // 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 def generateInflightTableEntry: InflightCoalReqTableEntry = { @@ -1136,7 +1144,6 @@ class InflightCoalReqTable(config: CoalescerConfig) extends Module { val full = Wire(Bool()) full := (0 until entries).map(table(_).valid).reduce(_ && _) - assert(!full, "inflight table is full and blocking coalescer") dontTouch(full) // Enqueue logic @@ -1156,9 +1163,8 @@ class InflightCoalReqTable(config: CoalescerConfig) extends Module { // Lookup logic io.lookup.valid := table(io.lookupSourceId).valid io.lookup.bits := table(io.lookupSourceId).bits - val lookupFire = io.lookup.ready && io.lookup.valid // Dequeue as soon as lookup succeeds - when(lookupFire) { + when(io.lookup.fire) { table(io.lookupSourceId).valid := false.B }