Day 3 · Procedural Combinational Logic

Blocking vs. Nonblocking

Video 4 of 4 · ~8 minutes

Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF

always @(*)if/else & caseLatch ProblemBlock vs Nonblock

🌍 Where This Lives

In Industry

Cliff Cummings's 2000 paper “Nonblocking Assignments in Verilog Synthesis” is the reference on this topic — cited in every corporate coding guideline. Nearly every lint rule in existence enforces: = only in combinational blocks, <= only in sequential blocks.

In This Course

Day 4 proves this rule with waveforms. Day 5 shift registers depend on it. Day 7 FSMs require it. The rule is absolute for the rest of the course — and the rest of your career.

⚠️ <= Is Not “Less Than Or Equal”

❌ Wrong Model

= and <= are similar — probably <= is some kind of ‘delayed’ assignment.” Or worse: “<= is a comparison.”

✓ Right Model

Inside an always block, <= means “nonblocking assignment” — a different operation entirely from =. Verilog overloaded the symbol badly. The two operators produce different simulator scheduling semantics, which in turn determine what hardware the synthesizer builds.

The receipt: Identical code, one with = and one with <= inside always @(posedge clk), produces different hardware. Not equivalent. Not interchangeable. Totally different circuits.

👁️ I Do — Two Operators, Two Rules

= Blocking

Evaluate RHS, assign to LHS, immediately. Next statement in the block sees the updated value.

Use in: always @(*)

Mental model: ordinary programming assignment.

<= Nonblocking

Evaluate all RHS using current values, schedule the updates, apply them simultaneously at end of timestep.

Use in: always @(posedge clk)

Mental model: all flip-flops capture at once, like real silicon.

= in always @(*).   <= in always @(posedge clk).   Never mix.

🤝 We Do — The Pipeline That Isn't

// WRONG: blocking in sequential (not a pipeline!)
always @(posedge clk) begin
    b = a;    // b gets a's value NOW
    c = b;    // c gets b's NEW value (= a!) — both same!
end
// CORRECT: nonblocking in sequential (proper pipeline)
always @(posedge clk) begin
    b <= a;   // scheduled: b will become a(old)
    c <= b;   // scheduled: c will become b(old)
end           // at end: b=a(old), c=b(old)
Together: The blocking version sees sequential execution — b = a, then c = new_b = a. Both registers end up holding the same value. The nonblocking version schedules both updates using the pre-edge values, modeling how real flip-flops behave: they all capture their D inputs simultaneously on the clock edge.

🧪 You Do — Trace the Execution

Initial values: a=1, b=2, c=3. After one clock edge, predict a, b, c:

always @(posedge clk) begin
    a <= b;
    b <= c;
    c <= a;
end
Answer: a=2, b=3, c=1. All RHS evaluated first: a's new value = b(old) = 2, b's new = c(old) = 3, c's new = a(old) = 1. All applied simultaneously. This is a 3-element rotator.
Same code with =: a=2, b=3 (b gets c=3, but wait — b was just overwritten by... no). Let's trace: a=b=2. b=c=3. c=a=2 (a was just updated!). Final: a=2, b=3, c=2. Rotator becomes garbage.
▶ LIVE DEMO

Shift Register: = vs <= Side-by-Side

~4 minutes

▸ COMMANDS

cd labs/week1_day03/ex4_blocking_demo/
# Two modules: shift_blocking, shift_nonblocking
make sim
make wave
# Load GTKWave .gtkw file: both traces side-by-side

▸ EXPECTED STDOUT

Cycle 1: blocking q=00 | nb q=80
Cycle 2: blocking q=00 | nb q=40
Cycle 3: blocking q=00 | nb q=20
Cycle 4: blocking q=00 | nb q=10
# blocking never shifts — 
#   all stages collapse to same value

▸ GTKWAVE

Two registers stacked: shift_blocking.q above shift_nonblocking.q. The nonblocking version shifts cleanly each cycle. The blocking version stays pinned — the pipeline collapsed on the first clock edge.

🔧 What Did the Tool Build?

4-stage shift register, both coding styles:

With = (bug)

SB_DFF:     1   ← ONE flop
             (collapses to 1 stage)

With <= (correct)

SB_DFF:     4   ← 4 flops
             (proper 4-stage pipeline)
Same source, different hardware. The blocking version got optimized to a single flop because the synthesizer can see that stages 2, 3, 4 all get the same value. Yosys is technically correct — it just built exactly what you described, which wasn't what you meant.

🤖 Check the Machine

Ask AI: “If I use = inside an always @(posedge clk) for a 3-stage shift register, what does the synthesized hardware actually look like?”

TASK

Ask AI to describe the synthesized hardware.

BEFORE

Predict: collapses to 1 stage. All registers hold the input value after one cycle.

AFTER

Strong AI explains collapse + cites Cummings. Weak AI says “3-stage shift register” — wrong.

TAKEAWAY

If AI says “still a 3-stage shift,” it's wrong. Verify with Yosys stat.

Key Takeaways

= (blocking) for combinational always @(*).

<= (nonblocking) for sequential always @(posedge clk).

 Mixing causes functional bugs — pipelines collapse.

 The synthesizer builds what you wrote, not what you meant.

= for combinational, <= for sequential. Never mix. Day 4 proves it.

Pre-Class Self-Check

Q1: Why must you use @(*) instead of a manual sensitivity list?

Manual lists risk sim/synth mismatch. @(*) automatically includes all signals read inside the block.

Q2: What causes an unintentional latch?

Not assigning a signal in every possible path through always @(*).

Pre-Class Self-Check (cont.)

Q3: Name three techniques to prevent latch inference.

1. Default assignment at top. 2. Complete if/else chains. 3. default in case statements.

Q4: When do you use = vs <=?

= in combinational always @(*). <= in sequential always @(posedge clk). Never mix.

🔗 End of Day 3

Tomorrow: Sequential Logic

Day 4 · Clocks, flip-flops, and your first blinking LED

▸ WHY THIS MATTERS NEXT

Days 1-3 covered combinational logic — logic with no memory. Day 4 adds time: flip-flops, clocks, sequential design. You'll build a counter that turns 25 MHz into a 1 Hz LED blink, and see with your own eyes why <= is the rule.