Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements To AMBA AXI4 Adapters #2676

Merged
merged 11 commits into from
Oct 26, 2020
55 changes: 38 additions & 17 deletions src/main/scala/amba/axi4/Deinterleaver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
22 changes: 15 additions & 7 deletions src/main/scala/amba/axi4/IdIndexer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand All @@ -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
})
Expand Down
8 changes: 8 additions & 0 deletions src/main/scala/amba/axi4/UserYanker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
59 changes: 30 additions & 29 deletions src/main/scala/tilelink/ToAXI4.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was a little confused at first why there are two sorts in a row. It may be worth adding another TLToAXI4.sortBy<something> method to provide the desired behavior. Perhaps sortByType should break ties with sourceId? Just a thought.

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)
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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)) =>
Expand All @@ -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)
}
Expand Down
11 changes: 0 additions & 11 deletions src/main/scala/util/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down