Commit 20323ee9 authored by Jens Korinth's avatar Jens Korinth
Browse files

Squashed 'miscutils/' changes from 6b570c6..c428dca

c428dca Decreased number of tests for DataWidthConverter
3f395f6 Bugfix concerning log2Ceil(1) == 0
0092287 Replace unit tests for DataWidthConverter with property spec
9ffa2fe Finish data width converter correctness spec
435ee4b Work on generator for valid data width conversions
f91ed9f Start implementation of data width conversion spec
1d57540 Improve debug output of DecoupledDataSource
4a4a7d2 Replace DecoupledDataSource testing with prop check
9da9dc5 Finish replacement of SignalGenerator tests
aeee2cd Started with property-based testing
20c08e2 Fix bug in DecoupledDataSource, remove crossVersions

git-subtree-dir: miscutils
git-subtree-split: c428dca006d0aa62d25c83ffdffad8baf229bcfb
parent 8a742587
......@@ -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
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