Skip to content

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$strobe("After posedge: q = %b", q);  // prints q=1 — the new value

$strobe runs in the Postponed regionafter 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.

TaskFiresRegionNewlineUse
$displayonce when executedActiveyesEvent-time one-shot prints
$writeonce when executedActivenoBuild one line from multiple calls
$monitoron argument changeMonitoryesContinuous tracking of a signal set
$strobeat end of timestepPostponedyesPost-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$monitor enforces 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-syntax.v — one-shot print
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display(format_string, arg1, arg2, ...);
$display("data = %h", data);              // prints "data = 5a", newline
$display();                               // prints empty newline

3.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 $display statements all fire independently.

3.3 Canonical example

display-example.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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-syntax.v — partial-line print
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$write(format_string, arg1, arg2, ...);
$write("progress: ");                     // no newline at end
$write("done");                           // appends to the same line
$display("");                             // explicit newline

4.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 $display print one, or call $write("\n") / $display("").

4.3 Canonical example — progress bar

write-example.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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-syntax.v — continuous watch
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$monitor(format_string, arg1, arg2, ...);
$monitor("clk=%b reset=%b state=%b", clk, reset, state);
$monitoroff;                              // pause monitoring
$monitoron;                               // resume monitoring

5.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 $monitor can be active at a time. Calling $monitor again replaces the previous instance.
  • Prints at time 0 with initial values (this is automatic; no extra code needed).
  • Companion tasks: $monitoroff pauses monitoring; $monitoron resumes.

5.3 Canonical example

monitor-example.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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-syntax.v — end-of-timestep print
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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)

strobe-vs-display.v — same code, different timing
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

SpecifierMeaningExample
%h / %HHexadecimal5a, ff
%d / %DSigned decimal-5, 255
%b / %BBinary10110101
%o / %OOctal377
%c / %CASCII characterA
%s / %SString"hello"
%t / %TSimulation time100
%m / %MHierarchical nametb.dut.u_alu
%v / %VNet signal strengthSt1, Pu0
%e / %EReal (exponential)1.5e+02
%f / %FReal (fixed-point)150.0
%g / %GReal (compact)150

8.1 Width and zero-padding

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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, hex

The 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("Time: %t",  $time);    // prints "                   100" — wide default
$display("Time: %0t", $time);    // prints "100" — compact

Use %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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 decimal

For 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 flow
Display family — four tasks, four rolesdisplay family$display / $write / $monitor / $strobe$displayActive, one-shot, newline$writeActive, one-shot, no newline$monitorMonitor, continuous, singleton$strobePostponed, one-shot, sees post-NBA
Four tasks split across two axes — frequency (one-shot vs continuous) and timing (Active vs Postponed). $display and $write are the immediate one-shots; $monitor is the continuous watch; $strobe is the post-NBA observation.

9.2 Visual B — scheduling regions and what each task sees

The single time-step's region timeline.

Scheduling regions and task timingActive region$display, $write, blockingassignsNBA regionq <= d applies hereMonitor region$monitor argument-changecheckPostponed region$strobe runs here12
One Verilog time-step divided into four regions. Active runs first (blocking assigns, $display, $write); NBA applies queued non-blocking updates next; Monitor checks $monitor's tracked-arg list (sees post-NBA values); Postponed runs last ($strobe). The same q read by $display in Active prints the OLD value; the same q read by $strobe in Postponed prints the NEW value.

9.3 Visual C — decision matrix

Pick the right task for each observation requirement.

Display task decision matrixONE printaxis: frequencyCONTINUOUSaxis: frequencypre-NBA OKaxis: timingneed post-NBAaxis: timing$display (or $write)Active, one-shot$monitorMonitor, continuous$strobePostponed, one-shot$monitorMonitor IS post-NBA12
Decision matrix. Top axis: do you want one print or continuous tracking? Side axis: do you need the post-NBA value (flop output after clock edge) or are pre-NBA values fine (most prints)? The four cells map to the four tasks. Bonus: $write replaces $display when you want to suppress the newline (build a line from multiple calls).

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 $write if 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:

NeedTask
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
end

11.3 Continuous handshake monitoring

Bus protocols with valid/ready handshakes are easier to follow with $monitor:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef DEBUG_VERBOSE
    $display("[verbose] state=%s a=%h b=%h", state, a, b);
`elsif DEBUG
    $display("[debug] state=%s", state);
`endif

Three 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 better

Fix: 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("t = %t",  $time);    // prints "t =                    100" (with default 1ns/1ps)
$display("t = %0t", $time);    // prints "t = 100" — compact

Fix: 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$monitor("clk=%b", clk);   // fires twice per clock period — log floods

Fix: 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$display("%h %h", a);          // forgot the second arg — prints "5a xxxxxxxx"
$display("%h", a, b);          // extra arg — b is silently ignored

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

  1. $display is the default. Reach for it first; switch to others only when a specific requirement (post-NBA, continuous, no-newline) applies.
  2. $strobe for post-NBA flop observation. Any time you print a signal that was assigned via <= in the same time-step, use $strobe.
  3. One $monitor per simulation. Combine all tracked signals; replace with $display-in-@always if you need multiple independent watches.
  4. %h for vectors, %0d for counts, %0t for time. Avoid bare %d on multi-bit vectors; avoid bare %t (default width is huge).
  5. Wrap every display task in `ifdef SIMULATION (or the project's equivalent). The 7.4 conditional-compilation page covers the discipline.
  6. 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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;
end

Hints. 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
1Print a one-line debug message at a specific code location
2Print the value of a flop's q after the clock edge applied its update
3Continuously track clk, reset, and state from time 0 to end
4Build a progress bar [*****] from 5 separate calls
5Print the current simulation time in nanoseconds
6Print a 32-bit register value in hex with no leading spaces
7Print 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

Hints. 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, $write fire. Non-blocking RHS captured.
  • NBA — queued non-blocking updates apply.
  • Monitor$monitor checks for argument changes. Sees post-NBA.
  • Postponed$strobe fires. Last region; sees post-NBA.

The decision matrix:

NeedTask
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:

  • %h for vectors (the engineer's native format).
  • %0d for unsigned counts.
  • %0t for time (avoid bare %t — default width is huge).
  • %b for bit-level inspection.
  • %d only when sign genuinely matters.
  • %m for the hierarchical name of the calling scope.

The day-to-day discipline:

  • $display is the default reach. Switch only when a specific requirement applies.
  • $strobe for post-NBA flop observation. Otherwise the testbench prints stale values and "the flop is broken" debug sessions waste hours.
  • One $monitor per simulation. Combine tracked signals; or use $display-in-@always for 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.

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