Testbenches
How RTL++ finds testbenches, the rules that decide pass and fail, and the PASS and $fatal convention every template follows.
Every module whose name starts with tb_ is a test. Add one in any .sv or .v file and it appears in the Tests rail.
Pass and fail
Each test runs in Icarus Verilog as SystemVerilog 2012. Its result follows these rules, in order:
- Compile errors: the test could not run, and the errors show in the editor.
$fatal,$error, a failed immediate assertion, or a run-time error: failed, at the time Icarus reports, with the message and the source line.- A line that starts with
FAIL, or readsTEST FAILED: failed, without a time. - A non-zero exit code with none of the above: failed.
- Otherwise, a run that ends by
$finish,$stopor running out of events passes, once the second simulator agrees. See Two simulators.
The convention
Every template's testbench checks its own results: it prints PASS and calls $finish when everything is right, and stops with $fatal and a sentence that says what went wrong when it is not. A timeout $fatal keeps a stuck design from running to the time limit. The Counter template's test:
`timescale 1ns/1ps
module tb_counter;
`include "rtlpp_check.svh"
logic clk = 0;
logic rst = 1;
logic en = 0;
logic [7:0] count;
logic wrap;
counter #(.WIDTH(8), .MAX(9)) dut (.clk(clk), .rst(rst), .en(en), .count(count), .wrap(wrap));
always #5 clk = ~clk;
initial begin
#100000 $fatal(1, "Timed out");
end
// Inputs change on the falling edge, so the design samples them cleanly on the rising edge.
initial begin
@(negedge clk);
`RTLPP_CHECK(count, 8'd0) $fatal(1, "Reset should hold the count at 0, got %0d", count);
rst = 0;
en = 1;
for (int i = 1; i <= 25; i++) begin
@(negedge clk);
`RTLPP_CHECK(count, i % 10) $fatal(1, "After %0d cycles count is %0d, expected %0d", i, count, i % 10);
`RTLPP_CHECK(wrap, (count == 9)) $fatal(1, "wrap is %b with count %0d", wrap, count);
end
en = 0;
@(negedge clk);
`RTLPP_CHECK(count, 5) $fatal(1, "count changed while en was low");
`RTLPP_CHECK(wrap, 1'b0) $fatal(1, "wrap must be low while en is low");
rst = 1;
@(negedge clk);
`RTLPP_CHECK(count, 8'd0) $fatal(1, "Reset did not clear the count");
$display("PASS");
$finish;
end
endmoduleThe sentence after $fatal is what the Tests rail shows and what Why this value reads the expected value from, so say what was expected and what was seen.
Time and waveforms
- Give every testbench a `timescale. Without one Icarus counts each delay unit as one second, and the editor warns about it.
- There is no need to dump waveforms: every signal under the testbench is recorded for you.
rtlpp_harnessis reserved as a module name, andrtlpp_harness.vas a file name.