Hardware Engineering · HDL and Synthesis

Blocking and Non-Blocking Assignment: What Each Maps To in Silicon

How the two assignment forms differ in when they update their target, why one models a wire and the other a flip-flop, and where mixing them produces a simulation that does not match the netlist.

A three-stage shift register written with blocking assignments does not shift. Written as

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

it produces three registers that all take the value of d on the same edge, because the statements execute in source order and q1 = d has already updated q1 before q2 = q1 reads it. The same three statements with <= produce a shift register, because every right-hand side is sampled before any left-hand side is written. This is not a stylistic preference. It is the difference between describing a chain of registers and describing one register fanned out to three outputs, and the synthesis tool is entitled to build whichever one the code describes.

The rule that follows is short: blocking assignment models combinational logic and variables local to a process; non-blocking assignment models state that survives a clock edge. Everything else in this article is the reasoning behind that rule and the cases where it is not enough.

What the two operators do to the event queue

Blocking assignment, =, evaluates its right-hand side and updates its target as part of the running process, before the process continues. Any later statement in the same block sees the new value, and any other process woken by the change runs after the current one finishes. The effect is a sequence of updates with a defined order inside the block and no defined order outside it.

Non-blocking assignment, <=, evaluates its right-hand side at that moment and places the update in a separate queue, the non-blocking update region. The process continues immediately with the old value of the target still visible. Only after every active process has run does the simulator drain that queue and write the new values, all together. The mechanics of those regions are covered in delta cycles and the event queue.

The two-phase evaluation is exactly what a flip-flop does physically. A D flip-flop’s input must be stable for a setup window before the edge; the edge samples whatever is present; the output changes after a clock-to-Q delay, well after the sampling instant. All flip-flops clocked by the same edge sample at essentially the same instant, so none of them sees another’s new output. Non-blocking assignment encodes that sampling discipline in the simulator, where zero-delay evaluation would otherwise let a downstream register see an upstream one’s new value in the same instant.

Blocking assignment has no such phase separation, which is why it models a wire or a chain of gates: a value presented at the input appears at the output as soon as the logic propagates, and downstream logic sees the propagated value immediately.

Why a chain of blocking assignments infers one register, not three

The synthesis tool does not simulate; it builds a data-flow graph from the code. In the blocking version the statements execute in source order, so by the time q2 = q1 runs, q1 already holds d. The next-state function of every one of the three variables is therefore d, and there is no causal dependency between them: the tool sees one value fanned out to three names, which is a single register duplicated in the netlist, not a pipeline.

Splitting the three statements into three separate always @(posedge clk) blocks does not restore the shift. The language fixes no order between processes, so whether q2 samples the old or the new q1 depends on the order the simulator or the elaborator happens to choose. Written that way, the design is not well defined by the source text, and what tools make of it varies: some warn and infer three registers in series, some infer one register under three names. The non-blocking version has one reading: each register samples its source at the edge, which is three registers in series.

The mixed case that is legitimate

One pattern uses both forms in a single clocked block and is correct: compute a combinational intermediate with blocking assignment, then register it with non-blocking assignment.

always @(posedge clk) begin
  parity = ^data;          /* combinational: no state, visible below */
  data_q <= data;          /* registered */
  parity_q <= parity;      /* samples the value computed just above */
end

Here parity is a temporary. Because the first statement is blocking, parity_q samples the freshly computed parity rather than last cycle’s. Because the second and third are non-blocking, both registers update together at the end of the step. Written entirely with non-blocking assignment, the block would register the old parity alongside the new data, an off-by-one-cycle bug that simulation would reproduce faithfully and the netlist would share.

The temporary is not free in synthesis terms: a blocking assignment to a variable that is read later in the same block creates a combinational path from data to the parity_q register’s input, which is fine, but if the temporary is read by another process it becomes shared state and the ordering guarantee evaporates. The safe discipline is that a variable assigned with blocking assignment inside a clocked block is never read outside that block.

Where the mapping breaks: shared state and multiple drivers

Three failure modes recur and each has a distinct cause.

A combinational block written with non-blocking assignment. An always @(*) block using <= still computes the right value, because the right-hand side is evaluated immediately. What changes is the timing of the update: it lands one delta cycle later, after the active region has drained. In a chain of such blocks the delay accumulates and a testbench that samples the output in the same time step sees the previous value. Synthesis ignores the distinction and builds the same gates, so the simulation and the netlist diverge. The pattern also buys nothing inside always_comb, which is required to execute once at time zero and whose targets may not be written by any other process; synthesis coding guides require blocking assignment in a combinational block for exactly this reason.

Two processes driving the same variable. Blocking assignment makes the last writer win within a delta cycle; non-blocking assignment makes both writes land in the update region, and the language specifies that the result is the value of one of them, chosen nondeterministically. Synthesis will either reject the design or infer a wire with two drivers, which is a short circuit in silicon. There is no coding style that makes multiple drivers safe; the structure has to change.

A state variable read by a later statement in the same block. With non-blocking assignment, count <= count + 1; describes an incrementer feeding a register, and every reader in the block sees the pre-edge value. The identical single statement with blocking assignment, count = count + 1;, happens to build the same hardware, because the right-hand side is evaluated before the target is written. The divergence appears at the second statement: a following if (count == 15) count = 0; under blocking assignment compares the already-incremented value, so the counter resets as soon as an increment produces 15 and the value 15 never appears at the output. Under non-blocking assignment the comparison sees the registered value, so 15 is held for a cycle and the wrap happens on the following edge.

Reset structure follows the same rule

An asynchronous reset is a second edge in the sensitivity list and must also be non-blocking:

always @(posedge clk or negedge rst_n)
  if (!rst_n) q <= 1'b0;
  else        q <= d;

The reset branch is not combinational logic; it is a second way to load the same register. Writing it with blocking assignment would make the reset value visible immediately to downstream logic in the same time step, which no flip-flop does. The structure also determines what the synthesis tool infers: a register with an asynchronous reset needs a flip-flop cell with a reset pin, while the same code without the reset term infers a plain flip-flop and relies on the initial value, which is a configuration detail of the target technology rather than a property of the language. That is the subject of inferred latches and registers.

Why the rule survives even where it seems unnecessary

A designer who writes only combinational logic with blocking assignment and only clocked logic with non-blocking assignment never has to reason about the queue. That is the entire value of the rule: it makes the two forms’ semantics coincide with the two hardware structures they describe, so the code can be read as a structural description rather than as a program.

The rule also has a real cost when ignored. A clocked block that mixes forms can produce a simulation that passes and a netlist that fails, and the failure appears as a timing-dependent functional bug rather than as a compile error. The SystemVerilog always_ff construct exists to make that class of mistake an elaboration error: it requires exactly one event control and no blocking timing controls, and it forbids any other process from writing the variables it drives. Where always_ff is available, using it removes the need for the discipline; where it is not, the discipline is the only enforcement available.

References

  1. IEEE Std 1364-2005: IEEE Standard for Verilog Hardware Description Language — IEEE
  2. Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill! — Clifford E. Cummings, SNUG 2000
  3. IEEE Std 1800-2017: IEEE Standard for SystemVerilog — IEEE
  4. Verilog HDL Synthesis: A Practical Primer — J. Bhasker, Star Galaxy Publishing