From 5f62529cef96bdf26f857f7d750c07e3a4e7356e Mon Sep 17 00:00:00 2001 From: OceanS2000 <30361859+OceanS2000@users.noreply.github.com> Date: Thu, 16 Feb 2023 00:20:29 +0800 Subject: [PATCH] [rtl] initial implementation of crossbar --- tilelink/src/utils/Serializers.scala | 13 + tilelink/src/xbar/TLCrossBar.scala | 261 +++++++++++++++++++ tilelink/src/xbar/TLCrossBarParameters.scala | 81 ++++++ tilelink/src/xbar/TLIdRange.scala | 60 +++++ 4 files changed, 415 insertions(+) create mode 100644 tilelink/src/utils/Serializers.scala create mode 100644 tilelink/src/xbar/TLCrossBar.scala create mode 100644 tilelink/src/xbar/TLCrossBarParameters.scala create mode 100644 tilelink/src/xbar/TLIdRange.scala diff --git a/tilelink/src/utils/Serializers.scala b/tilelink/src/utils/Serializers.scala new file mode 100644 index 0000000..9c684fc --- /dev/null +++ b/tilelink/src/utils/Serializers.scala @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import upickle.default.{readwriter, ReadWriter => RW} + +object Serializers { + // Inject serializers for chisel3 BitSet and BitPat + implicit val bitPatSerializer: RW[chisel3.util.BitPat] = + readwriter[String].bimap(_.rawString, chisel3.util.BitPat(_)) + implicit val bitSetSerializer: RW[chisel3.util.experimental.BitSet] = + readwriter[Seq[chisel3.util.BitPat]].bimap(_.terms.toSeq, chisel3.util.experimental.BitSet(_: _*)) +} diff --git a/tilelink/src/xbar/TLCrossBar.scala b/tilelink/src/xbar/TLCrossBar.scala new file mode 100644 index 0000000..57c12bb --- /dev/null +++ b/tilelink/src/xbar/TLCrossBar.scala @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2016-2017 SiFive, Inc. See LICENSE.SiFive for details + +package xbar + +import chisel3._ +import chisel3.util._ +import chisel3.util.experimental.BitSet +import chisel3.util.experimental.decode.decoder + +import bundle._ + +object TLCrossBar { + private def fanout( + input: DecoupledIO[TLChannel], + select: Seq[Bool] + ): Seq[DecoupledIO[TLChannel]] = { + val filtered = Wire(Vec(select.size, chiselTypeOf(input))) + filtered.zip(select).foreach { case (chan, selected) => + chan.bits := input.bits + chan.valid := input.valid && (selected || (select.size == 1).B) + } + input.ready := Mux1H(select, filtered.map(_.ready)) + filtered + } +} + +class TLCrossBar(val parameter: TLCrossBarParameter) + extends Module + with chisel3.experimental.SerializableModule[TLCrossBarParameter] { + + val masterLinksIO = parameter.masters.map(_.linkParameter).map { link => + IO(Flipped(new TLLink(link))) + } + val slaveLinksIO = parameter.slaves.map(_.linkParameter).map { link => + IO(new TLLink(link)) + } + + val masterLinksRemapped = Wire( + Vec(parameter.masters.size, new TLLink(parameter.commonLinkParameter)) + ) + val slaveLinksRemapped = Wire( + Vec(parameter.slaves.size, new TLLink(parameter.commonLinkParameter)) + ) + + private def trim(id: UInt, size: Int): UInt = id(log2Ceil(size) - 1, 0) + + // id remapping + parameter.adReachableIO + .lazyZip(masterLinksIO) + .lazyZip(masterLinksRemapped) + .lazyZip(parameter.srcIdRemapTable) + .foreach { case (connects, io, remapped, range) => + if (connects.exists(x => x)) { + remapped.a :<>= io.a + remapped.a.bits.source := io.a.bits.source | range.start.U + + io.d :<>= remapped.d + io.d.bits.source := trim(remapped.d.bits.source, range.size) + } else { + remapped.a.valid := false.B + remapped.a.bits := DontCare + io.a.ready := false.B + io.a.bits := DontCare + + io.d.valid := false.B + io.d.bits := DontCare + remapped.d.ready := false.B + remapped.d.bits := DontCare + } + } + parameter.bceReachableIO + .lazyZip(masterLinksIO) + .lazyZip(masterLinksRemapped) + .lazyZip(parameter.srcIdRemapTable) + .foreach { case (connects, io, remapped, range) => + if (connects.exists(x => x)) { + io.b :<>= remapped.b + io.b.bits.source := trim(remapped.b.bits.source, range.size) + + remapped.c :<>= io.c + remapped.c.bits.source := io.c.bits.source | range.start.U + + remapped.e :<>= io.e + } else { + io.b.valid := false.B + io.b.bits := DontCare + remapped.b.ready := false.B + remapped.b.bits := DontCare + + remapped.c.valid := false.B + remapped.c.bits := DontCare + io.c.ready := false.B + io.c.bits := DontCare + + remapped.e.valid := false.B + remapped.e.bits := DontCare + io.e.ready := false.B + io.e.bits := DontCare + } + } + + parameter.adReachableOI + .lazyZip(slaveLinksIO) + .lazyZip(slaveLinksRemapped) + .lazyZip(parameter.sinkIdRemapTable) + .foreach { case (connects, io, remapped, range) => + if (connects.exists(x => x)) { + remapped.a :<>= io.a + + io.d :<>= remapped.d + io.d.bits.sink := trim(remapped.d.bits.sink, range.size) + } else { + remapped.a.valid := false.B + remapped.a.bits := DontCare + io.a.ready := false.B + io.a.bits := DontCare + + io.d.valid := false.B + io.d.bits := DontCare + remapped.d.ready := false.B + remapped.d.bits := DontCare + } + } + + parameter.bceReachableOI + .lazyZip(slaveLinksIO) + .lazyZip(slaveLinksRemapped) + .lazyZip(parameter.sinkIdRemapTable) + .foreach { case (connects, io, remapped, range) => + if (connects.exists(x => x)) { + io.b :<>= remapped.b + remapped.c :<>= io.c + remapped.e :<>= io.e + + remapped.e.bits.sink := io.e.bits.sink | range.start.U + } else { + io.b.valid := false.B + io.b.bits := DontCare + remapped.b.ready := false.B + remapped.b.bits := DontCare + + remapped.c.valid := false.B + remapped.c.bits := DontCare + io.c.ready := false.B + io.c.bits := DontCare + + remapped.e.valid := false.B + remapped.e.bits := DontCare + io.e.ready := false.B + io.e.bits := DontCare + } + } + + private def unique(x: Vector[Boolean]) = x.count(x => x) <= 1 + private def filter[T](data: Seq[T], mask: Seq[Boolean]) = data.zip(mask).filter(_._2).map(_._1) + + // Based on input=>output connectivity, create per-input minimal address decode circuits + val addressableOs = (parameter.adReachableIO ++ parameter.bceReachableIO).distinct + val outputPortFns: Map[Vector[Boolean], UInt => Seq[Bool]] = + addressableOs.map { addressable => + if (unique(addressable)) { + (addressable, (_: UInt) => addressable.map(_.B)) + } else { + val ports = parameter.slaves.map(_.addressRange) + val maxBits = log2Ceil(1 + ports.map(_.getWidth).max) + val maskedPorts = ports.zip(addressable).map { + case (port, true) => port.intersect(BitPat.dontCare(maxBits)) + case (_, false) => BitSet.empty + } + //noinspection RedundantDefaultArgument + (addressable, (addr: UInt) => decoder.bitset(addr, maskedPorts, errorBit = false).asBools) + } + }.toMap + + val addressA = masterLinksRemapped.map(_.a.bits.address) + val addressC = masterLinksRemapped.map(_.c.bits.address) + + val requestAIO = parameter.adReachableIO.zip(addressA).map { case (c, a) => outputPortFns(c)(a) } + val requestCIO = parameter.bceReachableIO.zip(addressC).map { case (c, a) => outputPortFns(c)(a) } + val requestBOI = slaveLinksRemapped.map { o => parameter.srcIdRemapTable.map { i => i.contains(o.b.bits.source) } } + val requestDOI = slaveLinksRemapped.map { o => parameter.srcIdRemapTable.map { i => i.contains(o.d.bits.source) } } + val requestEIO = masterLinksRemapped.map { i => parameter.sinkIdRemapTable.map { o => o.contains(i.e.bits.sink) } } + + val portsAOI = masterLinksRemapped.zip(requestAIO).map { case (i, r) => TLCrossBar.fanout(i.a, r) }.transpose + val portsBIO = slaveLinksRemapped.zip(requestBOI).map { case (o, r) => TLCrossBar.fanout(o.b, r) }.transpose + val portsCOI = masterLinksRemapped.zip(requestCIO).map { case (i, r) => TLCrossBar.fanout(i.c, r) }.transpose + val portsDIO = slaveLinksRemapped.zip(requestDOI).map { case (o, r) => TLCrossBar.fanout(o.d, r) }.transpose + val portsEOI = masterLinksRemapped.zip(requestEIO).map { case (i, r) => TLCrossBar.fanout(i.e, r) }.transpose + + slaveLinksRemapped.lazyZip(portsAOI).lazyZip(parameter.adReachableOI).foreach { case (portO, portI, reachable) => + val arbiter = Module( + new TLArbiter( + TLArbiterParameter( + policy = parameter.arbitrationPolicy, + inputLinkParameters = filter(portI.map(_.bits.parameter), reachable), + outputLinkParameter = portO.a.bits.parameter + ) + ) + ) + arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i } + portO.a :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelA]] + filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B } + } + masterLinksRemapped.lazyZip(portsBIO).lazyZip(parameter.bceReachableIO).foreach { case (portI, portO, reachable) => + val arbiter = Module( + new TLArbiter( + TLArbiterParameter( + policy = parameter.arbitrationPolicy, + inputLinkParameters = filter(portO.map(_.bits.parameter), reachable), + outputLinkParameter = portI.b.bits.parameter + ) + ) + ) + arbiter.sources.zip(filter(portO, reachable)).foreach { case (i, o) => i :<>= o } + portI.b :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelB]] + filter(portO, reachable.map(!_)).foreach { o => o.ready := false.B } + } + slaveLinksRemapped.lazyZip(portsCOI).lazyZip(parameter.bceReachableOI).foreach { case (portO, portI, reachable) => + val arbiter = Module( + new TLArbiter( + TLArbiterParameter( + policy = parameter.arbitrationPolicy, + inputLinkParameters = filter(portI.map(_.bits.parameter), reachable), + outputLinkParameter = portO.c.bits.parameter + ) + ) + ) + arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i } + portO.c :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelC]] + filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B } + } + masterLinksRemapped.lazyZip(portsDIO).lazyZip(parameter.adReachableIO).foreach { case (portI, portO, reachable) => + val arbiter = Module( + new TLArbiter( + TLArbiterParameter( + policy = parameter.arbitrationPolicy, + inputLinkParameters = filter(portO.map(_.bits.parameter), reachable), + outputLinkParameter = portI.d.bits.parameter + ) + ) + ) + arbiter.sources.zip(filter(portO, reachable)).foreach { case (i, o) => i :<>= o } + portI.d :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelD]] + filter(portO, reachable.map(!_)).foreach { o => o.ready := false.B } + } + slaveLinksRemapped.lazyZip(portsEOI).lazyZip(parameter.bceReachableOI).foreach { case (portO, portI, reachable) => + val arbiter = Module( + new TLArbiter( + TLArbiterParameter( + policy = parameter.arbitrationPolicy, + inputLinkParameters = filter(portI.map(_.bits.parameter), reachable), + outputLinkParameter = portO.e.bits.parameter + ) + ) + ) + arbiter.sources.zip(filter(portI, reachable)).foreach { case (o, i) => o :<>= i } + portO.e :<>= arbiter.sink.asInstanceOf[DecoupledIO[TLChannelE]] + filter(portI, reachable.map(!_)).foreach { i => i.ready := false.B } + } +} diff --git a/tilelink/src/xbar/TLCrossBarParameters.scala b/tilelink/src/xbar/TLCrossBarParameters.scala new file mode 100644 index 0000000..aeef60d --- /dev/null +++ b/tilelink/src/xbar/TLCrossBarParameters.scala @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2016-2017 SiFive, Inc. See LICENSE.SiFive for details + +package xbar + +import bundle._ + +import chisel3.util.log2Ceil +import upickle.default.{macroRW, readwriter, ReadWriter => RW} + +case class TLCrossBarMasterLinkParameter( + linkParameter: TLLinkParameter, + adVisibility: chisel3.util.experimental.BitSet, + bceVisibility: chisel3.util.experimental.BitSet, + srcIdRange: TLIdRange) +object TLCrossBarMasterLinkParameter { + import utils.Serializers._ + implicit val rw: RW[TLCrossBarMasterLinkParameter] = macroRW +} + +case class TLCrossBarSlaveLinkParameter( + linkParameter: TLLinkParameter, + adVisibility: chisel3.util.experimental.BitSet, + bceVisibility: chisel3.util.experimental.BitSet, + sinkIdRange: TLIdRange, + addressRange: chisel3.util.experimental.BitSet) +object TLCrossBarSlaveLinkParameter { + import utils.Serializers._ + implicit val rw: RW[TLCrossBarSlaveLinkParameter] = macroRW +} + +case class TLCrossBarParameter( + arbitrationPolicy: TLArbiterPolicy, + masters: Seq[TLCrossBarMasterLinkParameter], + slaves: Seq[TLCrossBarSlaveLinkParameter]) + extends chisel3.experimental.SerializableModuleParameter { + + slaves.map(_.addressRange).combinations(2).foreach { case Seq(a, b) => + require(!a.overlap(b), s"Address ranges of different slaves cannot overlap: $a, $b.") + } + + private[tilelink] def adReachableIO = masters.map { case TLCrossBarMasterLinkParameter(_, visibility, _, _) => + slaves.map { case TLCrossBarSlaveLinkParameter(_, severability, _, _, _) => + visibility.overlap(severability) + }.toVector + }.toVector + private[tilelink] def bceReachableIO = masters.map { case TLCrossBarMasterLinkParameter(_, _, visibility, _) => + slaves.map { case TLCrossBarSlaveLinkParameter(_, _, severability, _, _) => + visibility.overlap(severability) + }.toVector + }.toVector + + private[tilelink] def adReachableOI = adReachableIO.transpose + private[tilelink] def bceReachableOI = bceReachableIO.transpose + private[tilelink] def srcIdRemapTable = + TLCrossBarParameter.assignIdRange(masters.map(_.srcIdRange.end)) + private[tilelink] def sinkIdRemapTable = + TLCrossBarParameter.assignIdRange(slaves.map(_.sinkIdRange.end)) + private[tilelink] def commonLinkParameter = TLLinkParameter.union( + masters.map(_.linkParameter) ++ slaves.map(_.linkParameter): _* + ) +} + +object TLCrossBarParameter { + private def assignIdRange(sizes: Seq[Int]) = { + val pow2Sizes = sizes.map { z => if (z == 0) 0 else 1 << log2Ceil(z) } + val tuples = pow2Sizes.zipWithIndex.sortBy( + _._1 + ) // record old index, then sort by increasing size + val starts = + tuples + .scanRight(0)(_._1 + _) + .tail // suffix-sum of the sizes = the start positions + val ranges = tuples.zip(starts).map { case ((sz, i), st) => + (if (sz == 0) TLIdRange(0, 0) else TLIdRange(st, st + sz), i) + } + ranges.sortBy(_._2).map(_._1) // Restore original order + } + + implicit val rw: RW[TLCrossBarParameter] = macroRW +} diff --git a/tilelink/src/xbar/TLIdRange.scala b/tilelink/src/xbar/TLIdRange.scala new file mode 100644 index 0000000..c7aa55f --- /dev/null +++ b/tilelink/src/xbar/TLIdRange.scala @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2016-2017 SiFive, Inc. See LICENSE.SiFive for details + +package xbar + +import chisel3._ +import chisel3.util._ + +import upickle.default.{macroRW, ReadWriter => RW} + +// A non-empty half-open range; [start, end) +// TODO: remove TLIdRange, enforce id range as power-of-2 limits +case class TLIdRange(start: Int, end: Int) extends Ordered[TLIdRange] { + require(start >= 0, s"Ids cannot be negative, but got: $start.") + require(start <= end, "Id ranges cannot be negative.") + + def compare(x: TLIdRange) = { + val primary = (this.start - x.start).sign + val secondary = (x.end - this.end).sign + if (primary != 0) primary else secondary + } + + def overlaps(x: TLIdRange) = start < x.end && x.start < end + def contains(x: TLIdRange) = start <= x.start && x.end <= end + + def contains(x: Int) = start <= x && x < end + def contains(x: UInt) = + if (size == 0) { + false.B + } else if (size == 1) { // simple comparison + x === start.U + } else { + // find index of largest different bit + val largestDeltaBit = log2Floor(start ^ (end - 1)) + val smallestCommonBit = largestDeltaBit + 1 // may not exist in x + val uncommonMask = (1 << smallestCommonBit) - 1 + val uncommonBits = (x | 0.U(largestDeltaBit.W))(largestDeltaBit, 0) + // the prefix must match exactly (note: may shift ALL bits away) + (x >> smallestCommonBit) === (start >> smallestCommonBit).U && + // firrtl constant prop range analysis can eliminate these two: + (start & uncommonMask).U <= uncommonBits && + uncommonBits <= ((end - 1) & uncommonMask).U + } + + def shift(x: Int) = TLIdRange(start + x, end + x) + def size = end - start + def isEmpty = end == start + + def range = start until end +} +object TLIdRange { + def overlaps(s: Seq[TLIdRange]) = + if (s.isEmpty) None + else { + val ranges = s.sorted + ranges.tail.zip(ranges.init).find { case (a, b) => a.overlaps(b) } + } + + implicit val rw: RW[TLIdRange] = macroRW +}