From 7cfb69e2d5a16ca7383adf9583d8eb91f58b4bbc Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Mon, 13 Nov 2017 17:32:54 -0800 Subject: [PATCH 1/9] Queue: silence some warnings --- src/main/scala/amba/axi4/Deinterleaver.scala | 4 ++-- src/main/scala/rocket/DCache.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/amba/axi4/Deinterleaver.scala b/src/main/scala/amba/axi4/Deinterleaver.scala index b7624258..bdc9db39 100644 --- a/src/main/scala/amba/axi4/Deinterleaver.scala +++ b/src/main/scala/amba/axi4/Deinterleaver.scala @@ -40,9 +40,9 @@ class AXI4Deinterleaver(maxReadBytes: Int)(implicit p: Parameters) extends LazyM val qs = Seq.tabulate(endId) { i => val depth = edgeOut.master.masters.find(_.id.contains(i)).flatMap(_.maxFlight).getOrElse(0) if (depth > 0) { - Module(new Queue(out.r.bits, beats)).io + Module(new Queue(out.r.bits.cloneType, beats)).io } else { - Wire(new QueueIO(out.r.bits, beats)) + Wire(new QueueIO(out.r.bits.cloneType, beats)) } } diff --git a/src/main/scala/rocket/DCache.scala b/src/main/scala/rocket/DCache.scala index 1f28ffc4..e1daa23b 100644 --- a/src/main/scala/rocket/DCache.scala +++ b/src/main/scala/rocket/DCache.scala @@ -99,7 +99,7 @@ class DCacheModule(outer: DCache) extends HellaCacheModule(outer) { val (tl_out_c, release_queue_empty) = if (cacheParams.acquireBeforeRelease) { - val q = Module(new Queue(tl_out.c.bits, cacheDataBeats, flow = true)) + val q = Module(new Queue(tl_out.c.bits.cloneType, cacheDataBeats, flow = true)) tl_out.c <> q.io.deq (q.io.enq, q.io.count === 0) } else { From 353ddffc111eab31f615a27e627cb52d62322113 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Mon, 13 Nov 2017 13:12:32 -0800 Subject: [PATCH 2/9] RAMModel: add a convenience object --- src/main/scala/tilelink/RAMModel.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/scala/tilelink/RAMModel.scala b/src/main/scala/tilelink/RAMModel.scala index 4ffbb2ef..c6e9113e 100644 --- a/src/main/scala/tilelink/RAMModel.scala +++ b/src/main/scala/tilelink/RAMModel.scala @@ -333,6 +333,9 @@ class TLRAMModel(log: String = "", ignoreErrorData: Boolean = false)(implicit p: object TLRAMModel { + def apply(log: String = "", ignoreErrorData: Boolean = false)(implicit p: Parameters) = + LazyModule(new TLRAMModel(log, ignoreErrorData)).node + case class MonitorParameters(addressBits: Int, sizeBits: Int) class ByteMonitor(params: MonitorParameters) extends GenericParameterizedBundle(params) { From 58a93e210097b32ef28a7222f660408acf9c622c Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Mon, 13 Nov 2017 13:16:40 -0800 Subject: [PATCH 3/9] AXI4SRAM: handy helper object --- src/main/scala/amba/axi4/SRAM.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/scala/amba/axi4/SRAM.scala b/src/main/scala/amba/axi4/SRAM.scala index 09c8321c..26e5a318 100644 --- a/src/main/scala/amba/axi4/SRAM.scala +++ b/src/main/scala/amba/axi4/SRAM.scala @@ -90,3 +90,14 @@ class AXI4RAM( in.r.bits.last := Bool(true) } } + +object AXI4RAM +{ + def apply( + address: AddressSet, + executable: Boolean = true, + beatBytes: Int = 4, + devName: Option[String] = None, + errors: Seq[AddressSet] = Nil) + (implicit p: Parameters) = LazyModule(new AXI4RAM(address, executable, beatBytes, devName, errors)).node +} From 1902ba063ae208e6a40ccd52cb52ba1f2b01137b Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Mon, 13 Nov 2017 13:15:56 -0800 Subject: [PATCH 4/9] Filter: can claim to be out-of-order when you are not --- src/main/scala/tilelink/Filter.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/tilelink/Filter.scala b/src/main/scala/tilelink/Filter.scala index dc1e4fe6..9465727a 100644 --- a/src/main/scala/tilelink/Filter.scala +++ b/src/main/scala/tilelink/Filter.scala @@ -42,7 +42,7 @@ class TLFilter( require (m.supportsPutFull.contains(o.supportsPutFull)) require (m.supportsPutPartial.contains(o.supportsPutPartial)) require (m.supportsHint.contains(o.supportsHint)) - require (m.fifoId == o.fifoId) // could relax this, but hard to validate + require (!o.fifoId.isDefined || m.fifoId == o.fifoId) } out })}) From bfc0ba679a8aa6ca8ffb3ab12e4f296df994421c Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Tue, 14 Nov 2017 12:32:32 -0800 Subject: [PATCH 5/9] axi4: add a Delayer for unit tests --- src/main/scala/amba/axi4/Delayer.scala | 83 ++++++++++++++++++++++++++ src/main/scala/amba/axi4/Nodes.scala | 4 +- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/amba/axi4/Delayer.scala diff --git a/src/main/scala/amba/axi4/Delayer.scala b/src/main/scala/amba/axi4/Delayer.scala new file mode 100644 index 00000000..2ca6d1b2 --- /dev/null +++ b/src/main/scala/amba/axi4/Delayer.scala @@ -0,0 +1,83 @@ +// See LICENSE.SiFive for license details. + +package freechips.rocketchip.amba.axi4 + +import Chisel._ +import chisel3.util.IrrevocableIO +import freechips.rocketchip.config.Parameters +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.tilelink.LFSRNoiseMaker + +// q is the probability to delay a request +class AXI4Delayer(q: Double)(implicit p: Parameters) extends LazyModule +{ + val node = AXI4AdapterNode() + require (0.0 <= q && q < 1) + + lazy val module = new LazyModuleImp(this) { + def feed[T <: Data](sink: IrrevocableIO[T], source: IrrevocableIO[T], noise: T) { + // irrevocable requires that we not lower valid + val hold = RegInit(Bool(false)) + when (sink.valid) { hold := Bool(true) } + when (sink.fire()) { hold := Bool(false) } + + val allow = hold || UInt((q * 65535.0).toInt) <= LFSRNoiseMaker(16, source.valid) + sink.valid := source.valid && allow + source.ready := sink.ready && allow + sink.bits := source.bits + when (!sink.valid) { sink.bits := noise } + } + + def anoise[T <: AXI4BundleA](bits: T) { + bits.id := LFSRNoiseMaker(bits.params.idBits) + bits.addr := LFSRNoiseMaker(bits.params.addrBits) + bits.len := LFSRNoiseMaker(bits.params.lenBits) + bits.size := LFSRNoiseMaker(bits.params.sizeBits) + bits.burst := LFSRNoiseMaker(bits.params.burstBits) + bits.lock := LFSRNoiseMaker(bits.params.lockBits) + bits.cache := LFSRNoiseMaker(bits.params.cacheBits) + bits.prot := LFSRNoiseMaker(bits.params.protBits) + bits.qos := LFSRNoiseMaker(bits.params.qosBits) + if (bits.params.userBits > 0) + bits.user.get := LFSRNoiseMaker(bits.params.userBits) + } + + (node.in zip node.out) foreach { case ((in, _), (out, _)) => + val arnoise = Wire(in.ar.bits) + val awnoise = Wire(in.aw.bits) + val wnoise = Wire(in.w .bits) + val rnoise = Wire(in.r .bits) + val bnoise = Wire(in.b .bits) + + anoise(arnoise) + anoise(awnoise) + + wnoise.data := LFSRNoiseMaker(wnoise.params.dataBits) + wnoise.strb := LFSRNoiseMaker(wnoise.params.dataBits/8) + wnoise.last := LFSRNoiseMaker(1)(0) + + rnoise.id := LFSRNoiseMaker(rnoise.params.idBits) + rnoise.data := LFSRNoiseMaker(rnoise.params.dataBits) + rnoise.resp := LFSRNoiseMaker(rnoise.params.respBits) + rnoise.last := LFSRNoiseMaker(1)(0) + if (rnoise.params.userBits > 0) + rnoise.user.get := LFSRNoiseMaker(rnoise.params.userBits) + + bnoise.id := LFSRNoiseMaker(bnoise.params.idBits) + bnoise.resp := LFSRNoiseMaker(bnoise.params.respBits) + if (bnoise.params.userBits > 0) + bnoise.user.get := LFSRNoiseMaker(bnoise.params.userBits) + + feed(out.ar, in.ar, arnoise) + feed(out.aw, in.aw, awnoise) + feed(out.w, in.w, wnoise) + feed(in.b, out.b, bnoise) + feed(in.r, out.r, rnoise) + } + } +} + +object AXI4Delayer +{ + def apply(q: Double)(implicit p: Parameters): AXI4Node = LazyModule(new AXI4Delayer(q)).node +} diff --git a/src/main/scala/amba/axi4/Nodes.scala b/src/main/scala/amba/axi4/Nodes.scala index 0c9f7f86..bb96759a 100644 --- a/src/main/scala/amba/axi4/Nodes.scala +++ b/src/main/scala/amba/axi4/Nodes.scala @@ -22,8 +22,8 @@ object AXI4Imp extends SimpleNodeImp[AXI4MasterPortParameters, AXI4SlavePortPara case class AXI4MasterNode(portParams: Seq[AXI4MasterPortParameters])(implicit valName: ValName) extends SourceNode(AXI4Imp)(portParams) case class AXI4SlaveNode(portParams: Seq[AXI4SlavePortParameters])(implicit valName: ValName) extends SinkNode(AXI4Imp)(portParams) case class AXI4AdapterNode( - masterFn: AXI4MasterPortParameters => AXI4MasterPortParameters, - slaveFn: AXI4SlavePortParameters => AXI4SlavePortParameters, + masterFn: AXI4MasterPortParameters => AXI4MasterPortParameters = { m => m }, + slaveFn: AXI4SlavePortParameters => AXI4SlavePortParameters = { s => s }, numPorts: Range.Inclusive = 0 to 999)( implicit valName: ValName) extends AdapterNode(AXI4Imp)(masterFn, slaveFn, numPorts) From 72c89f7e3063951b6a572bc78898435af0018f3a Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Tue, 14 Nov 2017 12:36:28 -0800 Subject: [PATCH 6/9] axi4: add a Filter suitable for manipulating test visibility --- src/main/scala/amba/axi4/Filter.scala | 62 +++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/main/scala/amba/axi4/Filter.scala diff --git a/src/main/scala/amba/axi4/Filter.scala b/src/main/scala/amba/axi4/Filter.scala new file mode 100644 index 00000000..99a2e9ca --- /dev/null +++ b/src/main/scala/amba/axi4/Filter.scala @@ -0,0 +1,62 @@ +// See LICENSE.SiFive for license details. + +package freechips.rocketchip.amba.axi4 + +import Chisel._ +import freechips.rocketchip.config._ +import freechips.rocketchip.diplomacy._ + +class AXI4Filter( + Sfilter: AXI4SlaveParameters => Option[AXI4SlaveParameters] = AXI4Filter.Sidentity, + Mfilter: AXI4MasterParameters => Option[AXI4MasterParameters] = AXI4Filter.Midentity + )(implicit p: Parameters) extends LazyModule +{ + val node = AXI4AdapterNode( + slaveFn = { sp => sp.copy(slaves = sp.slaves.flatMap { s => + val out = Sfilter(s) + out.foreach { o => // Confirm the filter only REMOVES capability + o.address.foreach { a => require (s.address.map(_.contains(a)).reduce(_||_)) } + require (o.regionType <= s.regionType) + // we allow executable to be changed both ways + require (s.supportsWrite.contains(o.supportsWrite)) + require (s.supportsRead .contains(o.supportsRead)) + require (!o.interleavedId.isDefined || s.interleavedId == o.interleavedId) + } + out + })}, + masterFn = { mp => mp.copy(masters = mp.masters.flatMap { m => + val out = Mfilter(m) + out.foreach { o => require (m.id.contains(o.id)) } + out + })}) + + lazy val module = new LazyModuleImp(this) { + (node.in zip node.out) foreach { case ((in, edgeIn), (out, edgeOut)) => + out <> in + } + } +} + +object AXI4Filter +{ + def Midentity: AXI4MasterParameters => Option[AXI4MasterParameters] = { m => Some(m) } + def Sidentity: AXI4SlaveParameters => Option[AXI4SlaveParameters] = { s => Some(s) } + def Smask(select: AddressSet): AXI4SlaveParameters => Option[AXI4SlaveParameters] = { s => + val filtered = s.address.map(_.intersect(select)).flatten + val alignment = select.alignment /* alignment 0 means 'select' selected everything */ + val maxTransfer = 1 << 30 + val capTransfer = if (alignment == 0 || alignment > maxTransfer) maxTransfer else alignment.toInt + val cap = TransferSizes(1, capTransfer) + if (filtered.isEmpty) { None } else { + Some(s.copy( + address = filtered, + supportsWrite = s.supportsWrite.intersect(cap), + supportsRead = s.supportsRead .intersect(cap))) + } + } + + def apply( + Sfilter: AXI4SlaveParameters => Option[AXI4SlaveParameters] = AXI4Filter.Sidentity, + Mfilter: AXI4MasterParameters => Option[AXI4MasterParameters] = AXI4Filter.Midentity + )(implicit p: Parameters): AXI4Node = LazyModule(new AXI4Filter(Sfilter, Mfilter)).node +} From 5875017956f0cdced8251db97d8d45847b72e911 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Fri, 10 Nov 2017 17:29:29 -0800 Subject: [PATCH 7/9] axi4: add an Xbar --- src/main/scala/amba/axi4/Nodes.scala | 7 + src/main/scala/amba/axi4/Xbar.scala | 320 +++++++++++++++++++++++++++ 2 files changed, 327 insertions(+) create mode 100644 src/main/scala/amba/axi4/Xbar.scala diff --git a/src/main/scala/amba/axi4/Nodes.scala b/src/main/scala/amba/axi4/Nodes.scala index bb96759a..172feff2 100644 --- a/src/main/scala/amba/axi4/Nodes.scala +++ b/src/main/scala/amba/axi4/Nodes.scala @@ -21,6 +21,13 @@ object AXI4Imp extends SimpleNodeImp[AXI4MasterPortParameters, AXI4SlavePortPara case class AXI4MasterNode(portParams: Seq[AXI4MasterPortParameters])(implicit valName: ValName) extends SourceNode(AXI4Imp)(portParams) case class AXI4SlaveNode(portParams: Seq[AXI4SlavePortParameters])(implicit valName: ValName) extends SinkNode(AXI4Imp)(portParams) +case class AXI4NexusNode( + masterFn: Seq[AXI4MasterPortParameters] => AXI4MasterPortParameters, + slaveFn: Seq[AXI4SlavePortParameters] => AXI4SlavePortParameters, + numMasterPorts: Range.Inclusive = 1 to 999, + numSlavePorts: Range.Inclusive = 1 to 999)( + implicit valName: ValName) + extends NexusNode(AXI4Imp)(masterFn, slaveFn, numMasterPorts, numSlavePorts) case class AXI4AdapterNode( masterFn: AXI4MasterPortParameters => AXI4MasterPortParameters = { m => m }, slaveFn: AXI4SlavePortParameters => AXI4SlavePortParameters = { s => s }, diff --git a/src/main/scala/amba/axi4/Xbar.scala b/src/main/scala/amba/axi4/Xbar.scala new file mode 100644 index 00000000..c824c0b0 --- /dev/null +++ b/src/main/scala/amba/axi4/Xbar.scala @@ -0,0 +1,320 @@ +// See LICENSE.SiFive for license details. + +package freechips.rocketchip.amba.axi4 + +import Chisel._ +import chisel3.util.IrrevocableIO +import freechips.rocketchip.config._ +import freechips.rocketchip.diplomacy._ +import freechips.rocketchip.unittest._ +import freechips.rocketchip.tilelink._ + +class AXI4Xbar( + arbitrationPolicy: TLArbiter.Policy = TLArbiter.roundRobin, + maxFlightPerId: Int = 7, + awQueueDepth: Int = 2)(implicit p: Parameters) extends LazyModule +{ + require (maxFlightPerId >= 1) + require (awQueueDepth >= 1) + + val node = AXI4NexusNode( + numMasterPorts = 1 to 999, + numSlavePorts = 1 to 999, + masterFn = { seq => + seq(0).copy( + userBits = seq.map(_.userBits).max, + masters = (AXI4Xbar.mapInputIds(seq) zip seq) flatMap { case (range, port) => + port.masters map { master => master.copy(id = master.id.shift(range.start)) } + } + ) + }, + slaveFn = { seq => + seq(0).copy( + minLatency = seq.map(_.minLatency).min, + slaves = seq.flatMap { port => + require (port.beatBytes == seq(0).beatBytes, + s"Xbar data widths don't match: ${port.slaves.map(_.name)} has ${port.beatBytes}B vs ${seq(0).slaves.map(_.name)} has ${seq(0).beatBytes}B") + port.slaves + } + ) + }) + + lazy val module = new LazyModuleImp(this) { + val (io_in, edgesIn) = node.in.unzip + val (io_out, edgesOut) = node.out.unzip + + // Grab the port ID mapping + val inputIdRanges = AXI4Xbar.mapInputIds(edgesIn.map(_.master)) + + // Find a good mask for address decoding + val port_addrs = edgesOut.map(_.slave.slaves.map(_.address).flatten) + val routingMask = AddressDecoder(port_addrs) + val route_addrs = port_addrs.map(seq => AddressSet.unify(seq.map(_.widen(~routingMask)).distinct)) + val outputPorts = route_addrs.map(seq => (addr: UInt) => seq.map(_.contains(addr)).reduce(_ || _)) + + // To route W we need to record where the AWs went + val awIn = Seq.fill(io_in .size) { Module(new Queue(UInt(width = io_out.size), awQueueDepth, flow = true)) } + val awOut = Seq.fill(io_out.size) { Module(new Queue(UInt(width = io_in .size), awQueueDepth, flow = true)) } + + val requestARIO = Vec(io_in.map { i => Vec(outputPorts.map { o => o(i.ar.bits.addr) }) }) + val requestAWIO = Vec(io_in.map { i => Vec(outputPorts.map { o => o(i.aw.bits.addr) }) }) + val requestROI = Vec(io_out.map { o => Vec(inputIdRanges.map { i => i.contains(o.r.bits.id) }) }) + val requestBOI = Vec(io_out.map { o => Vec(inputIdRanges.map { i => i.contains(o.b.bits.id) }) }) + + // W follows the path dictated by the AW Q + for (i <- 0 until io_in.size) { awIn(i).io.enq.bits := requestAWIO(i).asUInt } + val requestWIO = Vec(awIn.map { q => if (io_out.size > 1) Vec(q.io.deq.bits.toBools) else Vec.fill(1){Bool(true)} }) + + // We need an intermediate size of bundle with the widest possible identifiers + val wide_bundle = AXI4BundleParameters.union(io_in.map(_.params) ++ io_out.map(_.params)) + + // Transform input bundles + val in = Wire(Vec(io_in.size, AXI4Bundle(wide_bundle))) + for (i <- 0 until in.size) { + in(i) <> io_in(i) + + // Handle size = 1 gracefully (Chisel3 empty range is broken) + def trim(id: UInt, size: Int) = if (size <= 1) UInt(0) else id(log2Ceil(size)-1, 0) + // Manipulate the AXI IDs to differentiate masters + val r = inputIdRanges(i) + in(i).aw.bits.id := io_in(i).aw.bits.id | UInt(r.start) + in(i).ar.bits.id := io_in(i).ar.bits.id | UInt(r.start) + io_in(i).r.bits.id := trim(in(i).r.bits.id, r.size) + io_in(i).b.bits.id := trim(in(i).b.bits.id, r.size) + + if (io_out.size > 1) { + // Block A[RW] if we switch ports, to ensure responses stay ordered (also: beware the dining philosophers) + val endId = edgesIn(i).master.endId + val arFIFOMap = Wire(init = Vec.fill(endId) { Bool(true) }) + val awFIFOMap = Wire(init = Vec.fill(endId) { Bool(true) }) + val arSel = UIntToOH(io_in(i).ar.bits.id, endId) + val awSel = UIntToOH(io_in(i).aw.bits.id, endId) + val rSel = UIntToOH(io_in(i).r .bits.id, endId) + val bSel = UIntToOH(io_in(i).b .bits.id, endId) + val arTag = OHToUInt(requestARIO(i).asUInt, io_out.size) + val awTag = OHToUInt(requestAWIO(i).asUInt, io_out.size) + + for (master <- edgesIn(i).master.masters) { + def idTracker(port: UInt, req_fire: Bool, resp_fire: Bool) = { + if (master.maxFlight == Some(1)) { + // No need to worry about response order if at most 1 request possible + Bool(true) + } else if (maxFlightPerId == 1) { + // No need to track where it went if we cap it at 1 request + val allow = RegInit(Bool(true)) + when (req_fire) { allow := Bool(false) } + when (resp_fire) { allow := Bool(true) } + allow + } else { + val legalFlight = master.maxFlight.getOrElse(maxFlightPerId+1) + val flight = legalFlight min maxFlightPerId + val canOverflow = legalFlight > flight + val count = RegInit(UInt(0, width = log2Ceil(flight+1))) + val last = Reg(UInt(width = log2Ceil(io_out.size))) + count := count + req_fire.asUInt - resp_fire.asUInt + assert (!resp_fire || count =/= UInt(0)) + assert (!req_fire || count =/= UInt(flight)) + when (req_fire) { last := port } + (count === UInt(0) || last === port) && (Bool(!canOverflow) || count =/= UInt(flight)) + } + } + + for (id <- master.id.start until master.id.end) { + arFIFOMap(id) := idTracker( + arTag, + arSel(id) && io_in(i).ar.fire(), + rSel(id) && io_in(i).r.fire() && io_in(i).r.bits.last) + awFIFOMap(id) := idTracker( + awTag, + awSel(id) && io_in(i).aw.fire(), + bSel(id) && io_in(i).b.fire()) + } + } + + val allowAR = arFIFOMap(io_in(i).ar.bits.id) + in(i).ar.valid := io_in(i).ar.valid && allowAR + io_in(i).ar.ready := in(i).ar.ready && allowAR + + // Keep in mind that slaves may do this: awready := wvalid, wready := awvalid + // To not cause a loop, we cannot have: wvalid := awready + + // Block AW if we cannot record the W destination + val allowAW = awFIFOMap(io_in(i).aw.bits.id) + val latched = RegInit(Bool(false)) // cut awIn(i).enq.valid from awready + in(i).aw.valid := io_in(i).aw.valid && (latched || awIn(i).io.enq.ready) && allowAW + io_in(i).aw.ready := in(i).aw.ready && (latched || awIn(i).io.enq.ready) && allowAW + awIn(i).io.enq.valid := io_in(i).aw.valid && !latched + when (awIn(i).io.enq.fire()) { latched := Bool(true) } + when (in(i).aw.fire()) { latched := Bool(false) } + + // Block W if we do not have an AW destination + in(i).w.valid := io_in(i).w.valid && awIn(i).io.deq.valid // depends on awvalid (but not awready) + io_in(i).w.ready := in(i).w.ready && awIn(i).io.deq.valid + awIn(i).io.deq.ready := io_in(i).w.valid && io_in(i).w.bits.last && in(i).w.ready + } + } + + // Transform output bundles + val out = Wire(Vec(io_out.size, AXI4Bundle(wide_bundle))) + for (i <- 0 until out.size) { + io_out(i) <> out(i) + + if (io_in.size > 1) { + // Block AW if we cannot record the W source + val latched = RegInit(Bool(false)) // cut awOut(i).enq.valid from awready + io_out(i).aw.valid := out(i).aw.valid && (latched || awOut(i).io.enq.ready) + out(i).aw.ready := io_out(i).aw.ready && (latched || awOut(i).io.enq.ready) + awOut(i).io.enq.valid := out(i).aw.valid && !latched + when (awOut(i).io.enq.fire()) { latched := Bool(true) } + when (out(i).aw.fire()) { latched := Bool(false) } + + // Block W if we do not have an AW source + io_out(i).w.valid := out(i).w.valid && awOut(i).io.deq.valid // depends on awvalid (but not awready) + out(i).w.ready := io_out(i).w.ready && awOut(i).io.deq.valid + awOut(i).io.deq.ready := out(i).w.valid && out(i).w.bits.last && io_out(i).w.ready + } + } + + // Fanout the input sources to the output sinks + def transpose[T](x: Seq[Seq[T]]) = Seq.tabulate(x(0).size) { i => Seq.tabulate(x.size) { j => x(j)(i) } } + val portsAROI = transpose((in zip requestARIO) map { case (i, r) => AXI4Xbar.fanout(i.ar, r) }) + val portsAWOI = transpose((in zip requestAWIO) map { case (i, r) => AXI4Xbar.fanout(i.aw, r) }) + val portsWOI = transpose((in zip requestWIO) map { case (i, r) => AXI4Xbar.fanout(i.w, r) }) + val portsRIO = transpose((out zip requestROI) map { case (o, r) => AXI4Xbar.fanout(o.r, r) }) + val portsBIO = transpose((out zip requestBOI) map { case (o, r) => AXI4Xbar.fanout(o.b, r) }) + + // Arbitrate amongst the sources + for (o <- 0 until out.size) { + awOut(o).io.enq.bits := // Record who won AW arbitration to select W + AXI4Arbiter.returnWinner(arbitrationPolicy)(out(o).aw, portsAWOI(o):_*).asUInt + AXI4Arbiter(arbitrationPolicy)(out(o).ar, portsAROI(o):_*) + // W arbitration is informed by the Q, not policy + out(o).w.valid := Mux1H(awOut(o).io.deq.bits, portsWOI(o).map(_.valid)) + out(o).w.bits := Mux1H(awOut(o).io.deq.bits, portsWOI(o).map(_.bits)) + portsWOI(o).zipWithIndex.map { case (p, i) => + if (in.size > 1) { + p.ready := out(o).w.ready && awOut(o).io.deq.bits(i) + } else { + p.ready := out(o).w.ready + } + } + } + + for (i <- 0 until in.size) { + AXI4Arbiter(arbitrationPolicy)(in(i).r, portsRIO(i):_*) + AXI4Arbiter(arbitrationPolicy)(in(i).b, portsBIO(i):_*) + } + } +} + +object AXI4Xbar +{ + def apply( + arbitrationPolicy: TLArbiter.Policy = TLArbiter.roundRobin, + maxFlightPerId: Int = 7, + awQueueDepth: Int = 2)(implicit p: Parameters) = LazyModule(new AXI4Xbar(arbitrationPolicy, maxFlightPerId, awQueueDepth)).node + + def mapInputIds(ports: Seq[AXI4MasterPortParameters]) = TLXbar.assignRanges(ports.map(_.endId)).map(_.get) + + // Replicate an input port to each output port + def fanout[T <: AXI4BundleBase](input: IrrevocableIO[T], select: Seq[Bool]) = { + val filtered = Wire(Vec(select.size, input)) + for (i <- 0 until select.size) { + filtered(i).bits := input.bits + filtered(i).valid := input.valid && select(i) + } + input.ready := Mux1H(select, filtered.map(_.ready)) + filtered + } +} + +object AXI4Arbiter +{ + def apply[T <: Data](policy: TLArbiter.Policy)(sink: IrrevocableIO[T], sources: IrrevocableIO[T]*) { + if (sources.isEmpty) { + sink.valid := Bool(false) + } else { + returnWinner(policy)(sink, sources:_*) + } + } + def returnWinner[T <: Data](policy: TLArbiter.Policy)(sink: IrrevocableIO[T], sources: IrrevocableIO[T]*) = { + require (!sources.isEmpty) + + // The arbiter is irrevocable; when !idle, repeat last request + val idle = RegInit(Bool(true)) + + // Who wants access to the sink? + val valids = sources.map(_.valid) + val anyValid = valids.reduce(_ || _) + // Arbitrate amongst the requests + val readys = Vec(policy(valids.size, Cat(valids.reverse), idle).toBools) + // Which request wins arbitration? + val winner = Vec((readys zip valids) map { case (r,v) => r&&v }) + + // Confirm the policy works properly + require (readys.size == valids.size) + // Never two winners + val prefixOR = winner.scanLeft(Bool(false))(_||_).init + assert((prefixOR zip winner) map { case (p,w) => !p || !w } reduce {_ && _}) + // If there was any request, there is a winner + assert (!anyValid || winner.reduce(_||_)) + + // The one-hot source granted access in the previous cycle + val state = RegInit(Vec.fill(sources.size)(Bool(false))) + val muxState = Mux(idle, winner, state) + state := muxState + + // Determine when we go idle + when (anyValid) { idle := Bool(false) } + when (sink.fire()) { idle := Bool(true) } + + if (sources.size > 1) { + val allowed = Mux(idle, readys, state) + (sources zip allowed) foreach { case (s, r) => + s.ready := sink.ready && r + } + } else { + sources(0).ready := sink.ready + } + + sink.valid := Mux(idle, anyValid, Mux1H(state, valids)) + sink.bits := Mux1H(muxState, sources.map(_.bits)) + muxState + } +} + +class AXI4XbarFuzzTest(name: String, txns: Int, nMasters: Int, nSlaves: Int)(implicit p: Parameters) extends LazyModule +{ + val xbar = AXI4Xbar() + val slaveSize = 0x1000 + val masterBandSize = slaveSize >> log2Ceil(nMasters) + def filter(i: Int) = TLFilter.Mmask(AddressSet(i * masterBandSize, ~BigInt(slaveSize - masterBandSize))) + + val slaves = Seq.tabulate(nSlaves) { i => LazyModule(new AXI4RAM(AddressSet(slaveSize * i, slaveSize-1))) } + slaves.foreach { s => (s.node + := AXI4Fragmenter() + := AXI4Buffer(BufferParams.flow) + := AXI4Buffer(BufferParams.flow) + := AXI4Delayer(0.25) + := xbar) } + + val masters = Seq.fill(nMasters) { LazyModule(new TLFuzzer(txns, 4, nOrdered = Some(1))) } + masters.zipWithIndex.foreach { case (m, i) => (xbar + := AXI4Delayer(0.25) + := AXI4Deinterleaver(4096) + := TLToAXI4() + := TLFilter(filter(i)) + := TLRAMModel(s"${name} Master $i") + := m.node) } + + lazy val module = new LazyModuleImp(this) with UnitTestModule { + io.finished := masters.map(_.module.io.finished).reduce(_ || _) + } +} + +class AXI4XbarTest(txns: Int = 5000, timeout: Int = 500000)(implicit p: Parameters) extends UnitTest(timeout) { + val dut21 = Module(LazyModule(new AXI4XbarFuzzTest("Xbar DUT21", txns, 2, 1)).module) + val dut12 = Module(LazyModule(new AXI4XbarFuzzTest("Xbar DUT12", txns, 1, 2)).module) + val dut22 = Module(LazyModule(new AXI4XbarFuzzTest("Xbar DUT22", txns, 2, 2)).module) + io.finished := Seq(dut21, dut12, dut22).map(_.io.finished).reduce(_ || _) +} From 9004ecdf255e6fd3107405375fd900a4346da079 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Mon, 13 Nov 2017 17:32:30 -0800 Subject: [PATCH 8/9] unittest: include AXI4Xbar in regression --- src/main/scala/unittest/Configs.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/scala/unittest/Configs.scala b/src/main/scala/unittest/Configs.scala index da17facc..4eb75458 100644 --- a/src/main/scala/unittest/Configs.scala +++ b/src/main/scala/unittest/Configs.scala @@ -31,6 +31,7 @@ class WithAMBAUnitTests extends Config((site, here, up) => { Module(new AXI4LiteFuzzRAMTest(txns=6*txns, timeout=timeout)), Module(new AXI4FullFuzzRAMTest(txns=3*txns, timeout=timeout)), Module(new AXI4BridgeTest( txns=3*txns, timeout=timeout)), + Module(new AXI4XbarTest( txns=1*txns, timeout=timeout)), Module(new AXI4RAMAsyncCrossingTest(txns=3*txns, timeout=timeout))) } }) From e370934c50a9c01fa91e300f12471c4a7c886cf8 Mon Sep 17 00:00:00 2001 From: "Wesley W. Terpstra" Date: Tue, 14 Nov 2017 13:23:12 -0800 Subject: [PATCH 9/9] AXI4Xbar: reduce number of special cases --- src/main/scala/amba/axi4/Xbar.scala | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/scala/amba/axi4/Xbar.scala b/src/main/scala/amba/axi4/Xbar.scala index c824c0b0..eab6d3b9 100644 --- a/src/main/scala/amba/axi4/Xbar.scala +++ b/src/main/scala/amba/axi4/Xbar.scala @@ -96,15 +96,8 @@ class AXI4Xbar( for (master <- edgesIn(i).master.masters) { def idTracker(port: UInt, req_fire: Bool, resp_fire: Bool) = { - if (master.maxFlight == Some(1)) { - // No need to worry about response order if at most 1 request possible + if (master.maxFlight == Some(0)) { Bool(true) - } else if (maxFlightPerId == 1) { - // No need to track where it went if we cap it at 1 request - val allow = RegInit(Bool(true)) - when (req_fire) { allow := Bool(false) } - when (resp_fire) { allow := Bool(true) } - allow } else { val legalFlight = master.maxFlight.getOrElse(maxFlightPerId+1) val flight = legalFlight min maxFlightPerId @@ -115,7 +108,9 @@ class AXI4Xbar( assert (!resp_fire || count =/= UInt(0)) assert (!req_fire || count =/= UInt(flight)) when (req_fire) { last := port } - (count === UInt(0) || last === port) && (Bool(!canOverflow) || count =/= UInt(flight)) + // No need to track where it went if we cap it at 1 request + val portMatch = if (flight == 1) { Bool(true) } else { last === port } + (count === UInt(0) || portMatch) && (Bool(!canOverflow) || count =/= UInt(flight)) } }