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