Verilog · Chapter 8.1 · System Tasks & Functions
$display vs $monitor vs $strobe vs $write — The Verilog Display Family
The four display tasks are the most-used system tasks in any Verilog testbench, and they look interchangeable on first read. But each one fires in a different scheduling region of the simulator event queue, and each carries a different timing meaning. The display and write tasks fire immediately, before non-blocking updates apply. The monitor task fires whenever any of its arguments change value. The strobe task fires at the end of the timestep, after non-blocking updates have applied. Picking the wrong task produces logs that look right at one glance and lie at the next. This page drills the whole family, covering region semantics, format specifiers, the monitor singleton rule, the on and off controls, and a decision matrix that picks the right tool for each observation you need to make.
Foundation20 min readVerilog$display$monitor$strobe$writeVerificationTestbench
Chapter 8 · Section 8.1 · System Tasks & Functions
1. The Engineering Problem
A verification engineer writes a D flip-flop testbench. The flop is correct. The simulator's log says it's broken.
module dff_test;
reg clk, d, q;
always @(posedge clk)
q <= d;
initial begin
clk = 0;
d = 1;
q = 0;
#5 clk = 1;
$display("After posedge: q = %b", q); // prints q=0
#5 $finish;
end
endmodule
// Output: "After posedge: q = 0"
// Engineer concludes the flop is broken — it didn't capture d=1 on the
// clock edge. Spends three hours debugging the RTL. The RTL is fine.The flop is correct. The simulator updated q to 1 on the clock edge. But $display printed q=0.
The reason: $display runs in the Active region of the event queue — before the non-blocking assignment q <= d actually applies. By the time the flop's update fires (in the NBA region), $display has already printed. The "wrong" value isn't a flop bug; it's a scheduling-region bug in the observation.
The right fix isn't to fix the flop — it's to use $strobe instead of $display:
$strobe("After posedge: q = %b", q); // prints q=1 — the new value$strobe runs in the Postponed region — after the non-blocking update applies. Same code, different timing, correct observation.
This is the canonical hazard of the display family: four tasks that look interchangeable in syntax but differ in when they run within a single time-step. This page drills the four tasks, the four regions, the format specifiers, the $monitor singleton discipline, and the decision matrix that picks the right tool every time.
2. The Four Tasks at a Glance
The display family has four members. One-line essence each.
| Task | Fires | Region | Newline | Use |
|---|---|---|---|---|
$display | once when executed | Active | yes | Event-time one-shot prints |
$write | once when executed | Active | no | Build one line from multiple calls |
$monitor | on argument change | Monitor | yes | Continuous tracking of a signal set |
$strobe | at end of timestep | Postponed | yes | Post-NBA values (flop outputs after clock edge) |
The five differentiators:
- Frequency — one-shot (
$display,$write,$strobe) vs continuous ($monitor). - Scheduling region — Active (
$display,$write) vs Monitor ($monitor) vs Postponed ($strobe). - Newline — automatic (
$display,$monitor,$strobe) vs none ($write). - Singleton —
$monitorenforces only-one-active; the others have no such limit. - Pre/post NBA — Active region sees PRE-NBA values; Postponed (and Monitor) see POST-NBA values.
The full sections that follow drill each.
3. $display — Immediate Print
The most-used member of the family.
3.1 Syntax
$display(format_string, arg1, arg2, ...);
$display("data = %h", data); // prints "data = 5a", newline
$display(); // prints empty newline3.2 Semantics
- Fires once when the statement executes.
- Runs in the Active region — same region as blocking assignments and non-blocking RHS evaluation.
- Automatic newline at end of the formatted output.
- No singleton rule — multiple
$displaystatements all fire independently.
3.3 Canonical example
module display_demo;
reg [7:0] data;
initial begin
$display("========== Simulation Start ==========");
data = 8'hA5;
$display("Time: %0t ns | Data: 0x%h (%b)", $time, data, data);
#10 data = 8'hFF;
$display("Time: %0t ns | Data: 0x%h (%b)", $time, data, data);
$display("========== Simulation End ==========");
$finish;
end
endmodule
/* Output:
========== Simulation Start ==========
Time: 0 ns | Data: 0xa5 (10100101)
Time: 10 ns | Data: 0xff (11111111)
========== Simulation End ==========
*/3.4 When to use
Event-time one-shot prints — banners, state transitions, debug checkpoints, test verdicts. The default reach when no other constraint applies.
4. $write — Same as $display, No Newline
The same as $display minus the automatic newline.
4.1 Syntax
$write(format_string, arg1, arg2, ...);
$write("progress: "); // no newline at end
$write("done"); // appends to the same line
$display(""); // explicit newline4.2 Semantics
Identical to $display except:
- No automatic newline. The next print appends to the same line.
- To end the line, either let the next
$displayprint one, or call$write("\n")/$display("").
4.3 Canonical example — progress bar
module write_demo;
integer i;
initial begin
$write("Testing: [");
for (i = 0; i < 10; i = i + 1) begin
#5 $write(".");
end
$display("] Complete!");
$finish;
end
endmodule
/* Output:
Testing: [..........] Complete!
*/4.4 When to use
Building one line from multiple calls — progress bars, dynamic table rows, custom-formatted output where each column comes from a separate computation.
5. $monitor — Continuous Watch
The "set it once, fires forever" task.
5.1 Syntax
$monitor(format_string, arg1, arg2, ...);
$monitor("clk=%b reset=%b state=%b", clk, reset, state);
$monitoroff; // pause monitoring
$monitoron; // resume monitoring5.2 Semantics
- Fires when any argument changes, automatically, once per time-step.
- Runs in the Monitor region — after the NBA region, before Postponed. Sees POST-NBA values.
- Only one
$monitorcan be active at a time. Calling$monitoragain replaces the previous instance. - Prints at time 0 with initial values (this is automatic; no extra code needed).
- Companion tasks:
$monitoroffpauses monitoring;$monitoronresumes.
5.3 Canonical example
module monitor_demo;
reg [2:0] state;
reg clk, reset;
initial begin
// Set up monitoring — fires automatically on any argument change.
$monitor("Time=%0t | Clk=%b | Reset=%b | State=%b (%0d)",
$time, clk, reset, state, state);
clk = 0; reset = 1; state = 0;
#5 reset = 0;
#5 state = 1;
#5 clk = 1;
#5 state = 2; clk = 0;
#5 $monitoroff; // pause
#5 state = 3; // not printed
#5 $monitoron; // resume
#5 state = 4; // printed
$finish;
end
endmodule
/* Output:
Time=0 | Clk=0 | Reset=1 | State=000 (0)
Time=5 | Clk=0 | Reset=0 | State=000 (0)
Time=10 | Clk=0 | Reset=0 | State=001 (1)
Time=15 | Clk=1 | Reset=0 | State=001 (1)
Time=20 | Clk=0 | Reset=0 | State=010 (2)
Time=35 | Clk=0 | Reset=0 | State=100 (4)
*/The state=3 change between t=30 and t=35 is skipped — $monitoroff was in effect.
5.4 When to use
Tracking a small set of signals through the entire simulation — clock + reset + state, or all signals on a bus, or all flags in a status register. One call at the top of the testbench replaces dozens of $display calls scattered across @(posedge clk) blocks.
6. $strobe — End-of-Timestep Print
The "see post-NBA values" task.
6.1 Syntax
$strobe(format_string, arg1, arg2, ...);
$strobe("q after posedge = %b", q);6.2 Semantics
- Fires once when the statement executes.
- Runs in the Postponed region — last region of the time-step, after Active, NBA, and Monitor. Sees POST-NBA values.
- Automatic newline.
- No singleton rule.
6.3 The pre-NBA vs post-NBA distinction (the canonical example)
module dff_observation;
reg clk, d, q;
always @(posedge clk) begin
q <= d; // non-blocking — fires in NBA region
$display("$display: t=%0t, q=%b", $time, q); // Active region — sees OLD q
$strobe ("$strobe: t=%0t, q=%b", $time, q); // Postponed region — sees NEW q
end
initial begin
clk = 0; d = 1; q = 0;
#5 clk = 1;
#5 $finish;
end
endmodule
/* Output at t=5:
$display: t=5, q=0 <-- shows q BEFORE the NBA update
$strobe: t=5, q=1 <-- shows q AFTER the NBA update
*/Same statement at the same time. $display prints the OLD value of q because it runs before the non-blocking assignment applies; $strobe prints the NEW value because it runs after. The RTL is identical — only the observation timing differs.
6.4 When to use
Anywhere you need to see the result of a non-blocking assignment in the same time-step it was scheduled. The most common case: printing the value of a flop's q after the clock edge applies its update.
7. The Active vs Postponed Region Mental Model
8. Format Specifiers
The format string syntax is borrowed from C's printf family with Verilog-specific extensions.
| Specifier | Meaning | Example |
|---|---|---|
%h / %H | Hexadecimal | 5a, ff |
%d / %D | Signed decimal | -5, 255 |
%b / %B | Binary | 10110101 |
%o / %O | Octal | 377 |
%c / %C | ASCII character | A |
%s / %S | String | "hello" |
%t / %T | Simulation time | 100 |
%m / %M | Hierarchical name | tb.dut.u_alu |
%v / %V | Net signal strength | St1, Pu0 |
%e / %E | Real (exponential) | 1.5e+02 |
%f / %F | Real (fixed-point) | 150.0 |
%g / %G | Real (compact) | 150 |
8.1 Width and zero-padding
$display("%5d", 42); // prints " 42" — width 5, space-padded
$display("%05d", 42); // prints "00042" — width 5, zero-padded
$display("%0d", 42); // prints "42" — minimum-width (no padding)
$display("%10h", 42); // prints " 2a" — width 10, hexThe leading 0 in the width spec forces zero-padding (otherwise space-padding); the %0 form prints the minimum width with no leading spaces.
8.2 The %0t convention
$display("Time: %t", $time); // prints " 100" — wide default
$display("Time: %0t", $time); // prints "100" — compactUse %0t for time prints in production code — the default %t width is huge (20+ chars on most simulators) and produces unreadable logs.
8.3 The %h vs %0d discipline
reg [31:0] data;
$display("data = %h", data); // prints "data = 5a3f9c01" — hex, predictable width
$display("data = %d", data); // prints "data = 1513037825" — signed decimal, hard to read
$display("data = %0d", data); // prints "data = 1513037825" — minimum-width decimalFor register values, prefer %h (hex is the engineer's native format for vectors). For unsigned counts, use %0d. Avoid bare %d for vector values — large positives can print as negative numbers due to signed interpretation.
9. Visual Explanation
Three figures cover the display family.
9.1 Visual A — task family tree
The four tasks and how they relate.
Display family — four tasks, four roles
data flow9.2 Visual B — scheduling regions and what each task sees
The single time-step's region timeline.
9.3 Visual C — decision matrix
Pick the right task for each observation requirement.
10. The Decision Matrix in Prose
Pick the task by answering two questions:
Question 1 — Frequency: Do you want one print at this specific point in time, or continuous tracking that fires on every change?
- One print →
$display(or$writeif you want no newline) or$strobe - Continuous →
$monitor
Question 2 — Timing: If one print, do you need to see POST-NBA values (the new value of a flop after its clock edge applied) or are pre-NBA values fine (almost all general-purpose prints)?
- Pre-NBA OK →
$display/$write - Need post-NBA →
$strobe
Combined rule:
| Need | Task |
|---|---|
| One-shot, pre-NBA OK, newline | $display |
| One-shot, pre-NBA OK, no newline (building a line) | $write |
| One-shot, need post-NBA | $strobe |
| Continuous tracking | $monitor |
For 90% of testbench code, the answer is $display. The exceptions are:
- Printing a flop's output after its clock edge →
$strobe. - Tracking a small set of signals end-to-end →
$monitor. - Building a progress bar or custom-format table row →
$write.
11. Industry Use Cases
Five patterns where the display family shows up in production.
11.1 Per-cycle status logging
Every non-trivial RTL module has $display lines that catch regressions. Common shapes: bus-transaction completion ($display("[bus] read addr=%h data=%h", a, d)), state-machine transitions, error conditions, watchdog events.
11.2 Post-NBA flop observation
Self-checking testbenches that compare expected vs actual register values after a clock edge use $strobe to read the updated q value:
always @(posedge clk) begin
expected_q <= compute_expected();
`ifdef SIMULATION
$strobe("[check] t=%0t q=%h expected=%h match=%b",
$time, dut.q, expected_q, (dut.q === expected_q));
`endif
end11.3 Continuous handshake monitoring
Bus protocols with valid/ready handshakes are easier to follow with $monitor:
$monitor("[t=%0t] valid=%b ready=%b data=%h",
$time, axi_valid, axi_ready, axi_data);One call replaces a dozen scattered $display instances in @(posedge clk) blocks.
11.4 Dynamic table generation
$write + $display build readable test reports:
$display("+-------+----------+--------+");
$display("| Cycle | Result | Status |");
$display("+-------+----------+--------+");
$write ("| %0d | 0x%h ", cycle, result);
$display("| %s |", pass ? "PASS" : "FAIL");
$display("+-------+----------+--------+");11.5 Verbosity-gated debug
Combine the display family with conditional compilation (the 7.4 page) for tiered verbosity:
`ifdef DEBUG_VERBOSE
$display("[verbose] state=%s a=%h b=%h", state, a, b);
`elsif DEBUG
$display("[debug] state=%s", state);
`endifThree build targets — silent (no DEBUG), normal (+define+DEBUG), verbose (+define+DEBUG_VERBOSE) — from one source file.
12. Common Mistakes
Six pitfalls that catch every working verification engineer at least once.
12.1 $display shows pre-NBA value of a flop output
The canonical hazard from §1. $display runs in the Active region, so it prints the OLD value of any signal that was updated via non-blocking assignment in the same time-step. Fix: use $strobe.
12.2 $monitor overwritten silently
Only one $monitor instance can be active at a time. Calling $monitor again replaces the previous one without warning:
$monitor("a = %h", a);
// ... later
$monitor("b = %h", b);
// The first monitor is silently disabled. Only the second fires.Fix: combine all tracked signals into one $monitor call, or use separate $display-in-@always blocks.
12.3 %d on a vector silently produces negative numbers
reg [31:0] addr = 32'hF000_0000;
$display("addr = %d", addr); // prints "addr = -268435456" (signed interpretation)
$display("addr = %0d", addr); // prints "addr = -268435456" still
$display("addr = %h", addr); // prints "addr = f0000000" — much betterFix: prefer %h for vector values; %0d for unsigned counts where decimal makes sense.
12.4 %t produces unreadable wide times
The default %t width is huge:
$display("t = %t", $time); // prints "t = 100" (with default 1ns/1ps)
$display("t = %0t", $time); // prints "t = 100" — compactFix: always use %0t in production logs.
12.5 Performance impact of $monitor on busy signals
$monitor fires on every change of any tracked argument. Tracking a fast-toggling clock signal can produce millions of log lines per simulation second:
$monitor("clk=%b", clk); // fires twice per clock period — log floodsFix: track edge-significant signals only; use clock-gated @(posedge clk) $display(...) for per-cycle prints; or use $monitoroff / $monitoron to gate observation windows.
12.6 Format-argument count mismatch
$display("%h %h", a); // forgot the second arg — prints "5a xxxxxxxx"
$display("%h", a, b); // extra arg — b is silently ignoredThe simulator's behaviour on count mismatch is vendor-dependent — some warn, some don't. Fix: count format specifiers against args every time, or use VCS's -format-warning / Questa's -warning 7033 flags to enforce the check.
13. Debugging Lab
Three display-family debug post-mortems
14. Coding Guidelines
Six rules teams converge on.
$displayis the default. Reach for it first; switch to others only when a specific requirement (post-NBA, continuous, no-newline) applies.$strobefor post-NBA flop observation. Any time you print a signal that was assigned via<=in the same time-step, use$strobe.- One
$monitorper simulation. Combine all tracked signals; replace with$display-in-@alwaysif you need multiple independent watches. %hfor vectors,%0dfor counts,%0tfor time. Avoid bare%don multi-bit vectors; avoid bare%t(default width is huge).- Wrap every display task in
`ifdef SIMULATION(or the project's equivalent). The 7.4 conditional-compilation page covers the discipline. - Prefix every log line with context — module name, transaction id, time, or category tag. Logs with no context are unreadable when 10,000 lines deep.
15. Interview Q&A
16. Exercises
Three exercises that turn display-family choice into reflex.
Exercise 1 — Predict the output
For each snippet, predict the printed output exactly.
// Snippet A
reg clk = 0, d = 1, q = 0;
always @(posedge clk) q <= d;
initial begin
#5 clk = 1;
$display("display: q = %b", q);
$strobe ("strobe: q = %b", q);
#5 $finish;
end
// Snippet B
reg [7:0] x;
initial begin
$monitor("x = %h", x);
x = 8'h00;
#5 x = 8'hAA;
#5 x = 8'hFF;
#5 $finish;
end
// Snippet C
integer i;
initial begin
$write("[");
for (i = 0; i < 5; i = i + 1) begin
#5 $write("*");
end
$display("]");
$finish;
endHints. Apply the scheduling-region rules from §7 to Snippet A. Apply the $monitor "fires on any change, once per time-step" rule to Snippet B. Apply the no-newline rule to Snippet C.
Exercise 2 — Pick the right task
For each requirement, name the best task and write the call.
| # | Requirement |
|---|---|
| 1 | Print a one-line debug message at a specific code location |
| 2 | Print the value of a flop's q after the clock edge applied its update |
| 3 | Continuously track clk, reset, and state from time 0 to end |
| 4 | Build a progress bar [*****] from 5 separate calls |
| 5 | Print the current simulation time in nanoseconds |
| 6 | Print a 32-bit register value in hex with no leading spaces |
| 7 | Print the hierarchical name of the calling scope |
What to produce. For each, name the task, the format string, and the argument list.
Exercise 3 — Fix the display bugs
This testbench has four display-family bugs. Identify and fix each.
module buggy_tb;
reg clk = 0, d = 1, q = 0;
reg [31:0] addr = 32'hF000_0000;
reg [7:0] state;
always @(posedge clk) q <= d;
always #5 clk = ~clk;
initial begin
$monitor("a: clk=%b", clk); // Bug 1?
$monitor("b: state=%h", state); // Bug 2?
#10 $display("After clock: q = %b", q); // Bug 3?
$display("base addr = %d", addr); // Bug 4?
// ... what else is missing?
#20 $finish;
end
endmoduleHints. Bug 1+2 are a singleton issue. Bug 3 is a scheduling-region issue. Bug 4 is a format-specifier issue.
17. Summary
The display family — $display, $write, $monitor, $strobe — has four members that look similar but differ in when they fire and what they observe.
The four tasks:
$display— one-shot, Active region, automatic newline. The default.$write— one-shot, Active region, no newline. For building lines from multiple calls.$monitor— continuous, Monitor region, singleton. For tracking signal sets.$strobe— one-shot, Postponed region. For post-NBA observations (flop outputs after clock edges).
The four scheduling regions:
- Active — blocking assigns,
$display,$writefire. Non-blocking RHS captured. - NBA — queued non-blocking updates apply.
- Monitor —
$monitorchecks for argument changes. Sees post-NBA. - Postponed —
$strobefires. Last region; sees post-NBA.
The decision matrix:
| Need | Task |
|---|---|
| One print, pre-NBA OK, newline | $display |
| One print, pre-NBA OK, no newline | $write |
| One print, need post-NBA value | $strobe |
| Continuous tracking | $monitor |
The format-specifier discipline:
%hfor vectors (the engineer's native format).%0dfor unsigned counts.%0tfor time (avoid bare%t— default width is huge).%bfor bit-level inspection.%donly when sign genuinely matters.%mfor the hierarchical name of the calling scope.
The day-to-day discipline:
$displayis the default reach. Switch only when a specific requirement applies.$strobefor post-NBA flop observation. Otherwise the testbench prints stale values and "the flop is broken" debug sessions waste hours.- One
$monitorper simulation. Combine tracked signals; or use$display-in-@alwaysfor independent watches. - Wrap every display call in
`ifdef SIMULATION. Synthesis tools strip them at best, delete containing blocks at worst.
The next four sub-pages drill the rest of the system-task family: 8.2 $random covers the randomization family, 8.3 Time Functions drills $time / $realtime / $stime, 8.4 Conditional Simulation covers the assertion-and-severity layer ($error, $warning, $fatal), and 8.5 Simulation Control closes Chapter 8 with $finish / $stop / $exit.
Related Tutorials
- System Tasks & Functions — Chapter 8; the parent overview of the eight functional families.
- [
timescale](/verilog/timescale-directive) — Chapter 7.3; the unit / precision contract that%t` formatting rides on. - Conditional Compilation — Chapter 7.4; the
\ifdef SIMULATION` discipline that gates every display call. - reg — Chapter 5.2.1; the blocking-vs-non-blocking semantics that produce the pre-NBA / post-NBA observation distinction.