Blocking vs Non-Blocking Assignment
The = vs <= distinction is the most common source of bugs for Verilog beginners and a frequent interview topic. Understanding the Verilog event scheduler and the golden rule will save you hours of debugging race conditions.
⚡ The Golden Rule
Use = in always @(*) combinational blocks · Use <= in always @(posedge clk) sequential blocks
Never mix them in the same always block.
1. Blocking Assignment (=)
Blocking assignments execute in order, like C code. The next statement in the block sees the updated value.
always @(*) begin temp = a & b; // temp updates immediately out = temp | c; // out sees the new temp — correct end
The sequential execution of blocking assignments in @(*) is intentional and useful — it lets you use intermediate variables without ambiguity. The final combinational output is purely a function of current inputs.
2. Non-Blocking Assignment (<=)
Non-blocking assignments schedule an update to the NBA (non-blocking assignment update) queue. The RHS is evaluated immediately, but the LHS is not updated until all active events for the current timestep are done.
always @(posedge clk) begin b <= a; // RHS evaluated: captures current a c <= b; // RHS evaluated: captures current b (OLD value) // At end of timestep: b gets old a, c gets old b simultaneously end
3. The Verilog Event Scheduler
Understanding the simulation scheduler explains why non-blocking works. Within each simulation timestep:
- Active region: all blocking = assignments execute;
alwaysblocks triggered by events run - NBA region: all pending
<=updates are applied simultaneously - Postponed region:
$strobeand final monitoring
Because all <= updates happen in the NBA region (after all active events), every flip-flop in a clocked block reads the pre-update values of other flip-flops — exactly how real hardware works. All flip-flops clock simultaneously.
4. Race Condition Example
Two always blocks that share a signal and use blocking assignment can race against each other:
// Two separate always blocks — order of execution is undefined! always @(posedge clk) a = b; // blocking always @(posedge clk) b = a; // blocking // Depending on simulator execution order: // Option A: a gets old b, b gets new a (already updated) // Option B: b gets old a, a gets new b (already updated) // → Simulation result is UNDEFINED and tool-dependent!
always @(posedge clk) a <= b; // schedule: a ← old b always @(posedge clk) b <= a; // schedule: b ← old a // NBA region applies both simultaneously: // a gets old b, b gets old a → correct swap, order-independent
5. Shift Register Bug (Classic Interview Question)
This is the most famous Verilog pitfall. Three flip-flops in series — a 3-stage shift register:
always @(posedge clk) begin b = a; // b gets new a immediately c = b; // c gets NEW b (which is already new a!) // Result: both b and c get value of a in ONE clock // Simulates as a 1-stage shift, not 3-stage! end // Starting: a=1, b=0, c=0 // After clk↑: b=1, c=1 — WRONG! (should be b=1, c=0)
always @(posedge clk) begin b <= a; // schedule: b ← old a c <= b; // schedule: c ← old b (not the new b!) // NBA region: b gets old a, c gets old b → correct 3-stage shift end // Starting: a=1, b=0, c=0 // After clk↑: b=1, c=0 — CORRECT!
6. Variable Swap
Non-blocking makes swapping two flip-flop values elegant — no temporary variable needed:
// Swap a and b every clock (ping-pong) always @(posedge clk) begin a <= b; // a gets old b b <= a; // b gets old a → clean swap end // With blocking =, you'd need a temp: // always @(posedge clk) begin // temp = a; // can't do this cleanly with = in seq block // end
7. Summary Table
| Feature | Blocking = | Non-Blocking <= |
|---|---|---|
| Execution | Immediate, sequential | Scheduled, simultaneous at end of timestep |
| Next statement sees | Updated value | Old value (from before the always block fired) |
| Use in combinational @(*) | ✅ Correct | ❌ Incorrect (causes simulation issues) |
| Use in sequential @(posedge clk) | ❌ Race condition risk | ✅ Correct |
| Mix in same always block | ❌ Never mix = and <= in the same block | |
| Models | Combinational logic / intermediate vars | Flip-flop behavior |
| Synthesizes to | Combinational (in @(*)) | Flip-flop (in @(posedge clk)) |
8. Complete Rule Set
- Use
<=for all assignments inalways @(posedge clk)oralways @(negedge clk) - Use
=for all assignments inalways @(*) - Never drive the same signal from two different
alwaysblocks - Never mix
=and<=in the samealwaysblock - Use
=freely ininitialblocks (simulation-only testbench code) - RHS of
<=is always evaluated with the old values — use this to your advantage for clean pipeline and shift register code