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.
1. if / else if / else
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
elseat the end is optional but required to avoid latch inference- The first
ifhas the highest hardware priority — it sits deepest in the mux chain - Nested
if-elseinside sequential blocks is fine — use<=for assignments
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.
// 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
case or one-hot encoding.3. case Statement
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
- The
caseexpression is compared against each item in order (but synthesis treats them as parallel) - Multiple values in one item:
3'b000, 3'b001: out = 0; defaultcovers all unspecified values — always include it to avoid latches- Case items are exact matches — X and Z in the case expression propagate as unknowns
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.
5. casex and casez
| Keyword | Don't-care characters | Use Case | Recommended? |
|---|---|---|---|
case | None — exact match including X and Z | General use, FSM | ✅ Default choice |
casez | Z and ? in the item pattern | Partial matching, instruction decode | ✅ Use for wildcards |
casex | X and Z and ? in the item pattern | Legacy wildcard matching | ⚠️ Avoid (hides X bugs) |
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
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:
// 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.
// 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
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
| Feature | if-else chain | case |
|---|---|---|
| Hardware structure | Priority mux chain (series) | Parallel N:1 mux (flat) |
| Timing | Grows with number of conditions | Constant depth (single mux level) |
| Priority | Inherent — first condition wins | None by default (all equal) |
| Best for | Priority encoding, interrupt ctrl | FSM, instruction decode, opcode select |
| Latch risk | Missing else → latch | Missing default → latch |
| Wildcard matching | Conditions can be any expression | casez/casex for don't-cares |
9. Practical Examples
Simple 3-state FSM output decode (case)
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
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