Verilog · Chapter 19.1 · Timing Regions
The Active Region & Evaluation Order in Verilog — Where Races Are Born
The Active region is where most of a simulation timestep happens, holding blocking assignments, continuous-assignment evaluations, gate-level updates, display calls, and the evaluation of every non-blocking assignment right-hand side. Because so much runs here, this is also where races are born. Verilog fixes the order of statements within one procedural block but leaves the order of events across blocks at the same instant undefined, so two always blocks on the same clock edge have no guaranteed ordering. This page explains what the Active region holds, the difference between within-block and across-block ordering, and the Inactive region with its zero-delay deferral, including why reaching for a zero delay to dodge a race is a fragile hack that breaks once the design is written correctly.
Advanced13 min readVerilogEvent RegionsActive Region#0Race Conditions
Chapter 19 · Section 19.1 · Timing Regions
1. The Engineering Problem
When a simulator reaches a clock edge, it has a pile of work to do at that single instant — and most of it lands in the Active region. The question that decides correctness is: in what order does the Active work happen?
// All scheduled at the SAME posedge clk, all ACTIVE-region events:
always @(posedge clk) a = b; // blocking assignment
assign y = a & c; // continuous assign evaluation
always @(posedge clk) $display(a); // a display
always @(posedge clk) q <= d; // the RHS (d) is read here; q updates later
// Within ONE block, statements run in source order. But ACROSS these four
// independent events, the standard defines NO order. The simulator may
// process them however it likes — and if any of them depends on another's
// result, the answer is simulator-dependent.The Active region is busy and unordered, and that combination is the origin of every Verilog race. Understanding exactly what is and is not guaranteed here is the foundation for the rest of the chapter.
The Active region holds most of a timestep's work — blocking assignments, continuous-assignment evaluation, gate I/O,
$display, and the evaluation of non-blocking right-hand sides. Statements within one block run in source order; events across blocks at the same instant have no defined order — which is the root of races.
2. Mental Model — Do Most Things Now; Within a Block Ordered, Across Blocks Not
3. What the Active Region Holds
| In the Active region | Notes |
|---|---|
Blocking assignments (=) | evaluate RHS and update LHS, immediately |
Continuous assignments (assign) | re-evaluated when an input changes |
| Primitive (gate) I/O | gate inputs read, outputs updated |
$display / $write | print now — see pre-NBA values |
Non-blocking RHS evaluation (<=) | the right-hand side is read here; the LHS update is deferred to NBA (19.2) |
Almost everything a learner thinks of as "the logic running" happens in Active. The one crucial split is the last row: a non-blocking assignment is half an Active event (its RHS read) and half an NBA event (its LHS update). Everything else completes within Active.
4. Evaluation Order — Defined Within a Block, Undefined Across Blocks
This is the single most important fact about the Active region.
// INTRA-block — DEFINED (source order):
always @(posedge clk) begin
a = b; // runs first
c = a; // runs second — sees the new a
end // c always gets b's value. Deterministic.
// INTER-block — UNDEFINED (no order between separate blocks):
always @(posedge clk) a = b; // block 1
always @(posedge clk) c = a; // block 2 — sees OLD or NEW a?
// block 1 first → c gets NEW a; block 2 first → c gets OLD a.
// The standard does not say which. RACE.- Within a block, statements are sequential and ordered — you can rely on
c = a;seeing theawritten by the line above. - Across blocks scheduled at the same instant, there is no guaranteed order. The simulator is free to run block 1 then block 2, or block 2 then block 1, and the standard considers both correct.
So a race is not a bug in the simulator — it is code that asks a question (which block ran first?) the language refuses to answer. The full treatment of races and the discipline that avoids them is 19.4; here the key takeaway is where they come from: the unordered Active region.
5. Non-Blocking Right-Hand Sides Evaluate in Active
A detail that matters for the next page: when the simulator processes a non-blocking assignment in the Active region, it evaluates the right-hand side immediately and schedules the left-hand side update for the NBA region.
always @(posedge clk) begin
q <= d; // ACTIVE: read d now. NBA: update q later.
endThe reason a <= b; b <= a; swaps correctly is precisely this: both right-hand sides are read in Active (capturing the old b and old a) before either left-hand side update is applied in NBA. The Active region samples; the NBA region (19.2) updates. Keeping the two halves straight is the key to the whole chapter.
Visual A — within-block vs across-block order
Active region — what is ordered, what is not
data flow6. The Inactive Region and #0
After the Active region fully drains, the simulator processes the Inactive region — events that were explicitly deferred with #0.
always @(posedge clk) begin
x = 1;
#0 y = x; // defer 'y = x' to the INACTIVE region (this instant)
end#0does not advance time. It defers the event to the Inactive region of the same timestep — a delta cycle later, but at the same simulation time.- Inactive runs after Active, before NBA. So a
#0-deferred statement runs after all "normal" Active work of the instant, but before any non-blocking updates apply. - It is sometimes used to say "run this after everything else active this instant" — to step around an ordering problem without consuming time.
Visual B — where #0 lands, and what it runs before
The order: Active → Inactive (#0) → NBA
data flow7. The #0 Anti-Pattern
Because #0 lets you reorder within an instant, it is tempting to use it to dodge a race — "make my block run after the other one." This is fragile, for two reasons rooted in the region order:
#0runs before NBA. A#0-deferred read sees blocking results but not non-blocking updates (they apply later, in NBA). So a#0sample of a signal written with<=reads the old value — and the moment a design is rewritten to use proper non-blocking assignments (as sequential logic should), the#0"fix" silently breaks (§9 DebugLab).- Multiple
#0events are themselves unordered. Two#0-deferred events both land in the Inactive region, which has the same "no defined order across events" property as Active. So#0does not create a reliable global ordering — it just moves the race to a different region.
The robust alternatives live in the later regions: to observe settled, post-update values, sample in the Postponed region with $strobe/$monitor (19.3); to make sequential logic deterministic, use the non-blocking discipline (19.2, 19.4). #0 is almost never the right tool — its main legitimate use is niche scheduling in some testbench idioms, and even there it is being supplanted. Treat a #0 in design or checking code as a smell.
8. Common Mistakes
- Relying on across-block Active order. Two blocks reading/writing a shared signal on the same edge race — the result is simulator-dependent (§4, 19.4).
- Using
#0to sample a non-blocking-updated signal.#0runs in Inactive, before NBA, so it reads the old value (§6, §7, DebugLab). - Believing
#0creates a reliable global order. Multiple#0events are unordered among themselves — it relocates the race, not removes it (§7). - Confusing within-block and across-block guarantees. Source order holds inside a block only; across blocks there is none (§4).
- Forgetting non-blocking RHS evaluates in Active. The
<=RHS is read in Active; only the update is deferred (§5).
9. Debugging Lab
The #0 sample that broke when the RTL was fixed
Pitfall — using #0 to sample a signal that becomes non-blocking
// A checker samples a DUT output on the clock edge. The DUT originally used
// a BLOCKING assignment, and the checker used #0 to read 'after' it:
module dut(input clk, input [7:0] in, output reg [7:0] out);
always @(posedge clk) out = in + 8'd1; // blocking (original)
endmodule
module checker(input clk, input [7:0] out);
always @(posedge clk) begin
#0; // defer to the Inactive region...
check(out); // ...to read 'out' AFTER the DUT updates it
end
endmodule
// With the BLOCKING DUT this happens to work: 'out = in + 1' completes in the
// Active region; the #0 check runs in the Inactive region (after Active), so
// it sees the new 'out'. Green.
//
// Later, the DUT is corrected to proper sequential RTL using NON-BLOCKING:
// always @(posedge clk) out <= in + 8'd1; // non-blocking (fixed RTL)
//
// Now the DUT's 'out' update is an NBA-region event — which happens AFTER the
// Inactive region. The checker's #0 read runs in Inactive, BEFORE NBA, so it
// reads the OLD 'out'. The checker silently compares against stale data and
// either flags false failures or misses real ones. The RTL fix 'broke' the
// checker.A checker that worked for months starts reading stale (one-cycle-old) DUT values — but only after the DUT was refactored from blocking to non-blocking assignments (a correctness improvement). The checker code did not change. It now reports false mismatches, and the team suspects the RTL refactor introduced a bug, when the RTL is actually now correct.
The checker relied on #0 to sample 'after' the DUT, and #0 defers to the INACTIVE region. The region order within an instant is:
Active → Inactive (#0) → NBA → Postponed
With the original BLOCKING DUT, 'out' updated in the Active region, so the #0 read (Inactive, after Active) saw the new value. But the corrected NON-BLOCKING DUT updates 'out' in the NBA region — which comes AFTER Inactive. So the #0 read now runs BEFORE the update and sees the old value.
The #0 'fix' was always fragile: it assumed the DUT update happened in or before the Inactive region, which is only true for blocking assignments. Proper sequential RTL uses non-blocking, whose update lands in NBA, after #0 — so the checker reads stale data.
// Sample in the POSTPONED region, which is guaranteed to run AFTER NBA — so
// it always sees the settled, post-update value, regardless of whether the
// DUT uses blocking or non-blocking. $strobe (and $monitor) run in Postponed:
module checker(input clk, input [7:0] out);
always @(posedge clk) begin
// $strobe reads in the Postponed region — after the DUT's NBA update.
$strobe("check out=%0d", out); // (or capture+compare in Postponed)
end
endmodule
// Now the checker observes 'out' after every update has settled, for both
// blocking and non-blocking DUTs. The dependence on region order is removed.
//
// Lesson: #0 lands in Inactive, BEFORE NBA — it cannot reliably sample a
// non-blocking-updated signal. To read settled values, use the Postponed
// region ($strobe/$monitor), not #0. (Chapter 19.3 drills Postponed sampling.)10. Interview Q&A
11. Exercises
Exercise 1 — Ordered or not?
For each, say whether the order is defined: (a) two statements in one always block; (b) two always blocks on the same edge; (c) a continuous assign and a blocking always write to a shared signal at the same time.
Exercise 2 — Where does #0 run?
Place these in region order for one instant: a blocking =, a #0-deferred read, a non-blocking <= update. Which reads can see the <= update?
Exercise 3 — Predict the break
A checker uses #0 to sample a DUT output that is written with a blocking assignment, and it works. The DUT is changed to non-blocking. Predict what the checker now reads and why.
Exercise 4 — The right tool
You need to observe a flop's value after its clock-edge update has settled. Why is the Postponed region ($strobe) correct and #0 wrong?
12. Summary
The Active region is the busy, mostly-unordered heart of a timestep:
- It holds blocking
=, continuous assigns, gate I/O,$display, and the evaluation of non-blocking RHS (the LHS update is deferred to NBA). - Within a block, statements run in source order (defined); across blocks at the same instant, there is no defined order — the root of races.
#0defers an event to the Inactive region (after Active, before NBA); it adds a delta cycle, not time.- The
#0anti-pattern — it runs before NBA (so it misses non-blocking updates) and multiple#0s are unordered; to sample settled values use Postponed ($strobe), to make sequential logic deterministic use non-blocking.
The crux to keep: within a block is ordered; across blocks is not — and #0 relocates a race rather than removing it.
The next sub-topic drills the region where non-blocking updates actually land: Chapter 19.2 The NBA Region — Why Non-Blocking Updates Last — the mechanical heart of the blocking/non-blocking rule.
Related Tutorials
- Timing Regions — Chapter 19 overview; the stratified event queue this page opens.
- The NBA Region — Why Non-Blocking Updates Last — Chapter 19.2; where the
<=updates this page deferred actually apply. - The Postponed Region — $strobe & $monitor — Chapter 19.3; the correct region for sampling settled values, instead of
#0. - Blocking and Non-Blocking Assignments — Chapter 14.3; the rule whose Active/NBA split this page begins to explain.