Skip to content

Commit

Permalink
Merge pull request #2676 from chipsalliance/improve-amba
Browse files Browse the repository at this point in the history
Improvements To AMBA AXI4 Adapters
  • Loading branch information
hcook authored Oct 26, 2020
2 parents 78e26ed + c5db42d commit 53c4363
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 64 deletions.
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)
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

0 comments on commit 53c4363

Please sign in to comment.