$finish, $stop and Time
A simulation that never ends is worse than one that fails. A failure tells you something; a hang burns compute and blocks the pipeline until someone kills it. Every testbench has an explicit exit and a watchdog that fires if the design deadlocks, and every failing check should say when it happened.
$finish and $stop
$finish ends the simulation and exits to the operating system with a return code a script can check. $stop suspends the run and drops into the simulator’s interactive prompt, where you can inspect and continue; in the commercial simulators that is a powerful debugger, in Icarus a basic one. In anything run from a script or a build system, use $finish; keep $stop for sitting at the keyboard.
if (error_count == 0)
$display("PASSED: all %0d tests.", test_count);
else
$display("FAILED: %0d of %0d tests.", error_count, test_count);
$finish;
Do not rely on the run ending when the initial block does. A clock generator keeps the event queue alive forever, and without $finish the clock ticks on until the scheduler kills the job.
Reading time
$time returns a 64-bit integer in the file’s time unit; it is the one to use. $realtime returns a real, for simulations with fractional steps, 1ns / 100ps say, where $time would round. $stime is a 32-bit relic that overflows after a few seconds of simulated time at nanosecond resolution.
`timescale 1ns / 1ps
module tb_time_demo();
initial begin
#10.5;
$display("$time = %0d", $time); // 10
$display("$realtime = %0f", $realtime); // 10.500000
$finish;
end
endmodule
With $timeformat set once at the top, %t in every message prints readable times:
initial $timeformat(-9, 3, " ns", 10);
$display("At %t: expected=%h actual=%h FAIL", $time, expected, actual);
The watchdog
The design should raise done. It has a deadlock, done never comes, and the testbench sits at wait(done) forever. A second initial block fixes that:
`timescale 1ns / 1ps
module tb_with_watchdog();
parameter MAX_SIM_TIME = 10_000; // ns
reg clk, rst_n, start;
wire done;
// ... instance ...
initial clk = 0;
always #5 clk = ~clk;
// the watchdog
initial begin
#MAX_SIM_TIME;
$fatal(1, "TIMEOUT at %0t: the design appears deadlocked", $time);
end
initial begin
$dumpfile("tb_with_watchdog.vcd");
$dumpvars(0, tb_with_watchdog);
rst_n = 0; start = 0;
repeat (4) @(posedge clk);
rst_n = 1;
@(posedge clk); #2;
start = 1;
@(posedge clk); #2;
start = 0;
wait (done); // if this never comes, the watchdog fires
$display("done at %t", $time);
$finish;
end
endmodule
A deadlock now ends with a message and a non-zero exit. Every production testbench has one; a MAX_SIM_TIME of about ten times the expected run is the usual setting, and when a test hangs the message says which one and when.
Severity
$fatal, $error, $warning and $info come from SystemVerilog and Icarus accepts them. They take the same format string as $display, and simulators can count and filter them by grade.
$fatal(level, message) prints and ends the run with a non-zero status; the level, 0 to 2, controls how much context the simulator prints. $error prints and continues, so that a run collects every failure instead of stopping at the first. $warning and $info are for things worth noting and progress messages.
// stop at the first mismatch
if (actual !== expected)
$fatal(1, "At %t: test %0d FAILED, expected=%h got=%h", $time, test_num, expected, actual);
// collect them all
if (actual !== expected) begin
$error("At %t: test %0d FAILED, expected=%h got=%h", $time, test_num, expected, actual);
error_count = error_count + 1;
end
$fatal for the watchdog, where continuing is pointless; $error for a failing vector, where you want to see the pattern across all the failures. Section 6.4 makes more of these.
The next article gets results out of the run in a form that outlives it: files.