Module 6: Verilog for Testbench & Simulation ยท 6.2 Simulation-Only Constructs

$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.

โ† $display, $monitor and $strobe Writing to Files โ†’