diff --git a/src/main/scala/uncore/axi4/Bundles.scala b/src/main/scala/uncore/axi4/Bundles.scala index 89fdbc9f..95068cab 100644 --- a/src/main/scala/uncore/axi4/Bundles.scala +++ b/src/main/scala/uncore/axi4/Bundles.scala @@ -20,6 +20,13 @@ abstract class AXI4BundleA(params: AXI4BundleParameters) extends AXI4BundleBase( val prot = UInt(width = params.protBits) val qos = UInt(width = params.qosBits) // 0=no QoS, bigger = higher priority // val region = UInt(width = 4) // optional + + // Number of bytes-1 in this operation + def bytes1(x:Int=0) = { + val maxShift = 1 << params.sizeBits + val tail = UInt((BigInt(1) << maxShift) - 1) + (Cat(len, tail) << size) >> maxShift + } } // A non-standard bundle that can be both AR and AW diff --git a/src/main/scala/uncore/axi4/Fragmenter.scala b/src/main/scala/uncore/axi4/Fragmenter.scala index 57015359..05ca2b0d 100644 --- a/src/main/scala/uncore/axi4/Fragmenter.scala +++ b/src/main/scala/uncore/axi4/Fragmenter.scala @@ -7,7 +7,7 @@ import chisel3.internal.sourceinfo.SourceInfo import chisel3.util.IrrevocableIO import diplomacy._ import scala.math.{min,max} -import uncore.tilelink2.{leftOR, rightOR, UIntToOH1} +import uncore.tilelink2.{leftOR, rightOR, UIntToOH1, OH1ToOH} // lite: masters all use only one ID => reads will not be interleaved class AXI4Fragmenter(lite: Boolean = false, maxInFlight: Int = 32, combinational: Boolean = true) extends LazyModule @@ -100,10 +100,10 @@ class AXI4Fragmenter(lite: Boolean = false, maxInFlight: Int = 32, combinational // The number of beats-1 to execute val beats1 = Mux(bad, UInt(0), maxSupported1) - val beats = ~(~(beats1 << 1 | UInt(1)) | beats1) // beats1 + 1 + val beats = OH1ToOH(beats1) // beats1 + 1 val inc_addr = addr + (beats << a.bits.size) // address after adding transfer - val wrapMask = ~(~a.bits.len << a.bits.size) // only these bits may change, if wrapping + val wrapMask = a.bits.bytes1() // only these bits may change, if wrapping val mux_addr = Wire(init = inc_addr) when (a.bits.burst === AXI4Parameters.BURST_WRAP) { mux_addr := (inc_addr & wrapMask) | ~(~a.bits.addr | wrapMask) diff --git a/src/main/scala/uncore/axi4/Test.scala b/src/main/scala/uncore/axi4/Test.scala index 53620ff3..42ce02bf 100644 --- a/src/main/scala/uncore/axi4/Test.scala +++ b/src/main/scala/uncore/axi4/Test.scala @@ -59,3 +59,53 @@ class AXI4FullFuzzRAMTest extends UnitTest(500000) { val dut = Module(LazyModule(new AXI4FullFuzzRAM).module) io.finished := dut.io.finished } + +class AXI4FuzzMaster extends LazyModule +{ + val node = AXI4OutputNode() + val fuzz = LazyModule(new TLFuzzer(5000)) + val model = LazyModule(new TLRAMModel("AXI4FuzzMaster")) + + model.node := fuzz.node + node := TLToAXI4(4)(model.node) + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val out = node.bundleOut + val finished = Bool(OUTPUT) + } + + io.finished := fuzz.module.io.finished + } +} + +class AXI4FuzzSlave extends LazyModule +{ + val node = AXI4InputNode() + val ram = LazyModule(new TLRAM(AddressSet(0x0, 0xfff))) + + ram.node := TLFragmenter(4, 16)(AXI4ToTL()(AXI4Fragmenter()(node))) + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + } + } +} + +class AXI4FuzzBridge extends LazyModule +{ + val master = LazyModule(new AXI4FuzzMaster) + val slave = LazyModule(new AXI4FuzzSlave) + + slave.node := master.node + + lazy val module = new LazyModuleImp(this) with HasUnitTestIO { + io.finished := master.module.io.finished + } +} + +class AXI4BridgeTest extends UnitTest(500000) { + val dut = Module(LazyModule(new AXI4FuzzBridge).module) + io.finished := dut.io.finished +} diff --git a/src/main/scala/uncore/axi4/ToTL.scala b/src/main/scala/uncore/axi4/ToTL.scala new file mode 100644 index 00000000..ea94da6a --- /dev/null +++ b/src/main/scala/uncore/axi4/ToTL.scala @@ -0,0 +1,184 @@ +// See LICENSE for license details. + +package uncore.axi4 + +import Chisel._ +import chisel3.internal.sourceinfo.SourceInfo +import diplomacy._ +import uncore.tilelink2._ + +case class AXI4ToTLNode() extends MixedNode(AXI4Imp, TLImp)( + dFn = { case (1, Seq(AXI4MasterPortParameters(masters))) => + Seq(TLClientPortParameters(clients = masters.map { m => + TLClientParameters( + sourceId = IdRange(m.id.start << 1, m.id.end << 1), // R+W ids are distinct + nodePath = m.nodePath) + })) + }, + uFn = { case (1, Seq(TLManagerPortParameters(managers, beatBytes, _))) => + Seq(AXI4SlavePortParameters(beatBytes = beatBytes, slaves = managers.map { m => + AXI4SlaveParameters( + address = m.address, + regionType = m.regionType, + executable = m.executable, + nodePath = m.nodePath, + supportsWrite = m.supportsPutPartial, + supportsRead = m.supportsGet, + interleavedId = Some(0)) // TL2 never interleaves D beats + })) + }, + numPO = 1 to 1, + numPI = 1 to 1) + +class AXI4ToTL extends LazyModule +{ + val node = AXI4ToTLNode() + + lazy val module = new LazyModuleImp(this) { + val io = new Bundle { + val in = node.bundleIn + val out = node.bundleOut + } + + val in = io.in(0) + val out = io.out(0) + val edgeIn = node.edgesIn(0) + val edgeOut = node.edgesOut(0) + val numIds = edgeIn.master.endId + val beatBytes = edgeOut.manager.beatBytes + val countBits = AXI4Parameters.lenBits + (1 << AXI4Parameters.sizeBits) - 1 + + require (edgeIn.master.masters(0).aligned) + + val r_out = Wire(out.a) + val r_inflight = RegInit(UInt(0, width = numIds)) + val r_block = r_inflight(in.ar.bits.id) + val r_size1 = in.ar.bits.bytes1() + val r_size = OH1ToUInt(r_size1) + val r_addr = in.ar.bits.addr + val r_ok = edgeOut.manager.supportsGetSafe(r_addr, r_size) + val r_err_in = Wire(Decoupled(new AXI4BundleRError(in.ar.bits.params))) + val r_err_out = Queue(r_err_in, 2) + val r_count = RegInit(UInt(0, width = in.ar.bits.params.lenBits)) + val r_last = r_count === in.ar.bits.len + + assert (!in.ar.valid || r_size1 === UIntToOH1(r_size, countBits)) // because aligned + in.ar.ready := Mux(r_ok, r_out.ready, r_err_in.ready && r_last) && !r_block + r_out.valid := in.ar.valid && !r_block && r_ok + r_out.bits := edgeOut.Get(in.ar.bits.id << 1 | UInt(1), r_addr, r_size)._2 + r_err_in.valid := in.ar.valid && !r_block && !r_ok + r_err_in.bits.last := r_last + r_err_in.bits.id := in.ar.bits.id + + when (r_err_in.fire()) { r_count := Mux(r_last, UInt(0), r_count + UInt(1)) } + + val w_out = Wire(out.a) + val w_inflight = RegInit(UInt(0, width = numIds)) + val w_block = w_inflight(in.aw.bits.id) + val w_size1 = in.aw.bits.bytes1() + val w_size = OH1ToUInt(w_size1) + val w_addr = in.aw.bits.addr + val w_ok = edgeOut.manager.supportsPutPartialSafe(w_addr, w_size) + val w_err_in = Wire(Decoupled(in.aw.bits.id)) + val w_err_out = Queue(w_err_in, 2) + + assert (!in.aw.valid || w_size1 === UIntToOH1(w_size, countBits)) // because aligned + assert (!in.aw.valid || in.aw.bits.len === UInt(0) || in.aw.bits.size === UInt(log2Ceil(beatBytes))) // because aligned + in.aw.ready := Mux(w_ok, w_out.ready, w_err_in.ready) && in.w.valid && in.w.bits.last && !w_block + in.w.ready := Mux(w_ok, w_out.ready, w_err_in.ready || !in.w.bits.last) && in.aw.valid && !w_block + w_out.valid := in.aw.valid && in.w.valid && !w_block && w_ok + w_out.bits := edgeOut.Put(in.aw.bits.id << 1, w_addr, w_size, in.w.bits.data, in.w.bits.strb)._2 + w_err_in.valid := in.aw.valid && in.w.valid && !w_block && !w_ok && in.w.bits.last + w_err_in.bits := in.aw.bits.id + + TLArbiter(TLArbiter.lowestIndexFirst)(out.a, (UInt(0), r_out), (in.aw.bits.len, w_out)) + + val ok_b = Wire(in.b) + val err_b = Wire(in.b) + val mux_b = Wire(in.b) + val ok_r = Wire(in.r) + val err_r = Wire(in.r) + val mux_r = Wire(in.r) + + val d_resp = Mux(out.d.bits.error, AXI4Parameters.RESP_SLVERR, AXI4Parameters.RESP_OKAY) + val d_hasData = edgeOut.hasData(out.d.bits) + val (_, d_last, _) = edgeOut.firstlast(out.d.bits, out.d.fire()) + + out.d.ready := Mux(d_hasData, ok_r.ready, ok_b.ready) + ok_r.valid := out.d.valid && d_hasData + ok_b.valid := out.d.valid && !d_hasData + + ok_r.bits.id := out.d.bits.source >> 1 + ok_r.bits.data := out.d.bits.data + ok_r.bits.resp := d_resp + ok_r.bits.last := d_last + + r_err_out.ready := err_r.ready + err_r.valid := r_err_out.valid + err_r.bits.id := r_err_out.bits.id + err_r.bits.data := out.d.bits.data // don't care + err_r.bits.resp := AXI4Parameters.RESP_DECERR + err_r.bits.last := r_err_out.bits.last + + // AXI4 must hold R to one source until last + val mux_lock_ok = RegInit(Bool(false)) + val mux_lock_err = RegInit(Bool(false)) + when (ok_r .fire()) { mux_lock_ok := !ok_r .bits.last } + when (err_r.fire()) { mux_lock_err := !err_r.bits.last } + assert (!mux_lock_ok || !mux_lock_err) + + // Prioritize err over ok (b/c err_r.valid comes from a register) + mux_r.valid := (!mux_lock_err && ok_r.valid) || (!mux_lock_ok && err_r.valid) + mux_r.bits := Mux(!mux_lock_ok && err_r.valid, err_r.bits, ok_r.bits) + ok_r.ready := !mux_lock_err && mux_r.ready && !err_r.valid + err_r.ready := !mux_lock_ok && mux_r.ready + + // AXI4 needs irrevocable behaviour + in.r <> Queue.irrevocable(mux_r, 1, flow=true) + + ok_b.bits.id := out.d.bits.source >> 1 + ok_b.bits.resp := d_resp + + w_err_out.ready := err_b.ready + err_b.valid := w_err_out.valid + err_b.bits.id := w_err_out.bits + err_b.bits.resp := AXI4Parameters.RESP_DECERR + + // Prioritize err over ok (b/c err_b.valid comes from a register) + mux_b.valid := ok_b.valid || err_b.valid + mux_b.bits := Mux(err_b.valid, err_b.bits, ok_b.bits) + ok_b.ready := mux_b.ready && !err_b.valid + err_b.ready := mux_b.ready + + // AXI4 needs irrevocable behaviour + in.b <> Queue.irrevocable(mux_b, 1, flow=true) + + // Update flight trackers + val r_set = in.ar.fire().asUInt << in.ar.bits.id + val r_clr = (in.r.fire() && in.r.bits.last).asUInt << in.r.bits.id + r_inflight := (r_inflight | r_set) & ~r_clr + val w_set = in.aw.fire().asUInt << in.aw.bits.id + val w_clr = in.b.fire().asUInt << in.b.bits.id + w_inflight := (w_inflight | w_set) & ~w_clr + + // Unused channels + out.b.ready := Bool(true) + out.c.valid := Bool(false) + out.e.valid := Bool(false) + } +} + +class AXI4BundleRError(params: AXI4BundleParameters) extends AXI4BundleBase(params) +{ + val id = UInt(width = params.idBits) + val last = Bool() +} + +object AXI4ToTL +{ + def apply()(x: AXI4OutwardNode)(implicit sourceInfo: SourceInfo): TLOutwardNode = { + val tl = LazyModule(new AXI4ToTL) + tl.node := x + tl.node + } +} diff --git a/src/main/scala/uncore/tilelink2/package.scala b/src/main/scala/uncore/tilelink2/package.scala index 415aa308..55ff6b27 100644 --- a/src/main/scala/uncore/tilelink2/package.scala +++ b/src/main/scala/uncore/tilelink2/package.scala @@ -8,7 +8,8 @@ package object tilelink2 type TLOutwardNode = OutwardNodeHandle[TLClientPortParameters, TLManagerPortParameters, TLBundle] type TLAsyncOutwardNode = OutwardNodeHandle[TLAsyncClientPortParameters, TLAsyncManagerPortParameters, TLAsyncBundle] type IntOutwardNode = OutwardNodeHandle[IntSourcePortParameters, IntSinkPortParameters, Vec[Bool]] - def OH1ToUInt(x: UInt) = OHToUInt((x << 1 | UInt(1)) ^ x) + def OH1ToOH(x: UInt) = (x << 1 | UInt(1)) & ~Cat(UInt(0, width=1), x) + def OH1ToUInt(x: UInt) = OHToUInt(OH1ToOH(x)) def UIntToOH1(x: UInt, width: Int) = ~(SInt(-1, width=width).asUInt << x)(width-1, 0) def trailingZeros(x: Int) = if (x > 0) Some(log2Ceil(x & -x)) else None // Fill 1s from low bits to high bits diff --git a/src/main/scala/unittest/Configs.scala b/src/main/scala/unittest/Configs.scala index ac0488af..054e47fb 100644 --- a/src/main/scala/unittest/Configs.scala +++ b/src/main/scala/unittest/Configs.scala @@ -27,7 +27,8 @@ class WithUncoreUnitTests extends Config( Module(new uncore.devices.TileLinkRAMTest()(p)), Module(new uncore.tilelink2.TLFuzzRAMTest), Module(new uncore.axi4.AXI4LiteFuzzRAMTest), - Module(new uncore.axi4.AXI4FullFuzzRAMTest)) + Module(new uncore.axi4.AXI4FullFuzzRAMTest), + Module(new uncore.axi4.AXI4BridgeTest)) case _ => throw new CDEMatchError } )