Menu

Verilog $display and $monitor: Printing From a Testbench

How $display, $write, and $monitor work - the format specifiers you'll use, the difference between them, and when each is the right tool.

This page includes runnable editors - edit, run, and see output instantly.

The C printf Family of Verilog

Three system tasks let you print to the simulator's stdout:

  • $display - print once, append a newline.
  • $write - print once, no newline.
  • $monitor - print automatically every time a watched signal changes.

All three take a format string and a list of arguments, just like printf. The format specifiers are similar but Verilog-specific.

$display: The Default

$display is the workhorse:

You'll see something like:

Hello, world.
byte_val  = ab
byte_val  = 10101011
byte_val  = 171
byte_val  = 171 (no padding)
multi: nibble=1010 count=42

Notes:

  • %d pads to a default width based on the operand size. For an 8-bit reg, that's 3 characters (room for 255). The leading spaces can look ugly in tabular output - use %0d to suppress them.
  • %h, %b, %o have similar padding by default. Most testbench code uses %0 variants when the alignment isn't useful.
  • The newline at the end is automatic. Don't put \n at the end of a $display format string - you'll get a blank line.

Format Specifiers

The set Verilog supports:

SpecifierMeaning
%bbinary
%ddecimal (signed if signal is signed)
%h or %xhex
%ooctal
%csingle ASCII character (low 8 bits)
%sstring
%tsimulation time
%mhierarchical name of the current scope
%%a literal %
%0Xno leading padding, for any of %b, %d, etc.

%b, %d, %h, %o are the four you'll use 95% of the time. %t is the next most common - any time you want a timestamped log line.

$write: No Newline

$write is identical to $display except it doesn't append a newline:

Output:

abc
done

Useful for building a single line from a loop body:

$write("[");
for (integer i = 0; i < 8; i = i + 1) $write("%h ", arr[i]);
$display("]");

$monitor: Auto-Print on Change

$monitor registers a watch list. The simulator re-evaluates and prints whenever any signal mentioned in the format string changes:

You'll see three lines, one for each change in the inputs. No need to manually call $display after every stimulus change - $monitor does it.

Two limitations:

  • Only one $monitor can be active. Calling it again replaces the previous watch list. Use $monitoroff and $monitoron to temporarily suppress and re-enable.
  • Changes within the same time step collapse to one print. If a and b both change at time 5, the monitor fires once with both new values, not twice.

When to Use Each

  • $display: most testbench output. Call it explicitly after stimulus, after important state transitions, or inside a sampling always @(posedge clk) block.
  • $write: when you want to build a single line from a loop or several small chunks.
  • $monitor: when you want to track a small set of signals continuously and only see output when they change. Useful for initial debugging; harder to use in regression scripts because the output isn't deterministic in terms of total line count.

For most workflows, $display covers everything. Reach for $monitor only when continuous change-driven output is genuinely what you want.

Working With Time

$time returns the current simulation time as a 64-bit integer. Pair it with %0t:

$display("at %0t: signal flipped", $time);

The output looks like at 25: signal flipped (the unit depends on your timescale).

If you need sub-tick precision (rare), use $realtime instead - it returns a real.

%t automatically formats time with a default width that the simulator picks. %0t strips the padding.

Sampling on the Clock Edge

A clean idiom for monitoring sequential designs: a separate always @(posedge clk) block that prints once per cycle:

This sampling pattern guarantees one log line per clock - perfect for regression tests that pattern-match on output.

Logging to a File

Open a file with $fopen, log with $fdisplay (which works like $display but writes to a file handle):

integer fd;
initial begin
    fd = $fopen("results.txt", "w");
    $fdisplay(fd, "test=%s status=%s", test_name, status);
    $fclose(fd);
end

$fopen returns a 32-bit handle; pass it as the first argument to $fdisplay, $fwrite, $fstrobe, and so on. The functions are otherwise identical to their console-printing siblings.

What Comes Next

$display and friends give you text logs. For visual debugging - watching signals as voltages over time - you want a VCD waveform. The next doc, Dumpfile and VCD, covers $dumpfile and $dumpvars, the two calls that turn your simulation into a graphical waveform you can scrub through.

Frequently Asked Questions

What is the difference between $display and $monitor in Verilog?

$display prints once, immediately, when it's executed - like printf in C. $monitor registers a watch list; whenever any signal in the list changes, the formatted message is printed automatically. Only one $monitor can be active at a time; calling it again replaces the previous watch list.

What format specifiers does Verilog $display support?

The common ones: %b (binary), %d (decimal), %h (hex), %o (octal), %c (single character from low byte), %s (string), %t (simulation time), %m (hierarchical instance name). Use the %0d form to drop leading zero-padding - %d pads to a default width, %0d produces no padding.

What is $write in Verilog?

$write is like $display but doesn't append a newline. Useful when you want to build a line of output from multiple calls. The end-of-line $display (with no arguments or a trailing newline) terminates the line.

How do I print the simulation time in Verilog?

Use $time (or $realtime for sub-tick resolution) with the %t format specifier: $display("at time %t: ...", $time);. Use %0t to suppress the default padding. For a plain integer count of time units, %0d with $time works too: $display("t=%0d", $time);.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED