Skip to article

Sequential RTL

Blocking vs Non-Blocking Assignments in Verilog

Learn when to use = and <= in Verilog, how event scheduling changes simultaneous register updates, and how to verify the difference with a runnable example.

3 min read · Updated


Use non-blocking assignment (<=) for state-holding variables updated by an edge-triggered process, and blocking assignment (=) for calculations in combinational procedural logic. The distinction is about simulation scheduling: = updates immediately within the current process, while <= evaluates its right-hand side now and schedules its left-hand-side update for the non-blocking-assignment region.

That is a strong RTL rule, not a claim that one character alone determines hardware. Event controls, assignment coverage, data flow, resets, and synthesis all matter.

Quick comparison

Question Blocking = Non-blocking <=
When does the left side change in simulation? Immediately before the next statement in that process Later in the current simulation time slot
What does a later statement in the same process see? The newly written value The pre-update value until scheduled updates occur
Normal RTL use always @(*) combinational calculations Edge-triggered register/state updates
Common failure Procedural ordering changes a clocked result Using it in combinational logic can obscure intended calculation order

Why simultaneous register updates expose the difference

On an active clock edge, real flip-flops sample their inputs together. Non-blocking assignments model that “evaluate together, update together” behavior.

Consider two one-bit registers that should exchange values on every rising edge:

always @(posedge clk) begin
  left  <= right;
  right <= left;
end

Both right-hand sides use pre-edge values, so 01 becomes 10.

With blocking assignments:

always @(posedge clk) begin
  left  = right;
  right = left;
end

The first statement changes left immediately. The second statement then reads that new left, so starting from 01 produces 11, not a swap.

Do not reduce this to “blocking always synthesizes fewer registers.” Tools infer and optimize from the complete design. The reliable conclusion is that the blocking process does not simulate the intended simultaneous sampling, and a synthesis tool may not diagnose that intent mismatch.

Predict before running

Both modules below use an active-high synchronous reset: rst is sampled only on posedge clk because it is not in the event control. Reset loads left=0 and right=1; the next two rising edges attempt the exchange.

Before running it, predict both pairs after each edge.

`timescale 1ns/1ps

module exchange_nonblocking(
  input wire clk, input wire rst,
  output reg left, output reg right
);
  always @(posedge clk) begin
    if (rst) begin
      left  <= 1'b0;
      right <= 1'b1;
    end else begin
      left  <= right;
      right <= left;
    end
  end
endmodule

module exchange_blocking(
  input wire clk, input wire rst,
  output reg left, output reg right
);
  always @(posedge clk) begin
    if (rst) begin
      left  = 1'b0;
      right = 1'b1;
    end else begin
      left  = right;
      right = left;
    end
  end
endmodule

module tb;
  reg clk, rst;
  wire nb_left, nb_right, b_left, b_right;

  exchange_nonblocking good(clk, rst, nb_left, nb_right);
  exchange_blocking comparison(clk, rst, b_left, b_right);
  always #5 clk = ~clk;

  initial begin
    clk = 1'b0; rst = 1'b1;
    @(posedge clk); #1;
    if ({nb_left, nb_right} !== 2'b01 || {b_left, b_right} !== 2'b01) begin $display("FAIL RESET"); $finish; end

    @(negedge clk); rst = 1'b0;
    @(posedge clk); #1;
    $display("EDGE 1 nonblocking=%0b%0b blocking=%0b%0b", nb_left, nb_right, b_left, b_right);
    if ({nb_left, nb_right} !== 2'b10 || {b_left, b_right} !== 2'b11) begin $display("FAIL EDGE1"); $finish; end

    @(posedge clk); #1;
    $display("EDGE 2 nonblocking=%0b%0b blocking=%0b%0b", nb_left, nb_right, b_left, b_right);
    if ({nb_left, nb_right} !== 2'b01 || {b_left, b_right} !== 2'b11) begin $display("FAIL EDGE2"); $finish; end

    $display("PASS");
    $finish;
  end
endmodule

Expected output:

EDGE 1 nonblocking=10 blocking=11
EDGE 2 nonblocking=01 blocking=11
PASS

The non-blocking pair swaps on each edge because both expressions read the pre-edge values. The blocking pair becomes 11 on the first exchange and stays there. The #1 after each rising edge is a testbench observation delay: capture happens on the edge; the later print avoids racing the scheduled non-blocking updates.

Practical rules

  1. Use non-blocking assignments for state-holding variables in an edge-triggered RTL process, including reset assignments to those variables.
  2. In an intended combinational process, assign defaults and calculate with blocking assignments.
  3. Never mix blocking and non-blocking assignments on the same state-holding variable or drive it from multiple processes.
  4. Treat lint warnings about blocking assignments in sequential RTL as design-review prompts.

Advanced code sometimes uses a local temporary with blocking assignment inside a sequential process, but that requires deliberate data-flow reasoning. It does not change the rule for the state-holding outputs.

Sources and verification

Example provenance: SkillLift Labs authored this exchange example as a parallel demonstration, not as a course build answer. tests/scripts/tutorial-verified-examples.test.ts compiles it in Verilog-2005 mode, reproduces the published output, and independently checks the reset and both edge results. The same test exercises the article-to-runner source split. Automation verifies those stated properties; human technical review for indexing remains a separate gate.