Commit b3857489 authored by Jens Korinth's avatar Jens Korinth
Browse files

Finish replacement of SignalGenerator tests

* finished implementation of property based testing with random
  generators, much more thorough than the previous tests
* implemented both regular clock input and generated random clock input
parent 187a74fb
......@@ -4,37 +4,77 @@ import org.scalacheck._, org.scalacheck.Prop._
import org.scalatest.prop.Checkers
import chisel3._
import chisel3.iotesters.{ChiselFlatSpec, Driver, PeekPokeTester}
import SignalGenerator._
import scala.util.Random
/** Connects a random clock signal generator to the main signal generator to test
* external clock port facilities.
*/
class SignalGeneratorWithClockInput(val signals: Waveform) extends Module {
val io = IO(new Bundle { val v = Output(Bool()); val clk = Output(Bool()) })
val clw = 0 to signals.length * 2 map (i => Signal(i % 2 == 0, 2 + Random.nextInt().abs % 10))
val clk = Module(new SignalGenerator(clw))
val sgn = Module(new SignalGenerator(signals, true))
sgn.io.in := clk.io.v
io.v := sgn.io.v
io.clk := clk.io.v
}
/** Checks the correctness of the waveform output w.r.t. its signal input. */
class SignalGeneratorWithClockInputWaveformTest(sgc: SignalGeneratorWithClockInput) extends PeekPokeTester(sgc) {
private var cc = 0
reset(10)
cc = 0
step(1)
/** Advance by one positive clock edge on the external clock. */
def clockEdge {
while (peek(sgc.io.clk) > 0) step(1)
while (peek(sgc.io.clk) == 0) step(1)
step(1) // one regular clock delay due to internal registers
cc += 1
}
clockEdge
for (i <- 0 until sgc.signals.length) {
//println(s"signal ${i+1}/${sgc.signals.length} started at $cc")
for (s <- 1 until sgc.signals(i).periods) {
val e = sgc.signals(i).value
val p = peek(sgc.io.v)
expect(sgc.io.v, sgc.signals(i).value.B, s"expected $e at clock cycle $cc")
clockEdge
}
//println(s"signal ${i+1}/${sgc.signals.length} finished at $cc")
clockEdge
}
}
class SignalGeneratorWaveformTest(sg: SignalGenerator) extends PeekPokeTester(sg) {
require(!sg.useInputAsClock, "cannot set input as clock, use a SignalWithClockInputWaveformTest instead!")
private var cc = 0
// re-define step to output progress info
override def step(n: Int) {
if (sg.useInputAsClock) {
super.step(1)
poke(sg.io.in, (peek(sg.io.in) + 1) % 2)
super.step(1)
cc += 1
} else {
super.step(n)
cc += n
}
super.step(n)
cc += n
}
poke(sg.io.in, 0)
reset(10)
step(10)
cc = 0
step(1)
var prev_input = peek(sg.io.in)
for (i <- 0 until sg.signals.length) {
println(s"signal ${i+1}/${sg.signals.length} started at $cc")
for (s <- 0 until sg.signals(i).periods) {
//println(s"signal ${i+1}/${sg.signals.length} started at $cc")
for (s <- 1 until sg.signals(i).periods) {
val e = sg.signals(i).value
val p = peek(sg.io.v)
expect(sg.io.v, sg.signals(i).value.B, s"expected $e at clock cycle $cc")
step(1)
}
println(s"signal ${i+1}/${sg.signals.length} finished at $cc")
//println(s"signal ${i+1}/${sg.signals.length} finished at $cc")
step(1)
}
}
......@@ -42,11 +82,17 @@ class SignalGeneratorWaveformTest(sg: SignalGenerator) extends PeekPokeTester(sg
class SignalGeneratorSpec extends ChiselFlatSpec with Checkers {
behavior of "SignalGenerator"
it should "generate arbitrary waveforms" in
check(forAll(signalGeneratorGen) { case (wave, useInputAsClock) =>
println(s"useInputAsClock: $useInputAsClock, wave: $wave")
it should "generate arbitrary waveforms with regular clock" in
check(forAll(waveformGen()) { wave =>
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/signalgenerator/spec"),
() => new SignalGenerator(wave, useInputAsClock))
() => new SignalGenerator(wave, false))
{ m => new SignalGeneratorWaveformTest(m) }
})
}, minSuccessful(100))
it should "generate arbitrary waveforms with random clock" in
check(forAll(waveformGen()) { wave =>
Driver.execute(Array("--fint-write-vcd", "--target-dir", "test/signalgenerator/spec"),
() => new SignalGeneratorWithClockInput(wave))
{ m => new SignalGeneratorWithClockInputWaveformTest(m) }
}, minSuccessful(25))
}
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")
}
}
}
......@@ -3,60 +3,70 @@ import org.scalacheck._
import SignalGenerator._
import scala.language.implicitConversions
/** Generators for the miscutils module configurations. */
package object generators {
/** An A that is limited to a range min <= a <= max.
* Used to generate Shrinks which respect min.
**/
final case class Limited[A](a: A, min: A, max: A)(implicit num: Numeric[A]) {
import num._
require (a >= min, s"$a must be >= $min")
require (a <= max, s"$a must be <= $max")
}
/** Implicit conversion from a Limited type to its underlying. */
implicit def limitedToA[A](l: Limited[A]): A = l.a
/** Generic Limited generator. */
def genLimited[A](min: A, max: A)(implicit num: Numeric[A], c: Gen.Choose[A]): Gen[Limited[A]] =
Gen.choose(min, max) map (v => Limited.apply(v, min, max))
/** Generic Limited shrinker. */
implicit def shrinkLimited[A](l: Limited[A])(implicit num: Numeric[A]): Shrink[Limited[A]] = Shrink { l =>
import num._
if (l.a <= l.min) Stream.empty[Limited[A]] else Stream(Limited(l.a - num.one, l.min, l.max))
}
/** A Limited[Int] representing bit widths. */
type BitWidth = Limited[Int]
type DataSize = Limited[Int]
def bitWidthGen(max: Int = 64): Gen[BitWidth] = genLimited(1, max)
/** A Limited[Int] representing data sizes. */
type DataSize = Limited[Int]
def dataSizeGen(max: Int = 1024): Gen[DataSize] = genLimited(1, max)
/** Generator for a DataWidthConverter test configuration consisting of
* input bit width, output bit width and endianess flag. */
def widthConversionGen(max: Int = 64): Gen[(BitWidth, BitWidth, Boolean)] = for {
inWidth <- bitWidthGen(max)
outWidth <- bitWidthGen(max)
littleEndian <- Arbitrary.arbitrary[Boolean]
} yield (inWidth, outWidth, littleEndian)
/** Generator for an DataSource test configuration consistign of bit width and size. */
def dataSourceGen(maxWidth: Int = 64, maxSize: Int = 1024): Gen[(BitWidth, DataSize, Boolean)] = for {
bw <- bitWidthGen(maxWidth)
ds <- dataSizeGen(maxSize)
r <- Arbitrary.arbitrary[Boolean]
} yield (bw, ds, r)
/** Generator for binary signals of random (but at least 2 periods) length. */
def signalGen(maxLength: Int = 15): Gen[Signal] = for {
v <- Arbitrary.arbitrary[Boolean]
p <- genLimited(2, maxLength)
} yield Signal(v, p)
/** Transforms a sequence of Signals such that value always alternates. */
def alternate(ss: Seq[Signal]): Seq[Signal] = ss match {
case s0 +: s1 +: sr => s0 +: alternate(s1.copy(value = !s0.value) +: sr)
case s +: sr => s +: sr
case Seq() => Seq()
}
/** Generates an alternating waveform of up to maxLength random length signals. */
def waveformGen(maxLength: Int = 20): Gen[Waveform] = Gen.sized { n =>
Gen.nonEmptyBuildableOf[Seq[Signal], Signal](signalGen()) map (ss => Waveform(alternate(ss)))
}
/** A valid shrink for waveforms (non-empty). */
implicit def waveformShrink(waveform: Waveform): Shrink[Waveform] =
Shrink { w => if (w.length <= 1) Stream.empty[Waveform] else Stream(w.drop(1)) }
def signalGeneratorGen: Gen[(Waveform, Boolean)] = for {
w <- waveformGen()
i <- Arbitrary.arbitrary[Boolean]
} yield (w, true)
}
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