Question bank — Blocking vs non-blocking assignments
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.

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 over — a 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.

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.

True or false — justify
Non-blocking <= samples every right-hand side before any left-hand side is updated.
<= line ever sees another <= line's fresh value. (Re-read the timeline figure: writing is a separate box.)Blocking = always synthesizes to combinational logic.
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.
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.
= 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.
<= 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.
= 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 <=.
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.
Spot the error
always @(posedge clk) begin q1 = d; q2 = q1; q3 = q2; end — intended as a 3-stage shift register.
= 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)
= 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)
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.
= 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.
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.
= 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?
Why does the swap work with <= but fail 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?
Why do we forbid mixing = and <= for the same signal?
Why is = the right choice for an intermediate value inside always @(*)?
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?
Why does the mnemonic "Clock? Non-blocK" capture rule 1?
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?
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?
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)?
What happens if you make two non-blocking writes to the same reg in one block, e.g. x <= a; x <= b;?
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?
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?
x in the same block.Does using <= on a wire work?
= 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?
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
- Blocking vs non-blocking assignments (parent)
- Always blocks and sensitivity lists
- Combinational vs Sequential logic
- D Flip-Flops and Registers
- Race conditions in RTL simulation
- Synthesis vs Simulation mismatch
- Verilog data types (reg vs wire)
- Pipelining and shift registers