diff --git a/src/main/scala/amba/axi4/Deinterleaver.scala b/src/main/scala/amba/axi4/Deinterleaver.scala index 0d13d5a8a34..7f560492dcd 100644 --- a/src/main/scala/amba/axi4/Deinterleaver.scala +++ b/src/main/scala/amba/axi4/Deinterleaver.scala @@ -9,38 +9,61 @@ import freechips.rocketchip.config.Parameters import freechips.rocketchip.diplomacy._ import freechips.rocketchip.util.leftOR +/** This adapter deinterleaves read responses on the R channel. + * + * Deinterleaving guarantees that once the first beat of a read response + * has been accepted by the recipient, all further presented read responses will + * be from the same burst transaction, until the burst is complete. + * + * @param maxReadBytes is the maximum supported read burst size that this adapter + * has been provisioned to support. + * @param buffer is the internal buffering to provide in the case where no deinterleaving is required. + */ class AXI4Deinterleaver(maxReadBytes: Int, buffer: BufferParams = BufferParams.default)(implicit p: Parameters) extends LazyModule { - require (maxReadBytes >= 1 && isPow2(maxReadBytes)) + require (maxReadBytes >= 1, s"AXI4Deinterleaver: maxReadBytes must be at least 1, not $maxReadBytes") + require (isPow2(maxReadBytes), s"AXI4Deinterleaver: maxReadBytes must be a power of two, not $maxReadBytes") - val node = AXI4AdapterNode( + private def maxBeats(slave: AXI4SlavePortParameters): Int = + (maxReadBytes+slave.beatBytes-1) / slave.beatBytes + + // Nothing to do if R channel only uses a single beat + private def nothingToDeinterleave(slave: AXI4SlavePortParameters): Boolean = + maxBeats(slave) <= 1 + + val node = new AXI4AdapterNode( masterFn = { mp => mp }, slaveFn = { sp => sp.copy(slaves = sp.slaves.map(s => s.copy( supportsRead = s.supportsRead.intersect(TransferSizes(1, maxReadBytes)), interleavedId = Some(0)))) - }) + }) { + override def circuitIdentity = edges.out.map(_.slave).forall(nothingToDeinterleave) + } lazy val module = new LazyModuleImp(this) { (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) => val endId = edgeOut.master.endId - val beatBytes = edgeOut.slave.beatBytes - val beats = (maxReadBytes+beatBytes-1) / beatBytes + val beats = maxBeats(edgeOut.slave) - // This adapter leaves the control + write paths completely untouched + // This adapter passes through the AR/AW control + W/B write data channels out.ar :<> in.ar out.aw :<> in.aw out.w :<> in.w in.b :<> out.b - if (beats <= 1) { - // Nothing to do if only single-beat R + // Only the R channel has the possibility of being changed + if (nothingToDeinterleave(edgeOut.slave)) { in.r.asInstanceOf[ReadyValidIO[AXI4BundleR]] :<> buffer.irrevocable(out.r) } else { + // We only care to deinterleave ids that are actually in use + val maxFlightPerId = Seq.tabulate(endId) { i => + edgeOut.master.masters.find(_.id.contains(i)).flatMap(_.maxFlight).getOrElse(0) + } + // Queues to buffer R responses - val qs = Seq.tabulate(endId) { i => - val depth = edgeOut.master.masters.find(_.id.contains(i)).flatMap(_.maxFlight).getOrElse(0) - if (depth > 0) { - val q = Module(new Queue(out.r.bits.cloneType, beats)) + val qs = maxFlightPerId.zipWithIndex.map { case (mf, i) => + if (mf > 0) { + val q = Module(new Queue(out.r.bits.cloneType, entries = beats)) q.suggestName(s"queue_${i}") q.io } else { @@ -63,11 +86,9 @@ class AXI4Deinterleaver(maxReadBytes: Int, buffer: BufferParams = BufferParams.d val enq_OH = UIntToOH(enq_id, endId) // Track the number of completely received bursts per FIFO id - val pending = Cat(Seq.tabulate(endId) { i => - val depth = edgeOut.master.masters.find(_.id.contains(i)).flatMap(_.maxFlight).getOrElse(0) - if (depth == 0) { - false.B - } else { + val pending = Cat(maxFlightPerId.zipWithIndex.map { + case (0, _) => false.B // any id not in use + case (_, i) => { // i is an id in use val count = RegInit(0.U(log2Ceil(beats+1).W)) val next = Wire(chiselTypeOf(count)) val inc = enq_OH(i) && out.r.fire() && out.r.bits.last diff --git a/src/main/scala/amba/axi4/IdIndexer.scala b/src/main/scala/amba/axi4/IdIndexer.scala index 1b7c4fd71e0..c95054f6de6 100644 --- a/src/main/scala/amba/axi4/IdIndexer.scala +++ b/src/main/scala/amba/axi4/IdIndexer.scala @@ -10,9 +10,17 @@ import freechips.rocketchip.util._ case object AXI4ExtraId extends ControlKey[UInt]("extra_id") case class AXI4ExtraIdField(width: Int) extends SimpleBundleField(AXI4ExtraId)(UInt(OUTPUT, width = width), UInt(0)) +/** This adapter limits the set of FIFO domain ids used by outbound transactions. + * + * Extra AWID and ARID bits from upstream transactions are stored in a User Bits field called AXI4ExtraId, + * which values are expected to be echoed back to this adapter alongside any downstream response messages, + * and are then prepended to the RID and BID field to restore the original identifier. + * + * @param idBits is the desired number of A[W|R]ID bits to be used + */ class AXI4IdIndexer(idBits: Int)(implicit p: Parameters) extends LazyModule { - require (idBits >= 0) + require (idBits >= 0, s"AXI4IdIndexer: idBits must be > 0, not $idBits") val node = AXI4AdapterNode( masterFn = { mp => @@ -29,19 +37,19 @@ class AXI4IdIndexer(idBits: Int)(implicit p: Parameters) extends LazyModule mp.masters.foreach { m => for (i <- m.id.start until m.id.end) { val j = i % (1 << idBits) - val old = masters(j) + val accumulated = masters(j) names(j) += m.name - masters(j) = old.copy( - aligned = old.aligned && m.aligned, - maxFlight = old.maxFlight.flatMap { o => m.maxFlight.map { n => o+n } }) + masters(j) = accumulated.copy( + aligned = accumulated.aligned && m.aligned, + maxFlight = accumulated.maxFlight.flatMap { o => m.maxFlight.map { n => o+n } }) } } - names.foreach { n => if (n.isEmpty) n += "(unused)" } + val finalNameStrings = names.map { n => if (n.isEmpty) "(unused)" else n.toList.mkString(", ") } val bits = log2Ceil(mp.endId) - idBits val field = if (bits > 0) Seq(AXI4ExtraIdField(bits)) else Nil mp.copy( echoFields = field ++ mp.echoFields, - masters = masters.zipWithIndex.map { case (m,i) => m.copy(name = names(i).toList.mkString(", "))}) + masters = masters.zip(finalNameStrings).map { case (m, n) => m.copy(name = n) }) }, slaveFn = { sp => sp }) diff --git a/src/main/scala/amba/axi4/UserYanker.scala b/src/main/scala/amba/axi4/UserYanker.scala index 47ce59ac1f1..053ed093d23 100644 --- a/src/main/scala/amba/axi4/UserYanker.scala +++ b/src/main/scala/amba/axi4/UserYanker.scala @@ -8,6 +8,14 @@ import freechips.rocketchip.config.Parameters import freechips.rocketchip.diplomacy._ import freechips.rocketchip.util._ +/** This adapter prunes all user bit fields of the echo type from request messages, + * storing them in queues and echoing them back when matching response messages are recevied. + * + * It also optionally rate limits the number of transactions that can be in flight simultaneously + * per FIFO domain / A[W|R]ID. + * + * @param capMaxFlight is an optional maximum number of transactions that can be in flight per A[W|R]ID. + */ class AXI4UserYanker(capMaxFlight: Option[Int] = None)(implicit p: Parameters) extends LazyModule { val node = AXI4AdapterNode( diff --git a/src/main/scala/tilelink/ToAXI4.scala b/src/main/scala/tilelink/ToAXI4.scala index 40dc6c3d64f..f60eb429a16 100644 --- a/src/main/scala/tilelink/ToAXI4.scala +++ b/src/main/scala/tilelink/ToAXI4.scala @@ -23,16 +23,32 @@ case class AXI4TLStateField(sourceBits: Int) extends BundleField(AXI4TLState) { } } -class TLtoAXI4IdMap(tl: TLMasterPortParameters, axi4: AXI4MasterPortParameters) - extends IdMap[TLToAXI4IdMapEntry] +/** TLtoAXI4IdMap serves as a record for the translation performed between id spaces. + * + * Its member [axi4Masters] is used as the new AXI4MasterParameters in diplomacy. + * Its member [mapping] is used as the template for the circuit generated in TLToAXI4Node.module. + */ +class TLtoAXI4IdMap(tlPort: TLMasterPortParameters) extends IdMap[TLToAXI4IdMapEntry] { - private val axiDigits = String.valueOf(axi4.endId-1).length() - private val tlDigits = String.valueOf(tl.endSourceId-1).length() + val tlMasters = tlPort.masters.sortBy(_.sourceId).sortWith(TLToAXI4.sortByType) + private val axi4IdSize = tlMasters.map { tl => if (tl.requestFifo) 1 else tl.sourceId.size } + private val axi4IdStart = axi4IdSize.scanLeft(0)(_+_).init + val axi4Masters = axi4IdStart.zip(axi4IdSize).zip(tlMasters).map { case ((start, size), tl) => + AXI4MasterParameters( + name = tl.name, + id = IdRange(start, start+size), + aligned = true, + maxFlight = Some(if (tl.requestFifo) tl.sourceId.size else 1), + nodePath = tl.nodePath) + } + + private val axi4IdEnd = axi4Masters.map(_.id.end).max + private val axiDigits = String.valueOf(axi4IdEnd-1).length() + private val tlDigits = String.valueOf(tlPort.endSourceId-1).length() protected val fmt = s"\t[%${axiDigits}d, %${axiDigits}d) <= [%${tlDigits}d, %${tlDigits}d) %s%s%s" - private val sorted = tl.clients.sortBy(_.sourceId).sortWith(TLToAXI4.sortByType) - val mapping: Seq[TLToAXI4IdMapEntry] = (sorted zip axi4.masters) map { case (c, m) => - TLToAXI4IdMapEntry(m.id, c.sourceId, c.name, c.supports.probe, c.requestFifo) + val mapping: Seq[TLToAXI4IdMapEntry] = tlMasters.zip(axi4Masters).map { case (tl, axi) => + TLToAXI4IdMapEntry(axi.id, tl.sourceId, tl.name, tl.supports.probe, tl.requestFifo) } } @@ -44,26 +60,10 @@ case class TLToAXI4IdMapEntry(axi4Id: IdRange, tlId: IdRange, name: String, isCa val maxTransactionsInFlight = Some(tlId.size) } -case class TLToAXI4Node(stripBits: Int = 0, wcorrupt: Boolean = true)(implicit valName: ValName) extends MixedAdapterNode(TLImp, AXI4Imp)( +case class TLToAXI4Node(wcorrupt: Boolean = true)(implicit valName: ValName) extends MixedAdapterNode(TLImp, AXI4Imp)( dFn = { p => - p.clients.foreach { c => - require (c.sourceId.start % (1 << stripBits) == 0 && - c.sourceId.end % (1 << stripBits) == 0, - s"Cannot strip bits of aligned client ${c.name}: ${c.sourceId}") - } - val clients = p.clients.sortWith(TLToAXI4.sortByType _) - val idSize = clients.map { c => if (c.requestFifo) 1 else (c.sourceId.size >> stripBits) } - val idStart = idSize.scanLeft(0)(_+_).init - val masters = ((idStart zip idSize) zip clients) map { case ((start, size), c) => - AXI4MasterParameters( - name = c.name, - id = IdRange(start, start+size), - aligned = true, - maxFlight = Some(if (c.requestFifo) c.sourceId.size else (1 << stripBits)), - nodePath = c.nodePath) - } AXI4MasterPortParameters( - masters = masters, + masters = (new TLtoAXI4IdMap(p)).axi4Masters, requestFields = (if (wcorrupt) Seq(AMBACorruptField()) else Seq()) ++ p.requestFields.filter(!_.isInstanceOf[AMBAProtField]), echoFields = AXI4TLStateField(log2Ceil(p.endSourceId)) +: p.echoFields, responseKeys = p.responseKeys) @@ -91,7 +91,8 @@ case class TLToAXI4Node(stripBits: Int = 0, wcorrupt: Boolean = true)(implicit v // wcorrupt alone is not enough; a slave must include AMBACorrupt in the slave port's requestKeys class TLToAXI4(val combinational: Boolean = true, val adapterName: Option[String] = None, val stripBits: Int = 0, val wcorrupt: Boolean = true)(implicit p: Parameters) extends LazyModule { - val node = TLToAXI4Node(stripBits, wcorrupt) + require(stripBits == 0, "stripBits > 0 is no longer supported on TLToAXI4") + val node = TLToAXI4Node(wcorrupt) lazy val module = new LazyModuleImp(this) { (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) => @@ -102,15 +103,15 @@ class TLToAXI4(val combinational: Boolean = true, val adapterName: Option[String slaves.foreach { s => require (s.interleavedId == slaves(0).interleavedId) } // Construct the source=>ID mapping table - val map = new TLtoAXI4IdMap(edgeIn.client, edgeOut.master) + val map = new TLtoAXI4IdMap(edgeIn.client) val sourceStall = Wire(Vec(edgeIn.client.endSourceId, Bool())) val sourceTable = Wire(Vec(edgeIn.client.endSourceId, out.aw.bits.id)) val idStall = Wire(init = Vec.fill(edgeOut.master.endId) { Bool(false) }) var idCount = Array.fill(edgeOut.master.endId) { None:Option[Int] } - Annotated.idMapping(this, map.mapping).foreach { case TLToAXI4IdMapEntry(axi4Id, tlId, _, _, fifo) => + map.mapping.foreach { case TLToAXI4IdMapEntry(axi4Id, tlId, _, _, fifo) => for (i <- 0 until tlId.size) { - val id = axi4Id.start + (if (fifo) 0 else (i >> stripBits)) + val id = axi4Id.start + (if (fifo) 0 else i) sourceStall(tlId.start + i) := idStall(id) sourceTable(tlId.start + i) := UInt(id) } diff --git a/src/main/scala/util/Annotations.scala b/src/main/scala/util/Annotations.scala index e4b7dd15c2e..5bce7335cd3 100644 --- a/src/main/scala/util/Annotations.scala +++ b/src/main/scala/util/Annotations.scala @@ -10,7 +10,6 @@ import firrtl.annotations._ import freechips.rocketchip.diplomacy._ import freechips.rocketchip.regmapper._ -import freechips.rocketchip.tilelink.TLToAXI4IdMapEntry import org.json4s.JsonDSL._ import org.json4s.jackson.JsonMethods.{pretty, render} @@ -64,11 +63,6 @@ case class AddressMapAnnotation(target: Named, mapping: Seq[AddressMapEntry], la "\n ]\n}" } -/** Record a conversion of TL source ids to AXI4 ids. */ -case class TLToAXI4IdMapAnnotation(target: Named, mapping: Seq[TLToAXI4IdMapEntry]) extends SingleTargetAnnotation[Named] { - def duplicate(n: Named) = this.copy(n) -} - /** Marks this module as a candidate for register retiming */ case class RetimeModuleAnnotation(target: ModuleName) extends SingleTargetAnnotation[ModuleName] { def duplicate(n: ModuleName) = this.copy(n) @@ -149,11 +143,6 @@ object Annotated { mapping } - def idMapping(component: InstanceId, mapping: Seq[TLToAXI4IdMapEntry]): Seq[TLToAXI4IdMapEntry] = { - annotate(new ChiselAnnotation { def toFirrtl = TLToAXI4IdMapAnnotation(component.toNamed, mapping) }) - mapping - } - def port[T <: Data]( data: T, protocol: String,