Commit 962277e9 authored by Jens Korinth's avatar Jens Korinth
Browse files

Implement AxiSlidingWindow module

* configurable sliding window module with AXI DMA backend
* uses AxiFifoAdapter internally to retrieve data from AXI slave
* shifts with Decoupled interface
* generic module for arbitrary bitwidths / data types
parent 9ea813c5
package chisel.axiutils
/**
* Configuration parameters for AXI-MM interfaces (slave, master).
* @param addrWidth Width of address line(s) in bits.
* @param dataWidth Width of data line(s) in bits.
* @param idWidth Width of id line(s) in bits.
**/
case class AxiConfiguration(addrWidth: Int, dataWidth: Int, idWidth: Int) {
require (addrWidth > 0 && addrWidth <= 128, "addrWidth (%d) must be 0 < addrWidth <= 128 bits".format(addrWidth))
require (dataWidth > 0 && dataWidth <= 128, "dataWidth (%d) must be 0 < dataWidth <= 128 bits".format(dataWidth))
require (idWidth > 0 && idWidth <= 128, "idWidth (%d) must be 0 < idWidth <= 128 bits".format(idWidth))
}
......@@ -64,6 +64,25 @@ object AxiModuleBuilder extends ModuleBuilder {
version = "0.1",
root = Paths.get(".").toAbsolutePath.resolve("ip").resolve("AxiFifoAdapter").toString
)
),
( // AXI-based sliding window
() => Module(new AxiSlidingWindow(AxiSlidingWindowConfiguration(
gen = UInt(width = 8),
width = 8,
depth = 3,
afa = AxiFifoAdapterConfiguration(
axi = AxiConfiguration(addrWidth = 32, dataWidth = 64, idWidth = 1),
fifoDepth = 32,
burstSize = Some(16)
)
))),
CoreDefinition(
name = "AxiSlidingWindow3x8",
vendor = "esa.cs.tu-darmstadt.de",
library = "chisel",
version = "0.1",
root = root("AxiSlidingWindow")
)
)
)
}
package chisel.axiutils
import chisel.miscutils.DataWidthConverter
import AXIDefs.AXIMasterIF
import Chisel._
/**
* Configuration parameters for an AxiSlidingWindow.
* @param gen Underlying element type.
* @param width Width of gen type in bits (TODO infer bitwidth of gen)
* @param depth Depth of the sliding window (accessible elements).
* @param axicfg AXI interface configuration.
**/
sealed case class AxiSlidingWindowConfiguration[T](
gen: T,
width: Int,
depth: Int,
afa: AxiFifoAdapterConfiguration
)
/**
* I/O Bundle for an AxiSlidingWindow:
* Consists of base address input, AXI master interface and access
* to the elements of the sliding window.
* @param cfg Configuration parameters.
**/
class AxiSlidingWindowIO[T <: Data](cfg: AxiSlidingWindowConfiguration[T]) extends Bundle {
val base = UInt(INPUT, width = cfg.afa.axi.addrWidth)
val maxi = new AXIMasterIF(cfg.afa.axi.addrWidth, cfg.afa.axi.dataWidth, cfg.afa.axi.idWidth)
val data = Decoupled(Vec.fill(cfg.depth) { cfg.gen.cloneType.asOutput })
def renameSignals() = {
base.setName("BASE")
data.ready.setName("DATA_READY")
data.valid.setName("DATA_VALID")
for (i <- 0 until cfg.depth) data.bits(i).setName("DATA_%02d".format(i))
maxi.renameSignals()
}
}
/**
* AxiSlidingWindow provides a one-dimensional, fixed-size sliding
* window backed by an AXI-MM master DMA engine:
* Starting at a programmable base address, the module will fetch
* data via its AXI master interface and provide a window of
* configurable depth; at each handshake, the window is shifted by
* one element.
* @param cfg Configuration parameters.
**/
class AxiSlidingWindow[T <: Data](val cfg: AxiSlidingWindowConfiguration[T]) extends Module {
val io = new AxiSlidingWindowIO(cfg)
io.renameSignals()
/** AXI DMA engine **/
val afa = Module(AxiFifoAdapter(cfg.afa))
/** insert data width conversion step, if required **/
val data_in = if (cfg.afa.axi.dataWidth == cfg.width) {
afa.io.deq
} else {
val dwc = Module(new DataWidthConverter(cfg.afa.axi.dataWidth, cfg.width, littleEndian = false))
dwc.io.inq <> afa.io.deq
dwc.io.deq
}
/** backing buffer for accessible elements **/
val mem = Reg(Vec.fill(cfg.depth) { cfg.gen.cloneType })
/** States: initial filling of buffer, full **/
val init :: full :: Nil = Enum(UInt(), 2)
/** state register **/
val state = Reg(init = init)
/** fill level **/
val cnt = Reg(UInt(width = log2Up(cfg.depth)))
// input readiness is wired through to data input
data_in.ready := Mux(state === init, !reset, io.data.ready)
io.data.valid := state === full && data_in.valid
for (i <- 0 until cfg.depth)
io.data.bits(i) := mem(i)
// base is wired through
afa.io.base := io.base
// connect AFA M-AXI to outside
io.maxi <> afa.io.maxi
when (!reset) {
// shift data on handshake
when (data_in.ready && data_in.valid) {
mem(0) := data_in.bits
for (i <- 1 until cfg.depth)
mem(i) := mem(i - 1)
when (state === init) {
cnt := cnt + UInt(1)
when (cnt === UInt(cfg.depth - 1)) { state := full }
}
}
}
}
/** AxiSlidingWindow companion object: Factory methods. **/
object AxiSlidingWindow {
def apply[T <: Data](cfg: AxiSlidingWindowConfiguration[T]): AxiSlidingWindow[T] =
new AxiSlidingWindow(cfg)
}
package chisel.axiutils
import Chisel._
import org.scalatest.junit.JUnitSuite
import org.scalatest.Assertions._
import org.junit.Test
/**
* Test module for AxiSlidingWindow:
* Connects AxiSlidingWindow instance to AXI slave model.
**/
class AxiSlidingWindowTestModule[T <: Data](cfg: AxiSlidingWindowConfiguration[T])(implicit axi: AxiConfiguration) extends Module {
val io = new Bundle
/** AXI memory model **/
val saxi = Module(new AxiSlaveModel(AxiSlaveModelConfiguration(
addrWidth = Some(axi.addrWidth),
dataWidth = axi.dataWidth,
idWidth = axi.idWidth,
size = Some(1024)
)))
/** AxiSlidingWindow instance (DUT) **/
val asw = Module(new AxiSlidingWindow(cfg))
asw.io.maxi <> saxi.io.saxi
val ready = Reg(init = Bool(false))
asw.io.data.ready := ready
val base = Reg(init = UInt(0, width = cfg.afa.axi.addrWidth))
asw.io.base := base
}
/**
* Tester class for AxiSlidingWindow:
* Fills memory model with increasing integers of configured element width,
* then checks sliding window against expected values at each step.
* Does not operate at full speed, each step has at least one cycle delay.
**/
class AxiSlidingWindowTester[T <: Data](m: AxiSlidingWindowTestModule[T], isTrace: Boolean = false) extends Tester(m, isTrace) {
// fill memory model
AxiSlaveModel.fillWithLinearSeq(m.saxi, m.asw.cfg.width)(this)
reset(10) // reset
var noErrors = true
var start = 0
val maxData = scala.math.pow(2, m.asw.cfg.width).toInt
val totalSteps = (m.saxi.cfg.size * m.saxi.cfg.dataWidth) / m.asw.cfg.width - m.asw.cfg.depth
printf("mem size = %d bytes, total steps = %d".format(m.saxi.cfg.size, totalSteps))
// wait for data to be valid
while (peek(m.asw.io.data.valid) == 0) step(1)
// check all sliding windows within size of memory slave (no border handling)
for (i <- 0 until totalSteps if noErrors) {
val expected = (0 until m.asw.cfg.depth) map (i => (i + start) % maxData)
val found = (0 until m.asw.cfg.depth) map (i => peek(m.asw.io.data.bits)(i))
noErrors = expected.equals(found)
if (!noErrors)
println("Mismatch at step #%d: expected %s, found %s".format(start, expected.toString, found.toString))
expect(noErrors, "sliding window #%d should match".format(i))
start += 1
// advance simulation with handshake
poke(m.ready, true)
step(1)
poke(m.ready, false)
// wait for next valid
while (peek(m.asw.io.data.valid) == 0) step(1)
}
}
/** Unit test suite for AxiSlidingWindow. **/
class AxiSlidingWindowSuite extends JUnitSuite {
val chiselArgs = Array("--backend", "c", "--genHarness", "--compile", "--test", "--vcd")
implicit val axi: AxiConfiguration = AxiConfiguration(addrWidth = 32, dataWidth = 64, idWidth = 1)
implicit val afa: AxiFifoAdapterConfiguration = AxiFifoAdapterConfiguration(
axi = axi,
fifoDepth = 16
)
private def slidingWindow(width: Int, depth: Int)(implicit afa: AxiFifoAdapterConfiguration) = {
val args = chiselArgs ++ Array("--targetDir", "test/slidingWindow/%dx%d".format(width, depth))
val cfg = AxiSlidingWindowConfiguration(
gen = UInt(width = width),
depth = depth,
width = width,
afa = afa
)
chiselMainTest(args, () => Module(new AxiSlidingWindowTestModule(cfg)))
{ m => new AxiSlidingWindowTester(m, true) }
}
@Test def slidingWindow_8_3 { slidingWindow(8, 3) }
@Test def slidingWindow_16_8 { slidingWindow(16, 3) }
@Test def slidingWindow_32_8 { slidingWindow(32, 3) }
@Test def slidingWindow_8_10 { slidingWindow(8, 10) }
@Test def slidingWindow_16_10 { slidingWindow(16, 10) }
@Test def slidingWindow_32_10 { slidingWindow(32, 10) }
@Test def slidingWindow_8_16 { slidingWindow(8, 16) }
@Test def slidingWindow_16_16 { slidingWindow(16, 16) }
@Test def slidingWindow_32_16 { slidingWindow(32, 16) }
}
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