Commit 9684a9c2 authored by Jens Korinth's avatar Jens Korinth
Browse files

AxiFifoAdapter: implement AXI mem to FIFO adapter

* tick-tock buffer approach: one buffer is filled via AXI4 read
  bursts, the other is made available for dequeuing
* can provide continuous data at full speed, given that the bursts
  finish fast enough
* also added some unit tests for fixed blocks of memory
* missing: support for wrap-around (currently via reset)
parent 90c05e9b
import Chisel._
import AXIDefs._
class AxiFifoAdapter(
fifoDepth: Int,
addrWidth: Int,
dataWidth: Int,
idWidth : Int = 1
) extends Module {
require (fifoDepth > 0, "FIFO depth (%d) must be > 0".format(fifoDepth))
require (fifoDepth <= 256, "FIFO depth (%d) must be <= 256".format(fifoDepth))
require (addrWidth > 0 && addrWidth <= 64, "AXI address width (%d) must 0 < width <= 64".format(addrWidth))
require (dataWidth >= 8 && dataWidth <= 256, "AXI data width (%d) must be 8 <= width <= 256".format(dataWidth))
require ((for (i <- 1 to 7) yield scala.math.pow(2, i).toInt).contains(dataWidth), "AXI data width (%d) must be a power of 2".format(dataWidth))
require ((idWidth > 0), "AXI id width (%d) must be > 0".format(idWidth))
val io = new Bundle {
val maxi = new AXIMasterIF(addrWidthBits = addrWidth, dataWidthBits = dataWidth, idBits = idWidth)
val deq = Decoupled(UInt(width = dataWidth))
val base = UInt(INPUT, width = addrWidth)
maxi.renameSignals()
base.setName("base")
}
val axi_fetch :: axi_wait :: Nil = Enum(UInt(), 2)
val axi_state = Reg(init = axi_wait)
val fifo_a = Module(new Queue(UInt(width = dataWidth), fifoDepth))
val fifo_b = Module(new Queue(UInt(width = dataWidth), fifoDepth))
val fifo_sel = Reg(Bool())
io.deq.valid := Mux(!fifo_sel, fifo_a.io.deq.valid, fifo_b.io.deq.valid)
io.deq.bits := Mux(!fifo_sel, fifo_a.io.deq.bits, fifo_b.io.deq.bits)
fifo_a.io.deq.ready := !fifo_sel && io.deq.ready
fifo_b.io.deq.ready := fifo_sel && io.deq.ready
val maxi_rdata = (io.maxi.readData.bits.data)
val maxi_rlast = (io.maxi.readData.bits.last)
val maxi_rvalid = (io.maxi.readData.valid)
val maxi_rready = (Mux(fifo_sel, fifo_a.io.enq.ready, fifo_b.io.enq.ready))
fifo_a.io.enq.bits := (maxi_rdata)
fifo_a.io.enq.valid := fifo_sel && maxi_rready && maxi_rvalid
fifo_b.io.enq.bits := (maxi_rdata)
fifo_b.io.enq.valid := !fifo_sel && maxi_rready && maxi_rvalid
val maxi_raddr = Reg(init = io.base)
val maxi_raddr_hs = Reg(Bool())
val maxi_ravalid = axi_state === axi_fetch && !maxi_raddr_hs
val maxi_raready = (io.maxi.readAddr.ready)
io.maxi.readAddr.bits.addr := maxi_raddr
io.maxi.readAddr.valid := maxi_ravalid
io.maxi.readAddr.bits.size := UInt(log2Up(dataWidth / 8 - 1))
io.maxi.readAddr.bits.len := UInt(fifoDepth)
io.maxi.readAddr.bits.burst:= UInt("b01") // INCR
io.maxi.readAddr.bits.id := UInt(0)
io.maxi.readAddr.bits.lock := UInt(0)
io.maxi.readAddr.bits.cache:= UInt("b1110") // write-through, RW alloc
io.maxi.readAddr.bits.prot := UInt(0)
io.maxi.readAddr.bits.qos := UInt(0)
io.maxi.readData.ready := maxi_rready
when (reset) {
fifo_sel := Bool(false)
axi_state := axi_wait
maxi_raddr := UInt(0)
maxi_raddr_hs := Bool(false)
}
.otherwise {
when (axi_state === axi_fetch) { // fetch data state
// handshake current address
when (maxi_raready && maxi_ravalid) {
maxi_raddr_hs := Bool(true)
maxi_raddr := maxi_raddr + UInt((dataWidth * fifoDepth) / 8)
}
// when read data is valid, enq fifo is ready and last is set
when (maxi_rready && maxi_rvalid && maxi_rlast) {
// go to wait state
axi_state := axi_wait
maxi_raddr_hs := Bool(false)
}
}
.otherwise { // wait-for-consumption state
// check fill state of deq FIFO: if empty, flip FIFOs
when (Mux(!fifo_sel, !fifo_a.io.deq.valid, !fifo_b.io.deq.valid)) {
fifo_sel := !fifo_sel
}
// check fill state of other FIFO
when (Mux( fifo_sel, !fifo_a.io.deq.valid, !fifo_b.io.deq.valid)) {
// if empty, start fetch
axi_state := axi_fetch
}
}
}
}
import Chisel._
import org.scalatest.junit.JUnitSuite
import org.junit.Test
import org.junit.Assert._
import java.nio.file.Paths
class AxiFifoAdapterModule1(
val dataWidth: Int,
val fifoDepth: Int,
val blockSize: Int
) extends Module {
val addrWidth = log2Up(dataWidth * fifoDepth * blockSize / 8)
val io = new Bundle
val afa = Module (new AxiFifoAdapter(addrWidth = addrWidth,
dataWidth = dataWidth, idWidth = 1, fifoDepth = fifoDepth))
val saxi = Module (new AxiSlaveModel(addrWidth = addrWidth,
dataWidth = dataWidth, idWidth = 1))
val dqr = Reg(init = Bool(true))
afa.io.base := UInt(0)
//afa.io.deq.ready := Bool(true)
afa.io.deq.ready := dqr
afa.io.maxi <> saxi.io.saxi
}
class AxiFifoAdapterModule1Test(m: AxiFifoAdapterModule1) extends Tester(m, false) {
import scala.util.Properties.{lineSeparator => NL}
private var _cc = 0
// re-define step to output progress info
override def step(n: Int) {
super.step(n)
_cc += n
if (cc % 1000 == 0) println("clock cycle: " + _cc)
}
// setup data
println("prepping %d (%d x %d) mem elements ...".format(m.fifoDepth * m.blockSize, m.fifoDepth, m.blockSize))
for (i <- 0 until m.fifoDepth * m.blockSize)
pokeAt(m.saxi.mem, i % scala.math.pow(2, m.dataWidth).toInt, i)
var res: List[BigInt] = List()
var cc: Int = m.fifoDepth * m.blockSize * 10 // upper bound on cycles
reset(10)
poke(m.dqr, true)
while (cc > 0 && res.length < m.fifoDepth * m.blockSize) {
if (peek(m.afa.io.deq.valid) != 0) {
val v = peek(m.afa.io.deq.bits)
res ++= List(v)
poke(m.dqr, false)
step(res.length % 20)
poke(m.dqr, true)
}
step(1)
cc -= 1
}
step(10) // settle
res.zipWithIndex map (_ match { case (v, i) =>
println("#%d: 0x%x (0b%s)".format(i, v, v.toString(2)))
})
val errors = (for (i <- 0 until res.length if res(i) != peekAt(m.saxi.mem, i))
yield "Mem[%03d] = %d (expected %d)".format(i, res(i), peekAt(m.saxi.mem, i))).toList
assertTrue (("mem does not match, errors: " :: errors).mkString(NL), errors.length == 0)
}
class AxiFifoAdapterSuite extends JUnitSuite {
def runTest(dataWidth: Int, fifoDepth: Int, blockSize: Int) {
val dir = Paths.get("test").resolve("dw%d_fd%d_bs%d".format(dataWidth, fifoDepth, blockSize)).toString
chiselMainTest(Array("--genHarness", "--backend", "c", "--vcd", "--targetDir", dir, "--compile", "--test"),
() => Module(new AxiFifoAdapterModule1(dataWidth = dataWidth, fifoDepth = fifoDepth, blockSize = blockSize))) { m => new AxiFifoAdapterModule1Test(m) }
}
@Test def checkDw32Fd1Bs256 { runTest(dataWidth = 32, fifoDepth = 1, blockSize = 256/1) }
@Test def checkDw32Fd8Bs32 { runTest(dataWidth = 32, fifoDepth = 8, blockSize = 256/8) }
@Test def checkDw8Fd8Bs32 { runTest(dataWidth = 8, fifoDepth = 8, blockSize = 256/8) }
@Test def checkDw8Fd2Bs128 { runTest(dataWidth = 8, fifoDepth = 2, blockSize = 256/2) }
@Test def checkDw64Fd16Bs512 { runTest(dataWidth = 64, fifoDepth = 16, blockSize = 512) }
@Test def checkDw128Fd128Bs1024 { runTest(dataWidth = 128, fifoDepth = 128, blockSize = 1024/128) }
// FIXME seems to work, but too slow
// @Test def checkDw8Fd1080Bs480 { runTest(dataWidth = 8, fifoDepth = 256, blockSize = 480*4) }
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment