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
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.