Tutorial 07 · Verilog Series

Verilog if, case, casex Statements

Conditional statements control which hardware path is active. Understanding how if-else synthesizes to priority logic and case to parallel muxes — and how to write them without accidentally inferring latches — is essential RTL knowledge.

if / else if / else case / endcase casex / casez default branch full_case parallel_case
if-else → Priority Chain MUX sel=cond1 MUX sel=cond2 MUX sel=cond3 out Higher priority = more gates in path → First condition has longest delay ⚠ Deep chains → timing issues case → Parallel Mux N:1 MUX d0 d1 d2 sel[1:0] out All inputs arrive together ✓ Better timing, preferred for FSM Both are combinational — the difference is priority (series) vs parallel (flat)
if-else chains synthesize as priority-encoded mux trees; case statements synthesize as flat parallel muxes — better for timing.

1. if / else if / else

verilog — if-else syntax
always @(*) begin
    if (rst)           // highest priority
        out = 8'b0;
    else if (load)
        out = data_in;
    else if (inc)
        out = data_in + 1;
    else               // covers all remaining cases
        out = data_in - 1;
end

2. if-else → Hardware (Priority Chain)

Each else if adds another mux stage in series. The condition at the top of the chain has the most logic levels (deepest). This creates a critical path that grows with each condition.

verilog — priority encoder using if-else
// 4-input priority encoder — highest bit wins
always @(*) begin
    grant = 2'd0;          // default prevents latch
    if      (req[3]) grant = 2'd3;
    else if (req[2]) grant = 2'd2;
    else if (req[1]) grant = 2'd1;
    // req[0] covered by the default grant=0
end
Timing impact: Synthesis tools can often optimize priority chains, but deeply nested if-else (8+ conditions) can lead to slow paths. For latency-critical logic, prefer case or one-hot encoding.

3. case Statement

verilog — case syntax
always @(*) begin
    case (opcode)
        3'b000: alu_out = a + b;
        3'b001: alu_out = a - b;
        3'b010: alu_out = a & b;
        3'b011: alu_out = a | b;
        3'b100: alu_out = a ^ b;
        3'b101: alu_out = a << 1;
        3'b110: alu_out = a >> 1;
        default: alu_out = 8'b0;    // covers 3'b111 and any X
    endcase
end

4. case → Hardware (Parallel Mux)

Synthesis tools map a case on a binary-encoded select to a flat N:1 multiplexer. All inputs arrive simultaneously with one mux select level — much better timing than a deep if-else chain for the same number of conditions.

Prefer case for FSM output logic: When the case expression is a state register and items are states, synthesis produces a clean parallel mux. If-else on state bits creates a priority chain that's harder to optimize.

5. casex and casez

KeywordDon't-care charactersUse CaseRecommended?
caseNone — exact match including X and ZGeneral use, FSM✅ Default choice
casezZ and ? in the item patternPartial matching, instruction decode✅ Use for wildcards
casexX and Z and ? in the item patternLegacy wildcard matching⚠️ Avoid (hides X bugs)
verilog — casez for instruction decode (RISC-V style)
always @(*) begin
    casez (instr[6:0])  // decode opcode field
        7'b0110011: alu_op = 3'b000;  // R-type
        7'b0010011: alu_op = 3'b001;  // I-type ALU
        7'b0000011: alu_op = 3'b010;  // Load
        7'b0100011: alu_op = 3'b011;  // Store
        7'b110?011: alu_op = 3'b100;  // ? = don't care (Z)
        default:    alu_op = 3'b111;  // illegal / NOP
    endcase
end
Why avoid casex: casex treats X values in the case expression (not just the items) as don't-care. This means an uninitialized signal can silently match the first item — hiding a real bug. casez only ignores Z in patterns, leaving X propagation intact for better simulation visibility.

6. Latch-Free Patterns

Both if and case can accidentally infer latches when not all outputs are assigned in all paths. Three safe patterns:

verilog — three latch-free patterns
// Pattern 1: always include else / default
always @(*) begin
    if (sel) out = a;
    else     out = b;   // covers sel=0
end

// Pattern 2: assign default before conditional
always @(*) begin
    y0 = 0; y1 = 0; y2 = 0;  // all outputs default to 0
    case (sel)
        2'b00: y0 = 1;   // only y0 changes, y1/y2 keep default
        2'b01: y1 = 1;
        2'b10: y2 = 1;
        // 2'b11: no change needed — defaults cover it
    endcase
end

// Pattern 3: case default item
always @(*) begin
    case (op)
        2'b00: result = a + b;
        2'b01: result = a - b;
        default: result = 8'b0;  // covers all other values
    endcase
end

7. full_case & parallel_case Directives

These are synthesis tool pragmas (comments with special meaning). They control how the tool treats incomplete or overlapping case statements.

verilog — synthesis directives
// full_case: tell synthesis all valid states are covered
// (prevents latch inference without adding logic for X/Z cases)
case (state) // synthesis full_case
    2'b00: next = 2'b01;
    2'b01: next = 2'b10;
    2'b10: next = 2'b00;
    // 2'b11: not reachable — full_case tells synthesizer to ignore it
endcase

// parallel_case: tell synthesis no two items overlap
// (synthesis creates flat mux tree, not priority chain)
case (req) // synthesis parallel_case full_case
    4'b???1: grant = 2'd0;
    4'b??10: grant = 2'd1;
    4'b?100: grant = 2'd2;
    4'b1000: grant = 2'd3;
endcase
Use directives carefully: full_case and parallel_case only affect synthesis — the simulator still evaluates normally. If your design actually reaches an uncovered case at runtime, the synthesized behavior is undefined. Always verify these assumptions with formal or exhaustive simulation.

8. if vs case Comparison

Featureif-else chaincase
Hardware structurePriority mux chain (series)Parallel N:1 mux (flat)
TimingGrows with number of conditionsConstant depth (single mux level)
PriorityInherent — first condition winsNone by default (all equal)
Best forPriority encoding, interrupt ctrlFSM, instruction decode, opcode select
Latch riskMissing else → latchMissing default → latch
Wildcard matchingConditions can be any expressioncasez/casex for don't-cares

9. Practical Examples

Simple 3-state FSM output decode (case)

verilog
parameter [1:0] IDLE=0, FETCH=1, EXEC=2;
reg [1:0] state;

always @(*) begin
    valid  = 0; fetch = 0; exec = 0;   // defaults
    case (state)
        IDLE:  valid  = 1;
        FETCH: fetch  = 1;
        EXEC:  begin exec = 1; valid = 1; end
        default: ; // null statement — defaults already applied
    endcase
end

Interrupt Priority using if-else

verilog
always @(*) begin
    isr_addr = 8'hFF;            // default: no interrupt
    if      (nmi)      isr_addr = 8'h00;  // highest priority
    else if (timer_irq) isr_addr = 8'h10;
    else if (uart_irq)  isr_addr = 8'h20;
    else if (gpio_irq)  isr_addr = 8'h30;
end