If Verilog code can be synthesized into logic gates without ever being simulated, why simulate at all? Because synthesis only translates the code; it cannot tell whether the code does what you intended. Simulation applies different inputs to the RTL at different times, the way a real chip would be used, and lets you check that the outputs are correct before anything is built.

11 min read | Beginner Level

What You'll Learn

  • How a simulator executes Verilog processes over time
  • How to read a simulation log and a waveform, and how to dump waveforms to a file
  • The five event regions of a time step and the order in which they run
  • How race conditions arise between processes, and how to avoid them

The testbench of the previous article connected a design to stimulus and checks. Consider a positive edge detector with a clock and an input signal: it should produce a one-cycle pulse on pe whenever the signal goes from 0 to 1. The timing diagram below shows how the output should respond to the input. A simulation produces exactly this kind of diagram from the RTL code, so it can be compared with the intended behavior.

testbench-setup timing-diagram

A simulator is a program that calculates the outputs of a design for the inputs it is given. Verilog follows a discrete event model: values only change at specific points in time, called events, and the simulator jumps from one event to the next without spending effort on the time in between. The code itself is a collection of processes, such as always blocks, initial blocks and continuous assignments, which all run in parallel, each waiting for its own delays and events.

Example

The testbench below has no design instance. It only creates two signals so that you can see how a simulator advances time. clk is a clock generated by an always block that toggles every 5 ns, and sig is driven by an initial block at different delays.


module tb;
  reg clk;
  reg sig;

  // Clock generation
  // Process starts at time 0ns and loops after every 5ns
  always #5 clk = ~clk;

  // Initial block : Process starts at time 0ns
  initial begin
    // This system task will print out the signal values everytime they change
    $monitor("Time = %0t clk = %0d sig = %0d", $time, clk, sig);

    // Also called stimulus, we simply assign different values to the variables
    // after some simulation "delay"
    sig = 0;
    #5 clk = 0;        // Assign clk to 0 at time 5ns
    #15  sig = 1;      // Assign sig to 1 at time 20ns (#5 + #15)
    #20  sig = 0;      // Assign sig to 0 at time 40ns (#5 + #15 + #20)
    #15  sig = 1;      // Assign sig to 1 at time 55ns (#5 + #15 + #20 + #15)
    #10  sig = 0;      // Assign sig to 0 at time 65ns (#5 + #15 + #20 + #15 + #10)
    #20 $finish;       // Finish simulation at time 85ns
  end
endmodule
Output
ncsim> run
Time = 0 clk = x sig = 0
Time = 5 clk = 0 sig = 0
Time = 10 clk = 1 sig = 0
Time = 15 clk = 0 sig = 0
Time = 20 clk = 1 sig = 1
Time = 25 clk = 0 sig = 1
Time = 30 clk = 1 sig = 1
Time = 35 clk = 0 sig = 1
Time = 40 clk = 1 sig = 0
Time = 45 clk = 0 sig = 0
Time = 50 clk = 1 sig = 0
Time = 55 clk = 0 sig = 1
Time = 60 clk = 1 sig = 1
Time = 65 clk = 0 sig = 0
Time = 70 clk = 1 sig = 0
Time = 75 clk = 0 sig = 0
Time = 80 clk = 1 sig = 0
Simulation complete via $finish(1) at time 85 NS + 0

Both processes start at time 0. clk has no value yet, so it prints as x, and ~x is still x. At 5 ns, the initial block sets clk to 0, and from then on the always block toggles it every 5 ns. sig changes at the times given by the accumulated delays, and the simulation stops at 85 ns because of $finish. Without it, the always block would keep toggling the clock forever.

Quick Check

At 5 ns, two processes assign clk: the always block computes clk = ~clk and the initial block sets clk = 0. Which order did this simulator use, and what would the log show with the other order?

Show Answer

The log shows clk = 0 at 5 ns, so the always block ran first (~x gives x) and the initial block then set 0. In the other order, clk would first become 0 and then be inverted to 1, and the whole clock would be shifted by 5 ns. Verilog does not define this order, which makes it a race condition. See Mistake #1 below for the fix.

What is a simulation waveform ?

