VerilogBeginner

Blocking vs nonblocking assignments in Verilog

When to use = and when to use <= in Verilog: what each assignment does, why mixing them breaks a shift register, and the rules that prevent simulation races.

On this page
  1. What each assignment does
  2. A two stage shift register
  3. What blocking does to the same code
  4. Races between always blocks
  5. Combinational logic: use blocking
  6. The rules
  7. Interview question: swap two registers
  8. VHDL has the same split

Key takeaways

  • A blocking assignment (=) updates its target before the next statement runs; a nonblocking assignment (<=) updates it at the end of the time step.
  • Use <= in clocked blocks and = in combinational blocks, and never mix the two in one block.
  • Blocking assignments in a clocked block make the result depend on statement order, and can collapse a shift register into a single stage.

Verilog has two ways to assign a value inside an always block, and they differ in exactly one thing: when the target changes. Get that one thing right and most simulation races, along with the simulation and synthesis mismatches they cause, never happen. This guide shows what each assignment does, the bug that mixing them produces, and the rules that prevent it.

What each assignment does

Blocking = Nonblocking <=
Target updates Immediately At the end of the time step
Statement order Changes the result Does not change the result
Use in always @(*), always_comb always @(posedge clk), always_ff
Models Combinational logic Registers

A two stage shift register

The clearest way to see the difference is a register chain that should delay a by two clocks. Here it is in all three languages:

VerilogVerilog
module shift2 (
  input  wire clk,
  input  wire a,
  output reg  c
);
  reg b;
  always @(posedge clk) begin
    b <= a;   // stage 1
    c <= b;   // stage 2 reads the old b
  end
endmodule
SystemVerilogSystemVerilog
module shift2 (input logic clk, a, output logic c);
  logic b;
  always_ff @(posedge clk) begin
    b <= a;
    c <= b;
  end
endmodule
VHDLVHDL
library ieee;
use ieee.std_logic_1164.all;
 
entity shift2 is
  port (clk, a : in std_logic; c : out std_logic);
end entity;
 
architecture rtl of shift2 is
  signal b : std_logic;
begin
  process (clk)
  begin
    if rising_edge(clk) then
      b <= a;  -- a signal updates after the process suspends,
      c <= b;  -- so this reads the old b: a second stage
    end if;
  end process;
end architecture;

On every rising edge both right hand sides are read first, so c takes the value b held before the edge. Two flops, two cycles of delay:

Timing diagram

With <=, c follows a two cycles later. With =, c collapses onto b and follows one cycle later.
WaveJSON source
{ "signal": [
  { "name": "clk", "wave": "p......" },
  { "name": "a", "wave": "01.0..." },
  { "name": "b", "wave": "x01.0.." },
  { "name": "c, nonblocking", "wave": "x.01.0." },
  { "name": "c, blocking", "wave": "x01.0.." }
]}

What blocking does to the same code

Write the two stages with = and the first line has already changed b by the time the second line reads it:

VerilogThe fix, as a diff
  always @(posedge clk) begin
-   b = a;
-   c = b;
+   b <= a;
+   c <= b;
  end

With =, c gets the new b, which is a. Synthesis builds two flops that both load a, and the second stage is gone. Swap the two blocking lines and the shift register comes back, which is the real problem: the meaning of the block now depends on the order of its statements.

Races between always blocks

Blocking assignments cause a second, worse problem when the stages live in separate blocks:

Verilog
always @(posedge clk) b = a;
always @(posedge clk) c = b;

Both blocks wake on the same edge, and the language does not say which runs first. If the first block runs first, c gets the new b; if the second runs first, it gets the old one. Two simulators can disagree, and synthesis always builds the shift register. With <= in both blocks the order stops mattering, because every right hand side is read before any register changes.

Combinational logic: use blocking

Inside a combinational block the rule flips. An intermediate value should be visible to the next statement at once:

VerilogCombinational, written with =
always @(*) begin
  tmp = a & b;
  y   = tmp | c;
end

Written with <=, y is first computed from the old tmp. With @(*) the block runs again when tmp changes and settles, at the cost of an extra pass. With a hand written sensitivity list such as @(a or b or c), nothing wakes the block again: y stays stale in simulation while the synthesized gates do not.

The rules

These are the core of the guidelines in Clifford Cummings' paper Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill! (SNUG 2000), and they are still the standard:

  1. Use <= in clocked always blocks, and for latches.
  2. Use = in combinational always blocks.
  3. When one block holds both sequential and combinational logic, use <=.
  4. Do not mix = and <= in the same block.
  5. Do not assign the same variable from more than one block.
  6. Print nonblocking results with $strobe, not $display.

Interview question: swap two registers

Show the solution

The first swaps a and b on every edge: both right hand sides are read before either register changes, so no temporary is needed. The second copies b into a, then copies that new a back into b, so both end up holding the old b.

VHDL has the same split

A VHDL signal assignment (<=) inside a process takes effect only after the process suspends, which is how a nonblocking assignment behaves. A variable assignment (:=) takes effect at once, like a blocking one. The VHDL tab in the shift register above works for the same reason the Verilog one does.

Questions

Can I use nonblocking assignments in combinational logic?

It simulates, but each intermediate value costs another pass through the block, and with an incomplete sensitivity list the simulation stops matching the hardware. Use = in always @(*) and always_comb.

Why does $display show the old value after a nonblocking assignment?

$display runs before the end of the time step, when the update has not happened yet. $strobe and $monitor print at the end of the time step and show the new value.

Does the assignment type change the hardware size or speed?

No. It changes what the code means, not how a given meaning is built. The same behaviour written either way synthesizes to the same logic; the risk is writing a behaviour you did not intend.

Practice this

  • Verilog
  • blocking assignment
  • nonblocking assignment
  • always block
  • simulation race
  • RTL coding

Practice it on a real simulator

Draw the circuit or write the Verilog. Codiode simulates your design against the problem's tests and shows you where it differs.