Skip to content

Verilog · Chapter 14.3 · Behavioural Modeling

Blocking vs Non-Blocking Assignments in Verilog — = vs <= (The Golden Rule)

This is the most important and most misunderstood rule in Verilog, the number one interview topic and the number one source of RTL bugs. Inside a procedural block an assignment is either blocking or non-blocking, and the two model fundamentally different behaviour. A blocking assignment executes immediately and in order, like a software statement, which is what combinational logic needs. A non-blocking assignment evaluates its right-hand side immediately but defers the update to the end of the time-step, so every block reads the old values first and all updates apply together, exactly how real flip-flops behave on a clock edge. From this comes the golden rule every RTL engineer lives by: blocking for combinational logic, non-blocking for sequential logic. This page proves it with the canonical shift-register example, explains the scheduling regions, and catalogs the races that follow from getting it wrong.

Foundation22 min readVerilogBlockingNon-BlockingSequential LogicShift RegisterRTL Design

Chapter 14 · Section 14.3 · Behavioural Modeling

1. The Engineering Problem

An engineer builds a 3-stage shift register — d shifts through q1, q2, q3, one stage per clock — using blocking assignments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 = d;          // blocking
    q2 = q1;         // blocking
    q3 = q2;         // blocking
end

It is not a shift register. On each clock edge, the statements execute in order, immediately: q1 becomes d, then q2 reads the new q1 (which is d), then q3 reads the new q2 (also d). So q1 = q2 = q3 = d — all three stages get d in a single cycle. The shift register collapsed into one flop's worth of behaviour.

The fix is one character per line — non-blocking assignment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin
    q1 <= d;         // non-blocking
    q2 <= q1;        // reads OLD q1
    q3 <= q2;        // reads OLD q2
end                  // all three update TOGETHER → a real 3-stage shift register  ✓

With <=, all three right-hand sides are read first (using the old values), then all three updates apply together — so q2 captures the previous q1, q3 the previous q2. That is exactly how three flip-flops behave on a clock edge: each captures its neighbour's old value. The shift register works.

Blocking (=) executes immediately and in order; non-blocking (<=) reads all right-hand sides first and updates all left-hand sides together. The golden rule: = for combinational logic, <= for sequential logic.

This page proves the rule, explains why, and shows the failures from breaking it. It is the most important page in the track.

2. Mental Model — = Is Immediate and Ordered; <= Is Deferred and Parallel

Visual A — blocking vs non-blocking within a block

Blocking versus non-blocking assignment behaviourblocking =update now, in ordernext stmt sees NEWvalue→ combinationalnon-blocking <=read all old, update alltogetherall see OLD values→ sequential (registers)12
Inside a block, blocking (=) updates each left-hand side immediately, so the next statement sees the new value (ordered, like software) — correct for combinational. Non-blocking (<=) reads every right-hand side first using the old values, then applies all updates together at the end of the time-step (parallel) — correct for sequential, because real flip-flops all sample their old inputs on the edge and update simultaneously.

3. Blocking Assignment (=)

A blocking assignment evaluates its right-hand side and updates its left-hand side immediately, before the next statement executes:

blocking.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   always @(*) begin
       temp = a & b;        // temp updated NOW
       y    = temp | c;     // uses the NEW temp → correct combinational chain
   end

The statements run in order, each seeing the results of the previous one — exactly like software, and exactly what combinational logic needs: you compute temp, then immediately use the updated temp to compute y. Blocking assignments are evaluated in the Active region of the time-step (Chapter 8.1), so their effects are visible immediately within the block. Use = for combinational logic in always @(*).

4. Non-Blocking Assignment (<=)

A non-blocking assignment evaluates its right-hand side immediately but defers the left-hand side update to the end of the time-step:

non-blocking.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   always @(posedge clk) begin
       q1 <= d;             // RHS (d) read now; q1 update DEFERRED
       q2 <= q1;            // RHS reads OLD q1 (its update hasn't applied)
   end                      // both updates apply TOGETHER at end of step

