FPGA

Blocking vs Nonblocking Assignments Verilog

By CourseTron Team ·  1 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: Use blocking assignments (=) for combinational logic and non-blocking assignments (<=) for clocked logic, because non-blocking updates are deferred to the NBA region and therefore cannot race with other always blocks sampling the same clock edge.

    Why two assignment operators exist

    Hardware is concurrent: on a clock edge, every flip-flop samples its D input at the same instant and only then presents a new Q. A software-style simulator, however, executes one process at a time. Verilog bridges this gap with two operators. A blocking assignment evaluates and updates immediately, so the next statement sees the new value. A non-blocking assignment evaluates the right side now but schedules the update for later, so every process that wakes on the same edge still sees the old value.

    If you want the short version of the syntax, our guide to blocking vs non blocking Verilog covers it. This article goes deeper into the scheduler, because that is what interviewers at Synopsys, Cadence and Siemens EDA partner companies actually probe.

    The stratified event queue

    IEEE 1364 divides each simulation time step into ordered regions. The ones that matter for RTL are the Active region, the Inactive region (#0 delays), the NBA region, and the Observed/Postponed regions where $monitor and $strobe print. Within the Active region, the order in which ready processes run is deliberately undefined. That non-determinism is the root of every Verilog race.

    One simulation time step, region by region Active blocking =, RHS of <= continuous assign Inactive #0 delays NBA LHS of <= updated Postponed $strobe NBA updates trigger new events, so the Active region runs again Active-region order between processes is undefined: this is where races live. CourseTron · coursetron.com
    The scheduler loops between Active and NBA until no events remain, then advances time.

    Key fact: A non-blocking assignment samples its right-hand side in the Active region but writes its left-hand side only in the NBA region, after every Active event at that time step has finished.

    The classic shift register mistake

    Consider a three-stage shift register written with blocking assignments in a clocked block:

    always @(posedge clk) begin
      q1 = d;
      q2 = q1;
      q3 = q2;
    end

    On each edge, q1 takes d, q2 immediately takes the new q1, and q3 takes the new q2. All three registers collapse to d after a single clock: you built one flop with two useless aliases. Synthesis tools such as Synopsys Design Compiler or Vivado will often infer the same collapsed hardware, so this is not merely a simulation quirk. Replace = with <= and the hardware becomes a true three-cycle delay line.

    Same code, different operator, different hardware Blocking (=): one flop edge 1: q1=1, q2=1, q3=1 All stages load d in the same time step. Delay line is lost. Non-blocking (<=): 3 flops edge 1: q1=1, q2=0, q3=0 edge 2: q1=x, q2=1, q3=0 edge 3: q1=x, q2=x, q3=1 Value ripples one stage/edge. (x here means whatever d is on that edge) CourseTron · coursetron.com
    Before and after: swapping the operator changes the synthesized structure from one flop to a three-stage delay line.

    Watching it happen

    The animation below shows the correct non-blocking behaviour: on each clock edge, every bit moves exactly one stage, because all right-hand sides were sampled before any left-hand side changed.

    A bit marching through q1, q2, q3 (one stage per clock) d q1 q2 q3 edge 1 edge 2 edge 3 CourseTron · coursetron.com
    Animated: the highlighted bit advances exactly one register per edge.

    Where races actually come from

    A race exists when two processes touch the same variable in the same time step and the result depends on the unspecified Active-region order. Two common patterns:

    • Clock-edge sampling. One block does a = b; on posedge clk while another does c = a;. Whether c gets the old or new a depends on which block the simulator picked first, and it can differ between VCS, Xcelium and Questa, or even between two runs after a recompile.
    • Testbench-versus-DUT. A testbench that drives inputs with blocking assignments exactly on the clock edge competes with the DUT sampling them. Driving with a small delay, or using a SystemVerilog clocking block, removes the race.

    Gate-level and zero-delay simulations make this worse, since every event lands in the same time step. Moving to non-blocking assignments for all registers makes the sampling order irrelevant, which is exactly why the rules below work.

    Key fact: Mixing = and <= on the same variable inside one always block is legal but undefined in synthesis behaviour, and most lint tools (Spyglass, Verilator, Ascent Lint) flag it as an error.

    The coding guidelines that avoid races

    SituationUseReason
    Sequential block, always @(posedge clk)<=Models flop behaviour; RHS sampled before any update
    Combinational block, always @(*)=Later statements must see earlier results
    Latch, always @(*) with enable<=Matches storage element semantics
    Continuous assignassignNet driver, not a procedural assignment
    Same variable from two always blocksNeverMultiple drivers, race and synthesis error
    • Never use a #delay on the left of a non-blocking assignment in synthesizable code; inter-assignment delays like q <= #1 d; are acceptable in simulation only.
    • Assign each variable from exactly one always block.
    • Use always @(*) (or always_comb in SystemVerilog) so the sensitivity list cannot go stale.
    • Keep types straight: variables written in procedural blocks are reg, nets are driven by assign. See reg vs wire in Verilog and the Verilog data types guide for the details.

    A subtle trap: a non-blocking assignment to a bit-select or array element in a combinational block can create ordering surprises, because partial updates are deferred while later reads see stale data. Keep combinational logic blocking, and compute into a temporary variable before registering it.

    For a second perspective on the same rules, see blocking vs non blocking assignments from the VLSI side, where the focus is on how synthesis maps each style to flops and gates.

    Practical verification tips

    Run a lint pass before simulation; Spyglass and Verilator catch mixed assignment styles in seconds. In simulation, vary the process order: VCS offers +ntb_random_seed, and Questa offers randomised scheduling options that expose latent races by reordering Active events. If a design behaves differently under reordering, you have a race. Finally, equivalence-check RTL against the netlist with Synopsys Formality or Cadence Conformal; a collapsed shift register will show up immediately as a mismatch in register count.

    Training teams that want hands-on practice with these flows can look at ChipXpert's VLSI training programmes, which pair RTL labs with real tool flows.

    Ready to code RTL that simulates and synthesizes the same way every time? Explore the hands-on Verilog and FPGA tracks on our CourseTron courses page.

    Frequently asked questions

    Can I use non-blocking assignments in combinational logic?

    You can, and a complete assignment list will still synthesize correctly, but simulation may glitch or lag by a delta cycle and readability suffers. The convention of blocking for combinational logic keeps behaviour predictable.

    Why does the order of blocking assignments matter but not non-blocking ones?

    Blocking assignments update immediately, so later statements read the new value. Non-blocking assignments all read their right sides first and write afterwards, so statement order within the block does not change the result, except when two writes target the same variable (the last one wins).

    Is a Verilog race a simulator bug?

    No. The standard intentionally leaves Active-region ordering undefined, so every compliant simulator may choose differently. The bug is in the RTL.

    Does this change in SystemVerilog?

    The operators behave identically, but SystemVerilog adds always_ff and always_comb, which let the tools check that you used the right style, and clocking blocks and program blocks that remove testbench races.

    Advertisement