Commit 1846cfbe authored by Jens Korinth's avatar Jens Korinth
Browse files

Add 'axi/' from commit 'b8f4c554'

git-subtree-dir: axi
git-subtree-mainline: bea8ec99
git-subtree-split: b8f4c554
parents bea8ec99 b8f4c554
*.log
*.jou
.Xil
/target/
/project/
/test/
/ip/
/packaging/target/
/miscutils/target/
name := "chisel-axiutils"
organization := "esa.cs.tu-darmstadt.de"
version := "0.4-SNAPSHOT"
scalaVersion := "2.11.11"
resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots"),
Resolver.sonatypeRepo("releases")
)
// Provide a managed dependency on X if -DXVersion="" is supplied on the command line.
val defaultVersions = Map("chisel3" -> "3.1-SNAPSHOT",
"chisel-iotesters" -> "1.2-SNAPSHOT")
libraryDependencies ++= (Seq("chisel3","chisel-iotesters").map {
dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) })
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.0.4" % "test",
"org.scalacheck" %% "scalacheck" % "1.13.5" % "test",
"com.typesafe.play" %% "play-json" % "2.6.3",
"org.scalactic" %% "scalactic" % "3.0.4"
)
scalacOptions ++= Seq("-language:implicitConversions", "-language:reflectiveCalls", "-deprecation", "-feature")
// project structure
lazy val packaging = project.in(file("packaging"))
lazy val miscutils = project.in(file("miscutils"))
lazy val axiutils = (project in file(".")).dependsOn(packaging, miscutils, miscutils % "test->test").aggregate(packaging, miscutils)
cleanFiles += (baseDirectory.value / "test")
aggregate in test := false
*.log
*.jou
.Xil
/target/
/project/
/test/
/ip/
# Miscellaneous Chisel IP
Some very basic IP for everyday use, written in Chisel 3.0. Currently contains:
* [DataWidthConverter][1]
Converts `UInt` of arbitrary bit width to `UInt` of different bit width.
Zero delay, handshaked channels using Chisels `Decoupled` interface.
* [DecoupledDataSource][2]
Generic data provider with fixed (compile-time) data; uses handshakes via
Chisels `Decoupled` interface.
* [SignalGenerator][3]
Primitive 1-bit signal generator: Specify via change list, can cycle.
These were basically warm-up exercises with Chisel, but can be useful now and
then. For usage examples see the [unit test suites][4].
[1]: src/main/scala/DataWidthConverter.scala
[2]: src/main/scala/DecoupledDataSource.scala
[3]: src/main/scala/SignalGenerator.scala
[4]: src/test/scala/
name := "chisel-miscutils"
organization := "esa.cs.tu-darmstadt.de"
version := "0.4-SNAPSHOT"
scalaVersion := "2.11.11"
resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots"),
Resolver.sonatypeRepo("releases")
)
// Provide a managed dependency on X if -DXVersion="" is supplied on the command line.
val defaultVersions = Map("chisel3" -> "3.0-SNAPSHOT",
"chisel-iotesters" -> "1.1-SNAPSHOT")
libraryDependencies ++= (Seq("chisel3","chisel-iotesters").map {
dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) })
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.0.4" % "test",
"org.scalacheck" %% "scalacheck" % "1.13.5" % "test",
"com.typesafe.play" %% "play-json" % "2.6.3"
)
// no parallel tests
parallelExecution in Test := false
testForkedParallel in Test := false
scalacOptions ++= Seq("-language:implicitConversions", "-language:reflectiveCalls", "-deprecation", "-feature")
cleanFiles += baseDirectory.value / "test"
package chisel.miscutils
import chisel3._
import chisel3.util._
object DataWidthConverter {
class IO(inWidth: Int, outWidth: Int) extends Bundle {
val inq = Flipped(Decoupled(UInt(inWidth.W)))
val deq = Decoupled(UInt(outWidth.W))
}
}
/**
* DataWidthConverter converts the data width of a Queue.
* Output is provided via a Queue, with increased or decreased
* data rate, depending on the direction of the conversion.
* Note: This would be much more useful, if the two Queues
* could use different clocks, but multi-clock support
* in Chisel is currently unstable.
* @param inWidth Data width of input DecoupledIO (bits).
* @param outWidth Data width of output DecoupledIO (bits); must
* be integer multiples of each other.
* @param littleEndian if inWidth > outWidth, determines
* the order of the nibbles (low to high)
**/
class DataWidthConverter(val inWidth: Int,
val outWidth: Int,
val littleEndian: Boolean = true)
(implicit logLevel: Logging.Level) extends Module with Logging {
require (inWidth > 0, "inWidth must be > 0")
require (outWidth > 0, "inWidth must be > 0")
require (inWidth != outWidth, "inWidth (%d) must be different from outWidth (%d)"
.format(inWidth, outWidth))
require (inWidth % outWidth == 0 || outWidth % inWidth == 0,
"inWidth (%d) and outWidth (%d) must be integer multiples of each other"
.format(inWidth, outWidth))
cinfo(s"inWidth = $inWidth, outWidth = $outWidth, littleEndian = $littleEndian")
val io = IO(new DataWidthConverter.IO(inWidth, outWidth))
val ratio: Int = if (inWidth > outWidth) inWidth / outWidth else outWidth / inWidth
val d_w = if (inWidth > outWidth) inWidth else outWidth // data register width
if (inWidth > outWidth)
downsize()
else
upsize()
private def upsize() = {
val i = RegInit(UInt(log2Ceil(ratio + 1).W), init = ratio.U)
val d = RegInit(UInt(outWidth.W), 0.U)
io.inq.ready := i =/= 0.U || (io.inq.valid && io.deq.ready)
io.deq.bits := d
io.deq.valid := i === 0.U
when (io.inq.ready && io.inq.valid) {
if (littleEndian)
d := Cat(io.inq.bits, d) >> inWidth.U
else
d := (d << inWidth.U) | io.inq.bits
i := i - 1.U
}
when (io.deq.valid && io.deq.ready) {
i := Mux(io.inq.valid, (ratio - 1).U, ratio.U)
}
}
private def downsize() = {
val i = RegInit(UInt(log2Ceil(ratio + 1).W), init = 0.U)
val d = RegInit(UInt(inWidth.W), init = 0.U)
io.inq.ready := i === 0.U || (i === 1.U && io.deq.ready)
io.deq.valid := i > 0.U
if (littleEndian)
io.deq.bits := d(outWidth - 1, 0)
else
io.deq.bits := d(inWidth - 1, inWidth - outWidth)
when (i > 0.U && io.deq.ready) {
if (littleEndian)
d := d >> outWidth.U
else
d := d << outWidth.U
i := i - 1.U
}
when (io.inq.ready && io.inq.valid) {
d := io.inq.bits
i := ratio.U
}
}
}
package chisel.miscutils
import chisel3._
import chisel3.util._
object DecoupledDataSource {
/** Interface for DecoupledDataSource. */
class IO[T <: Data](gen: T) extends Bundle {
val out = Decoupled(gen.cloneType)
}
}
/** Data source providing fixed data via Decoupled interface.
* Provides the data given via Decoupled handshakes; if repeat
* is true, data is wrapped around.
* @param gen Type.
* @param size Total number of elements.
* @param data Function providing data for each index
* @param repeat If true, will always have data via wrap-around,
* otherwise valid will go low after data was
* consumed.
**/
class DecoupledDataSource[T <: Data](gen: T,
val size : Int,
val data: (Int) => T,
val repeat: Boolean = true)
(implicit l: Logging.Level) extends Module with Logging {
cinfo("size = %d, repeat = %s, addrWidth = %d".format(size,
if (repeat) "true" else "false", log2Ceil(if (repeat) size else size + 1)))
val ds = for (i <- 0 until size) yield data(i) // evaluate data to array
val io = IO(new DecoupledDataSource.IO(gen)) // interface
val i = RegInit(UInt(log2Ceil(if (repeat) size else size + 1).W), 0.U) // index
val rom = Vec.tabulate(size)(n => ds(n)) // ROM with data
io.out.bits := rom(i) // current index data
io.out.valid := repeat.B | i < size.U // valid until exceeded
when (io.out.fire()) {
val next = if (repeat) {
if (math.pow(2, log2Ceil(size)).toInt == size)
i + 1.U
else
Mux((i + 1.U) < size.U, i + 1.U, 0.U)
} else {
Mux(i < size.U, i + 1.U, i)
}
info(p"i = $i -> $next, bits = 0x${Hexadecimal(io.out.bits.asUInt())}")
i := next
}
}
package chisel.miscutils
import chisel3._
import Logging._
import scala.util.Properties.{lineSeparator => NL}
trait Logging {
self: Module =>
def info(msg: => core.Printable)(implicit level: Level) { log(Level.Info, msg) }
def warn(msg: => core.Printable)(implicit level: Level) { log(Level.Warn, msg) }
def error(msg: => core.Printable)(implicit level: Level) { log(Level.None, msg) }
def log(msgLevel: Level, msg: => core.Printable)(implicit l: Level): Unit = msgLevel match {
case Level.Info if (l == Level.Info) => printf(p"[INFO] $className: $msg$NL")
case Level.Warn if (l == Level.Info || l == Level.Warn) => printf(p"[WARN] $className: $msg$NL")
case Level.None => printf(p"[ERROR] $className: $msg$NL")
case _ => ()
}
def cinfo(msg: => String)(implicit level: Level) { clog(Level.Info, msg) }
def cwarn(msg: => String)(implicit level: Level) { clog(Level.Warn, msg) }
def cerror(msg: => String)(implicit level: Level) { clog(Level.None, msg) }
def clog(msgLevel: Level, msg: => String)(implicit l: Level): Unit = msgLevel match {
case Level.Info if (l == Level.Info) => println(s"[INFO] $className: $msg")
case Level.Warn if (l == Level.Info || l == Level.Warn) => println(s"[WARN] $className: $msg")
case Level.None => println(s"[ERROR] $className: $msg")
case _ => ()
}
private[this] final lazy val className = self.getClass.getSimpleName
}
object Logging {
sealed trait Level
object Level {
final case object Info extends Level
final case object Warn extends Level
final case object None extends Level
}
}
package chisel.miscutils
import chisel3._
import chisel3.util._
object SignalGenerator {
sealed case class Signal(value: Boolean, periods: Int = 1) extends Ordered[Signal] {
import scala.math.Ordered.orderingToOrdered
def compare(that: Signal): Int = periods compare that.periods
def unary_! = this.copy(value = !value)
}
final case class Waveform(signals: Seq[Signal]) {
require (signals.length > 0, "waveform must not be empty")
}
implicit def waveformToSeq(w: Waveform): Seq[Signal] = w.signals
implicit def seqToWaveform(s: Seq[Signal]): Waveform = Waveform.apply(s)
implicit def makeSignal(sd: (Boolean, Int)): Signal = Signal(sd._1, sd._2)
implicit def makeWaveform(ls: List[(Boolean, Int)]): Waveform = ls map makeSignal
class IO extends Bundle {
val v = Output(Bool())
val in = Input(Bool())
}
}
class SignalGenerator(val signals: SignalGenerator.Waveform,
val useInputAsClock: Boolean = false) extends Module {
require (signals.length > 0, "Waveform must not be empty.")
require (signals map (_.periods > 1) reduce (_&&_),
"All signals must have at least two clock cycles length.")
val io = IO(new SignalGenerator.IO)
val cnts_rom = Vec(signals map (n => (n.periods - 1).U))
val vals_rom = Vec(signals map (n => (n.value).B))
val cnt = RegInit(UInt(log2Ceil(signals.max.periods).W), init = cnts_rom(0))
val curr_idx = RegInit(UInt(log2Ceil(signals.length).W), init = 0.U)
val vreg = RegInit(Bool(), init = vals_rom(0))
io.v := vreg
vreg := vals_rom(curr_idx)
// trigger on either clock or pos input edge
when (if (useInputAsClock) io.in && !RegNext(io.in) else true.B) {
when (cnt === 0.U) {
val next_idx = Mux(curr_idx < (signals.length - 1).U, curr_idx + 1.U, 0.U)
curr_idx := next_idx
cnt := cnts_rom(next_idx)
}
.otherwise {
cnt := cnt - 1.U
}
}
}
package chisel.miscutils
import chisel3._
import chisel3.util._
/** A slow queue which delays each element by a configurable delay. */
class SlowQueue(width: Int, val delay: Int = 10) extends Module {
val io = IO(new Bundle {
val enq = Flipped(Decoupled(UInt(width.W)))
val deq = Decoupled(UInt(width.W))
val dly = Input(UInt(log2Ceil(delay).W))
})
val waiting :: ready :: Nil = Enum(2)
val state = RegInit(ready)
val wr = RegInit(UInt(log2Ceil(delay).W), io.dly)
io.deq.bits := io.enq.bits
io.enq.ready := io.deq.ready && state === ready
io.deq.valid := io.enq.valid && state === ready
when (state === ready && io.enq.ready && io.deq.valid) {
state := waiting
wr := io.dly
}
when (state === waiting) {
wr := wr - 1.U
when (wr === 0.U) { state := ready }
}
}
package chisel.miscutils.datawidthconverter
import chisel.miscutils._
import chisel3._
import chisel3.util._
import math.pow
/** Correctness test harness for DataWidthConverter:
* A DecoupledDataSource with random data is connected to a pair
* of data width converters with inverted params. This circuit
* must behave exactly like a delay on the input stream (where
* the length of the delay is 2 * in/out-width-ratio).
* There's a slow queue in-between to simulate receivers with
* varying speed of consumption.
* @param inWidth Bit width of input data.
* @param outWidth Bit width of output data (in and out must be
* integer multiples/fractions of each other)
* @param littleEndian Byte-endianess.
* @param delay Clock cycle delay in [[SlowQueue]].
*/
class CorrectnessHarness(inWidth: Int,
outWidth: Int,
littleEndian: Boolean,
delay: Int = 10)
(implicit logLevel: Logging.Level) extends Module {
require (delay > 0, "delay bitwidth must be > 0")
val io = IO(new Bundle {
val dly = Input(UInt(Seq(log2Ceil(delay), 1).max.W))
val dsrc_out_valid = Output(Bool())
val dsrc_out_bits = Output(UInt())
val dwc_inq_valid = Output(Bool())
val dwc_inq_ready = Output(Bool())
val dwc_deq_valid = Output(Bool())
val dwc_deq_ready = Output(Bool())
val dwc2_inq_valid = Output(Bool())
val dwc2_deq_valid = Output(Bool())
val dwc2_deq_ready = Output(Bool())
val dwc2_deq_bits = Output(UInt())
})
val dwc = Module(new DataWidthConverter(inWidth, outWidth, littleEndian))
val dsrc = Module(new DecoupledDataSource(UInt(inWidth.W),
Seq(Seq(pow(2, inWidth).toLong, dwc.ratio).max, 500.toLong).min.toInt,
n => (scala.math.random * pow(2, inWidth)).toLong.U,
repeat = false))
val dwc2 = Module(new DataWidthConverter(outWidth, inWidth, littleEndian))
val slq = Module(new SlowQueue(outWidth, delay))
dwc.io.inq <> dsrc.io.out
slq.io.enq <> dwc.io.deq
slq.io.dly := io.dly
dwc2.io.inq <> slq.io.deq
dwc2.io.deq.ready := true.B
// internal peek-and-poke does not work, need to wire as outputs:
io.dsrc_out_valid := dsrc.io.out.valid
io.dsrc_out_bits := dsrc.io.out.bits
io.dwc_inq_valid := dwc.io.inq.valid
io.dwc_inq_ready := dwc.io.inq.ready
io.dwc_deq_valid := dwc.io.deq.valid
io.dwc_deq_ready := dwc.io.deq.ready
io.dwc2_inq_valid := dwc2.io.inq.valid
io.dwc2_deq_valid := dwc2.io.deq.valid
io.dwc2_deq_ready := dwc2.io.deq.ready
io.dwc2_deq_bits := dwc2.io.deq.bits
}
package chisel.miscutils.datawidthconverter
import chisel.miscutils._
import chisel3._, chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
/** Generic tester for [[CorrectnessHarness]]:
* Uses DataWidthConverterHarness class to check output correctness.
* Tracks incoming data from the data source in expecteds list.
* Whenever output is valid, it is compared to the expecteds,
* mismatches are reported accordingly.
* Does NOT check timing, only correctness of the output values.
**/
class CorrectnessTester[T <: UInt](m: CorrectnessHarness) extends PeekPokeTester(m) {
import scala.util.Properties.{lineSeparator => NL}
// returns binary string for Int, e.g., 0011 for 3, width 4
private def toBinaryString(n: BigInt, width: Int) =
"%%%ds".format(width).format(n.toString(2)).replace(' ', '0')
/** Performs data correctness check. **/
def check() = {
var i = 0
var delay = m.slq.delay - 1
poke (m.io.dly, delay)
var expecteds: List[BigInt] = List()
def running = peek(m.io.dsrc_out_valid) > 0 ||
peek(m.io.dwc_inq_valid) > 0 ||
peek(m.io.dwc_deq_valid) > 0 ||
peek(m.io.dwc2_inq_valid) > 0 ||
peek(m.io.dwc2_deq_valid) > 0
while (running) {
// scan output element and add to end of expected list
if (peek(m.io.dsrc_out_valid) > 0 && peek(m.io.dwc_inq_ready) > 0) {
val e = peek(m.io.dsrc_out_bits)
expecteds = expecteds :+ e
//println ("adding expected value: %d (%s)".format(e, toBinaryString(e, m.dwc.inWidth)))
}
// check output element: must match head of expecteds
if (peek(m.io.dwc2_deq_valid) > 0 && peek(m.io.dwc2_deq_ready) > 0) {
// update delay (decreasing with each output)
delay = if (delay == 0) m.slq.delay - 1 else delay - 1
poke(m.io.dly, delay)
// check output
val v = peek(m.io.dwc2_deq_bits)
if (expecteds.isEmpty) {
val errmsg = "received value output value %d (%s), but none expected yet"
.format(v, toBinaryString(v, m.dwc.inWidth))
println (errmsg)
expect(false, errmsg)
} else {
if (v == expecteds.head) {
// println ("element #%d ok!".format(i))
} else {
val errmsg = "element #%d wrong: expected %d (%s), found %d (%s)".format(
i, expecteds.head, toBinaryString(expecteds.head, m.dwc.inWidth),
v, toBinaryString(v, m.dwc.inWidth))
println (errmsg)
expect(v == expecteds.head, errmsg)
}
expecteds = expecteds.tail
}
i += 1
}
// advance sim
step (1)
}
}
reset(10) // reset for 10 cycles
check()
step (20) // settle output
}
package chisel.miscutils.datawidthconverter
import chisel.miscutils._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import org.scalacheck._, org.scalacheck.Prop._
import org.scalatest.prop.Checkers
import generators._
class DataWidthConverterSpec extends ChiselFlatSpec with Checkers {
implicit val logLevel = Logging.Level.Warn
behavior of "DataWidthConverter"
it should "preserve data integrity in arbitrary conversions" in
check(forAll(bitWidthGen(64), Arbitrary.arbitrary[Boolean], genLimited(1, 15)) {
case (inW, littleEndian, delay) =>
forAll(conversionWidthGen(inW)) { outW =>
println("Testing bitwidth conversion from %d bits -> %d bits (%s) with %d delay"
.format(inW:Int, outW:Int, if (littleEndian) "little-endian" else "big-endian", delay:Int))
val end = if (littleEndian) "littleEndian" else "bigEndian"
val dir = s"in${inW:Int}out${outW:Int}${end}delay${delay:Int}"
Driver.execute(Array("--fint-write-vcd", "--target-dir", s"test/DataWidthConverter/$dir"),
() => new CorrectnessHarness(inW, outW, littleEndian, 1))
{ m => new CorrectnessTester(m) }
}
}, minSuccessful(15))
it should "transfer data with minimal delays" in
check(forAll(bitWidthGen(64), Arbitrary.arbitrary[Boolean]) { case (inW, littleEndian) =>
forAll(conversionWidthGen(inW)) { outW =>
println("Testing bitwidth conversion from %d bits -> %d bits (%s)"
.format(inW:Int, outW:Int, if (littleEndian) "little-endian" else "big-endian"))
val end = if (littleEndian) "littleEndian" else "bigEndian"
val dir = s"in${inW:Int}out${outW:Int}${end}delay0"
Driver.execute(Array("--fint-write-vcd", "--target-dir", s"test/DataWidthConverter/$dir"),
() => new MinimalDelayHarness(inW, outW, littleEndian))
{ m => new MinimalDelayTester(m) }
}
}, minSuccessful(15))
}