How to avoid infinite loops in a combinational logic block in Verilog
Ever wonder why we use hardware description languages (HDLs) such as Verilog, VHDL and not general-purpose programming languages like C in order to model and describe the behavior and/or structure of digital circuits? After all, a HDL looks much like any other software programming language. However a few important factors make HDLs a popular choice for hardware modeling. One among them is that they explicitly include the notion of time, which is an inherent attribute of hardware. In case of C, there is no need to consider issues related to synchronization and timing.
Another major reason is that most programming languages are inherently procedural/ single-threaded, while HDLs have the ability to model multiple concurrent processes that execute automatically independently of one another. That being said, practically there is no way to actually run all these processes in parallel. Simulator tools will have to choose which process to go first.
Verilog implements event-driven simulation algorithm implying that the simulator models the design activity as a sequence of ordered events and processes those events one at a time. This is a clear deviation from how the design activity in the actual hardware takes place, which is concurrent. This induces races that are not present in the design, but are an artifact of the simulator. One such race condition occurs when two or more statements that are scheduled to execute in the same simulation time-step, would give different results when the order of statement execution is changed.
To avoid such simulation-induced race conditions, Verilog simulation events are scheduled into four distinct yet ordered event regions/queues, as shown below. Understanding the scheduling of blocking and nonblocking assignment statements provides a chance to peek into the inner workings of Verilog. For the purpose of current discussion, we focus on two event regions namely Active event region and NBA (Non-Blocking Assignment) region.
A non-blocking assignment statement evaluates the RHS expression in the active region and assigns to the LHS in the NBA region. This delta delay between evaluation and assignment behaves as a clock-to-q delay in case of sequential logic. So, non-blocking assignments are preferably used to model the sequential logic circuits.
However, using non-blocking assignments in a combinational logic procedural block could result in a potential self-triggering loop (with some delay) or in a simulation lock up in the same simulation time step (with no delay).
领英推荐
Let’s consider the below code snippet having non-blocking assignment statements inside its always block.
module DUT_nonblocking(a);
reg a;
initial a = 0;
always@(a) begin
#10 a <= a+1;
end
endmodule
Here, the always block triggers whenever there is a change in ‘a’ value. An update event on ‘a’ (change in ‘a’ from default ‘x’ to ‘0’) results in @(a) trigger. After this initial @(a) trigger, the non-blocking assignment statement (a <= a+1) evaluates the RHS expression in active region and schedules the assignment to the LHS in NBA region.
Since the nonblocking assignment “does not block” the execution flow of the procedural block, @(a) statement is encountered before the LHS is updated in the NBA region later in the same time step, and so the always block becomes sensitive to changes in ‘a’. An event update on ‘a’ in NBA region then triggers the @(a). So, this process locks the simulation by continuously scheduling changes to ‘a’ and triggering on that change, resulting in a self-triggering loop.
Now, this issue of simulation lock-up (due to infinite loops) can be eliminated by replacing the nonblocking assignment with blocking assignment statement as shown below.
always@(a) begin
#10 a = a+1;
end
Since the procedural statement uses blocking assignments, RHS expression is evaluated and updated immediately to the LHS. By the time @(a) trigger event gets scheduled/ encountered, ‘a’ will have a new and stable value and hence no event update within the always block to re-trigger the @(a).