Tutorial 05 · Verilog Series

Verilog always & assign Blocks

The assign statement and always block are the two fundamental ways to describe hardware behavior in Verilog. Getting them right — especially the sensitivity list and the combinational/sequential split — is the most important skill in RTL design.

assign (continuous) always @(*) always @(posedge clk) Sensitivity List Latch Inference Async/Sync Reset
assign (continuous) assign y = a & b; Drives: wire only Trigger: any RHS change Hardware: gates ✓ Combinational Logic always @(*) always @(*) begin out = a | b; end Drives: reg Trigger: any input change ✓ Combinational Logic always @(posedge clk) always @(posedge clk) q <= d; Drives: reg Trigger: rising clock edge Use <= (non-blocking) ✓ Flip-Flop / Register All blocks execute concurrently — Verilog models hardware parallelism
Three ways to describe hardware: continuous assign, combinational always @(*), and sequential always @(posedge clk) — all run in parallel.

1. Continuous assign Statement

The assign statement drives a wire continuously. Whenever the right-hand side expression changes, the left-hand side updates immediately.

verilog
module logic_gates (
    input  a, b, sel,
    output y_and, y_or, y_mux
);
    assign y_and = a & b;
    assign y_or  = a | b;
    assign y_mux = sel ? a : b;   // mux
endmodule
Multi-driver rule: A wire can only be driven by one assign (or one module output). Driving from two assign statements creates a multi-driver conflict — the result is X in simulation.

2. The always Block

An always block runs repeatedly (forever) in simulation. It is triggered by events in its sensitivity list. The hardware it infers depends on what triggers it.

verilog — structure
always @(/* sensitivity list */) begin
    // procedural statements
    // if / case / for / while
    // = (blocking) or <= (non-blocking) assignments
end

Key properties:

3. Sensitivity List

The sensitivity list is the event list that triggers the always block. Three forms:

verilog
// 1. Explicit list — trigger on any change to a or b or c
always @(a, b, sel) begin
    out = sel ? a : b;
end

// 2. Wildcard @(*) — automatically includes all signals read inside
always @(*) begin
    out = sel ? a : b;
end

// 3. Edge-triggered — trigger only on posedge or negedge
always @(posedge clk) begin
    q <= d;
end

// 4. Multiple edges (async reset)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) q <= 0;
    else        q <= d;
end
Best practice: Always use @(*) for combinational blocks — never list signals manually. Forgetting a signal in an explicit list creates a simulation/synthesis mismatch and is a very common bug.

4. Combinational always @(*)

A combinational always block models logic without memory. Rules:

verilog — 4:1 mux using always @(*)
module mux4 (
    input  [1:0] sel,
    input        d0, d1, d2, d3,
    output reg   out
);
    always @(*) begin
        case (sel)
            2'b00: out = d0;
            2'b01: out = d1;
            2'b10: out = d2;
            default: out = d3;  // covers 2'b11 and any X
        endcase
    end
endmodule

5. Sequential always @(posedge clk)

A sequential always block models flip-flops — state that changes only on clock edges. Rules:

verilog — D flip-flop
module dff (
    input      clk, d,
    output reg q
);
    always @(posedge clk)
        q <= d;      // non-blocking: q captures d on rising edge
endmodule
verilog — 8-bit shift register with enable
module shift_reg8 (
    input       clk, rst_n, en, sin,
    output reg [7:0] q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)    q <= 8'b0;
        else if (en) q <= {q[6:0], sin};  // shift left, insert sin
        // en=0: q retains its value (flip-flop holds state)
    end
endmodule

6. Asynchronous vs Synchronous Reset

Asynchronous Reset (most common)

Reset takes effect immediately, regardless of clock. Include rst_n in sensitivity list.

Synchronous Reset

Reset only takes effect on the next clock edge. rst_n is NOT in the sensitivity list.

verilog
// Asynchronous active-low reset
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) q <= 0;
    else        q <= d;
end

// Synchronous active-high reset
always @(posedge clk) begin
    if (rst) q <= 0;    // rst NOT in sensitivity list
    else     q <= d;
end
Industry convention: Active-low asynchronous reset (rst_n) is the most widely used style in professional RTL. Most standard cell libraries have both D and RB (reset-bar) pins on their flip-flops. Always name your reset signal with _n suffix to indicate active-low.

7. Latch Inference — How to Avoid It

A latch is inferred when a reg inside always @(*) is not assigned in every execution path. The synthesizer must insert a level-sensitive latch to hold the old value.

verilog — BAD: latch inferred
always @(*) begin
    if (en) out = data;   // when en=0: out retains old value → LATCH
end
verilog — GOOD: no latch (three methods)
// Method 1: always use else
always @(*) begin
    if (en) out = data;
    else    out = 8'b0;  // explicit else: out always assigned
end

// Method 2: assign default at top of block
always @(*) begin
    out = 8'b0;           // default: covers all unspecified paths
    if (en) out = data;   // override only when en=1
end

// Method 3: case with default
always @(*) begin
    case (sel)
        2'd0: out = a;
        2'd1: out = b;
        default: out = 8'b0;  // covers 2'd2, 2'd3, X
    endcase
end
Why latches are bad in RTL: Latches are level-sensitive — they are transparent whenever the enable is high. This creates timing paths that are very difficult to close with static timing analysis (STA). Most teams ban latches from RTL unless deliberately intended (e.g. power-domain isolation cells). Synthesis tools will warn you: "latch inferred for signal X".

8. Summary Table

Featureassignalways @(*)always @(posedge clk)
Driveswireregreg
Assignment operator== (blocking)<= (non-blocking)
Triggered byAny RHS changeAny input in @(*)Rising clock edge
Infers hardwareCombinationalCombinationalFlip-flop
Procedural statementsNoYes (if/case/for)Yes (if/case/for)
Missing assignment pathN/ALatch ⚠️Flip-flop retains (OK)

9. Full Design Example: 4-bit Up Counter with Load

verilog
module counter4 (
    input        clk, rst_n, load, en,
    input  [3:0] d,
    output [3:0] q,
    output       carry
);
    reg [3:0] count;

    // Sequential block: flip-flops
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)      count <= 4'b0;
        else if (load)  count <= d;
        else if (en)    count <= count + 1;
        // en=0, load=0: count retains value
    end

    // Continuous assigns: combinational outputs
    assign q     = count;
    assign carry = (count == 4'hF) & en;  // carry out on 15→0 rollover
endmodule