3.5.4 · D2HDL & Digital Design Flow

Visual walkthrough — Blocking vs non-blocking assignments

2,396 words11 min readBack to topic

Step 0 — The words we must define before anything else

We are going to use a handful of words on every line below. Let's pin each to a picture idea first.


Step 1 — Split one time step into two rooms

Here is the single idea the whole topic hangs on. One time step is not instantaneous inside the simulator — it has two internal "rooms" (regions) that run in order.

WHAT: the simulator, at each edge, first runs the Active room, then runs the NBA room. NBA is short for Non-Blocking Assignment — we will use that acronym from here on, but it always just means "the room where <= writes happen."

WHY: because hardware genuinely has two behaviours (compute-in-order vs update-together, see Combinational vs Sequential logic), one time step is deliberately given two phases so both can be modelled with one clock edge.

PICTURE: two boxes stacked in time. In the left/first box, blocking assignments finish and non-blocking right-hand-sides get photographed into a stash. In the right/second box, the stashed photos are poured into the left-hand sides.

Figure — Blocking vs non-blocking assignments

  • Active region — where = fully completes, and where every <= reads its right side and saves the number, but does not yet write it.
  • NBA (Non-Blocking Assignment) region — the room where all the saved numbers are written into their targets, all at once.

Step 2 — Blocking = : write immediately, then move on

Let's give a and b starting values a = 3, b = 7 and run the classic swap with blocking.

a = b;   // line 1
b = a;   // line 2

WHAT: we walk the two lines top to bottom, and after each line the track's height changes on the spot.

WHY these are ordered: = lives entirely in room 1 and completes before the next statement starts — exactly like a line of C code. So line 2 sees whatever line 1 already did.

PICTURE — follow the yellow arrows:

Figure — Blocking vs non-blocking assignments
  • Line 1 a = b: read old b = 7, write it into a now. Track a jumps 3→7.
  • Line 2 b = a: read a — but a is already 7 — write into b. Track b stays 7.

Result: both are 7. The 3 is gone. No swap. The value of a was overwritten before line 2 could read it.


Step 3 — Non-blocking <= : photograph everything, then write

Same start (a = 3, b = 7), same two lines, but non-blocking:

a <= b;   // line 1
b <= a;   // line 2

WHAT: in room 1 we only read and stash both right sides. Neither track moves yet. In room 2 both writes land together.

WHY: <= deliberately defers its write to room 2, so line 2's right side still sees the pre-edge a.

PICTURE — dashed blue = "photo taken", solid pink = "written in room 2":

Figure — Blocking vs non-blocking assignments

Room 1 (photograph):

Room 2 (write, simultaneously):

Result: a = 7, b = 3. True swap. The key term-by-term difference from Step 2: in line 2 the symbol a was read as the old 3, because no write happened until room 2.


Step 4 — Order-independence (the degenerate "does line order matter?" case)

A software brain screams "but line 2 comes after line 1, order must matter!" Let's kill that fear with a picture. Swap the two <= lines and re-run:

b <= a;   // now first
a <= b;   // now second

WHAT: run the same two-room process on the reordered code.

WHY it doesn't change anything: all right-hand sides are photographed in room 1 before any left-hand side is written. Reading order among photos is irrelevant — a photo of a is 3 and a photo of b is 7 no matter which shutter clicked first.

PICTURE — same final heights, different line order:

Figure — Blocking vs non-blocking assignments

Identical result to Step 3. This is why non-blocking never causes an ordering race among itself — there is nothing to race.


Step 5 — Chaining flip-flops: the shift register (why <= scales)

Now three tracks: q1, q2, q3, with an input d. Start d=1, q1=0, q2=0, q3=0. Non-blocking:

q3 <= q2;
q2 <= q1;
q1 <= d;

WHAT: one edge = one room-1 photo of {q2, q1, d}, then one room-2 write. Data moves exactly one stage per edge.

WHY it is a true 3-stage shift register: every right side is a pre-edge value. q3 grabs the old q2, q2 grabs the old q1, q1 grabs the old d. Nobody sees a neighbour's freshly-written value.

PICTURE — the four snapshots are the SAME circuit at clk0, clk1, clk2, clk3; watch the 1 step one box right each tick:

Figure — Blocking vs non-blocking assignments

Trace the multi-cycle progression explicitly (each row is one edge; every value is read from the row above it):

after edge q1 q2 q3
clk0 (start) 0 0 0
clk1 1 0 0
clk2 1 1 0
clk3 1 1 1

The 1 needs three edges to walk from q1 to q3. Three flip-flops, three cycles of delay — a real shift register.


Step 6 — The same code with = collapses the register (degenerate hardware)

q1 = d;    // q1 becomes 1 NOW
q2 = q1;   // q1 is ALREADY 1, so q2 = 1 NOW
q3 = q2;   // q2 is ALREADY 1, so q3 = 1 NOW

WHAT: walk top-to-bottom, each write immediate.

WHY it breaks: because room-1 immediate writes let d ripple through all three stages inside one edge.

PICTURE — one clock, the 1 shoots straight to q3:

Figure — Blocking vs non-blocking assignments

All three become 1 in a single clock. The synthesizer sees "one input, everything equals it" and builds one flip-flop, not three. This is the classic sim/synthesis disaster the rule prevents.


Step 7 — When blocking is the correct choice (combinational)

Not everything wants photos. Pure combinational logic is a data-flow: a value ripples through gates, and later gates need the earlier result immediately.

always @(*) begin
    tmp = a & b;   // intermediate must be ready
    y   = tmp | c; // uses the just-computed tmp
end

WHAT: compute tmp, then use it — in order, in one pass.

WHY = here and not <=: if we used <=, y would read the stale tmp from before this pass (room-2 timing), modelling a value the real wire never held — an unintended latch.

Figure — Blocking vs non-blocking assignments

See Always blocks and sensitivity lists for why @(*) re-runs on any input change.


The one-picture summary

Figure — Blocking vs non-blocking assignments

One time step, two rooms. Blocking (=) does its whole job in room 1 and each write is visible to the next line — good for the ordered ripple of combinational logic. Non-blocking (<=) photographs in room 1, writes in room 2 (the NBA region) — every left side receives a pre-edge value, which is exactly what a bank of clocked flip-flops does, and why line order among different <= targets can never matter.

Recall Feynman retelling — the whole walkthrough in plain words

Picture two kids, A holding a "3" card and B holding a "7" card, standing on a stage.

Blocking version: the teacher says "A, copy B" — A instantly changes its card to 7. Then "B, copy A" — but A is already 7, so B copies 7. Both hold 7. The 3 vanished. No swap. Now stretch this to a line of kids passing a card down: because each copies instantly, one card zooms from the first kid to the last in one breath — a three-person line collapses into "everyone's the same".

Non-blocking version: the teacher first takes a photo of the whole stage (A=3, B=7). Then says "everyone, become what your partner was in the photo." A becomes 7, B becomes 3 — a real swap. Because everyone obeyed the same frozen photo, it made no difference who the teacher named first. In the line of kids, the photo makes each kid copy the old card of the person before them, so the card moves exactly one seat per photo — a genuine shift register. (If the teacher tells the same kid two things in one photo, only the last order counts.)

The word @(posedge clk) is just "wait until the clock's metronome ticks up, then everyone act." Real clocked chips are the photo kind. So — clocked block? use <= everywhere (never mix in =). Combinational ripple? use =. The two-room timeline is the whole story.


Connections