diff --git a/junctions/src/main/scala/nasti.scala b/junctions/src/main/scala/nasti.scala index 5c2e7dc5..7d401655 100644 --- a/junctions/src/main/scala/nasti.scala +++ b/junctions/src/main/scala/nasti.scala @@ -311,48 +311,6 @@ class NastiArbiter(val arbN: Int)(implicit p: Parameters) extends NastiModule { } else { io.slave <> io.master.head } } -/** Locking RR arbiter for Nasti read data channel - * Arbiter locks until last message in channel is sent */ -class NastiReadDataArbiter(arbN: Int)(implicit p: Parameters) extends NastiModule { - val io = new Bundle { - val in = Vec(Decoupled(new NastiReadDataChannel), arbN).flip - val out = Decoupled(new NastiReadDataChannel) - } - - def rotateLeft[T <: Data](norm: Vec[T], rot: UInt): Vec[T] = { - val n = norm.size - Vec.tabulate(n) { i => - Mux(rot < UInt(n - i), norm(UInt(i) + rot), norm(rot - UInt(n - i))) - } - } - - val lockIdx = Reg(init = UInt(0, log2Up(arbN))) - val locked = Reg(init = Bool(false)) - - // use rotation to give priority to the input after the last one granted - val choice = PriorityMux( - rotateLeft(Vec(io.in.map(_.valid)), lockIdx + UInt(1)), - rotateLeft(Vec((0 until arbN).map(UInt(_))), lockIdx + UInt(1))) - - val chosen = Mux(locked, lockIdx, choice) - - for (i <- 0 until arbN) { - io.in(i).ready := io.out.ready && chosen === UInt(i) - } - - io.out.valid := io.in(chosen).valid - io.out.bits := io.in(chosen).bits - - when (io.out.fire()) { - when (!locked) { - lockIdx := choice - locked := !io.out.bits.last - } .elsewhen (io.out.bits.last) { - locked := Bool(false) - } - } -} - /** A slave that send decode error for every request it receives */ class NastiErrorSlave(implicit p: Parameters) extends NastiModule { val io = (new NastiIO).flip @@ -466,7 +424,10 @@ class NastiRouter(addrmap: Seq[(BigInt, BigInt)])(implicit p: Parameters) extend io.master.w.ready := w_ready || err_slave.io.w.ready val b_arb = Module(new RRArbiter(new NastiWriteResponseChannel, nSlaves + 1)) - val r_arb = Module(new NastiReadDataArbiter(nSlaves + 1)) + val r_arb = Module(new JunctionsPeekingArbiter( + new NastiReadDataChannel, nSlaves + 1, + // we can unlock if it's the last beat + (r: NastiReadDataChannel) => r.last)) for (i <- 0 until nSlaves) { b_arb.io.in(i) <> io.slave(i).b diff --git a/junctions/src/main/scala/util.scala b/junctions/src/main/scala/util.scala index 62b1e189..9f59ba4f 100644 --- a/junctions/src/main/scala/util.scala +++ b/junctions/src/main/scala/util.scala @@ -58,3 +58,87 @@ object HellaQueue { q.io.deq } } + +/** A generalized locking RR arbiter that addresses the limitations of the + * version in the Chisel standard library */ +abstract class JunctionsAbstractLockingArbiter[T <: Data](typ: T, arbN: Int) + extends Module { + + val io = new Bundle { + val in = Vec(Decoupled(typ.cloneType), arbN).flip + val out = Decoupled(typ.cloneType) + } + + def rotateLeft[T <: Data](norm: Vec[T], rot: UInt): Vec[T] = { + val n = norm.size + Vec.tabulate(n) { i => + Mux(rot < UInt(n - i), norm(UInt(i) + rot), norm(rot - UInt(n - i))) + } + } + + val lockIdx = Reg(init = UInt(0, log2Up(arbN))) + val locked = Reg(init = Bool(false)) + + val choice = PriorityMux( + rotateLeft(Vec(io.in.map(_.valid)), lockIdx + UInt(1)), + rotateLeft(Vec((0 until arbN).map(UInt(_))), lockIdx + UInt(1))) + + val chosen = Mux(locked, lockIdx, choice) + + for (i <- 0 until arbN) { + io.in(i).ready := io.out.ready && chosen === UInt(i) + } + + io.out.valid := io.in(chosen).valid + io.out.bits := io.in(chosen).bits +} + +/** This locking arbiter determines when it is safe to unlock + * by peeking at the data */ +class JunctionsPeekingArbiter[T <: Data]( + typ: T, arbN: Int, + canUnlock: T => Bool, + needsLock: Option[T => Bool] = None) + extends JunctionsAbstractLockingArbiter(typ, arbN) { + + def realNeedsLock(data: T): Bool = + needsLock.map(_(data)).getOrElse(Bool(true)) + + when (io.out.fire()) { + when (!locked && realNeedsLock(io.out.bits)) { + lockIdx := choice + locked := Bool(true) + } + // the unlock statement takes precedent + when (canUnlock(io.out.bits)) { + locked := Bool(false) + } + } +} + +/** This arbiter determines when it is safe to unlock by counting transactions */ +class JunctionsCountingArbiter[T <: Data]( + typ: T, arbN: Int, count: Int, + val needsLock: Option[T => Bool] = None) + extends JunctionsAbstractLockingArbiter(typ, arbN) { + + def realNeedsLock(data: T): Bool = + needsLock.map(_(data)).getOrElse(Bool(true)) + + // if count is 1, you should use a non-locking arbiter + require(count > 1, "CountingArbiter cannot have count <= 1") + + val lock_ctr = Counter(count) + + when (io.out.fire()) { + when (!locked && realNeedsLock(io.out.bits)) { + lockIdx := choice + locked := Bool(true) + lock_ctr.inc() + } + + when (locked) { + when (lock_ctr.inc()) { locked := Bool(false) } + } + } +}