Skip to content

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?

what-order.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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 regionNotes
Blocking assignments (=)evaluate RHS and update LHS, immediately
Continuous assignments (assign)re-evaluated when an input changes
Primitive (gate) I/Ogate inputs read, outputs updated
$display / $writeprint 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-vs-inter-block.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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 the a written 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.

nba-rhs-in-active.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   always @(posedge clk) begin
       q <= d;      // ACTIVE: read d now.  NBA: update q later.
   end

The 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 flow
Active region — what is ordered, what is notwithin a blocksource order — DEFINEDacross blocksno order — UNDEFINED→ race if a result depends on it→ race if aresult depends o…simulator-dependent
Statements within one procedural block run in source order (deterministic). Independent events across blocks at the same instant have no defined order. Any result that depends on across-block order is a race — undefined by the standard and potentially different on every simulator.

6. The Inactive Region and #0

After the Active region fully drains, the simulator processes the Inactive region — events that were explicitly deferred with #0.

zero-delay.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   always @(posedge clk) begin
       x = 1;
       #0 y = x;     // defer 'y = x' to the INACTIVE region (this instant)
   end
  • #0 does 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 flow
The order: Active → Inactive (#0) → NBAActiveblocking, assigns, $display, <= RHS evalInactive (#0)deferred — after ActiveNBA<= updates apply — AFTER #0
A #0-deferred event runs in the Inactive region — after all Active work but BEFORE non-blocking updates land in NBA. So a #0 read sees the results of blocking assignments but NOT yet the results of non-blocking updates. This ordering is exactly why #0 is a fragile tool for sampling.

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

  1. #0 runs before NBA. A #0-deferred read sees blocking results but not non-blocking updates (they apply later, in NBA). So a #0 sample 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).
  2. Multiple #0 events 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 #0 does 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

  1. 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).
  2. Using #0 to sample a non-blocking-updated signal. #0 runs in Inactive, before NBA, so it reads the old value (§6, §7, DebugLab).
  3. Believing #0 creates a reliable global order. Multiple #0 events are unordered among themselves — it relocates the race, not removes it (§7).
  4. Confusing within-block and across-block guarantees. Source order holds inside a block only; across blocks there is none (§4).
  5. 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.
  • #0 defers an event to the Inactive region (after Active, before NBA); it adds a delta cycle, not time.
  • The #0 anti-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.