diff --git a/src/main/scala/util/RationalCrossing.scala b/src/main/scala/util/RationalCrossing.scala index 8a0f5448..26a7673d 100644 --- a/src/main/scala/util/RationalCrossing.scala +++ b/src/main/scala/util/RationalCrossing.scala @@ -7,6 +7,32 @@ package util import Chisel._ +// A rational crossing must put registers on the slow side. +// This trait covers the options of how/where to put the registers. +// BEWARE: the source+sink must agree on the direction! +sealed trait RationalDirection { + def flip: RationalDirection +} + +// If it's unclear which side will be slow (or it is variable), +// place registers on both sides of the crossing, by splitting +// a Queue into flow and pipe parts on either side. This is safe +// for all possible clock ratios, but has the downside that the +// path from the slow domain must close timing in the fast domain. +case object Symmetric extends RationalDirection { + def flip = Symmetric +} + +// If the source is fast, place the registers at the sink. +case object FastToSlow extends RationalDirection { + def flip = SlowToFast +} + +// If the source is slow, place the registers at the source. +case object SlowToFast extends RationalDirection { + def flip = FastToSlow +} + final class RationalIO[T <: Data](gen: T) extends Bundle { val bits = gen.chiselCloneType @@ -23,15 +49,19 @@ object RationalIO def apply[T <: Data](gen: T) = new RationalIO(gen) } -class RationalCrossingSource[T <: Data](gen: T) extends Module +class RationalCrossingSource[T <: Data](gen: T, direction: RationalDirection = Symmetric) extends Module { val io = new Bundle { val enq = DecoupledIO(gen).flip val deq = RationalIO(gen) } - val enq = Queue(io.enq, 1, flow=true) val deq = io.deq + val enq = direction match { + case Symmetric => Queue(io.enq, 1, flow=true) + case FastToSlow => io.enq + case SlowToFast => Queue(io.enq, 2) + } val count = RegInit(UInt(0, width = 2)) val equal = count === deq.sink @@ -42,9 +72,16 @@ class RationalCrossingSource[T <: Data](gen: T) extends Module enq.ready := Mux(equal, deq.ready, count(1) =/= deq.sink(0)) when (enq.fire()) { count := Cat(count(0), !count(1)) } + + // Ensure the clocking is setup correctly + direction match { + case Symmetric => () // always safe + case FastToSlow => assert (equal || count(1) === deq.sink(0)) + case SlowToFast => assert (equal || count(1) =/= deq.sink(0)) + } } -class RationalCrossingSink[T <: Data](gen: T) extends Module +class RationalCrossingSink[T <: Data](gen: T, direction: RationalDirection = Symmetric) extends Module { val io = new Bundle { val enq = RationalIO(gen).flip @@ -53,7 +90,11 @@ class RationalCrossingSink[T <: Data](gen: T) extends Module val enq = io.enq val deq = Wire(io.deq) - io.deq <> Queue(deq, 1, pipe=true) + direction match { + case Symmetric => io.deq <> Queue(deq, 1, pipe=true) + case FastToSlow => io.deq <> Queue(deq, 2) + case SlowToFast => io.deq <> deq + } val count = RegInit(UInt(0, width = 2)) val equal = count === enq.source @@ -64,14 +105,21 @@ class RationalCrossingSink[T <: Data](gen: T) extends Module deq.valid := Mux(equal, enq.valid, count(1) =/= enq.source(0)) when (deq.fire()) { count := Cat(count(0), !count(1)) } + + // Ensure the clocking is setup correctly + direction match { + case Symmetric => () // always safe + case FastToSlow => assert (equal || count(1) =/= enq.source(0)) + case SlowToFast => assert (equal || count(1) === enq.source(0)) + } } -class RationalCrossing[T <: Data](gen: T) extends Module +class RationalCrossing[T <: Data](gen: T, direction: RationalDirection = Symmetric) extends Module { val io = new CrossingIO(gen) - val source = Module(new RationalCrossingSource(gen)) - val sink = Module(new RationalCrossingSink(gen)) + val source = Module(new RationalCrossingSource(gen, direction)) + val sink = Module(new RationalCrossingSink(gen, direction)) source.clock := io.enq_clock source.reset := io.enq_reset @@ -84,8 +132,8 @@ class RationalCrossing[T <: Data](gen: T) extends Module object ToRational { - def apply[T <: Data](x: DecoupledIO[T]): RationalIO[T] = { - val source = Module(new RationalCrossingSource(x.bits)) + def apply[T <: Data](x: DecoupledIO[T], direction: RationalDirection = Symmetric): RationalIO[T] = { + val source = Module(new RationalCrossingSource(x.bits, direction)) source.io.enq <> x source.io.deq } @@ -93,8 +141,8 @@ object ToRational object FromRational { - def apply[T <: Data](x: RationalIO[T]): DecoupledIO[T] = { - val sink = Module(new RationalCrossingSink(x.bits)) + def apply[T <: Data](x: RationalIO[T], direction: RationalDirection = Symmetric): DecoupledIO[T] = { + val sink = Module(new RationalCrossingSink(x.bits, direction)) sink.io.enq <> x sink.io.deq }