Skip to content

Blocking and Non-Blocking assignment in Verilog

Introduction

In Verilog, assignment operators determine how values are updated inside procedural blocks.
Two assignment styles — blocking (=) and non-blocking (<=) — define the timing and execution order of assignments.

Even though they look similar, the difference between = and <= can drastically change simulation results and hardware behavior.
This guide explains both types in detail, including how they work, where to use them, and common mistakes that cause unexpected results.

What Are Blocking and Non-Blocking Assignments?

Procedural assignments in Verilog appear inside always or initial blocks.
They can be blocking (=) or non-blocking (<=).

Type Symbol Execution Typical Use
Blocking assignment = Executes immediately in sequence Combinational logic
Non-blocking assignment <= Executes concurrently at end of time step Sequential logic (flip-flops)

Blocking Assignment (=)

Behavior

A blocking assignment acts like a traditional software assignment — the statement blocks execution until it completes.

Example

always @(*) begin
  x = a & b;
  y = x | c;
end

Execution flow:

  1. x gets a & b.
  2. Only after that, y gets x | c.

It’s like sequential statements in C or Python. In combinational logic, blocking assignments are often used to model signals that depend on earlier computed values. It is like cascade of and-gate followed by or-gate in the above example.

Non-Blocking Assignment (<=)

A non-blocking assignment schedules the new value to update after the current simulation time step, allowing all right-hand sides to be evaluated before any updates occur.

always @(*) begin
  x <= a & b;
  y <= x | c;
end

Here, both x and y are updated concurrently on the same clock edge:

  • x gets the old value of a & b
  • y gets the old value of x | c

Non-blocking assignments model flip-flops or registers, which all update together on a clock edge.

When to Use Which

Situation Recommended Assignment Reason
Combinational logic = (blocking) Sequential evaluation of logic
Sequential (clocked) logic <= (non-blocking) Parallel update of registers
Testbench / simulation variables Either Use = for clarity; <= rarely needed

In Summary, use blocking (=) for combinational logic, non-blocking (<=) for sequential logic.

Mixing Blocking and Non-Blocking

It’s legal to mix them, but doing so in the same always block often leads to race conditions.

In a single always block, both blocking and non-blocking statements can be used, though not-recommended.

always @(posedge clk) begin
  sum = a + b;  // blocking
  out <= sum;   // non-blocking
end

Here, how the above code works :

  • a updates immediately (=),
  • out gets old sum (not predictable in synthesis).

Uncertain simulation and synthesis behaviour.

always @(*) begin
  sum = a + b;  // blocking (combinational)
end

always @(posedge clk) begin
  out <= sum;   // non-blocking (sequential)
end

This cleanly separates combinational and sequential parts.

Scheduling and Event Queue

To understand simulation order, know the Verilog event queue:

  1. Active region : Blocking assignments (=) execute immediately.
  2. Non-blocking assignment (NBA) region : <= updates scheduled after all active events.
  3. Post-poned region : Waveform dump, $monitor, etc.

So non-blocking updates happen after all blocking ones complete at that simulation time.

initial begin
  a = 0;
  b = 0;
  #5 a = 1;
  b <= 1;
end

At time 5:

  • a = 1 happens now (blocking)
  • b <= 1 happens after the current time step (non-blocking)

Common Mistakes and Simulation Mismatches

Mistake Description Result
Using = in clocked logic Updates in sequence, not parallel Wrong pipeline timing
Using <= in combinational logic All signals update later Possible unintended latches
Mixing = and <= in one block Race conditions Non-deterministic results
Missing sensitivity list in @(*) Not updating properly Simulation mismatch
No reset for registers Uninitialized state X propagation in simulation

Example — Race Condition

reg a, b, clk;
always @(posedge clk)
  a = b;

always @(posedge clk)
  b = a;

Both blocks use blocking assignments on the same clock. In simulation, the order of execution is undefined — one may execute before the other.

In hardware, both flip-flops sample the old values simultaneously. Fix by using non-blocking assignments:

always @(posedge clk)
  a <= b; // changed to non-blocking assignment

always @(posedge clk)
  b <= a; // changed to non-blocking assignment

Now simulation matches real hardware behavior.

Advanced Example — Pipeline Delay

Let’s model a 3-stage pipeline.

always @(posedge clk) begin
  stage1 <= in_data;
  stage2 <= stage1;
  stage3 <= stage2;
end
always @(posedge clk) begin
  stage1 = in_data;
  stage2 = stage1;
  stage3 = stage2;
end

The blocking version collapses all stages into one — simulation gives the illusion that data moves through instantly, which is not synthesizable hardware behavior.

Synthesizable Examples

Example 1: Counter

always @(posedge clk or posedge rst) begin
  if (rst)
    count <= 0;
  else
    count <= count + 1;
end

Example 2: ALU

always @(*) begin
  case(op)
    2'b00: y = a + b;
    2'b01: y = a - b;
    2'b10: y = a & b;
    2'b11: y = a | b;
  endcase
end

Guidelines for Clean RTL Code

Do

  • Use = in combinational logic always @(*).
  • Use <= in sequential logic always @(posedge clk).
  • Separate combinational and sequential blocks.
  • Provide resets for registers.

Don't

  • Mix = and <= in the same block.
  • Use = in flip-flop code.
  • Forget @(*) in combinational always blocks.
  • Rely on simulation order — synthesis ignores statement order in concurrent logic.
Feature Blocking (=) Non-Blocking (<=)
Execution Sequential (immediate) Concurrent (scheduled)
Typical use Combinational logic Sequential (clocked) logic
Simulation order One after another All update after current time step
Hardware mapping Wires, gates Flip-flops, registers
Safe to mix in same block? No No
Synthesis behavior Follows order Parallel register updates
Used for testbenches? Often Rarely

Example: Correcting a Bug

Buggy code:

always @(posedge clk)
  sum = a + b;
Works in simulation but fails in synthesis (timing mismatch).

Fix:

always @(posedge clk)
  sum <= a + b;

Now the register updates only on clock edge, matching real flip-flop timing.