Verilog · lesson 8

Blocking vs Non-Blocking

What = and <= actually schedule inside a simulator, a 3-stage pipeline that silently collapses to one register when coded wrong, and the guidelines that prevent it.

15 min read · Free, no login

Two operators, two different scheduling models

Earlier lessons have already used both = (lesson 2's testbench) and <= (lesson 7's clocked flip-flop template) without explaining why they behave differently inside a procedural block. This lesson is entirely about that difference, because it is the single most consequential thing to understand about Verilog simulation, and one of the most reliably asked topics in a Verilog interview. It is not a matter of style preference; it is a real category of bug with a precise mechanical cause.

A blocking assignment (=) does exactly what its name says: it evaluates its right-hand side and updates its left-hand side immediately, in one step, before the next statement in the same block is even allowed to begin. A non-blocking assignment (<=) splits into two steps: it evaluates its right-hand side immediately, using whatever values exist at that instant, but schedules the actual update to the left-hand side to happen only after every other active event in that same simulation time step has been evaluated. Every non-blocking assignment that fires at the same clock edge reads the old values and only writes the new ones once all of them have finished reading โ€” which is exactly what real, simultaneously-clocked flip-flops do in hardware, and exactly why <= is the correct way to describe a clocked register.

A 3-stage pipeline, coded two ways

The clearest way to see the difference is to watch it change a real circuit's behavior, not just read about it. Here is a simple 3-stage pipeline register โ€” three flip-flops in series, each passing its input to the next on every clock edge โ€” written identically except for the assignment operator, with the statements left in the most natural top-to-bottom reading order.

pipe_blocking_bad.v
module pipe_blocking_bad (
    input  wire       clk,
    input  wire [7:0] d,
    output reg  [7:0] q1, q2, q3
);
    always @(posedge clk) begin
        q1 = d;
        q2 = q1;
        q3 = q2;
    end
endmodule
pipe_nonblocking_good.v -- identical, except for the operator
module pipe_nonblocking_good (
    input  wire       clk,
    input  wire [7:0] d,
    output reg  [7:0] q1, q2, q3
);
    always @(posedge clk) begin
        q1 <= d;
        q2 <= q1;
        q3 <= q2;
    end
endmodule

Driving d = 0xAA on the first clock edge and d = 0x00 on every edge after that, and watching all three outputs of both modules every cycle, gives this:

cyc  d    | blocking: q1 q2 q3 | nonblocking: q1 q2 q3
 1  aa   |           aa aa aa   |              aa xx xx
 2  00   |           00 00 00   |              00 aa xx
 3  00   |           00 00 00   |              00 00 aa

