FPGA

Verilog System Tasks Display Monitor Finish

By CourseTron Team ·  2 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Verilog system tasks such as $display, $monitor, $finish, $random and $readmemh are simulator-only built-ins that print values, stop the run, create stimulus and load memory contents, and together they turn a bare testbench into a self-checking one.

    What a system task actually is

    Every identifier that starts with a dollar sign is a system task or system function. These are not synthesizable hardware. They are hooks into the simulator kernel (VCS, Xcelium, Questa or the open-source Icarus Verilog), which is why synthesis tools such as Design Compiler, Genus or Vivado either ignore them or flag them. Treat them as testbench plumbing: they belong in simulation files, or inside translate_off style guards in rare cases.

    The IEEE 1364 and 1800 standards group them into display tasks, simulation control, random and probability functions, file I/O and timing queries. This article covers the five that appear in almost every interview and every real testbench. If you are still shaky on how simulation time is measured, read our guide to the timescale directive in Verilog first, because every time value these tasks print depends on it.

    $display, $write and $strobe: print once, right now

    $display prints its arguments the moment the statement executes and appends a newline. $write is identical without the newline. The format specifiers follow C: %b binary, %h hex, %d decimal, %t time, %s string, %m hierarchical scope name.

    initial begin
      a = 8'hA5;
      $display("t=%0t a=%b (%h) in %m", $time, a, a);
    end

    A subtle trap: $display executes in the active region, so it sees the value before any non-blocking assignment in the same time step has updated. If you write q <= d; and then call $display("%b", q) in the same block, you print the old value. The fix is $strobe, which waits until the end of the time step. This is exactly the kind of behaviour explained in blocking vs non-blocking assignments.

    Key fact: $display samples in the active region; $strobe samples after all non-blocking updates in that time step. Use $strobe whenever you print a register driven by <=.

    One time step: when each task samples Active region blocking assigns $display runs here NBA region q <= d updates now new value visible Postponed region $strobe prints $monitor prints prints OLD q prints NEW q Time moves left to right inside a single simulation timestamp CourseTron · coursetron.com
    Why $display can show a stale register value while $strobe and $monitor show the settled one.

    $monitor: a watcher that prints on change

    $monitor registers a list of signals once and then prints a line automatically at the end of any time step in which one of them changes. Only one $monitor is active at a time; a second call replaces the first. You can pause and resume it with $monitoroff and $monitoron.

    initial $monitor("%0t: rst=%b en=%b count=%d", $time, rst, en, count);

    For a 4-bit counter running a 10 ns clock, this prints roughly one line per clock edge, which is perfect for a 20-cycle smoke test but unreadable at 20 million cycles. Beyond a few hundred lines, switch to waveform dumps with $dumpfile and $dumpvars, or to SystemVerilog assertions.

    Note that %t and $time return time in the units of the module's timescale, rounded to an integer. Use $realtime if your delays are fractional, such as #0.5 with a 1 ns/1 ps scale.

    $monitor in motion: a change triggers a print DUT: 4-bit counter count changes every clock edge Simulator console 10: count=1 20: count=2 30: count=3 value-change events No clock edge, no change, no line printed CourseTron · coursetron.com
    Animation: each change on the watched signal sends an event to the console, which prints a new line.

    $finish and $stop: ending the run

    $finish terminates the simulator and returns to the OS. $stop only suspends it and drops you to the interactive prompt. A regression farm running thousands of tests overnight needs $finish, otherwise a forgotten $stop hangs a licence seat until someone notices.

    The optional argument sets how much the tool prints on exit: 0 prints nothing, 1 (the default) prints simulation time and location, 2 adds memory and CPU statistics. Without any $finish, a free-running clock like always #5 clk = ~clk; keeps the event queue alive forever, so the simulation never ends by itself.

    Key fact: A testbench with a free-running clock and no $finish never terminates. Always put $finish at the end of the stimulus thread, ideally after a watchdog timeout as well.

    $random and $urandom: stimulus without typing vectors

    $random returns a signed 32-bit pseudo-random integer. To get a value in a range, mask or use modulus: $random % 256 can be negative, so write {$random} % 256 or use $urandom_range(255,0) in SystemVerilog-capable tools. Passing a seed variable, $random(seed), makes the sequence reproducible and updates the seed in place.

    • Fixed seed gives repeatable failures you can debug.
    • Different seed per regression run widens coverage.
    • Always print the seed with $display so a failing run can be replayed.

    Purely random stimulus rarely hits corner cases such as a FIFO going full, so real flows add constraints through SystemVerilog or UVM. Still, a quick $random loop finds shallow bugs in a combinational adder in minutes. Remember the width: assigning a 32-bit result to an 8-bit reg truncates, and mixing signed and unsigned operands follows the rules in reg versus wire in Verilog and the sign handling covered there.

    $readmemh and $readmemb: load memory from a file

    $readmemh("rom.hex", mem); reads hexadecimal words from a text file into an array; $readmemb reads binary. The file may contain @address markers and // comments. This is how you preload instruction memory for a small CPU, coefficients for a FIR filter or test vectors for a block.

    reg [15:0] rom [0:255];
    initial $readmemh("coeff.hex", rom);

    Practical numbers: if the file has 200 words and the array holds 256, the remaining 56 entries stay at x, and many simulators warn only when the file has more words than the array. On FPGAs, Vivado and Quartus both infer block RAM initial contents from $readmemh inside an initial block, which makes it one of the few system tasks that is genuinely useful in synthesizable code. Check your vendor's guide, and compare device families in our Xilinx vs Intel FPGA overview.

    Putting it together: a self-checking pattern

    TaskWhen it actsTypical testbench use
    $displayOnce, immediatelyMessages, PASS/FAIL lines
    $strobeEnd of time stepPrint registers driven by <=
    $monitorOn every changeQuick trace of a few signals
    $randomEach callRandom input vectors
    $readmemhTime 0 usuallyPreload ROM or golden vectors
    $finishOnceEnd the test cleanly
    initial begin
      $readmemh("golden.hex", exp);
      for (i = 0; i < 64; i = i + 1) begin
        din = $random(seed);
        #10;
        if (dout !== exp[i]) $display("FAIL %0d: got %h exp %h", i, dout, exp[i]);
      end
      $display("DONE seed=%0d", seed);
      $finish;
    end

    Note the !== operator: it compares x and z literally, so an unknown output is flagged instead of silently passing. Learning this pattern, then extending it to structural designs as in structural modeling in Verilog, is the fastest route to a testbench a reviewer will trust.

    Testbench flow with system tasks $readmemh golden vectors $random drive inputs DUT design under test compare !== exp $display $finish expected values travel to the checker CourseTron · coursetron.com
    Expected data comes from a file, stimulus from a random generator, and the verdict is printed before the run ends.

    Key fact: Interviewers love the question "why does my $display show the old value?" The answer is region ordering, not a tool bug.

    Teams that need production-grade verification environments around these basics can talk to the design-services group at Avecas, while students preparing for placements can follow structured lab tracks at ChipXpert.

    Want to practise these tasks on real testbenches with guided labs? Explore the hands-on CourseTron FPGA and Verilog courses and start building self-checking benches today.

    Frequently asked questions

    Are system tasks synthesizable?

    No, except for a few such as $readmemh in memory initialisation, which Vivado and Quartus support. Most are ignored or rejected by synthesis tools.

    What is the difference between $display and $monitor?

    $display prints once when executed. $monitor is armed once and prints automatically whenever any listed argument changes.

    Why does $random sometimes give negative numbers?

    It returns a signed 32-bit integer. Use concatenation {$random} or $urandom to get an unsigned value.

    What happens if I forget $finish?

    With a free-running clock the simulation runs until it hits a time limit or you kill it, wasting licences and regression time.

    Advertisement