RTL Design — Core Concept

always @(*) vs
always @(posedge clk)

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.

Verilog & SystemVerilog
Synthesis-Ready
Latch Inference
ASIC & FPGA
1

Combinational → always @(*)

Use always @(*) for combinational logic. Use blocking = inside. The block re-evaluates whenever any input changes. Synthesizes to gates — no flip-flops.

2

Sequential → always @(posedge clk)

Use always @(posedge clk) for registers. Use non-blocking <= inside. The block evaluates only on the rising clock edge. Synthesizes to flip-flops.

3

Never Omit Assignments in Combinational

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.

The Two Forms

Combinational vs Sequential always Blocks

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.

always @(*) — Combinational

Synthesizes to: Gates / Muxes

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.

always @(posedge clk) — Sequential

Synthesizes to: Flip-Flops (Registers)

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.

COMBINATIONAL — always @(*) a, b, sel MUX / GATE Combinational out ✓ No flip-flops Output changes immediately with any input change SEQUENTIAL — always @(posedge clk) d_in clk ↑ D FLIP-FLOP D → Q on posedge D Q q_out ✓ One flip-flop per output bit Output updates only on rising clock edge
Combinational Logic

Writing Correct Combinational always Blocks

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).

❌ Latch Inferred — Missing 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
✅ Correct — Default Assignment
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
❌ Latch Inferred — Incomplete case
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
✅ Correct — default branch
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
⚠️

Why Latches Are Dangerous in RTL

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 Logic

Sequential always — Registers & Reset Strategies

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.

Synchronous Reset (Preferred for ASIC)
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
Asynchronous Reset (Power-on critical)
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
Complete Sequential Patterns
// ── 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
Sensitivity List

Manual vs Wildcard Sensitivity Lists

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 LIST always @(a or b or sel) begin out = sel ? a : b; end // Added new input 'c' later: always @(a or b or sel) begin out = sel ? a+c : b; // c not listed! ✓ WILDCARD @(*) always @(*) begin out = sel ? a+c : b; end // a, b, c, sel auto-included Simulator auto-derives: a, b, c, sel — always correct
💡

Always Use @(*) in Verilog, always_comb in SystemVerilog

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

always_comb, always_ff, always_latch

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.

SystemVerilog
// ── 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
Anti-Patterns

Common Mistakes That Break Synthesis

❌ Driving the same signal from two always blocks
// 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
❌ Using blocking = in sequential block
// 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
🚫

Never Drive the Same Variable from Two Different always Blocks

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.

Synthesis Implications

What Each Pattern Synthesizes To

RTL PatternSynthesis OutputNote
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)
FAQ

Frequently Asked Questions

What is the difference between always @(*) and 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.
What is latch inference and why is it bad?
A latch is inferred when a combinational 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.
Should I use synchronous or asynchronous reset?
Synchronous reset (reset checked inside 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.

Asynchronous reset (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.
What does always_comb add over always @(*)?
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.
Can I have both combinational and sequential logic in the same always block?
No — mixing them in a single always block causes severe sim/synth mismatches and is flagged as an error by all modern lint tools. Combinational logic belongs in 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.
What happens if I omit a signal from the sensitivity list?
If a signal is used inside a combinational 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.