The blocking version is simply wrong. On cycle 1, the value aa appears in q1, q2, and q3 simultaneously โ€” one clock edge carried it straight through all three stages, because each blocking assignment completes immediately, so by the time q2 = q1 executes, q1 already holds the brand-new value from the line above it, not the value it held before this clock edge. The non-blocking version does the opposite, and does it correctly: on cycle 1, only q1 updates (q2 and q3 are still the simulator's uninitialized x, from lesson 3); the aa only reaches q2 on cycle 2 and q3 on cycle 3 โ€” a genuine three-cycle pipeline latency, which is the entire point of building a pipeline in the first place. Every non-blocking assignment in that block read the register values as they stood before this clock edge, exactly as three real, independently clocked flip-flops would.

This is not just a simulation quirk โ€” it can change what gets built

It would be a reasonable guess that the blocking version "still simulates wrong but synthesizes right," since the intent of the code is clearly three separate registers. Running both modules through Yosys's synthesis flow (synth; stat) and counting flip-flop cells shows that this guess is wrong, and shows exactly why an incorrect blocking-assignment coding style here is so dangerous: the blocking version synthesizes to 8 flip-flops โ€” one 8-bit register, not three โ€” while the non-blocking version synthesizes to 24 flip-flops โ€” three genuine 8-bit registers, the pipeline actually intended. A synthesis tool reading q1 = d; q2 = q1; q3 = q2; correctly notices that, since each line immediately overwrites the one before it, q1 and q2 never hold a value for any meaningful duration and optimizes them away entirely, collapsing the whole block to a single register. The simulation was not simply "a little off" โ€” it was quietly describing different hardware than the designer almost certainly intended, and a synthesis tool optimized that hardware down to match what the code actually said rather than what it was meant to say.

Why mixing styles inside a feedback loop is worse, not just different

The pipeline example has no feedback โ€” each stage depends only on the stage before it, so a sufficiently careful ordering of blocking assignments (q3 = q2 then q2 = q1 then q1 = d, bottom to top instead of top to bottom) can actually happen to simulate correctly, even though it is still fragile and still not synthesis-proof once the logic is split across more than one always block. A circuit with real feedback โ€” a register whose next value depends on another register that, through some other path, depends back on the first one โ€” has no such escape: there is no ordering of blocking assignments within or across blocks that correctly models two values that must both appear to update "at once." Non-blocking assignment handles this for free, because every right-hand side is evaluated using the previous cycle's values regardless of the order the assignments are written in, which is exactly the simultaneity real flip-flops have and blocking assignment cannot describe.

The practical guidelines

All of this condenses into a short set of rules, and this course follows them exactly, because the reasoning behind each one is the mechanism just demonstrated above, not a matter of taste.

  • Use non-blocking assignment (<=) for sequential logic โ€” any always block triggered by a clock edge. This is the template every example since lesson 7 has used.
  • Use blocking assignment (=) for combinational logic โ€” any always @* block, as every example since lesson 5 has used. Non-blocking assignment inside a combinational block still works, but can require an extra, wasteful pass through the block to settle, since each assignment's new value is not visible to the next line until the whole block finishes.
  • If a block genuinely mixes sequential and combinational behavior (a clocked block that also needs an intermediate calculation), code the whole block with non-blocking assignment rather than splitting styles within it.
  • Never mix blocking and non-blocking assignment to the same variable inside one block โ€” even where a simulator tolerates it, a synthesis tool is entitled to reject it outright.
  • Never let more than one always block drive the same variable. Even with non-blocking assignment throughout, two blocks assigning the same signal on the same clock edge is a genuine race โ€” which block's value wins is left to the simulator's own scheduling order, which the Verilog standard explicitly does not pin down.
  • Avoid #0-delayed assignments as a way to "win" a race between two blocks. It is a workaround for the previous rule's violation, not a fix for it, and it makes the resulting event ordering far harder to read back out of the code later.

One more practical trap: displaying a value you just non-blocking-assigned

A non-blocking assignment's right-hand side is evaluated immediately, but its left-hand side is not updated until later in the same time step โ€” which means a $display placed right after a non-blocking assignment, in the same block, prints the old value, not the one just scheduled.

$display : a = 0  (ran immediately, before the nonblocking update)
$strobe  : a = 1  (ran after all updates in this time step settled)

Both lines run against the exact same assignment, a <= 1;, executed moments earlier in the same initial block. $display runs immediately, in the same active region as the assignment's own right-hand-side evaluation, so it reports a's value from before the update took effect. $strobe is specifically defined to run only after every other event scheduled for that time step โ€” including every pending non-blocking update โ€” has finished, so it reliably reports the final, settled value. Whenever a testbench needs to print or check a value that was just driven with <=, $strobe is the correct tool; $display will appear to be "one step behind," which is confusing to debug if the reason for it is not already understood.

Interview questions

What is the practical difference between = and <= inside a clocked always block? = (blocking) updates its target immediately, so a later line in the same block sees the new value right away. <= (non-blocking) evaluates its right-hand side immediately but defers the actual update until every other event scheduled for that same simulation time step has been processed โ€” so every non-blocking assignment triggered by the same clock edge reads pre-edge values, matching how real, simultaneously-clocked flip-flops behave.

Why does a 3-stage pipeline written with blocking assignment, in the natural top-to-bottom order, simulate as a single-cycle passthrough instead of a pipeline? Each blocking assignment completes before the next line executes, so by the time the second line runs, it already sees the brand-new value the first line just wrote, not the value from before the clock edge โ€” the new input value rushes through all three stages on the same edge instead of taking three cycles.

Why is it dangerous for two different always blocks to assign the same variable, even with non-blocking assignment used correctly in both? The relative order in which a simulator schedules two separate blocks triggered by the same event is not defined by the Verilog standard โ€” it is a genuine race condition, and which block's value ends up winning can differ between simulators, or even between runs.

Key takeaways

  • Blocking (=) updates immediately, in program order; non-blocking (<=) evaluates immediately but defers every update in a time step until all of that step's other events have been processed โ€” matching how real, simultaneously-clocked flip-flops behave.
  • A pipeline coded with blocking assignment in top-to-bottom order collapses: the new input value rushes through every stage on one clock edge instead of taking one cycle per stage โ€” confirmed here both in simulation and, worse, in real synthesized flip-flop counts (8 instead of 24).
  • Use non-blocking assignment for every clocked (sequential) block and blocking assignment for every combinational block; never mix the two on the same variable, and never let two different blocks drive the same variable at all.
  • $display run right after a non-blocking assignment prints the pre-update value; $strobe prints the settled, post-update value โ€” use $strobe whenever a testbench needs to check a value just driven with <=.