Within the block, every right-hand side is evaluated using the current (old) values, and the updates are all applied together at the end of the time-step. So q2 reads the old q1, not the value just assigned to it — which is precisely how two flip-flops behave: both sample their inputs on the clock edge (the old values) and update simultaneously. The right-hand side is read in the Active region, but the left-hand side update happens in the NBA (non-blocking assignment) region, which runs after all Active-region events (Chapter 8.1). Use <= for sequential logic in always @(posedge clk).

5. The Shift Register — The Canonical Proof

The §1 example, both ways, side by side — the definitive demonstration:

shift-register.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // NON-BLOCKING (<=) — CORRECT 3-stage shift register:
   always @(posedge clk) begin
       q1 <= d;     // q1 ← d
       q2 <= q1;    // q2 ← OLD q1
       q3 <= q2;    // q3 ← OLD q2
   end
   // Each stage captures its neighbour's PREVIOUS value → proper shift.
 
   // BLOCKING (=) — WRONG, collapses to one flop:
   always @(posedge clk) begin
       q1 = d;      // q1 = d NOW
       q2 = q1;     // q2 = NEW q1 = d
       q3 = q2;     // q3 = NEW q2 = d
   end
   // All three become d in one cycle → NOT a shift register.

With non-blocking, the three stages read old values and update together, so d advances one stage per clock — a real shift register. With blocking, each statement sees the just-updated value, so d races all the way through in a single cycle, collapsing three stages into one. This single example is why the rule exists: sequential logic must use non-blocking so registers update together, not in a chain.

Visual B — the shift register, both ways

Shift register: non-blocking works, blocking collapses

data flow
Shift register: non-blocking works, blocking collapses<= : q1←d, q2←oldq1, q3←old q2reads old, updatestogether3-stage shiftregister ✓d advances onestage/clock= : q1=d, q2=newq1, q3=new q2each sees the newvalueone flop ✗d races through in onecycle
Non-blocking assignments read the old values and update together, so each stage captures its neighbour's previous value — a correct 3-stage shift register. Blocking assignments update immediately, so each statement sees the just-updated value and d propagates through all three stages in a single cycle — the shift register collapses to one flop. Sequential logic requires non-blocking.

6. The Scheduling Regions — Why <= Models Registers

The reason non-blocking models registers is the simulator's scheduling regions within one time-step (the Active-vs-NBA distinction from Chapter 8.1):

  • Active region — blocking assignments execute (update immediately), and non-blocking right-hand sides are evaluated (reading current values).
  • NBA region — non-blocking left-hand side updates apply, after all Active-region activity.

So when several <= assignments fire on a clock edge, they all read their right-hand sides in the Active region (old values), and all their updates land in the NBA region (together). This two-phase "read all, then update all" is exactly the behaviour of a bank of flip-flops sampling on the edge — which is why non-blocking correctly models sequential logic, and why blocking (immediate update) does not. (This is also why $display shows the old value and $strobe the new — Chapter 8.1.)

This page teaches the rule; the simulator mechanism behind it — the stratified event queue and why the NBA region applies last — is drilled in Chapter 19.2 The NBA Region. Together they close the loop: 19.2 explains why <= updates last, and the rule here is its practical form.

Visual C — Active and NBA regions

One clock edge: Active then NBA

data flow
One clock edge: Active then NBAActive region= executes; <= RHSread (old values)NBA region<= LHS updates apply(together)registers updatedall read old, allwrote new
Within one clock edge, non-blocking right-hand sides are read in the Active region (old values) and the left-hand-side updates apply in the NBA region afterward. This 'read all, then update all' is exactly how a bank of flip-flops samples on the edge and updates together — which is why non-blocking models sequential logic correctly.

7. The Golden Rules

The complete discipline, from decades of RTL practice:

  1. Use = (blocking) for combinational logic in always @(*). Ordered evaluation is what combinational chains need.
  2. Use <= (non-blocking) for sequential logic in always @(posedge clk). Deferred, together-update is what registers need.
  3. Do not mix = and <= in the same always block. Pick the one that matches the block's type; mixing causes confusion and bugs.
  4. Do not assign the same variable from more than one always block. Two blocks writing one signal is a multi-driver race (§7 below).

