1
0

tilelink: fix Fragmenter source re-use bug (#888)

Consider the following waveform for two 4-beat bursts:
---A----A------------
-------D-----DDD-DDDD
Under TL rules, the second A can use the same source as the first A,
because the source is released for reuse on the first response beat.

However, if we fragment the requests, it looks like this:
---3210-3210---------
-------3-----210-3210
... now we've broken the rules because 210 are twice inflight.

To solve this, we alternate an a.source bit every time D completes a txn.
This commit is contained in:
Wesley W. Terpstra 2017-07-25 16:23:55 -07:00 committed by GitHub
parent c9e467a668
commit c2b8b08461

View File

@ -21,10 +21,8 @@ class TLFragmenter(val minSize: Int, val maxSize: Int, val alwaysMin: Boolean =
require (isPow2 (minSize)) require (isPow2 (minSize))
require (minSize < maxSize) require (minSize < maxSize)
// EarlyAck means that 1.999 transactions can be inflight at a time
// Thus, we need an extra toggle bit to prevent source collisions
val fragmentBits = log2Ceil(maxSize / minSize) val fragmentBits = log2Ceil(maxSize / minSize)
val toggleBits = if (earlyAck) 1 else 0 val toggleBits = 1
val addedBits = fragmentBits + toggleBits val addedBits = fragmentBits + toggleBits
def expandTransfer(x: TransferSizes) = if (!x) x else { def expandTransfer(x: TransferSizes) = if (!x) x else {
@ -141,9 +139,28 @@ class TLFragmenter(val minSize: Int, val maxSize: Int, val alwaysMin: Boolean =
val counterBits = log2Up(maxSize/beatBytes) val counterBits = log2Up(maxSize/beatBytes)
val maxDownSize = if (alwaysMin) minSize else min(manager.maxTransfer, maxSize) val maxDownSize = if (alwaysMin) minSize else min(manager.maxTransfer, maxSize)
// Consider the following waveform for two 4-beat bursts:
// ---A----A------------
// -------D-----DDD-DDDD
// Under TL rules, the second A can use the same source as the first A,
// because the source is released for reuse on the first response beat.
//
// However, if we fragment the requests, it looks like this:
// ---3210-3210---------
// -------3-----210-3210
// ... now we've broken the rules because 210 are twice inflight.
//
// This phenomenon means we can have essentially 2*maxSize/minSize-1
// fragmented transactions in flight per original transaction source.
//
// To keep the source unique, we encode the beat counter in the low
// bits of the source. To solve the overlap, we use a toggle bit.
// Whatever toggle bit the D is reassembling, A will use the opposite.
// First, handle the return path // First, handle the return path
val acknum = RegInit(UInt(0, width = counterBits)) val acknum = RegInit(UInt(0, width = counterBits))
val dOrig = Reg(UInt()) val dOrig = Reg(UInt())
val dToggle = Reg(Bool())
val dFragnum = out.d.bits.source(fragmentBits-1, 0) val dFragnum = out.d.bits.source(fragmentBits-1, 0)
val dFirst = acknum === UInt(0) val dFirst = acknum === UInt(0)
val dLast = dFragnum === UInt(0) val dLast = dFragnum === UInt(0)
@ -162,7 +179,10 @@ class TLFragmenter(val minSize: Int, val maxSize: Int, val alwaysMin: Boolean =
when (out.d.fire()) { when (out.d.fire()) {
acknum := Mux(dFirst, dFirst_acknum, acknum - ack_decrement) acknum := Mux(dFirst, dFirst_acknum, acknum - ack_decrement)
when (dFirst) { dOrig := dFirst_size } when (dFirst) {
dOrig := dFirst_size
dToggle := out.d.bits.source(fragmentBits)
}
} }
// Swallow up non-data ack fragments // Swallow up non-data ack fragments
@ -240,22 +260,14 @@ class TLFragmenter(val minSize: Int, val maxSize: Int, val alwaysMin: Boolean =
val new_gennum = ~(~old_gennum1 | (aMask >> log2Ceil(beatBytes))) // ~(~x|y) is width safe val new_gennum = ~(~old_gennum1 | (aMask >> log2Ceil(beatBytes))) // ~(~x|y) is width safe
val aFragnum = ~(~(old_gennum1 >> log2Ceil(minSize/beatBytes)) | (aFragOH1 >> log2Ceil(minSize))) val aFragnum = ~(~(old_gennum1 >> log2Ceil(minSize/beatBytes)) | (aFragOH1 >> log2Ceil(minSize)))
val aLast = aFragnum === UInt(0) val aLast = aFragnum === UInt(0)
val aToggle = !Mux(aFirst, dToggle, RegEnable(dToggle, aFirst))
when (out.a.fire()) { gennum := new_gennum } when (out.a.fire()) { gennum := new_gennum }
// We need to alternate bits by source to handle the 1.999 txns inflight per Id
val toggleBitOpt = if (!earlyAck) None else {
val state = Reg(UInt(width = edgeIn.client.endSourceId))
val toggle = Wire(init = UInt(0, width = edgeIn.client.endSourceId))
when (in_a.fire() && aLast) { toggle := UIntToOH(in_a.bits.source) }
state := state ^ toggle
Some(state(in_a.bits.source))
}
repeater.io.repeat := !aHasData && aFragnum =/= UInt(0) repeater.io.repeat := !aHasData && aFragnum =/= UInt(0)
out.a <> in_a out.a <> in_a
out.a.bits.address := in_a.bits.address | ~(old_gennum1 << log2Ceil(beatBytes) | ~aOrigOH1 | aFragOH1 | UInt(minSize-1)) out.a.bits.address := in_a.bits.address | ~(old_gennum1 << log2Ceil(beatBytes) | ~aOrigOH1 | aFragOH1 | UInt(minSize-1))
out.a.bits.source := Cat(Seq(in_a.bits.source) ++ toggleBitOpt.toList ++ Seq(aFragnum)) out.a.bits.source := Cat(Seq(in_a.bits.source) ++ Seq(aToggle.asUInt, aFragnum))
out.a.bits.size := aFrag out.a.bits.size := aFrag
// Optimize away some of the Repeater's registers // Optimize away some of the Repeater's registers