An event-driven simulator evaluates a circuit element only when one of its inputs changes. A signal change on a net is called an event; when it happens, the simulator evaluates just the elements that the net drives, and if their outputs do not change, the activity stops there. Because only a small part of a digital design is active at any instant, this saves a great deal of work compared with re-evaluating every gate on every time step. Verilog, VHDL and SystemVerilog simulators are all built on this idea.
A latch or flip-flop shows why it works. It does not always respond to activity on its inputs: if the enable or clock is inactive, changes at the data input have no effect on the circuit. In a typical time step very little of the circuit is switching, and that activity often dies out after a few levels of logic.
Key takeaways
- An event is a change of value on a net or variable at a particular simulation time.
- When an event occurs on a net, only the elements driven by that net are scheduled for evaluation (its fanout).
- If an evaluation does not change an element's output, no new event is created and simulation along that path ends.
- In Verilog, the sensitivity list of an
alwaysblock is the designer's way of telling the simulator which events should wake that block. - Event-driven simulation handles any timing and delay model, at the cost of queue management overhead; cycle-based simulation trades timing detail for speed.
Why not simulate the whole circuit?
Since the amount of activity in a time step is small, why simulate the entire circuit? A simpler, "compiled" approach evaluates every element on every step. That is easy to implement but wasteful: if 5 percent of the nets toggle in a cycle, 95 percent of the evaluations recompute values that have not changed.
The event-driven strategy is to simulate only the elements that see signal changes at their inputs. Earlier simulators used a local version of this idea called stimulus bypass, where an element was skipped if its own inputs had not changed. Event-driven simulation applies it globally, through the whole circuit, using an event queue.
As an illustration (numbers chosen for the example, not measured): a block of 100,000 gates with 3 percent of nets switching per clock needs about 3,000 gate evaluations per cycle event-driven, plus some fanout bookkeeping, against 100,000 evaluations for the evaluate-everything approach.
How an event-driven simulator works
The simulator keeps a time-ordered queue of pending events, often implemented as a timing wheel. Its main loop is short:
- Advance simulation time to the earliest time that has pending events.
- Apply each event at that time: update the value of the net.
- For every element in that net's fanout, schedule an evaluation.
- Evaluate each scheduled element with its current input values.
- If an element's new output differs from its old one, schedule an output event after the element's delay (zero delay places it in the same time step). If the output is unchanged, nothing is scheduled.
- Repeat until no events remain at this time, then go back to step 1.
Step 5 is where the savings come from. When a signal change occurs on a primary input or on the output of a circuit element, an event is said to have occurred on the net driven by that input or element. When an event occurs on a net, all elements driven by that net are evaluated. If an event on a device input does not cause an event on the device output, simulation is terminated along that signal path.
A small worked example
Take y = (a AND b) OR c built from an AND gate driving net n1 and an OR gate driving y. Start with a=0, b=0, c=1, so n1=0 and y=1.
| Time | Event | Evaluated | Result |
|---|---|---|---|
| 10 | a: 0 to 1 | AND gate | n1 stays 0 (b is 0): no new event, path ends |
| 20 | b: 0 to 1 | AND gate | n1 goes 0 to 1: event on n1 |
| 20 (+delay) | n1: 0 to 1 | OR gate | y stays 1 (c is 1): no new event, path ends |
| 30 | c: 1 to 0 | OR gate | y stays 1 (n1 is 1): no new event |
Four input changes caused only four gate evaluations and no change at the output. An evaluate-everything simulator would have evaluated both gates at every time step whether or not anything moved.
The sensitivity list in Verilog
Verilog supports this strategy through the sensitivity list. The module below describes a three-bit state machine. The line beginning with always holds the sensitivity list. The if-else block is evaluated only in response to a 1 to 0 transition (negedge) of the reset input or a 0 to 1 transition (posedge) of the clk input. The result depends on the current value of tag, but activity on tag by itself is ignored.
`timescale 1ns/1ps
module reg3bit (
input wire clk,
input wire reset, // active-low asynchronous reset
input wire tag,
output reg [2:0] reg3
);
always @(posedge clk or negedge reset)
if (reset == 1'b0)
reg3 <= 3'b110;
else // rising edge on clock
case (reg3)
3'b110: reg3 <= tag ? 3'b011 : 3'b001;
3'b011: reg3 <= tag ? 3'b110 : 3'b001;
3'b001: reg3 <= tag ? 3'b001 : 3'b011;
default: reg3 <= 3'b001;
endcase
endmodule
Corrections to the original listing. First, it declared output reg3; as a scalar port and then reg [2:0] reg3; as a 3-bit vector, which a standard-compliant tool rejects (Icarus Verilog reports that a scalar port has a vectored declaration); the output must be declared [2:0], done here with ANSI-style ports. Second, the assignments now use non-blocking <= instead of blocking =. Blocking assignments in a clocked block can cause race conditions when other blocks read reg3 on the same clock edge, so non-blocking is the standard style for sequential logic.
Testbench and result
`timescale 1ns/1ps
module tb;
reg clk = 1'b0, reset = 1'b0, tag = 1'b0;
wire [2:0] reg3;
reg3bit dut (.clk(clk), .reset(reset), .tag(tag), .reg3(reg3));
always #5 clk = ~clk;
initial begin
$monitor("%3d clk=%b reset=%b tag=%b reg3=%b", $time, clk, reset, tag, reg3);
#12 reset = 1'b1; // release reset at 12 ns
#1 tag = 1'b1; // tag pulses between clock edges:
#1 tag = 1'b0; // no evaluation of the always block
#3 tag = 1'b1; // 17 ns
#20 tag = 1'b0; // 37 ns
#20 $finish;
end
endmodule
Simulated with Icarus Verilog 12.0, the key lines of output are:
12 clk=0 reset=1 tag=0 reg3=110
13 clk=0 reset=1 tag=1 reg3=110
14 clk=0 reset=1 tag=0 reg3=110
15 clk=1 reset=1 tag=0 reg3=001
25 clk=1 reset=1 tag=1 reg3=001
35 clk=1 reset=1 tag=1 reg3=001
45 clk=1 reset=1 tag=0 reg3=011
55 clk=1 reset=1 tag=0 reg3=001
The pulse on tag at 13 to 14 ns produces events on tag, but the always block is not sensitive to tag, so it is not evaluated and reg3 does not move. Only the clock edges at 15, 25, 35, 45 and 55 ns wake the block. At 25 and 35 ns the block runs but reg3 stays at 001, so no event is created on reg3 and nothing downstream is evaluated.
Inside a Verilog time step
Real simulators refine the simple queue into ordered regions within each time step. IEEE 1364 (Verilog) defines, in order, the active region (blocking assignments, continuous assignments, evaluation of right-hand sides of non-blocking assignments), the inactive region (#0 delays), the non-blocking assignment update region, and the monitor region ($monitor, $strobe). IEEE 1800 (SystemVerilog) adds further regions such as preponed, observed and reactive for assertions and testbench code. Non-blocking assignments work because all flip-flops sample their inputs in the active region before any of them update in the NBA region.
Event-driven versus cycle-based simulation
| Aspect | Event-driven | Cycle-based |
|---|---|---|
| What is evaluated | Only elements whose inputs changed | All logic once per clock cycle |
| Timing | Any delays, glitches, multiple time points per cycle | Values at clock edges only, no gate delays |
| Good for | Gate-level timing simulation, asynchronous logic, testbenches | Large synchronous RTL regressions |
| Overhead | Queue management per event | Wasted work on idle logic |
| Typical activity sweet spot | Low activity | High activity |
Most commercial Verilog simulators are event-driven at heart but compile RTL into optimised native code and apply cycle-based style optimisations where the design allows.
Common mistakes
- Incomplete sensitivity lists in combinational blocks. If
always @(a)readsb, a change onbcreates no evaluation and simulation no longer matches the synthesised hardware. Usealways @*or SystemVerilogalways_comb. - Blocking assignments in clocked blocks. The order in which the simulator happens to evaluate blocks then decides the result.
- Zero-delay feedback loops. A combinational loop with no delay can keep generating events in the same time step and the simulator never advances time.
- Assuming fewer events means less run time. Very high activity designs, such as data paths with dense random stimulus, can make event handling the bottleneck.
FAQ
What is an event in simulation?
A change in value of a net or variable at a specific simulation time. Evaluating an element is not itself an event; only a resulting change of value is.
Why does the simulator stop along a path?
If an element is evaluated and its output is the same as before, there is nothing new to propagate, so the elements it drives do not need to be evaluated.
Is the sensitivity list used by synthesis?
Synthesis tools largely infer hardware from the code inside the block and the edge keywords; they usually warn about incomplete lists rather than model them. The list matters mainly to simulation, which is why a wrong list causes simulation and synthesis mismatches.


Your comments will be moderated before it appears here.