16.6 Blocking vs. Nonblocking Assignment: = vs. <=
Distinguish blocking (=) from nonblocking (<=) assignment in Verilog/SystemVerilog, apply the standard RTL convention pairing each with the right block type, and identify the ordering/race-condition bugs that result from mixing them up.
Verilog has two assignment operators that look almost interchangeable at a glance — = and <= — but they have genuinely different execution semantics, and using the wrong one inside sequential logic is one of the most consequential mistakes a beginner (and even experienced engineers, occasionally) can make in RTL design. This is not a stylistic choice like brace placement in software; blocking versus nonblocking assignment changes what hardware is actually described, and getting it wrong can produce a simulation that runs but computes the wrong answer, or a design that behaves correctly in simulation yet synthesizes into different, broken hardware. This lesson builds directly on the assign/always_comb style from 16.4 and the always_ff style from 16.5 to explain exactly why each context demands a specific assignment operator.
Blocking Assignment (=): Sequential, Immediate
Blocking assignment, written a = b;, executes immediately and in program order: the right-hand side is evaluated, the left-hand side is updated right then, and only after that update completes does execution move on to the next statement in the block. This is exactly how assignment works in an imperative programming language like C or Python, which is why it feels the most 'natural' to a beginner — and why it is easy to reach for it out of habit in the wrong place.
Blocking assignment is the correct choice inside combinational logic (always_comb, as used throughout 16.4) precisely because combinational logic has no real notion of time or ordering to preserve between statements — each statement's effect should be immediately visible to the statements after it within the same evaluation, the same way each line of a mathematical derivation depends on the line before it.
Nonblocking Assignment (<=): Scheduled, Simultaneous
Nonblocking assignment, written a <= b;, does not update its left-hand side immediately. Instead, the simulator evaluates the right-hand side using the values signals held before the current time step, and schedules the actual update to happen only after every other statement in the current time step (across every nonblocking assignment triggered by the same event) has also finished evaluating its right-hand side. In effect, every nonblocking assignment in a block reads 'old' values and all writes land together, at the same simulated instant, regardless of the order the assignment statements appear in the source.
This scheduling behavior is exactly what real clocked flip-flops do physically: every flip-flop triggered by the same clock edge samples its input using the value present just before the edge, and all of them update simultaneously at that edge — none of them can 'see' another flip-flop's new value during that same edge. Nonblocking assignment is therefore the semantically correct way to model a bank of flip-flops updating together, which is why it belongs inside always_ff.
The Standard RTL Convention
The module states this convention directly, and it is close to a universal rule across the industry: always_comb blocks use blocking assignment (=); always_ff blocks use nonblocking assignment (<=). Mixing the two within the same type of block, or worse, using the wrong operator for the block type, is one of the most reliable ways to introduce a subtle bug, because both operators are syntactically valid nearly everywhere, so nothing about the code fails to compile — it simply describes different, and usually wrong, behavior.
A useful mnemonic for remembering the pairing: blocking assignment models 'do this step, then the next step sees the result' (matching combinational logic evaluating through a chain of gates), while nonblocking assignment models 'everyone samples now, everyone updates together later' (matching a bank of flip-flops on the same clock edge).
• always_comb → blocking = • always_ff → nonblocking <= • Never mix = and <= for different signals within the same always_ff block unless you have a specific, well-understood reason (rare, advanced usage) — as a learner, treat this as an absolute rule.
Why the Wrong Operator Causes Race Conditions
The module's tricky-problem statement is precise: changing <= to = inside sequential logic can change simulation behavior and introduce ordering/race problems. Consider a 2-stage shift register written inside one always_ff block: with nonblocking assignment (q1 <= d; q2 <= q1;), both statements read the pre-edge value of their right-hand side, so q2 receives the OLD value of q1 (the value from before this clock edge) — exactly the one-cycle-delay shift-register behavior intended. If blocking assignment is used instead (q1 = d; q2 = q1;), the first statement updates q1 immediately, and the second statement then reads that brand-new value of q1 — so q2 ends up equal to d on the very same clock edge, collapsing the intended two-stage delay into effectively one stage. The circuit that gets synthesized may not even match this broken simulation behavior, because synthesis tools interpret sequences of blocking assignments to registers differently than a simulator executing them line by line — meaning the bug can be a mismatch between simulation and hardware, not just a logic error visible in either one alone.
This exact failure mode is called a race condition because the 'wrong' behavior depends on the accidental order in which the simulator happens to schedule and execute statements that should have been logically simultaneous — with nonblocking assignment, that accidental ordering is removed entirely, because every right-hand side is guaranteed to be evaluated against pre-edge values no matter what order the statements are written in.
Blocking vs. Nonblocking Assignment
Blocking (=) executes and updates immediately, in the exact order written — like an ordinary imperative assignment. Nonblocking (<=) evaluates its right-hand side against pre-update values and defers the actual update until every nonblocking assignment in the same time step has been evaluated. The convention pairing is not arbitrary style — it is what makes the RTL's simulated behavior match its synthesized hardware behavior.
// Blocking — sequential, immediate (use in always_comb)
a = b;
// Nonblocking — scheduled, simultaneous (use in always_ff)
a <= b;
// Standard convention:
// always_comb -> blocking =
// always_ff -> nonblocking <=Code Examples
Because both assignments use <=, q2 <= q1; reads the value q1 held just before this clock edge, not the value being written to q1 on this same edge. The result: d takes exactly two clock cycles to appear on q2, which is the correct, intended behavior of a 2-stage shift register — each flip-flop delays its input by exactly one clock cycle, and the two delays compose correctly regardless of the order the two lines are written in. Expected result: d propagates to q1 after 1 clock edge, and to q2 after 2 clock edges — a clean two-cycle delay chain.
q2 <= q1; // stage 2 samples the OLD q1 (pre-edge value) q1 = d; updates q1 immediately. The very next line, q2 = q1;, then reads that just-updated value, so on the same clock edge q2 ends up equal to d — the two-stage delay has effectively collapsed into a single stage's worth of delay, purely because of statement order. Reordering the two lines (q2 = q1; q1 = d;) would produce yet another, still-wrong result, which is exactly the 'ordering/race problem' described in the module: with blocking assignment, correctness of a multi-register block silently depends on line order, something well-designed sequential RTL should never depend on.
q1 = d; // BUG: blocking assignment in sequential logic Common mistake: Using = instead of <= inside always_ff turns a simultaneous-update flip-flop bank into an order-dependent chain of immediate updates.
module shift_reg_correct (
input wire clk,
input wire d,
output logic q1,
output logic q2
);
always_ff @(posedge clk) begin
q1 <= d; // stage 1 samples d
q2 <= q1; // stage 2 samples the OLD q1 (pre-edge value)
end
endmodulemodule shift_reg_buggy (
input wire clk,
input wire d,
output logic q1,
output logic q2
);
always_ff @(posedge clk) begin
q1 = d; // BUG: blocking assignment in sequential logic
q2 = q1; // reads the NEW q1, not the old one
end
endmoduleNonblocking vs. blocking update timing
Nonblocking assignment guarantees every register in the block reads pre-edge values; blocking assignment does not, making the result depend on statement order.
NONBLOCKING (correct):
time T (posedge clk):
read q1_old, d_old → compute updates
THEN apply: q1 <- d_old, q2 <- q1_old (simultaneous)
BLOCKING (bug, in always_ff):
time T (posedge clk):
q1 = d → q1 updated immediately
q2 = q1 → reads the just-written NEW q1, not q1_old
result: q2 gets d's value one cycle too earlyImportant Rules
Blocking (=) executes immediately and in written order — the correct choice inside always_comb.
Nonblocking (<=) evaluates all right-hand sides against pre-update values and applies every update simultaneously — the correct choice inside always_ff.
Standard convention: always_comb → blocking =; always_ff → nonblocking <=. Treat this as a hard rule, not a stylistic preference.
Using blocking assignment inside sequential (always_ff) logic can make correctness depend on statement order, introducing race/ordering bugs.
A simulation mismatch caused by the wrong assignment type may not even match what the design synthesizes into — the bug can hide differently in simulation than in real hardware.
Pipeline Stages Depend on This Rule
Every pipelined processor design relies on exactly this shift-register pattern scaled up to dozens or hundreds of bits per stage: each pipeline register must sample its input using the value present at the start of the clock cycle, and every register across the whole pipeline must update together. If a design team mixed up blocking and nonblocking assignment across pipeline stages, cycle counts would silently shift, causing pipeline stages to see data one cycle earlier or later than intended — which is exactly why this rule is treated as close to inviolable in professional RTL coding guidelines and why lint tools automatically flag blocking assignment inside always_ff blocks as an error.
always_ff @(posedge clk) begin q1 = d; q2 = q1; end always_ff @(posedge clk) begin q1 <= d; q2 <= q1; end
Swapping = for <= restores the correct, order-independent, simultaneous-update semantics that model real flip-flops: q2 now reliably receives the value q1 held before this clock edge (a proper 2-stage delay) rather than the value q1 is being updated to on this very same edge.
[tricky-problem] Inside an always_ff block implementing a 2-stage shift register, an engineer changes q1 <= d; q2 <= q1; to q1 = d; q2 = q1;. What changes? A. q2 now receives the newly updated value of q1 on the same clock edge instead of q1's pre-edge value, collapsing the intended 2-cycle delay into effectively 1 cycle B. Nothing changes — blocking and nonblocking assignment are functionally identical inside always_ff C. The design now fails to compile because blocking assignment is illegal inside always_ff D. q1 stops updating entirely
A. q2 now receives the newly updated value of q1 on the same clock edge instead of q1's pre-edge value, collapsing the intended 2-cycle delay into effectively 1 cycle — This is exactly the module's stated tricky problem. Blocking assignment updates q1 immediately, so the very next statement (q2 = q1) reads that brand-new value rather than the value q1 held before the clock edge — turning a 2-stage delay chain into something that behaves like a single stage on this edge, and making the result depend on the order the statements happen to be written in.
[interview] Why does the standard RTL convention pair always_comb with blocking = and always_ff with nonblocking <=? A. Because blocking assignment's immediate, ordered updates correctly model a chain of combinational logic, while nonblocking assignment's simultaneous, pre-edge-value updates correctly model a bank of flip-flops sampling and updating together B. Because nonblocking assignment is not legal syntax inside always_comb blocks C. Because blocking assignment is faster to simulate and nonblocking assignment is faster to synthesize D. Because always_comb blocks cannot contain more than one assignment, making the choice irrelevant there
A. Because blocking assignment's immediate, ordered updates correctly model a chain of combinational logic, while nonblocking assignment's simultaneous, pre-edge-value updates correctly model a bank of flip-flops sampling and updating together — The pairing follows directly from what each operator's execution semantics actually model: combinational logic is a chain where each step should see the previous step's result (matching blocking's immediate update), while a set of flip-flops on the same clock edge must all sample old values and update together with no ordering dependency (matching nonblocking's deferred, simultaneous update).
What kind of bug does mixing blocking and nonblocking assignment incorrectly within sequential logic typically introduce? A. An ordering/race condition, where the result depends on the sequence statements happen to be written in rather than on well-defined simultaneous updates B. A compile-time syntax error that always prevents the design from building C. A gate-count increase with no change in functional behavior D. A change in the module's port list
A. An ordering/race condition, where the result depends on the sequence statements happen to be written in rather than on well-defined simultaneous updates — The module's own tricky problem note describes this directly: swapping nonblocking for blocking assignment inside sequential logic can change simulation behavior and introduce ordering/race problems, because correctness that should be guaranteed by nonblocking assignment's simultaneous-update semantics becomes accidentally dependent on statement order instead.
A 3-stage shift register is needed: q1 samples d, q2 samples q1's previous value, q3 samples q2's previous value, all updating on the same posedge clk. Write the always_ff block correctly.
module shift_reg_3stage ( input wire clk, input wire d, output logic q1, output logic q2, output logic q3 ); always_ff @(posedge clk) begin q1 <= d; q2 <= q1; q3 <= q2; end endmodule
Use nonblocking assignment for all three lines, and note that the order the three lines are written in should not matter for the result — that's the property nonblocking assignment guarantees.