Skip to content

Instantly share code, notes, and snippets.

@xiangze
Created December 29, 2025 01:48
Show Gist options
  • Select an option

  • Save xiangze/cf7d8cecd6d4ff0dadb548128d3d9028 to your computer and use it in GitHub Desktop.

Select an option

Save xiangze/cf7d8cecd6d4ff0dadb548128d3d9028 to your computer and use it in GitHub Desktop.
AXI_memory_outstanding2_skidbuffer
// 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