3.5.4 · D5HDL & Digital Design Flow

Question bank — Blocking vs non-blocking assignments

2,782 words13 min readBack to topic

For the underlying mechanics (scheduler regions, the swap example, the golden rules), keep the parent note open beside this one.


Vocabulary you need before the cards

Six terms below recur on every card. They come from how the simulator processes one clock tick and how that description turns into real gates, so pin them down first — nothing later is allowed to use a word you haven't seen here.

Now walk the timeline figure below left to right. The blue box on the left is the Active region: notice it lists two jobs — "apply =" and "stash RHS of <=", but crucially no <= write yet. Follow the green "then" arrow across to the orange box: that is the NBA region, where every stashed value is written at once. The dashed outer box reminds you this all happens inside one clock tick.

Figure — Blocking vs non-blocking assignments

Next, read the swap waveform figure as two stacked scope traces. Top (green title, non-blocking): at each dashed clock edge the blue a line and orange b line cross overa becomes old b and b becomes old a, a true swap, because both were sampled before either was written. Bottom (red title, blocking): at the first edge a drops to old b, and because line 2 immediately re-reads the new a, b also lands on old b — both lines flatten together, no crossover, no swap. The contrast between "crossing lines" and "flat merged lines" is the whole lesson.

Figure — Blocking vs non-blocking assignments

Finally, trace the shift-register figure. Three blue D-FF boxes sit in a row; the orange d feeds q1, and the green arrows carry each flop's output to the next. The caption under them spells out the sampling: q3<=old q2, q2<=old q1, q1<=old d. Follow one bit: on each clock tick it hops exactly one box to the right, because every flop reads its neighbour's pre-edge value. That single-hop-per-tick motion is what makes it a real 3-stage pipeline instead of a wire.

Figure — Blocking vs non-blocking assignments

True or false — justify

Non-blocking <= samples every right-hand side before any left-hand side is updated.
True. All RHS are read and stashed in the Active region; the LHS writes happen in a later delta cycle (the NBA region), so no <= line ever sees another <= line's fresh value. (Re-read the timeline figure: writing is a separate box.)
Blocking = always synthesizes to combinational logic.
False. Inside always @(posedge clk), a single = still infers a flip-flop; blocking controls ordering within the block, not clocked-vs-combinational. The posedge in the sensitivity list is what makes it sequential.
Reordering the three lines of a <= shift register changes the resulting hardware.
False. Every RHS (q2, q1, d) is sampled in the Active region before any LHS is written in the NBA region, so each flop always captures its neighbour's pre-edge value (see the shift-register figure); whether you write q3<=q2 first or last, the same three flip-flops result. See Pipelining and shift registers.
a = b; b = a; in a clocked block swaps the two registers.
False. = updates a immediately (in the Active region), so line 2 reads the new a (already equal to b) — both end up holding old b, and no swap occurs. This is the flat "merged lines" bottom trace of the waveform figure.
Using <= inside always @(*) is a "safer" choice because it prevents races.
False. In a combinational block an intermediate computed with <= won't be written until the NBA region, so later lines in the same pass read stale values, risking sim/synth mismatch and unintended latches.
The line order of = (blocking) statements matters.
True. Each = finishes (writes its LHS in the Active region) before the next runs, so tmp = a & b; y = tmp | c; needs tmp computed first — reordering breaks the data-flow.
Two different always blocks may both assign the same reg if one uses = and the other <=.
False. Driving one variable from two always blocks creates a multiple-driver / race situation regardless of operator; the outcome is simulator-dependent and usually un-synthesizable.
<= introduces a real time delay in the hardware.
False. The "deferral" is only across a delta cycle within one simulation time step (Active → NBA region). It's a scheduling concept, not a physical delay; the arrow shape is just a memory aid.

Spot the error

always @(posedge clk) begin q1 = d; q2 = q1; q3 = q2; end — intended as a 3-stage shift register.
The = makes d ripple through all three in one edge (each writes in the Active region before the next reads), so q3 = q2 = q1 = d instantly — it collapses to a single flip-flop instead of three. Use <= for the shift (compare the correct trace in the shift-register figure).
always @(*) begin tmp <= a & b; y <= tmp | c; end — intended combinational AND-OR.
y reads the old tmp because the <= to tmp isn't written until the NBA region, so y lags one evaluation behind and the tool may infer a latch. Use = in combinational blocks.
always @(posedge clk) begin count = count + 1; done = (count == 8); end — a counter with a done flag. (root cause: blocking creates an off-by-one)
Because = writes count immediately, line 2 reads the already incremented count, so done asserts when the pre-edge count was 7, not 8 — an off-by-one versus the intended flip-flop behaviour. Fix the value logic with non-blocking: count <= count+1; done <= (count==7);.
always @(posedge clk) begin count <= count + 1; other <= count; end in a design where a second block also assigns count. (root cause: race / style hazard, separate from the off-by-one above)
The logic inside this block is fine, but assigning count from two always blocks (or later mixing = and <= on it) makes the final value depend on scheduler internals — a race condition, not an arithmetic bug. Fix: give each register exactly one driving block.
always @(posedge clk) begin a <= b; a = c; end — assigning a with both operators.
This is legal syntax (it compiles), but mixing = and <= on the same variable in one block is a race hazard, not a compile error: a=c writes in the Active region while a<=b writes in the later NBA region, so the NBA write wins and the final value depends on scheduling. Never mix operators on one signal.
always @(a or b) y = a & b & c; — combinational AND of three inputs.
The sensitivity list omits c, so y won't recompute when only c changes → stale output in simulation, a mismatch versus synthesis which reads all inputs. Use always @(*). See Always blocks and sensitivity lists.
always @(posedge clk) begin if (!rst_n) q = 0; else q = d; end — a flip-flop with synchronous reset.
The logic is fine but blocking = in a clocked block is bad style and races if other lines read q; use q <= 0 / q <= d. Note also that with only clk in the sensitivity list this is a synchronous reset — rst_n is sampled at the edge, not asynchronously.

