Menu

Verilog Blocking vs Non-Blocking Assignment: When to Use = vs <=

The single most-confused topic in beginner Verilog. What = and <= actually mean inside an always block, and the rule that prevents most race conditions.

This page includes runnable editors - edit, run, and see output instantly.

The Two Operators

Inside always and initial blocks, Verilog has two assignment operators:

  • = is a blocking assignment. Update the LHS now, before moving to the next statement.
  • <= is a non-blocking assignment. Evaluate the RHS now, schedule the LHS to be updated at the end of the current time step.

Outside of procedural blocks (in assign) only = exists - assign a <= b is a syntax error. Inside procedural blocks, both are legal, and choosing the right one is the single most important decision in beginner Verilog.

What Blocking Does

Blocking is what you'd expect from a software language: line by line, top to bottom. Statement N completes before statement N+1 starts:

This is purely sequential. Each statement sees the effects of the ones above it. That matches software intuition.

What Non-Blocking Does

Non-blocking is hardware-shaped. All right-hand sides evaluate using the values at the start of the time step. All left-hand sides update at the end of the time step. The order of statements in the source doesn't change the dependencies between signals:

That snippet implements a 3-way rotate of a → b → c → a in one step. With blocking, you'd need a temporary variable to avoid clobbering one of them. With non-blocking, the swap happens atomically because every RHS reads the pre-step values.

This is exactly how three flip-flops behave on a clock edge: they all capture their inputs at the same instant, regardless of the dependencies between them.

The Rule That Saves You

Use <= in clocked blocks. Use = in combinational blocks.

That's it. Memorize it. Code it without thinking. Most race conditions, simulation/synthesis mismatches, and "it works for me but not in synthesis" bugs come from violating this rule.

The rule has a hardware reason: clocked blocks model flip-flops that all sample their inputs simultaneously. Combinational blocks model logic that propagates as fast as it can. The assignment operator semantics match those behaviors.

Each <= reads the current value of the source register and schedules the destination register to take that value at the end of the time step. The net effect is exactly what a hardware shift register does: every flip-flop captures its neighbor's value, all on the same clock edge, no race.

Now consider what would have happened with blocking assignment:

// WRONG - this is not a shift register!
always @(posedge clk) begin
    out[3] = out[2];   // out[3] becomes out[2]
    out[2] = out[1];   // out[2] becomes out[1], which we just set above
    out[1] = out[0];
    out[0] = in;
end

Each statement clobbers the source before the next statement reads it. In one clock cycle, in would propagate all the way through to out[3], because each line sees the freshly-written value of the one above. The behavior on real hardware (which uses non-blocking semantics) would be completely different from what the simulator showed.

Combinational Blocks: Blocking Is Right

For always @(*), blocking assignment is correct. There are no flip-flops, no simultaneous-capture rule to enforce, and intermediate variables are useful:

sum is computed first, then result uses the just-computed value. The combinational logic flattens to a single piece of hardware: result = ~(a + b). No flip-flops appear because there's no clock.

If you used <= here, the simulator would still update sum before result was evaluated against it (because both updates happen at end-of-step), but the order would be subtly different and many synthesis tools complain. Don't mix; pick the operator that matches the block type.

The Mistake That Hurts the Most

Here it is: a clocked block with blocking assignment.

// BUG: race condition waiting to happen
always @(posedge clk) begin
    a = b;
    b = c;
    c = a;
end

In simulation, the simulator might give a first, then b, then c, producing one set of values. The hardware will produce another set, because real flip-flops capture simultaneously. The two diverge silently and you'll waste a day finding the bug. Use <= in clocked blocks.

Why Two Operators Exist At All

Verilog's designers could have picked one assignment semantic and stuck with it. They didn't, because the language has to model two distinct hardware behaviors:

  • Combinational logic: signals propagate continuously, dependencies matter, "what does this gate compute" makes sense.
  • Sequential logic: a clock edge happens, every flip-flop captures simultaneously, dependencies between flip-flop inputs and outputs are decoupled.

Blocking is for the first. Non-blocking is for the second. The operator picks the semantics; the simulator does the rest.

What Comes Next

You now have the rules to write any procedural block correctly. The next chapter steps up from individual blocks to the control-flow constructs that go inside them: if/else, case, and for loops. The rules of blocking vs non-blocking still apply inside all of them.

Frequently Asked Questions

What is the difference between blocking and non-blocking assignment in Verilog?

Blocking (=) updates the target immediately, before the next statement runs. It models sequential execution. Non-blocking (<=) schedules the update for the end of the current time step - every non-blocking RHS is evaluated using the old values of all signals, then every LHS is updated in a single coordinated step. Non-blocking is how flip-flops actually behave.

When should I use = vs <= in Verilog?

Rule: use <= in clocked always @(posedge clk) blocks, and use = in combinational always @(*) blocks. That single rule prevents the entire class of race conditions that mixed assignments produce. Inside testbench initial blocks, = is the normal choice; <= shows up rarely there.

Why is non-blocking assignment needed in Verilog?

Because hardware flip-flops all capture their inputs simultaneously on a clock edge. If you used = (blocking) in clocked code, the order of statements in your file would change which signal sees the new value of which other signal - a race condition between simulation and real hardware. <= matches the hardware behavior by evaluating all RHS first, then doing all the updates.

What happens if you mix = and <= in the same Verilog always block?

You get a race condition. The mix produces hardware whose behavior depends on simulator internals (event scheduling order), and worse, the simulation may not match what synthesis produces. Most lint tools flag this as an error. The fix is to commit to one or the other based on the block's role - non-blocking for clocked, blocking for combinational.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED