Verilog Scheduling Semantics
Verilog design and testbench typically have many code lines comprising of always or initial blocks, continuous assignments, and other procedural statements that become active at different times in the course of a simulation.
Every change in the value of a signal in the Verilog model is considered an update event. And processes such as always and assign blocks sensitive to these update events are evaluated in an arbitrary order and called an evaluation event.
Since these events can happen at different times, they are better managed and ensured their correct order of execution by scheduling them into event queues arranged by simulation time.
A simulation step can be segmented into four different regions. An active event queue is just a set of processes that need to execute at the current time, resulting in more processes to be scheduled into active or other event queues. Events can be added to any of the regions, but always removed from the active region.
When all events in the active queue for the current time step has been executed. The simulator advances time to the next time step and executes its active queue.
Simulation starts at time 0, and the first statement is scheduled to be executed when simulation time reaches 1 time unit at which it assigns x and y to 1.
This is the active queue for the current time, which is a 1-time unit. The simulator then schedules the next statement after 1 more time unit at which z is assigned 0.
During the simulation, there can be race conditions that end up giving different outputs for the same design and testbench. One of the reasons for non-deterministic behavior is because active events can be removed from the queue and processed in any order.
When multiple processes are triggered simultaneously, then the order in which the processes are executed is not specified by the Institute of Electrical and Electronics Engineers (IEEE) standards. It is arbitrary, and it varies from the simulator to the simulator. This is called the non-determinism.
There are two common cases of non-determinism that are caused by the same root cause but that manifest in different ways.
Case 1: When multiple statements execute in zero time, then the order they execute affects the results. Therefore, a different order of execution gives different but correct results. Execution in zero time means that the statements are evaluated without advancing simulation time.
These two processes, one procedural block and one continuous assignment are scheduled to execute at the same time when variable d changes.
If the always block is evaluated first, variable q is assigned the new value of d by the always block. Then the continuous assignment is executed. It assigns the complement of the new value of d to variable q.
If the continuous assignment is evaluated first, q gets the complement of the new value of d. Then the procedural assignment assigns the new value (non-complemented) to q. Therefore, the two orders produce the opposite results.
Case 2: This case is considered when the interleaving procedural statements in blocks are executed simultaneously. When two procedural blocks are scheduled simultaneously, there is no guarantee that all statements in a block finish before the statements in the other block begin. The statements from the two blocks can execute in an interleaving order.
Both always blocks are triggered when a positive edge of the clk arrives. One interleaving order is
In this case, y gets 0. And another interleaving order is
In this case, y gets 1.