diff --git a/src/main/scala/uncore/tilelink2/Crossing.scala b/src/main/scala/uncore/tilelink2/AsyncCrossing.scala similarity index 95% rename from src/main/scala/uncore/tilelink2/Crossing.scala rename to src/main/scala/uncore/tilelink2/AsyncCrossing.scala index 93e63f2c..5999cb9c 100644 --- a/src/main/scala/uncore/tilelink2/Crossing.scala +++ b/src/main/scala/uncore/tilelink2/AsyncCrossing.scala @@ -137,7 +137,7 @@ class TLAsyncCrossing(depth: Int = 8, sync: Int = 3)(implicit p: Parameters) ext /** Synthesizeable unit tests */ import unittest._ -class TLRAMCrossing(implicit p: Parameters) extends LazyModule { +class TLRAMAsyncCrossing(implicit p: Parameters) extends LazyModule { val model = LazyModule(new TLRAMModel) val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff))) val fuzz = LazyModule(new TLFuzzer(5000)) @@ -168,6 +168,6 @@ class TLRAMCrossing(implicit p: Parameters) extends LazyModule { } } -class TLRAMCrossingTest(implicit p: Parameters) extends UnitTest(timeout = 500000) { - io.finished := Module(LazyModule(new TLRAMCrossing).module).io.finished +class TLRAMAsyncCrossingTest(implicit p: Parameters) extends UnitTest(timeout = 500000) { + io.finished := Module(LazyModule(new TLRAMAsyncCrossing).module).io.finished } diff --git a/src/main/scala/uncore/tilelink2/Bundles.scala b/src/main/scala/uncore/tilelink2/Bundles.scala index 3645ecb8..38e076b1 100644 --- a/src/main/scala/uncore/tilelink2/Bundles.scala +++ b/src/main/scala/uncore/tilelink2/Bundles.scala @@ -251,3 +251,12 @@ class TLAsyncBundle(params: TLAsyncBundleParameters) extends TLAsyncBundleBase(p val d = new AsyncBundle(params.depth, new TLBundleD(params.base)).flip val e = new AsyncBundle(params.depth, new TLBundleE(params.base)) } + +class TLRationalBundle(params: TLBundleParameters) extends TLBundleBase(params) +{ + val a = RationalIO(new TLBundleA(params)) + val b = RationalIO(new TLBundleB(params)).flip + val c = RationalIO(new TLBundleC(params)) + val d = RationalIO(new TLBundleD(params)).flip + val e = RationalIO(new TLBundleE(params)) +} diff --git a/src/main/scala/uncore/tilelink2/Nodes.scala b/src/main/scala/uncore/tilelink2/Nodes.scala index 28fe22d9..3c2bd137 100644 --- a/src/main/scala/uncore/tilelink2/Nodes.scala +++ b/src/main/scala/uncore/tilelink2/Nodes.scala @@ -180,3 +180,39 @@ case class TLAsyncSinkNode(depth: Int, sync: Int) extends MixedNode(TLAsyncImp, uFn = { case (1, Seq(p)) => Seq(TLAsyncManagerPortParameters(depth, p)) }, numPO = 1 to 1, numPI = 1 to 1) + +object TLRationalImp extends NodeImp[TLClientPortParameters, TLManagerPortParameters, TLEdgeParameters, TLEdgeParameters, TLRationalBundle] +{ + def edgeO(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeParameters = TLEdgeParameters(pd, pu) + def edgeI(pd: TLClientPortParameters, pu: TLManagerPortParameters): TLEdgeParameters = TLEdgeParameters(pd, pu) + + def bundleO(eo: Seq[TLEdgeParameters]): Vec[TLRationalBundle] = Vec(eo.size, new TLRationalBundle(TLBundleParameters.union(eo.map(_.bundle)))) + def bundleI(ei: Seq[TLEdgeParameters]): Vec[TLRationalBundle] = Vec(ei.size, new TLRationalBundle(TLBundleParameters.union(ei.map(_.bundle)))) + + def colour = "#00ff00" // green + + def connect(bo: => TLRationalBundle, bi: => TLRationalBundle, ei: => TLEdgeParameters)(implicit p: Parameters, sourceInfo: SourceInfo): (Option[LazyModule], () => Unit) = { + (None, () => { bi <> bo }) + } + + override def mixO(pd: TLClientPortParameters, node: OutwardNode[TLClientPortParameters, TLManagerPortParameters, TLRationalBundle]): TLClientPortParameters = + pd.copy(clients = pd.clients.map { c => c.copy (nodePath = node +: c.nodePath) }) + override def mixI(pu: TLManagerPortParameters, node: InwardNode[TLClientPortParameters, TLManagerPortParameters, TLRationalBundle]): TLManagerPortParameters = + pu.copy(managers = pu.managers.map { m => m.copy (nodePath = node +: m.nodePath) }) +} + +case class TLRationalIdentityNode() extends IdentityNode(TLRationalImp) +case class TLRationalOutputNode() extends OutputNode(TLRationalImp) +case class TLRationalInputNode() extends InputNode(TLRationalImp) + +case class TLRationalSourceNode() extends MixedNode(TLImp, TLRationalImp)( + dFn = { case (_, s) => s }, + uFn = { case (_, s) => s.map(p => p.copy(minLatency = 1)) }, // discard cycles from other clock domain + numPO = 0 to 999, + numPI = 0 to 999) + +case class TLRationalSinkNode() extends MixedNode(TLRationalImp, TLImp)( + dFn = { case (_, s) => s.map(p => p.copy(minLatency = 1)) }, + uFn = { case (_, s) => s }, + numPO = 0 to 999, + numPI = 0 to 999) diff --git a/src/main/scala/uncore/tilelink2/RationalCrossing.scala b/src/main/scala/uncore/tilelink2/RationalCrossing.scala new file mode 100644 index 00000000..6d3b70fd --- /dev/null +++ b/src/main/scala/uncore/tilelink2/RationalCrossing.scala @@ -0,0 +1,184 @@ +// See LICENSE.SiFive for license details. + +// If you know two clocks are related with a N:1 or 1:N relationship, you +// can cross the clock domains with lower latency than an AsyncQueue. +// This clock crossing behaves almost identically to a TLBuffer(2): +// - It adds one cycle latency to each clock domain. +// - All outputs of TLRational are registers (bits, valid, and ready). +// - It costs 3*bits registers as opposed to 2*bits in a TLBuffer(2) + +package uncore.tilelink2 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo +import config._ +import diplomacy._ +import util._ + +class TLRationalCrossingSource(implicit p: Parameters) extends LazyModule +{ + val node = TLRationalSourceNode() + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + ((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) => + val bce = edgeIn.manager.anySupportAcquireB && edgeIn.client.anySupportProbe + + out.a <> ToRational(in.a) + in.d <> FromRational(out.d) + + if (bce) { + in.b <> FromRational(out.b) + out.c <> ToRational(in.c) + out.e <> ToRational(in.e) + } else { + in.b.valid := Bool(false) + in.c.ready := Bool(true) + in.e.ready := Bool(true) + out.b.ready := Bool(true) + out.c.valid := Bool(false) + out.e.valid := Bool(false) + out.b.sink := UInt(0) + out.c.source := UInt(0) + out.e.source := UInt(0) + } + } + } +} + +class TLRationalCrossingSink(implicit p: Parameters) extends LazyModule +{ + val node = TLRationalSinkNode() + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + ((io.in zip io.out) zip (node.edgesIn zip node.edgesOut)) foreach { case ((in, out), (edgeIn, edgeOut)) => + val bce = edgeOut.manager.anySupportAcquireB && edgeOut.client.anySupportProbe + + out.a <> FromRational(in.a) + in.d <> ToRational(out.d) + + if (bce) { + in.b <> ToRational(out.b) + out.c <> FromRational(in.c) + out.e <> FromRational(in.e) + } else { + out.b.ready := Bool(true) + out.c.valid := Bool(false) + out.e.valid := Bool(false) + in.b.valid := Bool(false) + in.c.ready := Bool(true) + in.e.ready := Bool(true) + in.b.source := UInt(0) + in.c.sink := UInt(0) + in.e.sink := UInt(0) + } + } + } +} + +object TLRationalCrossingSource +{ + // applied to the TL source node; y.node := TLRationalCrossingSource()(x.node) + def apply()(x: TLOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLRationalOutwardNode = { + val source = LazyModule(new TLRationalCrossingSource) + source.node := x + source.node + } +} + +object TLRationalCrossingSink +{ + // applied to the TL source node; y.node := TLRationalCrossingSink()(x.node) + def apply()(x: TLRationalOutwardNode)(implicit p: Parameters, sourceInfo: SourceInfo): TLOutwardNode = { + val sink = LazyModule(new TLRationalCrossingSink) + sink.node := x + sink.node + } +} + +class TLRationalCrossing(implicit p: Parameters) extends LazyModule +{ + val nodeIn = TLInputNode() + val nodeOut = TLOutputNode() + val node = NodeHandle(nodeIn, nodeOut) + + val source = LazyModule(new TLRationalCrossingSource) + val sink = LazyModule(new TLRationalCrossingSink) + + val _ = (sink.node := source.node) // no monitor + val in = (source.node := nodeIn) + val out = (nodeOut := sink.node) + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = nodeIn.bundleIn + val in_clock = Clock(INPUT) + val in_reset = Bool(INPUT) + val out = nodeOut.bundleOut + val out_clock = Clock(INPUT) + val out_reset = Bool(INPUT) + } + + source.module.clock := io.in_clock + source.module.reset := io.in_reset + in.foreach { lm => + lm.module.clock := io.in_clock + lm.module.reset := io.in_reset + } + + sink.module.clock := io.out_clock + sink.module.reset := io.out_reset + out.foreach { lm => + lm.module.clock := io.out_clock + lm.module.reset := io.out_reset + } + } +} + +/** Synthesizeable unit tests */ +import unittest._ + +class TLRAMRationalCrossing(implicit p: Parameters) extends LazyModule { + val fuzz = LazyModule(new TLFuzzer(5000)) + val model = LazyModule(new TLRAMModel) + val cross = LazyModule(new TLRationalCrossing) + val delay = LazyModule(new TLDelayer(0.25)) + val ram = LazyModule(new TLRAM(AddressSet(0x0, 0x3ff))) + + model.node := fuzz.node + cross.node := TLDelayer(0.25)(TLFragmenter(4, 256)(model.node)) + val monitor1 = (delay.node := cross.node) + val monitor2 = (ram.node := delay.node) + val monitors = monitor1.toList ++ monitor2.toList + + lazy val module = new LazyModuleImp(this) with HasUnitTestIO { + io.finished := fuzz.module.io.finished + + // Shove the RAM into another clock domain + val clocks = Module(new util.Pow2ClockDivider(2)) + ram.module.clock := clocks.io.clock_out + delay.module.clock := clocks.io.clock_out + + // ... and safely cross TL2 into it + cross.module.io.in_clock := clock + cross.module.io.in_reset := reset + cross.module.io.out_clock := clocks.io.clock_out + cross.module.io.out_reset := reset + + // Push the Monitors into the right clock domain + monitors.foreach { m => m.module.clock := clocks.io.clock_out } + } +} + +class TLRAMRationalCrossingTest(implicit p: Parameters) extends UnitTest(timeout = 500000) { + io.finished := Module(LazyModule(new TLRAMRationalCrossing).module).io.finished +} diff --git a/src/main/scala/uncore/tilelink2/package.scala b/src/main/scala/uncore/tilelink2/package.scala index ffbff327..f382ebdf 100644 --- a/src/main/scala/uncore/tilelink2/package.scala +++ b/src/main/scala/uncore/tilelink2/package.scala @@ -10,6 +10,7 @@ package object tilelink2 type TLInwardNode = InwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLBundle] type TLOutwardNode = OutwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLBundle] type TLAsyncOutwardNode = OutwardNodeHandle[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle] + type TLRationalOutwardNode = OutwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLRationalBundle] type IntOutwardNode = OutwardNodeHandle[IntSourcePortParameters, IntSinkPortParameters, Vec[Bool]] def OH1ToOH(x: UInt) = (x << 1 | UInt(1)) & ~Cat(UInt(0, width=1), x) diff --git a/src/main/scala/unittest/Configs.scala b/src/main/scala/unittest/Configs.scala index e29cd329..95a22002 100644 --- a/src/main/scala/unittest/Configs.scala +++ b/src/main/scala/unittest/Configs.scala @@ -30,7 +30,8 @@ class WithTLSimpleUnitTests extends Config((site, here, up) => { Module(new uncore.tilelink2.TLRAMSimpleTest(16)), Module(new uncore.tilelink2.TLRR0Test), Module(new uncore.tilelink2.TLRR1Test), - Module(new uncore.tilelink2.TLRAMCrossingTest) ) } + Module(new uncore.tilelink2.TLRAMRationalCrossingTest), + Module(new uncore.tilelink2.TLRAMAsyncCrossingTest) ) } }) class WithTLWidthUnitTests extends Config((site, here, up) => { diff --git a/src/main/scala/util/RationalCrossing.scala b/src/main/scala/util/RationalCrossing.scala new file mode 100644 index 00000000..8a0f5448 --- /dev/null +++ b/src/main/scala/util/RationalCrossing.scala @@ -0,0 +1,101 @@ +// See LICENSE.SiFive for license details. + +// If you know two clocks are related with a N:1 or 1:N relationship, you +// can cross the clock domains with lower latency than an AsyncQueue. This +// crossing adds 1 cycle in the target clock domain. + +package util +import Chisel._ + +final class RationalIO[T <: Data](gen: T) extends Bundle +{ + val bits = gen.chiselCloneType + val valid = Bool() + val source = UInt(width = 2) + val ready = Bool().flip + val sink = UInt(width = 2).flip + + override def cloneType: this.type = new RationalIO(gen).asInstanceOf[this.type] +} + +object RationalIO +{ + def apply[T <: Data](gen: T) = new RationalIO(gen) +} + +class RationalCrossingSource[T <: Data](gen: T) 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 count = RegInit(UInt(0, width = 2)) + val equal = count === deq.sink + + deq.valid := enq.valid + deq.source := count + deq.bits := Mux(equal, enq.bits, RegEnable(enq.bits, equal)) + enq.ready := Mux(equal, deq.ready, count(1) =/= deq.sink(0)) + + when (enq.fire()) { count := Cat(count(0), !count(1)) } +} + +class RationalCrossingSink[T <: Data](gen: T) extends Module +{ + val io = new Bundle { + val enq = RationalIO(gen).flip + val deq = DecoupledIO(gen) + } + + val enq = io.enq + val deq = Wire(io.deq) + io.deq <> Queue(deq, 1, pipe=true) + + val count = RegInit(UInt(0, width = 2)) + val equal = count === enq.source + + enq.ready := deq.ready + enq.sink := count + deq.bits := enq.bits + deq.valid := Mux(equal, enq.valid, count(1) =/= enq.source(0)) + + when (deq.fire()) { count := Cat(count(0), !count(1)) } +} + +class RationalCrossing[T <: Data](gen: T) extends Module +{ + val io = new CrossingIO(gen) + + val source = Module(new RationalCrossingSource(gen)) + val sink = Module(new RationalCrossingSink(gen)) + + source.clock := io.enq_clock + source.reset := io.enq_reset + sink .clock := io.deq_clock + sink .reset := io.deq_reset + + source.io.enq <> io.enq + io.deq <> sink.io.deq +} + +object ToRational +{ + def apply[T <: Data](x: DecoupledIO[T]): RationalIO[T] = { + val source = Module(new RationalCrossingSource(x.bits)) + source.io.enq <> x + source.io.deq + } +} + +object FromRational +{ + def apply[T <: Data](x: RationalIO[T]): DecoupledIO[T] = { + val sink = Module(new RationalCrossingSink(x.bits)) + sink.io.enq <> x + sink.io.deq + } +}