Skip to content

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always @(a or b)             // sensitivity list: a, b  — but NOT c
    y = a & b & c;           // y depends on a, b, AND c

In 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 always block 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-initial.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
   end
  • always is the workhorse of synthesizable design — it models continuously-active hardware (combinational logic or registers) that responds to its triggers forever.
  • initial runs once at simulation start; it is for testbench stimulus and initialization (the design's real starting state comes from reset, not initial). An initial block 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:

sensitivity.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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; an if (!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:

combinational.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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;
       endcase

This 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):

sequential.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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 edge

Because 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

Combinational versus sequential inference by sensitivityalways @(*)any input change→ combinational logicno statealways @(posedge clk)clock edge only→ registers(flip-flops)holds state12
The sensitivity list decides what an always block infers. always @(*) re-evaluates on any input change, building combinational logic with no state (a mux, decoder, adder). always @(posedge clk) updates only on the clock edge, building registers (flip-flops) that hold state between edges (a register, counter, FSM). Same construct, different hardware — read the sensitivity to know which.

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.

incomplete-sensitivity.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   always @(a or b)  y = a & b & c;     // BUG: c missing → sim stale, synth full
   always @(*)       y = a & b & c;     // FIX: @* includes c automatically

The 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 flow
Incomplete sensitivity → sim/synth mismatchalways @(a or b)y = a & b & cc omittedsimulationmisses c changes → stale ysynthesisbuilds full AND → correct y@(*) fixes itlist always complete
An incomplete sensitivity list makes simulation and synthesis disagree: simulation re-runs the block only on the listed signals (missing c, so y is stale), while synthesis builds the full combinational logic from what the block reads. The @(*) form derives the list automatically, so it is always complete — eliminating the mismatch.

8. Worked Examples

8.1 Example 1 — a combinational block

comb-block.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

A 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

seq-block.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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)
endmodule

A 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

sensitivity-fix.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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, correct

The @(*) 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) and always @(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.
  • initial is testbench-only. Synthesizable design never relies on initial for state; the starting value comes from reset.

10. Common Mistakes

  1. Incomplete explicit sensitivity list — omitting a read signal causes a sim/synth mismatch; use @(*) (§1/§7, DebugLab 1).
  2. Incomplete assignment in a combinational block — not assigning an output on every path infers a latch (§5, DebugLab 2; full detail 14.5).
  3. initial for design state — runs once, ignored by synthesis; use reset (§3, DebugLab 3).
  4. Wrong assignment operator= for combinational, <= for sequential (14.3).
  5. reg confusion — a reg in @(*) 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:

  • always re-runs on its trigger (the design workhorse); initial runs 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 a reg in @(*) 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.