diff --git a/src/main/scala/prci/ClockCrossingType.scala b/src/main/scala/prci/ClockCrossingType.scala index cbb5183a6db..a9c663f7dcc 100644 --- a/src/main/scala/prci/ClockCrossingType.scala +++ b/src/main/scala/prci/ClockCrossingType.scala @@ -16,22 +16,29 @@ trait HasClockDomainCrossing extends HasDomainCrossing { this: LazyModule => } /** Enumerates the types of clock crossings generally supported by Diplomatic bus protocols */ -sealed trait ClockCrossingType extends CrossingType +trait ClockCrossingType extends CrossingType { - def sameClock = this match { - case _: SynchronousCrossing | _: CreditedCrossing => true - case _ => false - } + def sameClock: Boolean } case object NoCrossing // converts to SynchronousCrossing(BufferParams.none) via implicit def in package case class SynchronousCrossing(params: BufferParams = BufferParams.default) extends ClockCrossingType +{ + def sameClock = true +} case class RationalCrossing(direction: RationalDirection = FastToSlow) extends ClockCrossingType +{ + def sameClock = false +} case class AsynchronousCrossing(depth: Int = 8, sourceSync: Int = 3, sinkSync: Int = 3, safe: Boolean = true, narrow: Boolean = false) extends ClockCrossingType { + def sameClock = false def asSinkParams = AsyncQueueParams(depth, sinkSync, safe, narrow) } case class CreditedCrossing(sourceDelay: CreditedDelay, sinkDelay: CreditedDelay) extends ClockCrossingType +{ + def sameClock = true +} object CreditedCrossing { def apply(delay: CreditedDelay): CreditedCrossing = CreditedCrossing(delay, delay.flip) diff --git a/src/main/scala/subsystem/Configs.scala b/src/main/scala/subsystem/Configs.scala index c3d11aca16e..603117953f6 100644 --- a/src/main/scala/subsystem/Configs.scala +++ b/src/main/scala/subsystem/Configs.scala @@ -12,7 +12,7 @@ import freechips.rocketchip.devices.debug.{DebugModuleKey, DefaultDebugModulePar import freechips.rocketchip.devices.tilelink.{ BuiltInErrorDeviceParams, BootROMLocated, BootROMParams, CLINTKey, DevNullDevice, CLINTParams, PLICKey, PLICParams, DevNullParams } -import freechips.rocketchip.prci.{SynchronousCrossing, AsynchronousCrossing, RationalCrossing, ClockCrossingType} +import freechips.rocketchip.prci.{SynchronousCrossing, AsynchronousCrossing, RationalCrossing, ClockCrossingType, CreditedCrossing} import freechips.rocketchip.diplomacy.{ AddressSet, MonitorsEnabled, } @@ -517,6 +517,16 @@ class WithRationalRocketTiles extends Config((site, here, up) => { } }) +class WithCreditedRocketTiles(mergedCredited: Boolean = false) extends Config((site, here, up) => { + case TilesLocated(location) => up(TilesLocated(location), site) map { + case tp: RocketTileAttachParams => tp.copy(crossingParams = tp.crossingParams.copy( + crossingType = CreditedCrossing(), + forceMergedCreditedTLCrossings = mergedCredited + )) + case t => t + } +}) + class WithEdgeDataBits(dataBits: Int) extends Config((site, here, up) => { case MemoryBusKey => up(MemoryBusKey, site).copy(beatBytes = dataBits/8) case ExtIn => up(ExtIn, site).map(_.copy(beatBytes = dataBits/8)) diff --git a/src/main/scala/subsystem/HierarchicalElement.scala b/src/main/scala/subsystem/HierarchicalElement.scala index 661fc67cfa0..460be22c6bd 100644 --- a/src/main/scala/subsystem/HierarchicalElement.scala +++ b/src/main/scala/subsystem/HierarchicalElement.scala @@ -35,6 +35,8 @@ trait HierarchicalElementCrossingParamsLike { def resetCrossingType: ResetCrossingType /** Keep the element clock separate from the interconnect clock (e.g. even if they are synchronous to one another) */ def forceSeparateClockReset: Boolean + /** Used a MergedCreditedTLCrossing for credited TL crossings to save pins */ + def forceMergedCreditedTLCrossings: Boolean } /** An interface for describing the parameterization of how a particular element port is connected to an interconnect */ diff --git a/src/main/scala/subsystem/HierarchicalElementPRCIDomain.scala b/src/main/scala/subsystem/HierarchicalElementPRCIDomain.scala index b38f208701e..2c5a8153334 100644 --- a/src/main/scala/subsystem/HierarchicalElementPRCIDomain.scala +++ b/src/main/scala/subsystem/HierarchicalElementPRCIDomain.scala @@ -11,7 +11,7 @@ import freechips.rocketchip.diplomacy.{DisableMonitors, FlipRendering} import freechips.rocketchip.interrupts.{IntInwardNode, IntOutwardNode} import freechips.rocketchip.prci.{ClockCrossingType, ResetCrossingType, ResetDomain, ClockSinkNode, ClockSinkParameters, ClockIdentityNode, FixedClockBroadcast, ClockDomain} import freechips.rocketchip.tile.{RocketTile, TraceBundle} -import freechips.rocketchip.tilelink.{TLInwardNode, TLOutwardNode} +import freechips.rocketchip.tilelink.{TLInwardNode, TLOutwardNode, UseTLMergedCreditedCrossing} import freechips.rocketchip.util.TraceCoreInterface import freechips.rocketchip.tilelink.TLClockDomainCrossing @@ -83,7 +83,9 @@ abstract class HierarchicalElementPRCIDomain[T <: BaseHierarchicalElement]( element { element.makeSlaveBoundaryBuffers(crossingType) } } val tlSlaveClockXing = this.crossIn(tlSlaveResetXing) - tlSlaveClockXing(crossingType) + tlSlaveClockXing(crossingType)(p.alterPartial { + case UseTLMergedCreditedCrossing => crossingParams.forceMergedCreditedTLCrossings + }) } } } /** External code looking to connect the ports where this tile masters an interconnect @@ -95,6 +97,8 @@ abstract class HierarchicalElementPRCIDomain[T <: BaseHierarchicalElement]( element_reset_domain.crossTLOut(element.masterNode) } } val tlMasterClockXing = this.crossOut(tlMasterResetXing) - tlMasterClockXing(crossingType) + tlMasterClockXing(crossingType)(p.alterPartial { + case UseTLMergedCreditedCrossing => crossingParams.forceMergedCreditedTLCrossings + }) } } diff --git a/src/main/scala/subsystem/RocketSubsystem.scala b/src/main/scala/subsystem/RocketSubsystem.scala index a10903d050f..aeef3162280 100644 --- a/src/main/scala/subsystem/RocketSubsystem.scala +++ b/src/main/scala/subsystem/RocketSubsystem.scala @@ -16,7 +16,8 @@ case class RocketCrossingParams( slave: HierarchicalElementSlavePortParams = HierarchicalElementSlavePortParams(), mmioBaseAddressPrefixWhere: TLBusWrapperLocation = CBUS, resetCrossingType: ResetCrossingType = NoResetCrossing(), - forceSeparateClockReset: Boolean = false + forceSeparateClockReset: Boolean = false, + forceMergedCreditedTLCrossings: Boolean = false ) extends HierarchicalElementCrossingParamsLike case class RocketTileAttachParams( diff --git a/src/main/scala/tilelink/Arbiter.scala b/src/main/scala/tilelink/Arbiter.scala index 452c1ba2665..5cdf97aaec2 100644 --- a/src/main/scala/tilelink/Arbiter.scala +++ b/src/main/scala/tilelink/Arbiter.scala @@ -123,7 +123,7 @@ abstract class DecoupledArbiterTest( case (z, i) => (beatsLeftFromIdx(i), z) }:_*) - count := count + 1.U + when (!io.finished) { count := count + 1.U } io.finished := count >= txns.U } diff --git a/src/main/scala/tilelink/Bundles.scala b/src/main/scala/tilelink/Bundles.scala index 5bb53dfe429..e9c8f33f677 100644 --- a/src/main/scala/tilelink/Bundles.scala +++ b/src/main/scala/tilelink/Bundles.scala @@ -5,8 +5,7 @@ package freechips.rocketchip.tilelink import chisel3._ import freechips.rocketchip.util._ import scala.collection.immutable.ListMap -import chisel3.util.Decoupled -import chisel3.util.DecoupledIO +import chisel3.util.{Decoupled, DecoupledIO, Valid} import chisel3.reflect.DataMirror abstract class TLBundleBase(val params: TLBundleParameters) extends Bundle @@ -317,3 +316,44 @@ class TLCreditedBundle(params: TLBundleParameters) extends TLBundleBase(params) val d = Flipped(CreditedIO(new TLBundleD(params))) val e = CreditedIO(new TLBundleE(params)) } + +class TLBundleACE(params: TLBundleParameters) extends TLBundleBase(params) +{ + // fixed fields during multibeat: + val opcode = UInt(3.W) + val param = UInt(List(TLAtomics.width, TLPermissions.aWidth, TLPermissions.cWidth, TLHints.width).max.W) + val size = UInt(params.sizeBits.W) + val source = UInt(params.sourceBits.W) // from + val address = UInt(params.addressBits.W) // to + val sink = UInt(params.sinkBits.W) // to + val user = BundleMap(params.requestFields) + val echo = BundleMap(params.echoFields) + // variable fields during multibeat: + val mask = UInt((params.dataBits/8).W) + val data = UInt(params.dataBits.W) + val corrupt = Bool() // only applies to *Data messages +} + +class TLBundleBD(params: TLBundleParameters) extends TLBundleBase(params) +{ + // fixed fields during multibeat: + val opcode = UInt(3.W) + val param = UInt(TLPermissions.bdWidth.W) // cap perms + val size = UInt(params.sizeBits.W) + val source = UInt(params.sourceBits.W) // to + val address = UInt(params.addressBits.W) // from + val sink = UInt(params.sinkBits.W) // from + val denied = Bool() // implies corrupt iff *Data + val user = BundleMap(params.responseFields) + val echo = BundleMap(params.echoFields) + // variable fields during multibeat: + val mask = UInt((params.dataBits/8).W) + val data = UInt(params.dataBits.W) + val corrupt = Bool() // only applies to *Data messages +} + +class TLMergedCreditedBundle(params: TLBundleParameters) extends TLBundleBase(params) +{ + val eca = MultiCreditedIO(new TLBundleACE(params), 3) + val db = Flipped(MultiCreditedIO(new TLBundleBD(params), 2)) +} diff --git a/src/main/scala/tilelink/CrossingHelper.scala b/src/main/scala/tilelink/CrossingHelper.scala index fd4bd2e74ce..f660fe0bf97 100644 --- a/src/main/scala/tilelink/CrossingHelper.scala +++ b/src/main/scala/tilelink/CrossingHelper.scala @@ -32,8 +32,11 @@ case class TLInwardClockCrossingHelper(name: String, scope: LazyScope, node: TLI node :*=* scope { TLRationalCrossingSink(direction.flip) :*=* TLRationalNameNode(name) } :*=* TLRationalNameNode(name) :*=* TLRationalCrossingSource() case SynchronousCrossing(buffer) => node :*=* scope { TLBuffer(buffer) :*=* TLNameNode(name) } :*=* TLNameNode(name) - case CreditedCrossing(sourceDelay, sinkDelay) => + case CreditedCrossing(sourceDelay, sinkDelay) => if (p(UseTLMergedCreditedCrossing)) { + node :*=* scope { TLMergedCreditedSink(sinkDelay) :*=* TLMergedCreditedNameNode(name) } :*=* TLMergedCreditedNameNode(name) :*=* TLMergedCreditedSource(sourceDelay) + } else { node :*=* scope { TLCreditedSink(sinkDelay) :*=* TLCreditedNameNode(name) } :*=* TLCreditedNameNode(name) :*=* TLCreditedSource(sourceDelay) + } } } } @@ -63,8 +66,11 @@ case class TLOutwardClockCrossingHelper(name: String, scope: LazyScope, node: TL TLRationalCrossingSink(direction) :*=* TLRationalNameNode(name) :*=* scope { TLRationalNameNode(name) :*=* TLRationalCrossingSource() } :*=* node case SynchronousCrossing(buffer) => TLNameNode(name) :*=* scope { TLNameNode(name) :*=* TLBuffer(buffer) } :*=* node - case CreditedCrossing(sourceDelay, sinkDelay) => + case CreditedCrossing(sourceDelay, sinkDelay) => if (p(UseTLMergedCreditedCrossing)) { + TLMergedCreditedSink(sinkDelay) :*=* TLMergedCreditedNameNode(name) :*=* scope { TLMergedCreditedNameNode(name) :*=* TLMergedCreditedSource(sourceDelay) } :*=* node + } else { TLCreditedSink(sinkDelay) :*=* TLCreditedNameNode(name) :*=* scope { TLCreditedNameNode(name) :*=* TLCreditedSource(sourceDelay) } :*=* node + } } } } diff --git a/src/main/scala/tilelink/MergedCredited.scala b/src/main/scala/tilelink/MergedCredited.scala new file mode 100644 index 00000000000..37f68a66e9b --- /dev/null +++ b/src/main/scala/tilelink/MergedCredited.scala @@ -0,0 +1,128 @@ +package freechips.rocketchip.tilelink + +import chisel3._ +import chisel3.util._ + +import org.chipsalliance.cde.config._ +import org.chipsalliance.diplomacy.lazymodule._ + +import freechips.rocketchip.diplomacy.{AddressSet} +import freechips.rocketchip.prci.{ClockCrossingType, CreditedCrossing} +import freechips.rocketchip.subsystem.CrossingWrapper +import freechips.rocketchip.util.{CreditedDelay, MultiCreditedIO} + +case object UseTLMergedCreditedCrossing extends Field[Boolean](false) + +class TLMergedCreditedSource(delay: TLMergedCreditedDelay)(implicit p: Parameters) extends LazyModule +{ + val node = TLMergedCreditedSourceNode(delay) + override lazy val module = new Impl + class Impl extends LazyModuleImp(this) { + (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) => + val tld = edgeOut.delay + + val to_credited = Wire(Vec(3, Decoupled(new TLBundleACE(edgeIn.bundle)))) + val from_credited = Wire(Vec(2, Decoupled(new TLBundleBD(edgeIn.bundle)))) + + out.eca <> MultiCreditedIO.fromSenders(to_credited, tld.ace.total).pipeline(delay.ace) + from_credited <> VecInit(out.db.pipeline(delay.bd).toReceivers(tld.bd.total)) + + Seq(in.e, in.c, in.a).zipWithIndex.foreach { case (channel, i) => + val out = to_credited(i) + channel.ready := out.ready + out.valid := channel.valid + out.bits := DontCare + (out.bits: Data).waiveAll :<>= (channel.bits: Data).waiveAll + } + + Seq(in.d, in.b).zipWithIndex.foreach { case (channel, i) => + val out = from_credited(i) + out.ready := channel.ready + channel.valid := out.valid + (channel.bits: Data).waiveAll :<>= (out.bits: Data).waiveAll + } + } + } +} + +object TLMergedCreditedSource { + def apply(delay: TLMergedCreditedDelay)(implicit p: Parameters): TLMergedCreditedSourceNode = { + val source = LazyModule(new TLMergedCreditedSource(delay)) + source.node + } + def apply(delay: CreditedDelay)(implicit p: Parameters): TLMergedCreditedSourceNode = apply(TLMergedCreditedDelay(delay)) + def apply()(implicit p: Parameters): TLMergedCreditedSourceNode = apply(CreditedDelay(1, 1)) +} + + +class TLMergedCreditedSink(delay: TLMergedCreditedDelay)(implicit p: Parameters) extends LazyModule +{ + val node = TLMergedCreditedSinkNode(delay) + override lazy val module = new Impl + class Impl extends LazyModuleImp(this) { + (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) => + val tld = edgeIn.delay + + val to_credited = Wire(Vec(2, Decoupled(new TLBundleBD(edgeOut.bundle)))) + val from_credited = Wire(Vec(3, Decoupled(new TLBundleACE(edgeOut.bundle)))) + + in.db <> MultiCreditedIO.fromSenders(to_credited, tld.bd.total).pipeline(delay.bd) + from_credited <> in.eca.pipeline(delay.ace).toReceivers(tld.ace.total) + + Seq(out.d, out.b).zipWithIndex.foreach { case (channel, i) => + val in = to_credited(i) + channel.ready := in.ready + in.valid := channel.valid + in.bits := DontCare + (in.bits: Data).waiveAll :<>= (channel.bits: Data).waiveAll + } + + Seq(out.e, out.c, out.a).zipWithIndex.foreach { case (channel, i) => + val in = from_credited(i) + in.ready := channel.ready + channel.valid := in.valid + (channel.bits: Data).waiveAll :<>= (in.bits: Data).waiveAll + } + } + } +} + +object TLMergedCreditedSink { + def apply(delay: TLMergedCreditedDelay)(implicit p: Parameters): TLMergedCreditedSinkNode = { + val sink = LazyModule(new TLMergedCreditedSink(delay)) + sink.node + } + def apply(delay: CreditedDelay)(implicit p: Parameters): TLMergedCreditedSinkNode = apply(TLMergedCreditedDelay(delay)) + def apply()(implicit p: Parameters): TLMergedCreditedSinkNode = apply(CreditedDelay(1, 1)) +} + +// Synthesizable unit tests +import freechips.rocketchip.unittest._ + +class TLRAMMergedCreditedCrossing(txns: Int, params: CreditedCrossing)(implicit p: Parameters) extends LazyModule { + val model = LazyModule(new TLRAMModel("MergedCreditedCrossing")) + val fuzz = LazyModule(new TLFuzzer(txns)) + val island = LazyModule(new CrossingWrapper(params)) + val ram = island { LazyModule(new TLRAM(AddressSet(0x0, 0x3ff))) } + + island.crossTLIn(ram.node) := TLFragmenter(4, 256) := TLDelayer(0.1) := model.node := fuzz.node + + lazy val module = new Impl + class Impl extends LazyModuleImp(this) with UnitTestModule { + io.finished := fuzz.module.io.finished + } +} + +class TLRAMMergedCreditedCrossingTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) { + val u = p.alterPartial { case UseTLMergedCreditedCrossing => true } + + val dut_1000 = Module(LazyModule(new TLRAMMergedCreditedCrossing(txns, CreditedCrossing(CreditedDelay(1, 0), CreditedDelay(0, 0)))(u)).module) + val dut_0100 = Module(LazyModule(new TLRAMMergedCreditedCrossing(txns, CreditedCrossing(CreditedDelay(0, 1), CreditedDelay(0, 0)))(u)).module) + val dut_0010 = Module(LazyModule(new TLRAMMergedCreditedCrossing(txns, CreditedCrossing(CreditedDelay(0, 0), CreditedDelay(1, 0)))(u)).module) + val dut_0001 = Module(LazyModule(new TLRAMMergedCreditedCrossing(txns, CreditedCrossing(CreditedDelay(0, 0), CreditedDelay(0, 1)))(u)).module) + val dut_1111 = Module(LazyModule(new TLRAMMergedCreditedCrossing(txns, CreditedCrossing(CreditedDelay(1, 1), CreditedDelay(1, 1)))(u)).module) + + val duts = Seq(dut_1000, dut_0100, dut_0010, dut_0001, dut_1111) + duts.foreach { _.io.start := true.B } + io.finished := duts.map(_.io.finished).reduce(_ && _) +} diff --git a/src/main/scala/tilelink/Nodes.scala b/src/main/scala/tilelink/Nodes.scala index 6b193f8ec0b..6e4d7f265b4 100644 --- a/src/main/scala/tilelink/Nodes.scala +++ b/src/main/scala/tilelink/Nodes.scala @@ -192,3 +192,42 @@ case class TLCreditedSinkNode(delay: TLCreditedDelay)(implicit valName: ValName) extends MixedAdapterNode(TLCreditedImp, TLImp)( dFn = { p => p.base.v1copy(minLatency = 1) }, uFn = { p => TLCreditedManagerPortParameters(delay, p) }) with FormatNode[TLCreditedEdgeParameters, TLEdgeOut] + +// Merged Credited version of TileLink channels +trait TLMergedCreditedFormatNode extends FormatNode[TLMergedCreditedEdgeParameters, TLMergedCreditedEdgeParameters] + +object TLMergedCreditedImp extends SimpleNodeImp[TLMergedCreditedClientPortParameters, TLMergedCreditedManagerPortParameters, TLMergedCreditedEdgeParameters, TLMergedCreditedBundle] +{ + def edge(pd: TLMergedCreditedClientPortParameters, pu: TLMergedCreditedManagerPortParameters, p: Parameters, sourceInfo: SourceInfo) = TLMergedCreditedEdgeParameters(pd, pu, p, sourceInfo) + def bundle(e: TLMergedCreditedEdgeParameters) = new TLMergedCreditedBundle(e.bundle) + def render(e: TLMergedCreditedEdgeParameters) = RenderedEdge(colour = "#ffff00" /* yellow */, e.delay.toString) + + override def mixO(pd: TLMergedCreditedClientPortParameters, node: OutwardNode[TLMergedCreditedClientPortParameters, TLMergedCreditedManagerPortParameters, TLMergedCreditedBundle]): TLMergedCreditedClientPortParameters = + pd.copy(base = pd.base.v1copy(clients = pd.base.clients.map { c => c.v1copy (nodePath = node +: c.nodePath) })) + override def mixI(pu: TLMergedCreditedManagerPortParameters, node: InwardNode[TLMergedCreditedClientPortParameters, TLMergedCreditedManagerPortParameters, TLMergedCreditedBundle]): TLMergedCreditedManagerPortParameters = + pu.copy(base = pu.base.v1copy(managers = pu.base.managers.map { m => m.v1copy (nodePath = node +: m.nodePath) })) +} + +case class TLMergedCreditedAdapterNode( + clientFn: TLMergedCreditedClientPortParameters => TLMergedCreditedClientPortParameters = { s => s }, + managerFn: TLMergedCreditedManagerPortParameters => TLMergedCreditedManagerPortParameters = { s => s })( + implicit valName: ValName) + extends AdapterNode(TLMergedCreditedImp)(clientFn, managerFn) with TLMergedCreditedFormatNode + +case class TLMergedCreditedIdentityNode()(implicit valName: ValName) extends IdentityNode(TLMergedCreditedImp)() with TLMergedCreditedFormatNode + +object TLMergedCreditedNameNode { + def apply(name: ValName) = TLMergedCreditedIdentityNode()(name) + def apply(name: Option[String]): TLMergedCreditedIdentityNode = apply(ValName(name.getOrElse("with_no_name"))) + def apply(name: String): TLMergedCreditedIdentityNode = apply(Some(name)) +} + +case class TLMergedCreditedSourceNode(delay: TLMergedCreditedDelay)(implicit valName: ValName) + extends MixedAdapterNode(TLImp, TLMergedCreditedImp)( + dFn = { p => TLMergedCreditedClientPortParameters(delay, p) }, + uFn = { p => p.base.v1copy(minLatency = 1) }) with FormatNode[TLEdgeIn, TLMergedCreditedEdgeParameters] // discard cycles from other clock domain + +case class TLMergedCreditedSinkNode(delay: TLMergedCreditedDelay)(implicit valName: ValName) + extends MixedAdapterNode(TLMergedCreditedImp, TLImp)( + dFn = { p => p.base.v1copy(minLatency = 1) }, + uFn = { p => TLMergedCreditedManagerPortParameters(delay, p) }) with FormatNode[TLMergedCreditedEdgeParameters, TLEdgeOut] diff --git a/src/main/scala/tilelink/Parameters.scala b/src/main/scala/tilelink/Parameters.scala index 3be0c2bd845..9578ef796ea 100644 --- a/src/main/scala/tilelink/Parameters.scala +++ b/src/main/scala/tilelink/Parameters.scala @@ -1405,6 +1405,32 @@ case class TLCreditedEdgeParameters(client: TLCreditedClientPortParameters, mana def formatEdge = client.infoString + "\n" + manager.infoString } +case class TLMergedCreditedDelay( + ace: CreditedDelay, + bd: CreditedDelay) +{ + def + (that: TLMergedCreditedDelay): TLMergedCreditedDelay = TLMergedCreditedDelay( + ace = ace + that.ace, + bd = bd + that.bd) + + override def toString = s"(${ace}, ${bd}" +} + +object TLMergedCreditedDelay { + def apply(delay: CreditedDelay): TLMergedCreditedDelay = apply(delay, delay.flip) +} + +case class TLMergedCreditedManagerPortParameters(delay: TLMergedCreditedDelay, base: TLSlavePortParameters) {def infoString = base.infoString} +case class TLMergedCreditedClientPortParameters(delay: TLMergedCreditedDelay, base: TLMasterPortParameters) {def infoString = base.infoString} +case class TLMergedCreditedEdgeParameters(client: TLMergedCreditedClientPortParameters, manager: TLMergedCreditedManagerPortParameters, params: Parameters, sourceInfo: SourceInfo) extends FormatEdge +{ + val delay = client.delay + manager.delay + val bundle = TLBundleParameters(client.base, manager.base) + def formatEdge = client.infoString + "\n" + manager.infoString +} + + + case class TLAsyncManagerPortParameters(async: AsyncQueueParams, base: TLSlavePortParameters) {def infoString = base.infoString} case class TLAsyncClientPortParameters(base: TLMasterPortParameters) {def infoString = base.infoString} case class TLAsyncBundleParameters(async: AsyncQueueParams, base: TLBundleParameters) diff --git a/src/main/scala/unittest/Configs.scala b/src/main/scala/unittest/Configs.scala index 655811576b8..4a3ef1ddac4 100644 --- a/src/main/scala/unittest/Configs.scala +++ b/src/main/scala/unittest/Configs.scala @@ -58,6 +58,7 @@ class WithTLSimpleUnitTests extends Config((site, here, up) => { Module(new TLRAMRationalCrossingTest(txns= 3*txns, timeout=timeout)), Module(new TLRAMAsyncCrossingTest( txns= 5*txns, timeout=timeout)), Module(new TLRAMCreditedCrossingTest(txns= 5*txns, timeout=timeout)), + Module(new TLRAMMergedCreditedCrossingTest(txns= 5*txns, timeout=timeout)), Module(new TLRAMAtomicAutomataTest( txns=10*txns, timeout=timeout)), Module(new TLRAMECCTest(8, 4, true, txns=15*txns, timeout=timeout)), Module(new TLRAMECCTest(4, 1, true, txns=15*txns, timeout=timeout)), diff --git a/src/main/scala/util/MultiCreditedIO.scala b/src/main/scala/util/MultiCreditedIO.scala new file mode 100644 index 00000000000..2d316ce7254 --- /dev/null +++ b/src/main/scala/util/MultiCreditedIO.scala @@ -0,0 +1,134 @@ +package freechips.rocketchip.util + +import chisel3._ +import chisel3.util._ + +/** MultiCreditedIO enables serializing multiple independent channels + * onto a shared credited link, with independent crediting per-channel + * This can be used to avoid introducing cross-channel resource dependencies + * to avoid deadlock in a multi-channel communication protocol + * The sender may only transmit Data when it has non-zero credits. + * Receivers provide 0 or 1 credits to senders using the credit field. + * Senders consume 0 or 1 credits by setting the debit field. + * The bits Data field is DontCare when debit=0. + * credit MAY depend combinationally on debit. + * debit MAY depend combinationally on credit. + * WARNING: The user must ensure the round trip time is > 0. + * Failure to comply will result in a combinational loop! + */ +final class MultiCreditedIO[T <: Data](gen: T, nChannels: Int) extends Bundle { + def genType: T = gen + + val channelBits = log2Ceil(nChannels) + + val credits = Input (Valid(UInt(channelBits.W))) + val debits = Output(Valid(UInt(channelBits.W))) + val bits = Output(genType) + + /** Provide a DecoupledIO interface for sending MultiCreditedIO[Data]. + * Convert an IrrevocableIO input to DecoupledIO via Decoupled(). + * depth controls the maximum number of Data beats inflight. + * Sender powers on with credits=depth, so sender and receiver must agree on depth. + * pipe=false increases the receiver=>sender trip time by one cycle. + * pipe=true causes debit to depend on credit. + */ + def toSenders(depths: Seq[Int], pipe: Boolean): Seq[DecoupledIO[T]] = { + require (depths.min >= 1 && depths.size == nChannels) + val senders = Wire(Vec(nChannels, Decoupled(genType))) + val arb = Module(new Arbiter(genType, nChannels)) + arb.io.out.ready := true.B + debits.valid := arb.io.out.valid + debits.bits := arb.io.chosen + bits := arb.io.out.bits + senders.zipWithIndex.map { case (sender, i) => + val counter = new CreditedIOCounter(depths(i), depths(i)) + val credit = credits.valid && credits.bits === i.U + val debit = debits.valid && debits.bits === i.U + counter.update(credit, debit) + val credit_available = !counter.empty || (pipe.B && credit) + + arb.io.in(i).valid := sender.valid && credit_available + arb.io.in(i).bits := sender.bits + sender.ready := arb.io.in(i).ready && credit_available + } + senders + } + + def toSenders(depth: Int, pipe: Boolean = true): Seq[DecoupledIO[T]] = + toSenders(Seq.fill(nChannels)(depth), pipe) + + /** Provide an DecoupledIO interface for receiving MultiCreditedIO[Data]. + * depth controls the Queue depth and thus maximum number of elements inflight. + * flow=false increases the sender=>receiver trip time by one cycle. + * flow=true causes credit to depend on debit. + */ + def toReceivers(depths: Seq[Int], flow: Boolean): Seq[DecoupledIO[T]] = { + require (depths.min >= 1 && depths.size == nChannels) + val credit_arb = Module(new Arbiter(UInt(channelBits.W), nChannels)) + credit_arb.io.out.ready := true.B + credits.valid := credit_arb.io.out.valid + credits.bits := credit_arb.io.out.bits + (0 until nChannels).map { i => + val enq = Wire(Decoupled(genType)) + enq.valid := debits.valid && debits.bits === i.U + enq.bits := bits + assert(!enq.valid || enq.ready) + val res = Queue(enq, depths(i), pipe=true, flow=flow) + val out = Wire(Decoupled(genType)) + out.bits := res.bits + credit_arb.io.in(i).bits := i.U + + credit_arb.io.in(i).valid := res.valid && out.ready + out.valid := res.valid && credit_arb.io.in(i).ready + res.ready := out.ready && credit_arb.io.in(i).ready + out + } + } + + def toReceivers(depth: Int, flow: Boolean = true): Seq[DecoupledIO[T]] = + toReceivers(Seq.fill(nChannels)(depth), flow) + + /** Add register stages to the sender and receiver paths. + * Apply this method to the producer/sender-facing bundle. + * The round-trip-time (RTT) is increased by sender+receiver. + */ + def pipeline(debitDelay: Int, creditDelay: Int): MultiCreditedIO[T] = { + val res = Wire(MultiCreditedIO(genType, nChannels)) + if (debitDelay <= 0) { + credits.valid := ShiftRegister(res.credits.valid, creditDelay, false.B, true.B) + credits.bits := ShiftRegister(res.credits.bits , creditDelay, true.B) + res.debits := debits + res.bits := bits + } else { + // We can't use ShiftRegister, because we want debit-gated enables + val out = pipeline(debitDelay-1, creditDelay) + out.credits := res.credits + res.debits.valid := RegNext(out.debits.valid, false.B) + res.debits.bits := RegEnable(out.debits.bits, out.debits.valid) + res.bits := RegEnable(out.bits , out.debits.valid) + } + res + } + + def pipeline(delay: CreditedDelay): MultiCreditedIO[T] = + pipeline(delay.debit, delay.credit) +} + +object MultiCreditedIO +{ + def apply[T <: Data](genType: T, nChannels: Int) = new MultiCreditedIO(genType, nChannels) + + def fromSenders[T <: Data](x: Seq[ReadyValidIO[T]], depth: Int, pipe: Boolean = true): MultiCreditedIO[T] = { + val res = Wire(MultiCreditedIO(chiselTypeOf(x.head.bits), x.size)) + val dec = res.toSenders(depth, pipe) + x.zip(dec).foreach(t => t._2 <> t._1) + res + } + + def fromReceivers[T <: Data](x: Seq[ReadyValidIO[T]], depth: Int, flow: Boolean = true): MultiCreditedIO[T] = { + val res = Wire(MultiCreditedIO(chiselTypeOf(x.head.bits), x.size)) + val dec = res.toReceivers(depth, flow) + x.zip(dec).foreach(t => t._1 <> t._2) + res + } +}