Commit 8f2faf2e authored by Jens Korinth's avatar Jens Korinth
Browse files

Merge of chisel-miscutils

Merge commit '61fdb4b48f81caf5ae3e68aead564fb34a2c4371'
parents 511f7779 20323ee9
......@@ -2,11 +2,9 @@ name := "chisel-miscutils"
organization := "esa.cs.tu-darmstadt.de"
version := "0.2-SNAPSHOT"
version := "0.3-SNAPSHOT"
scalaVersion := "2.11.7"
crossScalaVersions := Seq("2.10.3", "2.10.4", "2.11.0")
scalaVersion := "2.11.11"
resolvers ++= Seq(
Resolver.sonatypeRepo("snapshots"),
......@@ -21,8 +19,9 @@ libraryDependencies ++= (Seq("chisel3","chisel-iotesters").map {
dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) })
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "2.2.6" % "test",
"com.typesafe.play" %% "play-json" % "2.4.8"
"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
......
......@@ -15,12 +15,9 @@ import chisel3.util._
* @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
) extends Module {
class DataWidthConverter(val inWidth: Int,
val outWidth: Int,
val littleEndian: Boolean = true) extends Module {
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)"
......
......@@ -22,9 +22,8 @@ class DecoupledDataSourceIO[T <: Data](gen: T) extends Bundle {
**/
class DecoupledDataSource[T <: Data](gen: T, val size : Int, val data: (Int) => T, val repeat: Boolean = true) extends Module {
println ("DecoupledDataSource: size = %d, repeat = %s".format(size, if (repeat) "true" else "false"))
println(" width = %d".format(log2Ceil(if (repeat) size else size + 1)))
println ("DecoupledDataSource: 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 DecoupledDataSourceIO(gen)) // interface
......
......@@ -2,18 +2,24 @@ package chisel.miscutils
import chisel3._
import chisel3.util._
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
}
object SignalGenerator {
type Waveform = List[Signal]
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 SignalGenerator(signals: SignalGenerator.Waveform, useInputAsClock: Boolean = false) extends Module {
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.")
......@@ -29,7 +35,7 @@ class SignalGenerator(signals: SignalGenerator.Waveform, useInputAsClock: Boolea
when (reset) {
curr_idx := 0.U
cnt := cnts_rom(0)
vreg := 0.U
vreg := vals_rom(0)
}
.otherwise {
vreg := vals_rom(curr_idx)
......
package chisel.miscutils
import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import org.scalatest.junit.JUnitSuite
import scala.math._
import java.nio.file.Paths
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(init = ready)
val wr = Reg(UInt(log2Ceil(delay).W))
io.deq.bits := io.enq.bits
io.enq.ready := io.deq.ready && state === ready
io.deq.valid := io.enq.valid && state === ready
when (reset) {
state := ready
}
.otherwise {
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 }
}
}
}
/**
* DataWidthConverterHarness: Correctness test harness.
* 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.
**/
class DataWidthConverterHarness(inWidth: Int, outWidth: Int, littleEndian: Boolean, delay: Int = 10) extends Module {
val io = IO(new Bundle {
val dly = Input(UInt(log2Ceil(delay).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
}
/**
* Generic tester for DataWidthConverterHarness:
* 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 DataWidthConverterCorrectnessTester[T <: UInt](m: DataWidthConverterHarness) 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
}
/** Unit test for DataWidthConverter hardware. **/
class DataWidthConverterSuite extends ChiselFlatSpec {
def resize(inWidth: Int, outWidth: Int, littleEndian: Boolean = true) = {
println("testing conversion of %d bit to %d bit, %s ..."
.format(inWidth, outWidth, if (littleEndian) "little-endian" else "big-endian"))
val dir = Paths.get("test")
.resolve("DataWidthConverterSuite")
.resolve("%dto%d%s".format(inWidth, outWidth, if (littleEndian) "le" else "be"))
.toString
Driver.execute(Array("--fint-write-vcd", "--target-dir", dir),
() => new DataWidthConverterHarness(inWidth, outWidth, littleEndian))
{ m => new DataWidthConverterCorrectnessTester(m) }
}
// simple test group, can be used for waveform analysis
/*"check16to4le" should "be ok" in { resize(16, 4, true) }
"check4to16le" should "be ok" in { resize(4, 16, true) }
"check16to4be" should "be ok" in { resize(16, 4, false) }
"check4to16be" should "be ok" in { resize(4, 16, false) }
"check64to32be" should "be ok" in { resize(64, 32, false) }*/
// downsizing tests
"check2to1le" should "be ok" in { resize(2, 1, true) }
"check2to1be" should "be ok" in { resize(2, 1, false) }
"check8to1le" should "be ok" in { resize(8, 1, true) }
"check8to1be" should "be ok" in { resize(8, 1, false) }
"check16to4le" should "be ok" in { resize(16, 4, true) }
"check16to4be" should "be ok" in { resize(16, 4, false) }
"check16to8le" should "be ok" in { resize(16, 8, true) }
"check16to8be" should "be ok" in { resize(16, 8, false) }
"check32to8le" should "be ok" in { resize(32, 8, true) }
"check32to8be" should "be ok" in { resize(32, 8, false) }
"check64ot8le" should "be ok" in { resize(64, 8, true) }
"check64to8be" should "be ok" in { resize(64, 8, false) }
"check64ot32le" should "be ok" in { resize(64, 32, true) }
"check64to32be" should "be ok" in { resize(64, 32, false) }
// upsizing tests
"check1to2le" should "be ok" in { resize(1, 2, true) }
"check1to2be" should "be ok" in { resize(1, 2, false) }
"check1to8le" should "be ok" in { resize(1, 8, true) }
"check1to8be" should "be ok" in { resize(1, 8, false) }
"check4to16le" should "be ok" in { resize(4, 16, true) }
"check4to16be" should "be ok" in { resize(4, 16, false) }
"check8to16le" should "be ok" in { resize(8, 16, true) }
"check8to16be" should "be ok" in { resize(8, 16, false) }
"check8to32le" should "be ok" in { resize(8, 32, true) }
"check8to32be" should "be ok" in { resize(8, 32, false) }
"check8ot64le" should "be ok" in { resize(8, 64, true) }
"check8to64be" should "be ok" in { resize(8, 64, false) }
"check32ot64le" should "be ok" in { resize(32, 64, true) }
"check32to64be" should "be ok" in { resize(32, 64, false) }
}
package chisel.miscutils
import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
/**
* Tester class for DecoupledDataSource.
* Automatically exhausts the internal data of the instance, checks that
* each dequeued output matches the expected value. Also checks whether
* or not the module wraps correctly (in case repeat is true).
**/
class DecoupledDataSource_OutputCheck[T <: UInt](m: DecoupledDataSource[T], data: Int => Int) extends PeekPokeTester(m) {
import scala.util.Properties.{lineSeparator => NL}
poke(m.io.out.ready, true)
var errors: List[String] = List()
var i = 0
while (peek(m.io.out.valid) > 0 && i <= m.size) {
if (i >= m.size) {
if (! m.repeat)
errors = "repeat is false, but index (%d) exceeds size(%d)".format(i, m.size) :: errors
} else {
if (peek(m.io.out.bits) != m.data(i)) {
errors = "output #%d: expected %d, found %d".format(i, data(i), peek(m.io.out.bits)) :: errors
} else {
// wait for random time up to 10 cycles
val wait = (scala.math.random * 10).toInt
poke(m.io.out.ready, false)
step(wait)
poke(m.io.out.ready, true)
}
}
i += 1
step(1)
}
expect (errors.length == 0, ("all elements should match, errors: " :: errors).mkString(NL))
}
class DecoupledDataSourceSuite extends ChiselFlatSpec {
/** Performs randomized tests with random data of random size. **/
"checkRandomOutputs" should "be ok" in {
for (i <- 0 until 1) {
val cnt = Seq(1, (scala.math.random * 1000).toInt).max
val width = Seq(1, (scala.math.random * 64).toInt).max
val repeat = scala.math.random > 0.5
val data = for (i <- 0 until cnt) yield (scala.math.random * scala.math.pow(2, width)).toInt
println("testing cnt = %d, width = %d, repeat = %b ...".format(cnt, width, repeat))
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/DecoupledDataSourceSuite/checkRandomOutputs"),
() => new DecoupledDataSource(UInt(width.W), cnt, i => data(i).U, repeat))
{ m => new DecoupledDataSource_OutputCheck(m, data) }
}
}
/** Performs test for 8bit wide sequential data with repeat. **/
"checkSequentialOutputsWithRepeat" should "be ok" in {
val data = 0 until 256
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/DecoupledDataSourceSuite/checkSequentialOutputsWithRepeat"),
() => new DecoupledDataSource(UInt(8.W), 256, i => data(i).U, true))
{ m => new DecoupledDataSource_OutputCheck(m, data) }
}
/** Performs test for 8bit wide sequential data without repeat. **/
"checkSequentialOutputsWithoutRepeat" should "be ok" in {
val data = 0 until 256
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/DecoupledDataSourceSuite/checkSequentialOutputWithoutRepeat"),
() => new DecoupledDataSource(UInt(8.W), 256, i => data(i).U, false))
{ m => new DecoupledDataSource_OutputCheck(m, data) }
}
}
package chisel.miscutils
import chisel3._
import chisel3.util._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import SignalGenerator._
class SignalGeneratorComposition1 extends Module {
val waveform: SignalGenerator.Waveform =
(for (i <- 2 until 30) yield List((false, 5), (true, i))) reduce (_++_)
val clock_sg = Module(new SignalGenerator(List((true, 2), (false, 2))))
val test_sg = Module(new SignalGenerator(waveform, true))
val io = IO(new Bundle { val v = Output(Bool()) })
test_sg.io.in := clock_sg.io.v
io.v := test_sg.io.v
}
class SignalGeneratorSuite extends ChiselFlatSpec {
"test1" should "be ok" in {
val waveform: SignalGenerator.Waveform =
(for (i <- 2 until 30) yield List((false, 5), (true, i))) reduce (_++_)
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/signalgenerator"),
() => new SignalGenerator(waveform))
{ m => new SignalGeneratorTest(m) }
}
"test2" should "be ok" in { // same as test1, but with user supplied clock
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/signalgenerator"),
() => new SignalGeneratorComposition1)
{ m => new SignalGeneratorComposition1Test(m) }
}
}
class SignalGeneratorTest(sg: SignalGenerator) extends PeekPokeTester(sg) {
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)
}
// waits for next positive edge on signal
def waitForPosEdge[T <: Chisel.Bits](s: T) {
while(peek(s) > 0) step(1)
while(peek(s) == 0) step(1)
}
// waits for next positive edge on signal
def waitForNegEdge[T <: Chisel.Bits](s: T) {
while(peek(s) == 0) step(1)
while(peek(s) > 0) step(1)
}
reset(10)
for (j <- 0 to 1) {
for (i <- 2 until 30) {
waitForPosEdge(sg.io.v)
val cc_start = cc
waitForNegEdge(sg.io.v)
expect (cc - cc_start == i, "wrong number of clock cycles")
}
}
}
class SignalGeneratorComposition1Test(sg: SignalGeneratorComposition1) extends PeekPokeTester(sg) {
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)
}
// waits for next positive edge on signal
def waitForPosEdge[T <: Chisel.Bits](s: T) {
while(peek(s) > 0) step(1)
while(peek(s) == 0) step(1)
}
// waits for next positive edge on signal
def waitForNegEdge[T <: Chisel.Bits](s: T) {
while(peek(s) == 0) step(1)
while(peek(s) > 0) step(1)
}
reset(10)
for (j <- 0 to 1) {
for (i <- 2 until 30) {
waitForPosEdge(sg.io.v)
val cc_start = cc
waitForNegEdge(sg.io.v)
expect (cc - cc_start == i * 4, "wrong number of clock cycles")
}
}
}
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 = Reg(UInt(log2Ceil(delay).W))
io.deq.bits := io.enq.bits
io.enq.ready := io.deq.ready && state === ready
io.deq.valid := io.enq.valid && state === ready
when (reset) {
state := ready
}
.otherwise {
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) 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