These rules are not negotiable in production RTL — they are the difference between code that works and code that races. Following them, your combinational logic evaluates correctly and your registers update like real flip-flops.

8. Races From Breaking the Rules

Violating the rules produces race conditions — non-deterministic behaviour that depends on simulation ordering:

  • Blocking in sequential logic collapses shift chains (§5) and can make one register see another's new value within the same edge, creating order-dependent results.
  • The same signal assigned from two always blocks is a race: which block "wins" depends on simulation order, and different simulators (or runs) can disagree.
  • Reading a blocking-assigned signal in another block at the same time races between the write and the read.

Non-blocking assignment removes this specific race: because the read (Active) and the write (NBA) are separated, every block sampling on the same edge sees the old value, so the outcome no longer depends on the order the simulator happened to evaluate those blocks. That is a real and important guarantee, and it is the second reason the golden rule mandates non-blocking for sequential logic.

It is not, however, a blanket immunity, and it is worth being exact about the limit. Non-blocking assignment does not make arbitrary multi-process RTL deterministic. Two blocks that both non-blocking-assign the same variable on the same edge are still indeterminate — which is precisely why rule 4 above forbids it. Nor does it order activity across different clocks, or between a procedural block and a continuous assignment, or between design code and a testbench reading the same signal in the same time-step. What <= buys you is that same-edge sampling of other registers is well-defined; every other race remains yours to design out. Why violating the rule makes a result simulator-dependent — and the full taxonomy of races — is drilled in Chapter 19.4 Race Conditions & Determinism.

9. Worked Examples

9.1 Example 1 — a register and a shift register (<=)

seq-nonblocking.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // simple register
   always @(posedge clk) q <= d;
 
   // 3-stage shift register — non-blocking, updates together
   always @(posedge clk) begin
       s1 <= in;
       s2 <= s1;
       s3 <= s2;
   end

Non-blocking for all sequential logic: the register captures d on the edge, the shift register advances one stage per clock. This is the everyday sequential pattern.

9.2 Example 2 — a combinational chain (=)

comb-blocking.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // combinational: compute an intermediate, then use it
   always @(*) begin
       sum   = a + b;           // blocking — sum updated now
       carry = sum[8];          // uses the NEW sum → correct
       y     = carry ? 8'hFF : sum[7:0];
   end

Blocking for combinational logic: each statement uses the just-computed value of the previous one, so the chain (sumcarryy) evaluates correctly in order. Non-blocking here would read stale values and need extra simulation steps to settle — = is correct for combinational.

9.3 Example 3 — the rule applied across a module

rule-applied.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // combinational next-state logic: blocking
   always @(*) begin
       next_count = count + 1;
       wrap       = (next_count == LIMIT);
   end
 
   // sequential state register: non-blocking
   always @(posedge clk or negedge rst_n)
       if (!rst_n) count <= 0;
       else        count <= next_count;

The canonical two-block structure: a combinational always @(*) (blocking =) computes the next value, and a sequential always @(posedge clk) (non-blocking <=) registers it. This separation — combinational logic with =, the register with <= — is the backbone of clean RTL and FSM design.

10. Industry Perspective

  • The golden rule is universal. "= for combinational, <= for sequential" is taught to every RTL engineer and enforced by every team — it is the single most-stated rule in digital design.
  • Lint enforces it. Tools flag blocking assignments in clocked blocks, non-blocking in combinational blocks, mixed assignments in one block, and multi-block writes to one signal — the exact violations this page covers.
  • It is the top interview question. The shift-register demonstration, the Active/NBA explanation, and "why <= for sequential" are asked in virtually every RTL interview, because they test whether a candidate truly understands simulation semantics.
  • Breaking it causes the worst bugs. Race conditions and sim/synth mismatches from wrong assignment style are subtle, intermittent, and hard to find — which is why the rule is drilled so hard.

