Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 22

Debugging with System Tasks: $display and $monitor

Recommended resources

Hello! In our previous lesson, we established a critical component of any sequential circuit testbench: the periodic clock signal. Now that we can generate inputs and a clock, the next logical step is to learn how to observe the behavior of our design and verify that it's working correctly.

This lesson introduces Verilog system tasks, which are built-in commands used for debugging and controlling the simulation environment. Our focus will be on the learning outcome: Use system tasks ($display, $monitor) to print signal values during simulation. You'll learn how to print messages to the console, track signal changes automatically, and format the output to make your testbenches more informative and easier to debug.

1. What are System Tasks?

In Verilog, any command that starts with a dollar sign ($) is a system task. These are commands interpreted by the simulator to perform utility functions during simulation, such as displaying text, reading files, or controlling the simulation's execution.

Crucially, system tasks are for simulation only and are not synthesizable. This means they have no hardware equivalent and are ignored by synthesis tools when converting your RTL code into logic gates. Their entire purpose is to help you, the designer, verify your code in a simulation environment.

The following short video provides a quick overview of system tasks and their categories.

Compiler directive & System tasks in Verilog | #14 | Verilog in English

This video from VLSI Point introduces the concept of system tasks and the main categories they fall into.

Watch from the beginning to understand what system tasks are (introduction). Then, watch the overview of the display tasks (variable monitoring) to get a first look at $display and $monitor. We will explore these in more detail.

As the video explained, we will focus on the display and monitoring tasks, which are the most common tools for "text-based" debugging.


2. $display: Taking a Snapshot

The $display task is the most straightforward way to print information. It works much like the printf() function in C or print() in Python. When the simulator executes a $display command, it prints the specified text and variable values to the console at that exact moment in simulation time and then moves on.

The basic syntax is:
$display("format string", signal_1, signal_2, ...);

The "format string" can contain plain text and format specifiers, which act as placeholders for the variables you list afterward.

Here's an example of how you can use $display with various format specifiers.

Verilog \$display System Task Examples
This code snippet shows how to use `\$display` with different format specifiers to print a variable in hexadecimal (%h), decimal (%d), and binary (%b), as well as printing strings (%s) and the current simulation time (%t).

To learn more about the available format specifiers, please review the table in the following resource.

Verilog Display Tasks

This article from ChipVerify provides a clear reference for display tasks.

Focus on the table under the heading "Verilog Format Specifiers". This lists the essential specifiers like %h, %d, %b, and %t. Also, quickly look at the table under "Verilog Escape Sequences" to see how to print special characters like a newline (\n).

Because $display executes only once when it's called, it's perfect for printing status messages or checking a signal's value at a specific, predetermined point in your testbench.

// Example of using $display in a stimulus block
initial begin
    $display("At time %0t: Starting the test.", $time);
    
    a = 0; b = 0;



    #10;
    $display("At time %0t: Inputs are a=%b, b=%b", $time, a, b);

    a = 1; b = 1;



    #10;
    $display("At time %0t: Inputs are a=%b, b=%b", $time, a, b);

    $display("At time %0t: Test finished.", $time);
end

3. $monitor: The Continuous Watcher

While $display is useful for specific moments, what if you want to see the value of a signal every time it changes, without having to manually place a $display statement after every single input change? This is exactly what $monitor is for.

You typically invoke $monitor only once in an initial block. The simulator then "watches" the list of variables you provided. Whenever any of those variables changes its value, the $monitor task automatically executes and prints the updated values.


4. Comparing $display and $monitor

The difference in behavior between $display and $monitor is fundamental to using them effectively.

System Tasks in Verilog - Difference between \$display and \$monitor

This article from VLSI Verify gives a very concise and clear summary of the key differences.

Read the three bullet points under the section "Difference between $display and $monitor". This will solidify your understanding of when to use each task.

To see this difference in action, let's watch how they are used in a practical testbench.

