The always block is the backbone of Verilog RTL. Its sensitivity list determines whether it synthesizes to combinational logic or flip-flops — and getting this wrong causes latches, simulation mismatches, and silicon failures. This guide covers every form, every pitfall, and the SystemVerilog upgrades that make intent explicit.
Use always @(*) for combinational logic. Use blocking = inside. The block re-evaluates whenever any input changes. Synthesizes to gates — no flip-flops.
Use always @(posedge clk) for registers. Use non-blocking <= inside. The block evaluates only on the rising clock edge. Synthesizes to flip-flops.
Every output variable of a combinational always @(*) must be assigned in every code path. A missing branch infers a latch — an unclocked storage element that breaks timing.
Every Verilog always block has a sensitivity list that controls when it executes. The sensitivity list is the sole determinant of whether the block synthesizes to combinational logic or flip-flops. There are exactly two synthesis-legal forms.
The wildcard * tells the simulator to automatically include every signal that is read inside the block in the sensitivity list. The block re-executes whenever any of those signals changes value.
In synthesis, this produces combinational logic: multiplexers, AND/OR trees, priority encoders, adders. There is no clock, no flip-flop in the output — unless a latch is inferred by an incomplete assignment.
Use for: decoders, muxes, arithmetic, priority logic, combinational FSM output logic.
The block only executes on the rising edge of clk. Between clock edges, all outputs hold their current value — exactly what a D flip-flop does. Synthesis maps each variable assigned with <= to a flip-flop.
Reset is typically included: always @(posedge clk or negedge rst_n) for asynchronous reset, or simply handled inside the posedge clk block for synchronous reset.
Use for: registers, pipeline stages, FSM state registers, FIFOs, accumulators.
A combinational always block must assign every output in every possible code path. The two most common patterns are the priority mux (if-else chain) and the parallel case (case statement with default).
always @(*) begin // ERROR: 'out' not assigned when sel==2 if (sel == 2'b00) out = a; else if (sel == 2'b01) out = b; // sel==10 and sel==11 → LATCH! end
always @(*) begin out = 1'b0; // default — prevents latch if (sel == 2'b00) out = a; else if (sel == 2'b01) out = b; // all other sel → out stays 0 end
always @(*) begin case (opcode) 3'b000: result = a + b; 3'b001: result = a - b; 3'b010: result = a & b; // 011-111 → LATCH on result! endcase end
always @(*) begin result = 8'h00; // safe default case (opcode) 3'b000: result = a + b; 3'b001: result = a - b; 3'b010: result = a & b; default: result = 8'h00; endcase end
A synthesized latch has no clock — its output changes whenever the enable and data inputs change. This creates a transparent window that is impossible to statically time with standard STA tools. Latches cause hold violations, glitches, and are flagged as errors in most ASIC lint flows. Unless you specifically intend a latch (rare), always prevent them with a default assignment at the top of the block.
Sequential always blocks synthesize every <=-assigned variable to a flip-flop. The two reset styles — synchronous and asynchronous — have different sensitivity lists and synthesis implications.
always @(posedge clk) begin if (!rst_n) // sync: checked on clock edge q <= 8'h00; else q <= d; end // Reset path goes through the FF's D input // → no extra timing arc, scan-friendly
always @(posedge clk or negedge rst_n) begin if (!rst_n) // async: independent of clk q <= 8'h00; else q <= d; end // FF has dedicated async reset pin (PRE/CLR) // rst_n must meet recovery/removal timing
// ── D Flip-Flop with synchronous reset ────────────────── always @(posedge clk) begin if (!rst_n) q <= 1'b0; else q <= d; end // ── Register with clock enable ────────────────────────── always @(posedge clk) begin if (!rst_n) data_reg <= 8'h00; else if (en) data_reg <= data_in; // holds when en=0 end // ── FSM state register ────────────────────────────────── always @(posedge clk) begin if (!rst_n) state <= IDLE; else state <= next_state; end // ── Pipeline stage register ───────────────────────────── always @(posedge clk) begin if (!rst_n) begin pipe_data <= '0; pipe_valid <= 1'b0; end else begin pipe_data <= stage_result; pipe_valid <= stage_valid; end end
Before Verilog-2001, designers had to list every input signal manually. The @(*) wildcard (Verilog-2001) auto-derives the list. Manual lists are a common source of sim/synth mismatch when a signal is accidentally omitted.
Manual sensitivity lists are a maintenance trap. If you add or rename a signal inside the block and forget to update the sensitivity list, simulation will silently diverge from synthesis — the simulator won't re-evaluate the block when that signal changes, but the synthesized hardware will. The mismatch is one of the hardest bugs to debug. Use @(*) always.
SystemVerilog introduces three typed always blocks that make design intent explicit, enable stronger compile-time checking, and communicate directly to synthesis tools and lint checkers.
// ── always_comb ───────────────────────────────────────────── // Replaces always @(*). Compiler ERROR if latch would be inferred. always_comb begin result = 8'h00; // default prevents latch unique case (opcode) // unique: tool warns on overlap/gaps 3'b000: result = a + b; 3'b001: result = a - b; 3'b010: result = a & b; default: result = 8'h00; endcase end // ── always_ff ─────────────────────────────────────────────── // Replaces always @(posedge clk). ONLY flip-flop inferences allowed. always_ff @(posedge clk) begin if (!rst_n) q <= 8'h00; else q <= d; end // ── always_latch ──────────────────────────────────────────── // Explicitly declares a latch. Use only when a latch is intended. always_latch begin if (en) latch_out <= latch_in; // transparent when en=1 end
| Property | always @(*) | always @(posedge clk) | always_comb (SV) | always_ff (SV) |
|---|---|---|---|---|
| Synthesizes to | Combinational gates | Flip-flops | Combinational gates | Flip-flops |
| Assignment type | Blocking = |
Non-blocking <= |
Blocking = |
Non-blocking <= |
| Sensitivity auto | Yes (wildcard) | No (explicit) | Yes (compiler) | Explicit (posedge/negedge) |
| Latch inference | Possible (silent) | N/A | Compile error | N/A |
| Triggered by | Any input change | Clock edge only | Any input change | Clock edge only |
| Standard | Verilog-2001 | Verilog-1995 | SystemVerilog | SystemVerilog |
| Recommended for ASIC | Yes | Yes | Preferred | Preferred |
// Multiple drivers: ILLEGAL in synthesis always @(*) begin out = a & b; // drives 'out' end always @(*) begin out = c | d; // also drives 'out' → X end // In simulation: last-always-wins (non-deterministic) // In synthesis: elaboration error / short circuit
// WRONG: blocking = in posedge clk always @(posedge clk) begin a = b; // blocking: a updates immediately c = a; // c gets new a (not old a!) end // Synthesis: same cycle behavior (combinational) // vs simulation: sequential ordering // → sim/synth mismatch, race conditions
Each net or register in RTL must have exactly one driver. If two always blocks both assign the same variable, simulation produces undefined (X) behavior because the last-executed block wins non-deterministically. Synthesis will either error out or produce a short circuit. In RTL, a single variable = a single always block driver.
| RTL Pattern | Synthesis Output | Note |
|---|---|---|
| always @(*) with complete assignments | Combinational logic (mux, gates) | Correct — no latches |
| always @(*) with incomplete if/case | Latch (transparent, unclocked) | Hold time violation risk |
| always @(posedge clk) with <= | D flip-flop per assigned bit | Correct sequential style |
| always @(posedge clk) with = | Sim/synth mismatch possible | Avoid — use <= in clocked blocks |
| always @(posedge clk or negedge rst_n) | FF with async reset pin (PRE/CLR) | rst_n needs recovery/removal check |
| always_comb (SystemVerilog) | Combinational, latch-checked | Preferred over always @(*) |
| always_ff @(posedge clk) (SystemVerilog) | FF only, compile-time checked | Preferred over always @(posedge clk) |
always @(*) is a combinational always block — it re-evaluates whenever any input changes and synthesizes to combinational gates (muxes, adders, logic trees) with no flip-flops. always @(posedge clk) is a sequential always block — it only evaluates on the rising clock edge and synthesizes to D flip-flops, one per output bit. Choosing the wrong form is one of the most common RTL mistakes: a register accidentally written as combinational becomes transparent, and combinational logic accidentally written as sequential adds unnecessary pipeline latency.
always @(*) block does not assign an output in every code path. For example, an if statement without an else, or a case without a default, leaves the output "remembered" — which requires a latch (a level-sensitive storage element) to maintain the value. Latches are bad because: (1) Static Timing Analysis cannot fully time them — they create timing windows impossible to close cleanly. (2) They are sensitive to glitches on the enable/data paths. (3) They are difficult to scan-test in DFT. The fix is a default assignment at the top of the block: out = 1'b0; before any if/case.
posedge clk) is preferred in most ASIC flows. It routes through the flip-flop's D input (no dedicated reset pin), avoids recovery/removal timing constraints, is scan-friendly, and cannot cause glitches from reset deassertion. The downside: it requires a clock to work — not usable for power-on initialization without clock.negedge rst_n in the sensitivity list) uses the FF's dedicated asynchronous preset/clear pin. It guarantees reset regardless of clock, which is needed for power-up sequencing. The downside: rst_n deassertion must meet recovery/removal timing relative to the clock, and asynchronous reset trees are harder to scan-test.
always_comb (SystemVerilog) gives you three improvements over always @(*): (1) Latch detection — the compiler/elaborator issues an error if the block would infer a latch, making the silent latch problem a compile-time failure instead of a silicon failure. (2) Zero-time initialization — always_comb executes once at time zero before simulation, ensuring outputs are not X at the start. (3) Explicit intent — tools and reviewers immediately know this block is meant to be combinational. Use always_comb in any flow that supports SystemVerilog.
always @(*) with blocking =. Sequential logic belongs in always @(posedge clk) with non-blocking <=. The correct pattern is to compute combinational intermediate values in an always @(*) block (or with assign statements), then register the result in a separate always @(posedge clk) block.
always block but not listed in the sensitivity list, the simulator will not re-evaluate the block when that signal changes. This causes a simulation/synthesis mismatch: synthesis ignores the sensitivity list entirely (it infers combinational logic from the code body), but simulation silently uses stale values. The result is a design that passes simulation but fails in silicon. This is precisely why @(*) exists — always use it instead of manual lists.