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

$display, $monitor and $strobe

Section 6.1 was the shape of a testbench. This section is the tools that only exist in simulation, and it starts with printing. Verilog has three ways to print a value and they are not interchangeable, because in an event-driven simulation when a print happens matters as much as what it prints. $display in the wrong place shows you a value from before the flip-flop updated; $monitor used carelessly buries you in duplicate lines. The reason is the time step of section 5.1: active region, then non-blocking updates, then postponed. Each task hooks into a different part of it.

When each fires

$display runs the moment the statement is reached, in the active region, and prints whatever the signals hold right then. That makes it right for logging what the stimulus just did. In the same step as a <=, it prints the old value:

always @(posedge clk) begin
    q <= d;
    $display("q=%b", q);     // the old q
end

$strobe waits until the end of the time step, after every non-blocking update has landed, and prints the settled values:

always @(posedge clk) begin
    q <= d;
    $strobe("q=%b", q);      // the new q
end

$monitor is called once and installs a watcher. Whenever any of its arguments changes, it prints a line at the end of that time step, with no further calls:

initial begin
    $monitor("At %0t: clk=%b d=%b q=%b", $time, clk, d, q);
end

Only one $monitor is active at a time; a second call replaces the first, silently, which confuses testbenches with several initial blocks each installing one. $monitoroff and $monitoron suspend and resume the output without removing the watcher, useful around a noisy initialisation.

Format specifiers

Specifier Meaning
%b binary
%o octal
%d decimal
%h hex
%t time, formatted by $timeformat
%s string
%f, %e real, fixed or exponential
%0d, %0h no leading spaces

The 0 modifier drops the padding and is what you want unless you are aligning columns.

%t prints the raw time in the smallest unit, usually picoseconds, which is unwieldy. $timeformat fixes it:

initial $timeformat(-9, 3, " ns", 10);    // nanoseconds, 3 decimals, suffix, width

The first argument is a power of ten: -9 for ns, -12 for ps, -6 for µs.

$write is $display without the newline, for building a line in pieces:

$write("a=%b b=%b  ", a, b);
$write("y=%b  ", y);
if (y !== expected) $write("FAIL"); else $write("pass");
$display("");

All three at once

`timescale 1ns / 1ps

module dff (
    input  wire clk,
    input  wire d,
    output reg  q
);
    always @(posedge clk)
        q <= d;
endmodule

module tb_print_demo();

    reg  clk, d;
    wire q;

    dff dut (.clk(clk), .d(d), .q(q));

    initial clk = 0;
    always #5 clk = ~clk;

    initial $timeformat(-9, 0, " ns", 6);

    initial begin
        $monitor("[MONITOR] %t  clk=%b d=%b q=%b", $time, clk, d, q);
    end

    initial begin
        $dumpfile("tb_print_demo.vcd");
        $dumpvars(0, tb_print_demo);

        d = 0;

        @(posedge clk);
        $display("[DISPLAY] %t  clk=%b d=%b q=%b  (old q, update pending)", $time, clk, d, q);
        $strobe ("[STROBE]  %t  clk=%b d=%b q=%b  (new q, after the update)", $time, clk, d, q);

        #2;
        d = 1;
        $display("[DISPLAY] %t  d=1 driven, q=%b", $time, q);

        @(posedge clk);
        $display("[DISPLAY] %t  clk=%b d=%b q=%b  (old q again)", $time, clk, d, q);
        $strobe ("[STROBE]  %t  clk=%b d=%b q=%b  (new q)", $time, clk, d, q);

        #2;
        d = 0;

        @(posedge clk);
        #2;

        $finish;
    end

endmodule
[MONITOR]      0 ns  clk=0 d=x q=x
[MONITOR]      0 ns  clk=0 d=0 q=x
[MONITOR]      5 ns  clk=1 d=0 q=x
[DISPLAY]      5 ns  clk=1 d=0 q=x  (old q, update pending)
[STROBE]       5 ns  clk=1 d=0 q=0  (new q, after the update)
[MONITOR]      5 ns  clk=1 d=0 q=0
[MONITOR]      7 ns  clk=1 d=1 q=0
[MONITOR]     10 ns  clk=0 d=1 q=0
[MONITOR]     15 ns  clk=1 d=1 q=0
[DISPLAY]     15 ns  clk=1 d=1 q=0  (old q again)
[STROBE]      15 ns  clk=1 d=1 q=1  (new q)
[MONITOR]     15 ns  clk=1 d=1 q=1

At 5 ns $display shows q=x and $strobe at the same time shows q=0. The monitor fires twice at 5 ns, once when the clock rises and once when q changes after the update, and fires at 7 ns on its own when d changes.

The rule of thumb: $display for what the stimulus just did, $strobe for what a flip-flop produced, $monitor for a running trace of a few signals, installed once and usually switched off once a real checker exists. Do not call $monitor inside a loop or an always block; each call replaces the watcher.

Printing is observing. The next article is controlling: ending a run, reading the time, and making sure a hung simulation stops on its own.

← Automatic Pass and Fail $finish, $stop and Time →