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.

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
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.
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.
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.
| Events | Description |
|---|---|
| Active | Occur at the current simulation time, and can be processed in any order |
| Inactive | Occur at the current simulation time, but are processed after all active events are done |
| Nonblocking | Evaluated at some previous time, but assignment is done in the current simulation time after active and inactive events are done |
| Monitor | Processed after all the active, inactive and non-blocking events are done |
| Future | Occur at some future simulation time |
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 ... endblock 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
$dumpfileand$dumpvars, show many signals over time - Each time step runs through the Active, Inactive, Nonblocking and Monitor regions before time advances to Future events
$displayprints in the Active region, while$strobeand$monitorprint after all updates of the time step- Statements in one
begin ... endblock run in order, but the order of different processes in the same time step is not defined and can cause races