Verilog · Chapter 14.1 · Behavioural Modeling
always & initial Blocks in Verilog — Combinational vs Sequential Inference
The procedural block is the foundation of behavioural modeling, and Verilog has two. The always block re-runs its statements every time it is triggered and is the workhorse of synthesizable design. The initial block runs once at time zero and is used in testbenches, not in design. The decisive concept here is inference: the same always construct builds combinational logic or clocked sequential hardware depending on its sensitivity list. An always block sensitive to all its inputs re-evaluates on any change and produces stateless combinational logic, while one sensitive to a clock edge updates only then and produces registers that hold state. This lesson drills always and initial, the sensitivity-list forms, the combinational versus sequential inference rule, and the classic incomplete-sensitivity-list bug that causes simulation and synthesis to disagree.
Foundation18 min readVerilogalwaysinitialSensitivity ListCombinationalSequential
Chapter 14 · Section 14.1 · Behavioural Modeling
1. The Engineering Problem
An engineer writes a 3-input AND in an always block, listing the sensitivity explicitly:
always @(a or b) // sensitivity list: a, b — but NOT c
y = a & b & c; // y depends on a, b, AND cIn simulation, y is wrong: the block re-evaluates only when a or b changes, so when c changes alone, y keeps its stale value. In synthesis, the tool ignores the (incomplete) list and builds the full 3-input AND, so y is correct in hardware. The two disagree — a simulation/synthesis mismatch, one of the most insidious bugs in RTL, caused by a sensitivity list that does not match the logic.
The fix is the automatic sensitivity form:
always @(*) // sensitive to ALL signals read in the block
y = a & b & c; // matches the logic automatically ✓always @(*) builds the sensitivity list from everything the block reads, so it can never be incomplete. This bug, and its fix, sit at the heart of what this page teaches: the always block, its sensitivity list, and how that list decides whether you get combinational logic or a register.
The sensitivity list of an
alwaysblock decides what hardware it infers — combinational (@(*)) or sequential (@(posedge clk)) — and an incomplete list causes a simulation/synthesis mismatch.
2. Mental Model — An always Block Re-Runs on Its Trigger
3. always vs initial
The two procedural blocks differ in how often they run:
// always — re-runs every time its sensitivity triggers (DESIGN workhorse)
always @(posedge clk)
q <= d;
// initial — runs ONCE at time 0 (TESTBENCH/sim only, not synthesizable design)
initial begin
clk = 0;
rst_n = 0; #20 rst_n = 1; // stimulus
endalwaysis the workhorse of synthesizable design — it models continuously-active hardware (combinational logic or registers) that responds to its triggers forever.initialruns once at simulation start; it is for testbench stimulus and initialization (the design's real starting state comes from reset, notinitial). Aninitialblock in synthesizable design logic is ignored by synthesis (a sim/synth mismatch — Chapter 9.4).
For design, you write always blocks; initial belongs to testbenches.
4. The Sensitivity List
The sensitivity list (after @) decides when the block runs — and therefore what hardware it infers:
always @(*) // COMBINATIONAL: all read signals
always @(a, b, sel) // explicit list (error-prone — prefer @*)
always @(posedge clk) // SEQUENTIAL: rising clock edge
always @(posedge clk or negedge rst_n) // SEQUENTIAL + async reset
always @(negedge clk) // falling edge (less common)@(*)(or@*) — combinational: automatically sensitive to every signal read in the block. The preferred combinational form.@(a, b, sel)— an explicit list; legal but error-prone (omit a signal and you get the §1 bug). Superseded by@(*).@(posedge clk)— edge-triggered: the block runs on the clock's rising edge, inferring flip-flops.@(posedge clk or negedge rst_n)— adds an asynchronous reset (the block also runs on the reset edge; anif (!rst_n)clause handles it).
The list is the difference between combinational logic and a register — the single most important reading of an always block.
5. Combinational Inference — always @(*)
A combinational always block re-evaluates on any input change, building logic that is a pure function of its inputs:
// a mux, written behaviourally
always @(*)
if (sel) y = a;
else y = b;
// a decoder
always @(*)
case (sel)
2'd0: onehot = 4'b0001;
2'd1: onehot = 4'b0010;
2'd2: onehot = 4'b0100;
default: onehot = 4'b1000;
endcaseThis is the same hardware dataflow builds (assign y = sel ? a : b;), written with statements — clearer for complex branching. Critical rule: a combinational block must assign every output on every path, or it infers an unintended latch (an output that "remembers" when no branch assigns it). The default in the case and the else in the if ensure complete assignment — the latch hazard is drilled in 14.5. Use blocking = in combinational blocks (14.3).
6. Sequential Inference — always @(posedge clk)
A sequential always block updates only on the clock edge, so its outputs are registers that hold state — the thing dataflow could not build (13.3):
// a register — captures d on the clock edge, holds until the next
always @(posedge clk)
q <= d;
// a counter with async reset — the counter dataflow couldn't make
always @(posedge clk or negedge rst_n)
if (!rst_n) count <= 0; // async reset to known state
else count <= count + 1; // advance each clock edgeBecause the block runs only on the edge, q and count are flip-flops: they capture a value on the rising clock and hold it until the next edge. The negedge rst_n clause gives an asynchronous reset that forces a known starting state (the real initialization, not initial). Use non-blocking <= in sequential blocks (14.3) so the registers update together on the edge. This is the foundation of all synchronous design — registers, counters, FSMs, pipelines.
Visual A — sensitivity decides comb vs seq
7. The Incomplete-Sensitivity-List Trap
The §1 bug, stated as a rule: in an explicit sensitivity list, omitting a signal the block reads makes simulation miss its updates (the block doesn't re-run when that signal changes), while synthesis builds the full logic — a simulation/synthesis mismatch. The block "works" in hardware but lies in simulation, or vice versa.
always @(a or b) y = a & b & c; // BUG: c missing → sim stale, synth full
always @(*) y = a & b & c; // FIX: @* includes c automaticallyThe fix is universal: use @(*) for every combinational always block. It derives the sensitivity from the block's reads, so it can never be incomplete. Explicit combinational sensitivity lists are a legacy hazard; @(*) (Verilog-2001) eliminated them. (Sequential blocks @(posedge clk) are unaffected — their sensitivity is the clock edge, not the data.)
Visual B — the sensitivity-list mismatch
Incomplete sensitivity → sim/synth mismatch
data flow8. Worked Examples
8.1 Example 1 — a combinational block
module alu_op (input [7:0] a, b, input [1:0] op, output reg [7:0] y);
always @(*) // combinational
case (op)
2'b00: y = a + b;
2'b01: y = a - b;
2'b10: y = a & b;
default: y = a | b; // default → every path assigns y (no latch)
endcase
endmoduleA combinational always @(*) with a complete case (the default ensures y is assigned on every path, avoiding a latch). Note y is declared reg because it is assigned procedurally — but it is combinational logic, not a register (the sensitivity is @(*), not a clock). "reg" is the assignment target type, not "register" (Chapter 5.2.1).
8.2 Example 2 — a register and a counter
module counter (input clk, rst_n, en, output reg [3:0] count);
always @(posedge clk or negedge rst_n) // sequential + async reset
if (!rst_n) count <= 4'd0; // reset to known state
else if (en) count <= count + 1; // advance on the edge
// (no else: count holds when !en — a register naturally holds)
endmoduleA sequential block: count is a register, updated only on the clock edge, holding its value otherwise. The async reset (negedge rst_n) sets the start state. This is the counter dataflow could not build (13.3) — the clock provides the cycle boundary, the register provides the state. Non-blocking <= is used (14.3).
8.3 Example 3 — incomplete sensitivity, fixed
// BEFORE — explicit list missing 'cin' → sim/synth mismatch:
// always @(a or b) {cout, sum} = a + b + cin;
// AFTER — @(*) includes everything read:
always @(*) {cout, sum} = a + b + cin; // complete, correctThe @(*) form derives the sensitivity from all reads (a, b, cin), so the block re-evaluates whenever any of them changes — matching the hardware. Always use @(*) for combinational logic.
9. Industry Perspective
@(*)for combinational,@(posedge clk)for sequential — always. This is the universal convention; explicit combinational sensitivity lists are banned in modern RTL because of the mismatch hazard.- Separate combinational and sequential blocks. Engineers keep
always @(*)(logic) andalways @(posedge clk)(state) in distinct blocks — clearer, and matches the comb/seq design split. - Lint enforces sensitivity and assignment style. Tools flag incomplete sensitivity lists, latch inference, and blocking/non-blocking misuse — the core behavioural hazards.
initialis testbench-only. Synthesizable design never relies oninitialfor state; the starting value comes from reset.
10. Common Mistakes
- Incomplete explicit sensitivity list — omitting a read signal causes a sim/synth mismatch; use
@(*)(§1/§7, DebugLab 1). - Incomplete assignment in a combinational block — not assigning an output on every path infers a latch (§5, DebugLab 2; full detail 14.5).
initialfor design state — runs once, ignored by synthesis; use reset (§3, DebugLab 3).- Wrong assignment operator —
=for combinational,<=for sequential (14.3). regconfusion — aregin@(*)is combinational, not a register; the sensitivity decides (§8).
11. Debugging Lab
Three behavioural-block debug post-mortems
12. Interview Q&A
13. Exercises
Exercise 1 — Comb or seq?
For each, state whether it infers combinational logic or registers: (a) always @(*) y = a + b;; (b) always @(posedge clk) q <= d;; (c) always @(a, b) y = a | b;; (d) always @(posedge clk or negedge rst_n) ....
Exercise 2 — Fix the sensitivity
Rewrite always @(a or b) y = (a & b) | c; to avoid the sim/synth mismatch.
Exercise 3 — Spot the latch
Why does always @(*) if (en) y = d; infer a latch, and how do you fix it?
Exercise 4 — Write the blocks
Write: (a) a combinational always block for a 2:1 mux; (b) a sequential always block for a D flip-flop with async reset.
14. Summary
The procedural blocks are the foundation of behavioural modeling:
alwaysre-runs on its trigger (the design workhorse);initialruns once (testbench only, not synthesizable design).- The sensitivity list infers the hardware:
always @(*)→ combinational (re-runs on input changes, no state);always @(posedge clk)→ sequential (registers that hold state). - The LHS is a
reg— but aregin@(*)is combinational, in@(posedge clk)a flip-flop; the sensitivity decides, not the keyword. - Incomplete sensitivity → sim/synth mismatch — use
@(*)so the list is always complete. - Incomplete assignment → latch — assign every output on every path in combinational blocks (14.5).
The discipline this page instils:
@(*)for combinational,@(posedge clk)for sequential — always; never an explicit combinational list.- Reset, not
initial, sets design start state. - Read the sensitivity to know the hardware — logic or registers.
The next page covers how statements are grouped inside these blocks: Chapter 14.2 Sequential & Parallel Blocks drills begin...end (sequential statement grouping) and fork...join (parallel) — and then the chapter reaches its crown jewel, 14.3 Blocking & Non-Blocking Assignments.
Related Tutorials
- Behavioural Modeling — Chapter 14 overview; the procedural-modeling context.
- reg — Chapter 5.2.1; the variable
alwaysdrives, and why "reg is not a register." - Dataflow Advanced Techniques — Chapter 13.3; the state wall sequential
alwayscrosses. - Conditional Operator — Chapter 10.11; the dataflow mux these
if/caseblocks parallel.