Why questions

Why does non-blocking correctly model a bank of flip-flops updating on the same edge?
A real D flip-flop copies the input as it was just before the edge. Sampling all RHS in the Active region then writing all LHS in the NBA region reproduces exactly this — every flop reads its neighbour's pre-edge value.
Why does the swap work with <= but fail with =?
With <=, both RHS read old values (Active region) then update together (NBA region), so a↔b. With =, line 1 writes a immediately, so line 2 reads the new a; both settle to old b.
Why do delta cycles matter for understanding non-blocking?
The NBA write is scheduled into a later delta cycle at the same simulated time; that explicit ordering — not luck — is why every line in the block reads the pre-update value before any update lands.
Why do we forbid mixing = and <= for the same signal?
The two operators schedule writes in different regions/deltas (Active vs NBA); combining them on one signal makes the final value depend on simulator internals — a classic race condition.
Why is = the right choice for an intermediate value inside always @(*)?
A combinational block is a data-flow: an intermediate like tmp must be written before the line that consumes it, exactly like a signal rippling through gates — immediate (Active-region) update captures that.
Why can blocking assignments in clocked logic cause sim/synth mismatch?
Simulation may honour immediate ordering while synthesis infers flip-flops that all update on the edge; the two then disagree on inter-flop values. See Synthesis vs Simulation mismatch.
Why does the mnemonic "Clock? Non-blocK" capture rule 1?
Any always @(posedge clk) (a clocked block) should use <=, so the presence of a clock signals non-blocking — the phrase pins the association.
Why doesn't the order of <= lines matter but the order of = lines does?
<= reads all RHS in the Active region before any NBA-region write, so no line can influence another within the step. = writes its LHS before the next line reads, so earlier lines feed later ones.
Why should the reset assignment inside a clocked flip-flop use the same operator (<=) as the normal-path assignment?
Both the reset branch and the else branch write the same register on the same edge; using <= for both keeps them in the NBA region together, avoiding an ordering race between the reset value and the data value.

Edge cases

What if a <= block assigns a variable that is never read elsewhere — does it still infer a flop?
Yes, in always @(posedge clk) it infers a register that is simply unused; the tool may optimise it away, but semantically it is still edge-triggered storage.
What if you write a <= a; in a clocked block (self-assignment)?
It's valid — the flop reloads its own pre-edge value each edge, i.e. holds its state. Harmless and sometimes intentional for a hold/enable pattern.
What happens if you make two non-blocking writes to the same reg in one block, e.g. x <= a; x <= b;?
Both writes are stashed and applied in the NBA region, and last write wins — the final x is b (old value of b), because the second scheduled update overwrites the first at update time. The intermediate x <= a has no visible effect.
What happens in always @(*) begin if (sel) y = a; end — a combinational block that doesn't assign y on every path?
When sel is false y keeps its old value, so the tool infers a latch (unintended memory). Assign y on all branches or give it a default first.
What is the behaviour of x = x + 1; versus x <= x + 1; in a clocked counter across many cycles?
Both increment once per clock and behave identically for a single self-referencing counter, because there's only one variable and one update — the difference surfaces only when other signals read x in the same block.
Does using <= on a wire work?
No — procedural assignments (both = and <=) require a reg-type target; a wire is driven by continuous assign. See Verilog data types (reg vs wire).
In a zero-delay loop where a <= block feeds itself combinationally, why is there no infinite immediate ripple?
Non-blocking defers writes to the NBA region (a later delta cycle), so each pass reads stable pre-update values — this breaks the immediate feedback that blocking assignments would create.
Asynchronous vs synchronous reset — how does the sensitivity list, not the operator, decide which you get?
always @(posedge clk) samples reset only at the edge → synchronous reset; always @(posedge clk or negedge rst_n) reacts the instant reset asserts → asynchronous reset. In both cases you still use <= for the assignments; the operator choice does not change reset timing.

Connections