Simulators can record the values of signals over time and show them as a waveform: one horizontal trace per signal, with time running from left to right. Waveforms are the main tool for debugging, because they show how many signals relate to each other at a glance, which a text log cannot do.

Waveform of clk and sig from the example testbench

Most simulators can also write the waveform to a file, which a separate viewer can open later. The standard Verilog format is the Value Change Dump (VCD), created with two system tasks at the start of the simulation:


initial begin
  $dumpfile("dump.vcd");    // Name of the waveform file
  $dumpvars(0, tb);         // Record all signals in tb and everything below it
end

Underneath the waveform, the simulator works with two kinds of events. An update event is a change in the value of a net or variable. An evaluation event is the execution of a process that is sensitive to that change. In the module below, the continuous assignment is a process that waits for an update event on a, b or c:


module des;
  wire abc;
  wire a, b, c;

  assign abc = a & b | c;  // abc is updated via the assign statement (process) whenever a, b or c change -> update event
endmodule

When a changes, that update event triggers an evaluation of the assign, which may produce a new value of abc, which is another update event, and so on. The simulator keeps all pending events in an event queue, sorted by simulation time. Simulation time is the time inside the simulated world, measured here in nanoseconds; it has nothing to do with how long the simulator takes to run on the computer.

simulation example

Synthesis result: The assign in des becomes an AND gate followed by an OR gate. The testbench, with its initial blocks, delays and system tasks, is never synthesized. Synthesis keeps the logic of the design, while simulation also models when things happen.

Debugging with Waveforms

When a test fails in a regression, engineers rerun it with waveform dumping enabled and open the file in a viewer. Starting from the error message in the log, they go back in time along the signals that led to the wrong value until they find the first point where the design deviates from the specification. For large designs, only the relevant parts of the hierarchy are dumped, because recording every signal of a whole chip produces enormous files and slows down the simulation.

Regions in event queue

Many events can be scheduled for the same simulation time. The Verilog event queue divides each time step into five regions, which control the order in which these events are processed. Events can only be executed from the Active region; events from the other regions are moved there when their turn comes.

EventsDescription
ActiveOccur at the current simulation time, and can be processed in any order
InactiveOccur at the current simulation time, but are processed after all active events are done
NonblockingEvaluated at some previous time, but assignment is done in the current simulation time after active and inactive events are done
MonitorProcessed after all the active, inactive and non-blocking events are done
FutureOccur at some future simulation time
Flow of one simulation time step: events for time T start in the Active region, where blocking assignments, continuous assignments and $display run and nonblocking right-hand sides are evaluated; when Active is empty, statements after #0 in the Inactive region run; when both are empty, the Nonblocking region updates the left-hand sides of nonblocking assignments, and any processes woken by these updates go back to Active; when everything is updated, the Monitor region prints $monitor and $strobe; then time advances to the next Future events, which become Active

One pass through these regions is called a simulation cycle, and a time step can need several cycles, because an update in the Nonblocking region can wake up other processes, whose events are then Active again. Only when all regions are empty does time advance. Two rules about order are guaranteed:

  • Statements inside one begin ... end block execute in the order they are written.
  • Nonblocking assignments to the same variable from one process are applied in the order they were executed.

Everything else, such as the order in which two different always blocks run in the Active region, is up to the simulator.


module tb;
  reg [3:0] a;
  reg [3:0] b;

  initial begin       // Statements are executed one after the other at appropriate simulation times
    a = 5;            // At time 0ns, a is assigned 5
    b = 2;            // In the same simulation step (time 0ns), b is assigned 2

    #10 a = 7;        // When simulation advances to 10ns, a is assigned 7
  end
endmodule

Because both assignments are in the same begin ... end block, the event queue guarantees that b is assigned after a, even though both happen at time 0. The #10 puts the last assignment into the Future region, where it waits until the simulation reaches 10 ns.

The regions explain why different print tasks can show different values in the same time step:


module regions;
  reg [3:0] a;

  initial begin
    a = 1;
    $display ("display : a=%0d", a);    // Active: a is 1
    $strobe  ("strobe  : a=%0d", a);    // Monitor: prints at the end of the time step
    a <= 2;                             // Nonblocking: a becomes 2 later in this time step
    #0 $display ("after #0: a=%0d", a); // Inactive: still before the nonblocking update
  end
