-
Notifications
You must be signed in to change notification settings - Fork 50
Using the PeekPokeTester
This will be a brief walk-through of the PeekPokeTester. We will look at the src/test/scala/examples/GCDSpec.scala example.
import chisel3._
import chisel3.util._
import chisel3.iotesters._
import org.scalatest.{Matchers, FlatSpec}
For the purposes of this discussion we are focused on the IO ports of the circuit or device under test (DUT).
object RealGCD2 {
val num_width = 16
}
class RealGCD2Input extends Bundle {
val a = Bits(width = RealGCD2.num_width)
val b = Bits(width = RealGCD2.num_width)
}
class RealGCD2 extends Module {
val io = new Bundle {
val in = Decoupled(new RealGCD2Input()).flip()
val out = Valid(UInt(width = RealGCD2.num_width))
}
...
The test harness API allows 4 interactions with the DUT
- To set the DUT'S inputs: poke
- To look at the DUT'S outputs: peek
- To test one of the DUT's outputs: expect
- To advance the clock of the DUT: step
The tester is constructed by subclassing PeekPokeTester
class GCDPeekPokeTester(c: RealGCD2) extends PeekPokeTester(c) {
for {
i <- 1 to 10
j <- 1 to 10
} {
val (gcd_value, cycles) = GCDCalculator.computeGcdResultsAndCycles(i, j)
poke(c.io.in.bits.a, i)
poke(c.io.in.bits.b, j)
poke(c.io.in.valid, 1)
var count = 0
while(peek(c.io.out.valid) == BigInt(0) && count < 20) {
step(1)
count += 1
}
if(count > 30) {
println(s"Waited $count cycles on gcd inputs $i, $j, giving up")
System.exit(0)
}
expect(c.io.out.bits, gcd_value)
step(1)
}
}
The GCD test pushes two numbers into the inputs and then checks that the returned GCD value is correct. There is some complexity here in the code that waits for the c.io.out.valid
to become high a sign that the GCD computation is complete.
The test (a subclass of a PeekPokeTester) is now ready to run. The simplest way is to embed the invocation of the test in a scala test.
class GCDSpec extends FlatSpec with Matchers {
behavior of "GCDSpec"
it should "compute gcd excellently" in {
chisel3.iotesters.Driver(() => new RealGCD2) { c =>
new GCDPeekPokeTester(c)
} should be(true)
}
}
The test can now be executed via sbt.
sbt
> test-only examples.GCDSpec
The test-only command will report with much text the result of your actions. The main types of output are
- Success. Everything worked, there is a lot's of output showing the progress of the test and a summary of the result
- Problem in the design. There is some error in the design of your circuit that caused either Chisel or Firrtl to error.
- Problem in the execution of the circuit. The circuit was successfully compiled into a simulatable state, but there is some problem with the execution. Typically this involves some error in the circuit logic, but can also result from an incorrect test with peek or expect.
The full scope of Chisel/Firrtl error reporting is a broad area that we are working hard to provide more materials for. As a simple illustration here. I have introduced a bad connection io.in := 7777.U
at line 40 of GCDSpec.scala.
The error message in this case looks like
[info] - should compute gcd excellently *** FAILED ***
[info] chisel3.internal.ChiselException: cannot connect chisel3.util.DecoupledIO@10 and chisel3.core.UInt@2f
[info] at chisel3.internal.throwException$.apply(Error.scala:13)
[info] at chisel3.core.Data.badConnect(Data.scala:50)
[info] at chisel3.core.Bundle.$less$greater(Aggregate.scala:288)
[info] at chisel3.core.Bundle.$colon$eq(Aggregate.scala:292)
[info] at examples.RealGCD2.<init>(GCDSpec.scala:40)
[info] at examples.GCDSpec$$anonfun$5$$anonfun$apply$mcV$sp$3.apply(GCDSpec.scala:86)
[info] at examples.GCDSpec$$anonfun$5$$anonfun$apply$mcV$sp$3.apply(GCDSpec.scala:86)
[info] at chisel3.core.Module$.do_apply(Module.scala:30)
[info] at chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:117)
[info] at chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:117)
A good place to start when confronted with an error message like this is to look at the earliest line in your code listed in the stack trace. In this case
[info] at examples.RealGCD2.<init>(GCDSpec.scala:40)
Find and fix the error and try again.
Here I introduced and error at line 49 of GCDSpec.scala
The output in this case is quite long but it ends with
STEP 779 -> 780
POKE io_in_bits_a <- 0xa
POKE io_in_bits_b <- 0xa
POKE io_in_valid <- 0x1
ti 780 x 0 y 1 in_ready 1 in_valid 1 out 1 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 780 -> 781
ti 781 x 10 y 10 in_ready 0 in_valid 1 out 11 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 781 -> 782
ti 782 x 10 y 0 in_ready 0 in_valid 1 out 11 out_valid 1==============
PEEK io_out_valid -> 0x1
EXPECT io_out_bits -> 0xb == 0xaFAIL
STEP 782 -> 783
RAN 783 CYCLES FAILED FIRST AT CYCLE 2
[info] GCDSpec:
[info] GCDSpec
[info] - should compute gcd excellently *** FAILED ***
[info] false was not true (GCDSpec.scala:86)
[info] ScalaCheck
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] ScalaTest
[info] Run completed in 1 second, 193 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
[info] *** 1 TEST FAILED ***
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error] examples.GCDSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
[error] Total time: 4 s, completed Sep 20, 2016 8
Note in particular EXPECT io_out_bits -> 0xb == 0xaFAIL
and the
[info] *** 1 TEST FAILED ***
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error] examples.GCDSpec
[error] (test:testOnly) sbt.TestsFailedException: Tests unsuccessful
There are a number of approaches to finding and fixing these types of errors. Going over the circuit. Printf's can be introduced into the circuit. It is even possible to run the firrtl interpreter backend in a scala debugger.
There are quite a few options options available when using the PeekPokeTester. A different invocation method is required, when invoking the PeekPokeTester use the Driver.execute method like this.
chisel3.iotesters.Driver.execute(Array("--fint-write-vcd"), () => new RealGCD2) { c =>
new GCDPeekPokeTester(c)
} should be(true)
Note the Array("--fint-write-vcd") argument to the execute function, it allows you to specify that a VCD file should be produced.
That file will generally be found in a subdirectory of you where you are starting with "test_run_dir", the full path will use the name of your DUT plus a random string and many other arguments. For example when testing this the relative path to my VCD file was test_run_dir/examples.GCDSpec1996890495/RealGCD2.vcd. (There are options that can control where the file goes)
To see the full gamut of available options change "--fint-write-vcd" to "--help". There are a number of good examples of uses in the /src/test/scala/examples/GCDSpec.scala Where it shows how to make the interpreter run verbosely, how to use verilator instead among other things.
The first thing you see will be the firrtl version of the circuit.
circuit RealGCD2 :
module RealGCD2 :
input clk : Clock
input reset : UInt<1>
output io_in_ready : UInt<1>
input io_in_valid : UInt<1>
input io_in_bits_a : UInt<16>
input io_in_bits_b : UInt<16>
output io_out_valid : UInt<1>
output io_out_bits : UInt<16>
reg x : UInt<16>, clk with :
reset => (UInt<1>("h0"), x)
reg y : UInt<16>, clk with :
reset => (UInt<1>("h0"), y)
reg p : UInt<1>, clk with :
reset => (reset, UInt<1>("h0"))
reg ti : UInt<16>, clk with :
reset => (reset, UInt<16>("h0"))
node T_31 = add(ti, UInt<1>("h1")) @[GCDSpec.scala 30:12]
node T_32 = tail(T_31, 1) @[GCDSpec.scala 30:12]
node T_34 = eq(p, UInt<1>("h0")) @[GCDSpec.scala 32:18]
node T_36 = eq(p, UInt<1>("h0")) @[GCDSpec.scala 34:24]
node T_37 = and(io_in_valid, T_36) @[GCDSpec.scala 34:21]
node GEN_0 = mux(T_37, io_in_bits_a, x) @[GCDSpec.scala 34:28]
node GEN_1 = mux(T_37, io_in_bits_b, y) @[GCDSpec.scala 34:28]
node GEN_2 = mux(T_37, UInt<1>("h1"), p) @[GCDSpec.scala 34:28]
node T_39 = gt(x, y) @[GCDSpec.scala 41:13]
node GEN_3 = mux(T_39, y, GEN_0) @[GCDSpec.scala 41:19]
node GEN_4 = mux(T_39, x, GEN_1) @[GCDSpec.scala 41:19]
node T_41 = eq(T_39, UInt<1>("h0")) @[GCDSpec.scala 41:19]
node T_42 = sub(y, x) @[GCDSpec.scala 42:30]
node T_43 = tail(T_42, 1) @[GCDSpec.scala 42:30]
node GEN_5 = mux(T_41, T_43, GEN_4) @[GCDSpec.scala 42:21]
node GEN_6 = mux(p, GEN_3, GEN_0) @[GCDSpec.scala 40:12]
node GEN_7 = mux(p, GEN_5, GEN_1) @[GCDSpec.scala 40:12]
node T_45 = eq(reset, UInt<1>("h0")) @[GCDSpec.scala 45:9]
node T_47 = eq(y, UInt<1>("h0")) @[GCDSpec.scala 50:21]
node T_48 = and(T_47, p) @[GCDSpec.scala 50:33]
node GEN_8 = mux(io_out_valid, UInt<1>("h0"), GEN_2) @[GCDSpec.scala 51:23]
io_in_ready <= T_34
io_out_valid <= T_48
io_out_bits <= x
x <= GEN_6
y <= GEN_7
p <= GEN_8
ti <= T_32
printf(clk, T_45, "ti %d x %d y %d in_ready %d in_valid %d out %d out_valid %d==============\n", ti, x, y, io_in_ready, io_in_valid, io_out_bits, io_out_valid) @[GCDSpec.scala 45:9]
Followed by a lot of output of the execution
SEED 1474359604763
POKE io_in_bits_a <- 0x1
POKE io_in_bits_b <- 0x1
POKE io_in_valid <- 0x1
ti 0 x 0 y 0 in_ready 1 in_valid 1 out 0 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 0 -> 1
ti 1 x 1 y 1 in_ready 0 in_valid 1 out 1 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 1 -> 2
ti 2 x 1 y 0 in_ready 0 in_valid 1 out 1 out_valid 1==============
PEEK io_out_valid -> 0x1
EXPECT io_out_bits -> 0x1 == 0x1PASS
STEP 2 -> 3
POKE io_in_bits_a <- 0x1
POKE io_in_bits_b <- 0x2
POKE io_in_valid <- 0x1
ti 3 x 0 y 1 in_ready 1 in_valid 1 out 0 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 3 -> 4
ti 4 x 1 y 2 in_ready 0 in_valid 1 out 1 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 4 -> 5
ti 5 x 1 y 1 in_ready 0 in_valid 1 out 1 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 5 -> 6
ti 6 x 1 y 0 in_ready 0 in_valid 1 out 1 out_valid 1==============
PEEK io_out_valid -> 0x1
EXPECT io_out_bits -> 0x1 == 0x1PASS
STEP 6 -> 7
POKE io_in_bits_a <- 0x1
POKE io_in_bits_b <- 0x3
POKE io_in_valid <- 0x1
ti 7 x 0 y 1 in_ready 1 in_valid 1 out 0 out_valid 0==============
PEEK io_out_valid -> 0x0
STEP 7 -> 8
...
Followed by the summary
EXPECT io_out_bits -> 0xa == 0xaPASS
STEP 782 -> 783
RAN 783 CYCLES PASSED
[info] GCDSpec:
[info] GCDSpec
[info] - should compute gcd excellently
[info] ScalaCheck
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0
[info] ScalaTest
[info] Run completed in 1 second, 135 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[info] Passed: Total 1, Failed 0, Errors 0, Passed 1
[success] Total time: 6 s, completed Sep 20, 2016 8:20:05 AM
Success! How sweet it is
In the examples above the amount of output for this particular test was too large for the scrollback buffer in my terminal so I did the following.
sbt 'test-only examples.GCDSpec' > out
tail -100 out # to see the result
head -100 out # to see if the circuit was included
less -r out # to move around through the output to figure out what was going on.