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:
%dpads to a default width based on the operand size. For an 8-bitreg, that's 3 characters (room for255). The leading spaces can look ugly in tabular output - use%0dto suppress them.%h,%b,%ohave similar padding by default. Most testbench code uses%0variants when the alignment isn't useful.- The newline at the end is automatic. Don't put
\nat the end of a$displayformat string - you'll get a blank line.
Format Specifiers
The set Verilog supports:
| Specifier | Meaning |
|---|---|
%b | binary |
%d | decimal (signed if signal is signed) |
%h or %x | hex |
%o | octal |
%c | single ASCII character (low 8 bits) |
%s | string |
%t | simulation time |
%m | hierarchical name of the current scope |
%% | a literal % |
%0X | no 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
$monitorcan be active. Calling it again replaces the previous watch list. Use$monitoroffand$monitoronto temporarily suppress and re-enable. - Changes within the same time step collapse to one print. If
aandbboth 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 samplingalways @(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);.