#22 How to write TESTBENCH in verilog || use of \$monitor, \$display,\$Stop,\$finish in verilog

This video from Component Byte walks through testbenches for an AND gate, first using $monitor and then using $display, making the comparison very clear.

First, watch the segment demonstrating $monitor in action. Notice how it's called only once but prints multiple lines of output as the inputs change. Next, watch the example of using $display. Here, the $display task must be called repeatedly after delays to see the outputs at specific times. Finally, the video provides a direct summary of the difference.

Key Distinction:

  • $display: Use for discrete, event-based messages. "Test X started." "Value at time 50ns is Y."
  • $monitor: Use for continuous signal tracing. "Show me the inputs and outputs whenever anything changes."

5. Putting It All Together

Let's integrate these system tasks into the testbench structure we've been building. We'll write a complete testbench for a simple 2-input AND gate.

First, here is the design we want to test (and_gate.v):

// Design Under Test (DUT)
module and_gate (
    input  logic a,
    input  logic b,
    output logic y
);
    assign y = a & b;
endmodule

Now, here is the testbench (and_gate_tb.v). It includes clock generation (though not used by this simple combinational DUT), stimulus, and both $display and $monitor tasks.

`timescale 1ns / 1ps

module and_gate_tb;

    // --- Signal Declarations ---
    reg clk;
    reg dut_a;
    reg dut_b;
    wire dut_y;

    // --- DUT Instantiation ---
    and_gate dut (
        .a(dut_a),
        .b(dut_b),
        .y(dut_y)
    );

    // --- Clock Generation (100 MHz) ---
    initial begin
        clk = 1'b0;
        forever #5 clk = ~clk;
    end

    // --- Continuous Monitoring ---
    initial begin
        // This will print every time dut_a, dut_b, or dut_y changes.
        $monitor("[$monitor] At time %0t: a=%b, b=%b -> y=%b", $time, dut_a, dut_b, dut_y);
    end

    // --- Stimulus and Simulation Control ---
    initial begin
        // Apply stimulus vectors
        $display("------ [Test Start] ------");
        
        dut_a = 0; dut_b = 0;



        #10;
        
        dut_a = 0; dut_b = 1;



        #10;
        
        dut_a = 1; dut_b = 0;



        #10;
        
        dut_a = 1; dut_b = 1;



        #10;
        
        $display("------ [Test End] ------");
        $finish; // System task to end the simulation
    end

endmodule

Expected Simulation Output:

------ [Test Start] ------
[$monitor] At time 0: a=0, b=0 -> y=0
[$monitor] At time 10: a=0, b=1 -> y=0
[$monitor] At time 20: a=1, b=0 -> y=0
[$monitor] At time 30: a=1, b=1 -> y=1
------ [Test End] ------

Analysis of the Output:

  • The $display tasks print their messages exactly once, at the beginning and end of the stimulus block.
  • The $monitor task, though written only once, is responsible for all four lines of signal output. It triggers at time 0 when the signals are first initialized, and then again at 10ns, 20ns, and 30ns each time the stimulus block changes the value of dut_a or dut_b.

Conclusion

You have now learned how to use Verilog's primary text-based debugging tools. This is a fundamental skill for verifying that your design behaves as expected before you ever try to synthesize it into hardware.

Key Takeaways:

  • System tasks like $display and $monitor are simulation-only constructs for debugging and control.
  • $display acts like a snapshot, printing information once at the moment it is executed.
  • $monitor acts like a continuous watcher, printing its information automatically whenever one of its argument signals changes value.
  • Use format specifiers (%t, %d, %h, %b, etc.) to control how values are printed.
  • A combination of $display for status messages and $monitor for signal tracing creates a powerful text-based debug environment.

While printing text to the console is incredibly useful, visualizing signal changes over time can often reveal bugs more intuitively. In our next lesson, we will explore the graphical equivalent of these tools by learning how to interpret a waveform viewer output to debug a simple design.