endmodule
display : a=1
after #0: a=1
strobe  : a=2

$display prints immediately. The #0 moves the second $display to the Inactive region, which still comes before the Nonblocking region, so a is 1. $strobe waits for the Monitor region, after the nonblocking update, and prints the final value 2.

Quick Check

If a <= 2; in the regions module were changed to the blocking a = 2;, what would each line print?

Show Answer

display : a=1, then after #0: a=2, then strobe : a=2. The first $display runs before the assignment, but a blocking assignment updates a immediately, so the later prints see 2.

Portable Simulation Results

Large projects often use more than one simulator, for example a commercial simulator for regressions and an open-source one for quick checks. Code that depends on the order of processes within a time step can pass on one simulator and fail on the other. Coding rules such as "nonblocking assignments in clocked blocks, blocking in combinational blocks" exist mainly to make results independent of that order.

Common Beginner Mistakes

Mistake #1: Initializing the clock in a different process at the same time it toggles

Wrong: always #5 clk = ~clk; together with #5 clk = 0; in an initial block
Right: Initialize at time 0, before the first toggle:


initial clk = 0;
always #5 clk = ~clk;

Why? Both processes write clk at 5 ns, and the order is not defined, so the clock can be shifted by half a period depending on the simulator. Setting clk = 0 at time 0 removes the race, because the first toggle only happens at 5 ns.

Mistake #2: Forgetting $finish with a free-running clock

Wrong: A testbench with always #5 clk = ~clk; and no $finish
Right: End the stimulus with $finish;

Why? The clock process always has a future event scheduled, so the event queue is never empty and the simulation runs until it is stopped by hand or by a time limit.

Mistake #3: Expecting $display to show a nonblocking update from the same time step

Wrong: q <= d; $display("q=%b", q);, expecting the new value
Right: Use $strobe("q=%b", q);, or display in the next time step

Why? $display runs in the Active region, before nonblocking assignments are applied. $strobe and $monitor run in the Monitor region, after all updates of the time step.

Mistake #4: Relying on the order of always blocks

Wrong: Two always @(posedge clk) blocks where one reads a variable the other writes with a blocking assignment
Right: Use nonblocking assignments in clocked blocks

Why? Both blocks wake up in the Active region at the same clock edge, in an undefined order. The reading block may see the old or the new value. With nonblocking assignments, all reads happen in the Active region and all writes in the Nonblocking region, so every block sees the old value.

Practice Exercise

Challenge: Predict the Log

Predict every line printed by this testbench, including the time of each line.


module tb;
  reg [3:0] x, y;

  initial begin
    x = 0; y = 0;
    $monitor ("T=%0t x=%0d y=%0d", $time, x, y);
    #10 x = 3;
    y <= x + 1;
    $display ("T=%0t display y=%0d", $time, y);
    #5 x <= 7;
    x <= 8;
    #5 $finish;
  end
endmodule
Hint

Remember that $monitor prints once at the end of each time step in which a value changed, that $display prints immediately, and that two nonblocking assignments to the same variable are applied in order.

Solution
T=0 x=0 y=0
T=10 display y=0
T=10 x=3 y=4
T=15 x=8 y=4

At 10 ns, x becomes 3 at once, and y <= x + 1 evaluates 4 but applies it later, so $display still prints y=0. $monitor prints at the end of the time step, when y is 4. At 15 ns, both nonblocking assignments are applied in order, so x ends at 8, and $monitor only shows that final value. The simulation ends at 20 ns with no further changes.

Summary

In this tutorial, you learned:

  • Simulation checks that RTL does what was intended, which synthesis alone cannot do
  • Verilog is simulated as a discrete event model: parallel processes wait for delays and events, and time jumps from one event to the next
  • Logs show values as text, while waveforms, which can be dumped to a VCD file with $dumpfile and $dumpvars, show many signals over time
  • Each time step runs through the Active, Inactive, Nonblocking and Monitor regions before time advances to Future events
  • $display prints in the Active region, while $strobe and $monitor print after all updates of the time step
  • Statements in one begin ... end block run in order, but the order of different processes in the same time step is not defined and can cause races
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5
Cron Job Starts