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:
always @(posedge clk) begin
q1 = d; // blocking
q2 = q1; // blocking
q3 = q2; // blocking
endIt 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:
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
3. Blocking Assignment (=)
A blocking assignment evaluates its right-hand side and updates its left-hand side immediately, before the next statement executes:
always @(*) begin
temp = a & b; // temp updated NOW
y = temp | c; // uses the NEW temp → correct combinational chain
endThe 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:
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 stepWithin 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:
// 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 flow6. 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 flow7. The Golden Rules
The complete discipline, from decades of RTL practice:
- Use
=(blocking) for combinational logic inalways @(*). Ordered evaluation is what combinational chains need. - Use
<=(non-blocking) for sequential logic inalways @(posedge clk). Deferred, together-update is what registers need. - Do not mix
=and<=in the samealwaysblock. Pick the one that matches the block's type; mixing causes confusion and bugs. - Do not assign the same variable from more than one
alwaysblock. 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
alwaysblocks 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 (<=)
// 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;
endNon-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 (=)
// 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];
endBlocking for combinational logic: each statement uses the just-computed value of the previous one, so the chain (sum → carry → y) 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
// 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
- Blocking (
=) in a clocked block — collapses shift registers and creates order-dependent results; use<=(§5, DebugLab 1). - Non-blocking (
<=) in a combinational block — reads stale intermediates, needs extra deltas; use=(§9, DebugLab 2). - Mixing
=and<=in one block — confusing and bug-prone; pick one per block (§7, DebugLab 3). - Assigning one signal from two
alwaysblocks — a race; one driver per signal (§8). - 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
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.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.
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.
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
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).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.
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.
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
// 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.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'.
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.
// 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 cyclesCompare 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:
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
endmoduleThe 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.
always @(posedge clk) begin a = b; c = a; end // shift register intent
always @(*) begin t <= x & y; z <= t | w; end // combinational chainExercise 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
alwaysdriver — 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.
Related Tutorials
- always & initial Blocks — Chapter 14.1; the combinational vs sequential blocks these assignments live in.
- The NBA Region — Why Non-Blocking Updates Last — Chapter 19.2; the simulator mechanism (Active → NBA) behind this rule.
- Race Conditions & Simulation Determinism — Chapter 19.4; why breaking this rule makes a result simulator-dependent.
- $display vs $monitor vs $strobe vs $write — Chapter 8.1; the Active/NBA scheduling regions, seen from the display family.
- reg — Chapter 5.2.1; the registers non-blocking assignments build, and "reg is not a register."
- Dataflow Advanced Techniques — Chapter 13.3; the sequential logic that requires these clocked blocks.