11. Common Mistakes

  1. Blocking (=) in a clocked block — collapses shift registers and creates order-dependent results; use <= (§5, DebugLab 1).
  2. Non-blocking (<=) in a combinational block — reads stale intermediates, needs extra deltas; use = (§9, DebugLab 2).
  3. Mixing = and <= in one block — confusing and bug-prone; pick one per block (§7, DebugLab 3).
  4. Assigning one signal from two always blocks — a race; one driver per signal (§8).
  5. Believing it's a style choice — it determines correctness, not aesthetics (§7).

12. Debugging Lab

Three blocking/non-blocking debug post-mortems

Pitfall 1 — blocking assignments collapse a shift register
Buggy Code
module shift3 (input clk, in, output reg q3);
  reg q1, q2;
  // Intent: 3-stage shift register. But blocking '=' is used.
  always @(posedge clk) begin
      q1 = in;
      q2 = q1;        // reads the NEW q1
      q3 = q2;        // reads the NEW q2
  end
endmodule

// On each clock edge the statements execute immediately in order, so q1, q2,
// and q3 ALL become 'in' in one cycle. 'in' races through all three stages —
// the register behaves like a single flop, not a 3-stage shift.
Symptom

A 3-stage shift register has no delay — the output q3 follows the input 'in' after just one clock instead of three. The intended 3-cycle delay is gone; it acts like a single flip-flop.

Root Cause

Blocking assignment in sequential logic. With '=', each statement updates its left-hand side IMMEDIATELY, so the next statement sees the new value. On a clock edge: q1 = in; then q2 = q1 reads the JUST-ASSIGNED q1 (= in); then q3 = q2 reads the new q2 (= in). All three become 'in' in one cycle — 'in' propagates through every stage at once, collapsing the shift register to one flop's behaviour. Real flip-flops all sample their OLD inputs on the edge and update together, which blocking does not model.

The fix is non-blocking '<=': all right-hand sides are read first (old values), then all updates apply together, so each stage captures its neighbour's PREVIOUS value — a correct shift.

Fix
module shift3 (input clk, in, output reg q3);
  reg q1, q2;
  always @(posedge clk) begin
      q1 <= in;
      q2 <= q1;       // reads OLD q1
      q3 <= q2;       // reads OLD q2
  end                 // all update together → proper 3-stage shift
endmodule

// Sequential logic ALWAYS uses non-blocking '<=' so registers update
// together, modeling real flip-flops.
Pitfall 2 — non-blocking in a combinational chain
Buggy Code
module comb_chain (input [7:0] a, b, c, output reg [7:0] y);
  reg [7:0] temp;
  // Intent: combinational y = (a & b) | c. But non-blocking is used.
  always @(*) begin
      temp <= a & b;       // non-blocking — temp update DEFERRED
      y    <= temp | c;    // reads OLD temp, not the just-computed value
  end
endmodule

// With '<=', 'temp <= a & b' defers the update, so 'y <= temp | c' reads the
// STALE temp (from a previous evaluation), not (a & b). y is computed from an
// out-of-date intermediate — wrong combinational result (and it takes extra
// simulation deltas to settle, if it settles correctly at all).
Symptom

A combinational block that chains an intermediate value (temp) into the output produces stale or wrong results — y reflects an old 'temp', not the current (a & b). The logic is correct on paper but simulates wrong.

Root Cause

Non-blocking assignment in combinational logic. With '<=', the update to 'temp' is DEFERRED to the end of the time-step, so the very next statement 'y <= temp | c' reads the OLD temp, not the value just computed from a & b. Combinational chains need ORDERED evaluation — compute temp, then use the new temp — which is what blocking '=' provides. Non-blocking breaks the chain by deferring updates, giving stale intermediates and extra delta cycles.

The fix is blocking '=' for combinational logic, so each statement uses the just-computed value of the previous one.

Fix
module comb_chain (input [7:0] a, b, c, output reg [7:0] y);
  reg [7:0] temp;
  always @(*) begin
      temp = a & b;        // blocking — temp updated now
      y    = temp | c;     // uses the NEW temp → correct
  end
endmodule

// Combinational logic ALWAYS uses blocking '=' for ordered evaluation.
Pitfall 3 — one signal assigned from two always blocks
Buggy Code
// Two always blocks both drive 'state' — a race.
always @(posedge clk) state <= next_state;     // block A
always @(posedge clk)
  if (error) state <= ERROR_STATE;            // block B (also drives state)

// Both blocks assign 'state' on the same clock edge. Which one 'wins' is
// non-deterministic — it depends on simulation ordering, and different
// simulators (or runs) can disagree. The result is a race condition.
Symptom

A state register behaves non-deterministically — sometimes taking next_state, sometimes ERROR_STATE, with results that vary between simulators or even runs. Lint reports 'multiple drivers' on 'state'.

Root Cause

The signal 'state' is assigned from TWO different always blocks. A signal should be driven by exactly ONE procedural block; when two blocks write it on the same edge, the outcome depends on which block the simulator happens to schedule last — a race condition that is non-deterministic and non-portable. Even with non-blocking assignments, two writers to one signal race in the NBA region.

The fix is to give 'state' a single driver: combine the logic into ONE always block (e.g. with the error condition as a priority branch), so there is exactly one writer.

Fix
// Single driver for 'state' — combine the logic with priority:
always @(posedge clk or negedge rst_n)
  if (!rst_n)      state <= IDLE;
  else if (error)  state <= ERROR_STATE;   // priority
  else             state <= next_state;

// One always block, one writer — no race. Each signal has exactly one
// procedural driver.

13. Seeing It, Then Proving It

§5 argued the shift register on paper and §12 dissected it after the fact. What neither shows is the thing itself: the same stimulus, both operators, unfolding over cycles. The collapse is a timing failure, so it is worth one look as timing.

The same pulse through both chains — three cycles of delay, or none

7 cycles
A seven-cycle waveform. A one-cycle pulse on d is applied at cycle 1. In the non-blocking chain q1 rises at cycle 2, q2 at cycle 3 and q3 at cycle 4. In the blocking chain q3 rises at cycle 2, the same cycle as q1, showing the shift register has collapsed to a single stage.delay blocking destroyeddelay blocking destroyedblocking: q3 already setblocking: q3 already setnon-blocking: q3 at edge 3non-blocking: q3 at edge 3clkdnb_q1nb_q2nb_q3b_q3t0t1t2t3t4t5t6
Figure — one single-cycle pulse on d, driving both a non-blocking and a blocking three-stage chain. With non-blocking, each stage reads its neighbour's previous value, so the pulse walks one stage per clock and reaches q3 three edges later. With blocking, the three statements execute in order within the same edge, each seeing the value just written, so the pulse arrives at q3 on the FIRST edge — three flip-flops of code behaving as one. Nothing errors and nothing warns; the design is simply three cycles early forever.

Compare the last two rows. nb_q3 and b_q3 are driven by code that differs only in one character per line, and they are three cycles apart. Nothing in that difference is visible in a schematic review, a lint report, or a compile — which is why the rule is taught as a rule rather than as something to be worked out case by case.

Now make it fail on its own. This testbench runs both chains from one clock and one stimulus and asserts the difference, so the claim is checked rather than asserted:

tb_shift_compare.v — both chains, one stimulus, explicit PASS/FAIL
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module tb_shift_compare;
 
  reg clk = 1'b0;
  reg d   = 1'b0;
 
  reg nb_q1, nb_q2, nb_q3;   // non-blocking chain
  reg  b_q1,  b_q2,  b_q3;   // blocking chain
 
  integer errors = 0;
 
  always #5 clk = ~clk;      // 10 ns period
 
  // CORRECT for sequential logic: every stage reads its neighbour's OLD value.
  always @(posedge clk) begin
      nb_q1 <= d;
      nb_q2 <= nb_q1;
      nb_q3 <= nb_q2;
  end
 
  // WRONG for sequential logic: each statement sees the value just written.
  always @(posedge clk) begin
      b_q1 = d;
      b_q2 = b_q1;
      b_q3 = b_q2;
  end
 
  task check(input cond, input [255:0] msg);
    begin
      if (!cond) begin
        errors = errors + 1;
        $display("[FAIL] %0s", msg);
      end
    end
  endtask
 
  initial begin
      nb_q1 = 0; nb_q2 = 0; nb_q3 = 0;
      b_q1  = 0; b_q2  = 0; b_q3  = 0;
 
      // Sample on the NEGEDGE: reading on the active edge would race the very
      // NBA updates under test. Checking half a cycle later is unambiguous.
      @(negedge clk);
      d <= 1'b1;                 // one-cycle pulse
      @(negedge clk);
      d <= 1'b0;
 
      // ONE edge after the pulse.
      check(nb_q3 === 1'b0, "non-blocking q3 must NOT have the data after 1 edge");
      check(b_q3  === 1'b1, "blocking q3 should already have it - the chain collapsed");
 
      // THREE edges after the pulse.
      @(negedge clk);
      @(negedge clk);
      check(nb_q3 === 1'b1, "non-blocking q3 must receive the data after 3 edges");
 
      if (errors == 0)
        $display("PASS - non-blocking delays 3 cycles; blocking collapses to 1");
      else
        $display("FAIL - %0d check(s) failed", errors);
      $finish;
  end
 
endmodule

The testbench asserts the bug as well as the fix — b_q3 is required to be wrong, one edge in. That is deliberate: it turns "blocking collapses the chain" from a claim you accept into a result the simulator reproduces in forty nanoseconds. If a future simulator ever made that check fail, the page's central claim would be the thing under suspicion, which is the property a teaching example should have.

Note the negedge sampling. Reading nb_q3 on the posedge would put the read in the same time-step as the NBA updates being tested, which is precisely the ambiguity §8 warns about — the testbench has to obey the rule it is demonstrating.

The definitive statement of the scheduling semantics is IEEE Std 1800-2023 (SystemVerilog), clause 4 — Scheduling semantics — which defines the stratified event region set including the Active and NBA regions; the same model is inherited from IEEE 1364 for Verilog. The IEEE Standards Association listing is the authoritative source. It is dense, but clause 4 is short, and reading it once is what converts the golden rule from something memorised into something derived.

14. Interview Q&A

15. Exercises

Exercise 1 — Predict the shift register

For always @(posedge clk) begin q1 ? in; q2 ? q1; end with the assignment operator filled in, give the behaviour for (a) blocking =; (b) non-blocking <=.

Exercise 2 — Pick the operator

For each, choose = or <=: (a) a combinational adder in always @(*); (b) a register in always @(posedge clk); (c) an intermediate value computed then used in a combinational block; (d) a counter.

Exercise 3 — Fix the assignments

Each has the wrong operator. Identify and fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(posedge clk) begin a = b; c = a; end       // shift register intent
always @(*) begin t <= x & y; z <= t | w; end        // combinational chain

Exercise 4 — Explain the regions

(a) In which region is a non-blocking right-hand side read? (b) In which region does its left-hand-side update apply? (c) Why does this make q2 <= q1 read the old q1?

16. Summary

Blocking (=) versus non-blocking (<=) is the most important rule in Verilog:

  • Blocking = — immediate, ordered; each statement sees the previous one's result. For combinational logic (always @(*)).
  • Non-blocking <= — deferred, parallel; all right-hand sides read old values, all updates apply together. For sequential logic (always @(posedge clk)).
  • The scheduling regions — non-blocking right-hand sides read in the Active region (old values), left-hand sides update in the NBA region (together) — make <= model flip-flops correctly.
  • The shift-register proof<= gives a correct 3-stage shift; = collapses it to one flop.

The golden rules:

  • = for combinational, <= for sequential — not a style choice, a correctness rule.
  • Don't mix = and <= in one block.
  • One signal, one always driver — multiple writers race.

The discipline this page instils: combinational logic uses blocking =; clocked logic uses non-blocking <= — and your shift registers shift, your registers update like real flip-flops, and your RTL is race-free.

You now hold the single most important rule in behavioural modeling. The next page covers the timing controls inside procedural blocks: Chapter 14.4 Timing Controls drills delay (#), event (@), and level (wait) controls — how procedural statements are timed and synchronized.