Created
December 29, 2025 01:48
-
-
Save xiangze/cf7d8cecd6d4ff0dadb548128d3d9028 to your computer and use it in GitHub Desktop.
AXI_memory_outstanding2_skidbuffer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: Apache-2.0 | |
| // Requires: chisel3, chisel3.util | |
| import chisel3._ | |
| import chisel3.util._ | |
| // ============================================================ | |
| // 1) AXI4 (minimal) channel bundles | |
| // ============================================================ | |
| class Axi4Aw(val addrW: Int, val idW: Int) extends Bundle { | |
| val id = UInt(idW.W) | |
| val addr = UInt(addrW.W) | |
| val len = UInt(8.W) | |
| val size = UInt(3.W) | |
| val burst = UInt(2.W) | |
| } | |
| class Axi4W(val dataW: Int) extends Bundle { | |
| val data = UInt(dataW.W) | |
| val strb = UInt((dataW / 8).W) | |
| val last = Bool() | |
| } | |
| class Axi4B(val idW: Int) extends Bundle { | |
| val id = UInt(idW.W) | |
| val resp = UInt(2.W) | |
| } | |
| class Axi4Ar(val addrW: Int, val idW: Int) extends Bundle { | |
| val id = UInt(idW.W) | |
| val addr = UInt(addrW.W) | |
| val len = UInt(8.W) | |
| val size = UInt(3.W) | |
| val burst = UInt(2.W) | |
| } | |
| class Axi4R(val dataW: Int, val idW: Int) extends Bundle { | |
| val id = UInt(idW.W) | |
| val data = UInt(dataW.W) | |
| val resp = UInt(2.W) | |
| val last = Bool() | |
| } | |
| class Axi4Liteish(val addrW: Int, val dataW: Int, val idW: Int) extends Bundle { | |
| val aw = Decoupled(new Axi4Aw(addrW, idW)) | |
| val w = Decoupled(new Axi4W(dataW)) | |
| val b = Flipped(Decoupled(new Axi4B(idW))) | |
| val ar = Decoupled(new Axi4Ar(addrW, idW)) | |
| val r = Flipped(Decoupled(new Axi4R(dataW, idW))) | |
| } | |
| // ============================================================ | |
| // 2) Skid Buffer (1-element hold), Decoupled -> Decoupled | |
| // Semantics: | |
| // - If downstream not ready when upstream fires, hold item. | |
| // - While holding, stall upstream. | |
| // ============================================================ | |
| class SkidBuffer[T <: Data](gen: T) extends Module { | |
| val io = IO(new Bundle { | |
| val in = Flipped(Decoupled(gen)) | |
| val out = Decoupled(gen) }) | |
| val holdValid = RegInit(false.B) | |
| val holdData = Reg(gen) | |
| // Output mux: hold has priority | |
| io.out.bits := Mux(holdValid, holdData, io.in.bits) | |
| io.out.valid := Mux(holdValid, true.B, io.in.valid) | |
| // When holding, upstream is stalled; else upstream follows downstream ready | |
| io.in.ready := Mux(holdValid, false.B, io.out.ready) | |
| // Capture into hold when not holding, input valid, but downstream not ready | |
| when(!holdValid && io.in.valid && !io.out.ready) { | |
| holdValid := true.B | |
| holdData := io.in.bits | |
| }.elsewhen(holdValid && io.out.ready) { | |
| holdValid := false.B | |
| } | |
| // ============================================================ | |
| // 3) AXI Master: accepts (a,b) stream, writes (a+b) to memory | |
| // - outstanding max = 2 | |
| // - AW and W issued together (single-beat) for simplicity | |
| // - skid buffers on AW and W | |
| // ============================================================ | |
| class AxiAddMasterOut2( | |
| addrW: Int = 32, | |
| dataW: Int = 32, | |
| idW: Int = 1, | |
| baseAddr: BigInt = 0x00000000L, | |
| strideBytes: Int = 4 | |
| ) extends Module { | |
| require(dataW % 8 == 0) | |
| require(idW == 1, "This demo assumes outstanding=2 using 1-bit ID {0,1}") | |
| val io = IO(new Bundle { | |
| val inA = Input(UInt(dataW.W)) | |
| val inB = Input(UInt(dataW.W)) | |
| val inValid = Input(Bool()) | |
| val inReady = Output(Bool()) | |
| val axi = new Axi4Liteish(addrW, dataW, idW) | |
| }) | |
| // Disable master reads (not used) | |
| io.axi.ar.valid := false.B | |
| io.axi.ar.bits := 0.U.asTypeOf(io.axi.ar.bits) | |
| io.axi.r.ready := true.B | |
| // Always accept B | |
| io.axi.b.ready := true.B | |
| val bHs = io.axi.b.valid && io.axi.b.ready | |
| // Outstanding counter 0..2 | |
| val outCnt = RegInit(0.U(2.W)) | |
| val idNext = RegInit(0.U(1.W)) | |
| val wrAddr = RegInit(baseAddr.U(addrW.W)) | |
| // Build AW/W "source side" as Decoupled | |
| val awSrc = Wire(Decoupled(new Axi4Aw(addrW, idW))) | |
| val wSrc = Wire(Decoupled(new Axi4W(dataW))) | |
| // Skid buffers | |
| val skidAw = Module(new SkidBuffer(new Axi4Aw(addrW, idW))) | |
| val skidW = Module(new SkidBuffer(new Axi4W(dataW))) | |
| skidAw.io.in <> awSrc | |
| skidW.io.in <> wSrc | |
| // Connect skid outputs to AXI | |
| io.axi.aw <> skidAw.io.out | |
| io.axi.w <> skidW.io.out | |
| val awHs = io.axi.aw.valid && io.axi.aw.ready | |
| val wHs = io.axi.w.valid && io.axi.w.ready | |
| val issueHs = awHs && wHs | |
| // Can issue when outstanding < 2 and both skid inputs ready | |
| val canIssue = (outCnt < 2.U) && awSrc.ready && wSrc.ready | |
| io.inReady := canIssue | |
| val fireIn = io.inValid && io.inReady | |
| // Prepare AW/W | |
| awSrc.valid := fireIn | |
| awSrc.bits.id := idNext | |
| awSrc.bits.addr := wrAddr | |
| awSrc.bits.len := 0.U | |
| awSrc.bits.size := log2Ceil(dataW/8).U | |
| awSrc.bits.burst := "b01".U // INCR | |
| wSrc.valid := fireIn | |
| wSrc.bits.data := io.inA + io.inB | |
| wSrc.bits.strb := Fill(dataW/8, 1.U(1.W)) | |
| wSrc.bits.last := true.B | |
| // Update state: handle b and issue (can occur same cycle) | |
| when(bHs) { | |
| when(outCnt =/= 0.U) { outCnt := outCnt - 1.U } | |
| } | |
| when(issueHs) { | |
| outCnt := outCnt + 1.U | |
| idNext := ~idNext | |
| wrAddr := wrAddr + strideBytes.U | |
| } | |
| } | |
| // ============================================================ | |
| // 4) AXI Slave Memory: accepts single-beat AW/W and returns B | |
| // Also supports single-beat reads for debug (AR/R) | |
| // ============================================================ | |
| class AxiMemSlave( | |
| addrW: Int = 32, | |
| dataW: Int = 32, | |
| idW: Int = 1, | |
| depthWords: Int = 1024, | |
| baseAddr: BigInt = 0x00000000L | |
| ) extends Module { | |
| require(dataW % 8 == 0) | |
| val io = IO(new Bundle { | |
| val axi = Flipped(new Axi4Liteish(addrW, dataW, idW)) | |
| }) | |
| val strbW = dataW / 8 | |
| val addrLsb = log2Ceil(strbW) | |
| val mem = SyncReadMem(depthWords, UInt(dataW.W)) | |
| // ---------------------------- | |
| // Write path | |
| // ---------------------------- | |
| // Always ready (simple) | |
| io.axi.aw.ready := true.B | |
| io.axi.w.ready := true.B | |
| val awAccepted = RegInit(false.B) | |
| val wAccepted = RegInit(false.B) | |
| val awidQ = Reg(UInt(idW.W)) | |
| val awaddrQ = Reg(UInt(addrW.W)) | |
| val awHs = io.axi.aw.valid && io.axi.aw.ready | |
| val wHs = io.axi.w.valid && io.axi.w.ready | |
| when(awHs) { | |
| awAccepted := true.B | |
| awidQ := io.axi.aw.bits.id | |
| awaddrQ := io.axi.aw.bits.addr | |
| } | |
| when(wHs) { | |
| wAccepted := true.B | |
| } | |
| // B channel | |
| val bValid = RegInit(false.B) | |
| val bBits = Reg(new Axi4B(idW)) | |
| io.axi.b.valid := bValid | |
| io.axi.b.bits := bBits | |
| when(bValid && io.axi.b.ready) { | |
| bValid := false.B | |
| } | |
| // When both AW and W are accepted and B is free, perform write and respond. | |
| when(awAccepted && wAccepted && !bValid) { | |
| val idx = ((awaddrQ - baseAddr.U) >> addrLsb.U) | |
| val inRange = idx < depthWords.U | |
| val old = Wire(UInt(dataW.W)) | |
| old := 0.U // unused in this simplistic masked-write; kept for clarity | |
| when(inRange) { | |
| // Masked byte write: read-modify-write (1-cycle latency with SyncReadMem is tricky). | |
| // For demo: assume full strobe (all 1) as master sends; support partial via a simple fallback. | |
| // If you need true byte-mask on SyncReadMem, implement mem as Vec(Reg) or use Mem + combinational read. | |
| when(io.axi.w.bits.strb.andR) { | |
| mem.write(idx, io.axi.w.bits.data) | |
| bBits.resp := "b00".U // OKAY | |
| }.otherwise { | |
| // Partial strobe: use a Reg-based memory for strict correctness (see note below). | |
| // Here we still write whole word for simplicity. | |
| mem.write(idx, io.axi.w.bits.data) | |
| bBits.resp := "b00".U | |
| } | |
| }.otherwise { | |
| bBits.resp := "b10".U // SLVERR | |
| } | |
| bBits.id := awidQ | |
| bValid := true.B | |
| awAccepted := false.B | |
| wAccepted := false.B | |
| } | |
| // ---------------------------- | |
| // Read path (single beat) | |
| // ---------------------------- | |
| io.axi.ar.ready := true.B | |
| val arHs = io.axi.ar.valid && io.axi.ar.ready | |
| val rValid = RegInit(false.B) | |
| val rBits = Reg(new Axi4R(dataW, idW)) | |
| io.axi.r.valid := rValid | |
| io.axi.r.bits := rBits | |
| when(rValid && io.axi.r.ready) { | |
| rValid := false.B | |
| } | |
| // SyncReadMem read latency: data appears next cycle. | |
| val rdIdxReg = Reg(UInt(addrW.W)) | |
| val rdIdReg = Reg(UInt(idW.W)) | |
| val rdPending = RegInit(false.B) | |
| when(arHs && !rValid && !rdPending) { | |
| rdIdxReg := (io.axi.ar.bits.addr - baseAddr.U) >> addrLsb.U | |
| rdIdReg := io.axi.ar.bits.id | |
| rdPending := true.B | |
| } | |
| val rdData = mem.read(rdIdxReg, rdPending) // valid when rdPending high (next cycle) | |
| when(rdPending) { | |
| val inRange = rdIdxReg < depthWords.U | |
| rBits.id := rdIdReg | |
| rBits.data := Mux(inRange, rdData, 0.U) | |
| rBits.resp := Mux(inRange, "b00".U, "b10".U) | |
| rBits.last := true.B | |
| rValid := true.B | |
| rdPending := false.B | |
| } | |
| } | |
| // ============================================================ | |
| // 5) Simple top wiring example | |
| // ============================================================ | |
| class TopDemo extends Module { | |
| val io = IO(new Bundle { | |
| val inA = Input(UInt(32.W)) | |
| val inB = Input(UInt(32.W)) | |
| val inValid = Input(Bool()) | |
| val inReady = Output(Bool()) | |
| }) | |
| val axi = Wire(new Axi4Liteish(32, 32, 1)) | |
| val m = Module(new AxiAddMasterOut2()) | |
| val s = Module(new AxiMemSlave()) | |
| m.io.inA := io.inA | |
| m.io.inB := io.inB | |
| m.io.inValid := io.inValid | |
| io.inReady := m.io.inReady | |
| m.io.axi <> axi | |
| s.io.axi <> axi | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment