Video 2 of 4 · ~14 minutes
Dr. Mike Borowczak · Electrical & Computer Engineering · CECS · UCF
The if/else vs case decision shows up every time you write a priority encoder vs a mux. CPU instruction decoders use case. Interrupt controllers use if/else (priority order matters). Bus arbiters often use casez with don't-cares. Each synthesizes to physically different topologies.
Day 3 ALU: case on opcode. Day 7 FSM: case on state. Day 9 memory: if/else for address decoding. Day 11 UART framing: case on state. The pattern recurs across nearly every lab.
“if/else and case are interchangeable. I'll pick whichever reads nicer.”
if/else if/else is priority-encoded: earlier conditions have shorter combinational paths — first match wins. case is parallel: all branches compared simultaneously, no priority. Different topologies, different timing, different LUT counts on wide cases.
if/else if. If alternatives are mutually exclusive (opcode decode, state decode), use case — the parallel topology is usually faster.
if/else if = Priority Chainalways @(*) begin
if (a) y = 4'd1; // highest priority
else if (b) y = 4'd2;
else if (c) y = 4'd3;
else y = 4'd0; // final else prevents latch
end
Synthesized structure:
a → MUX ─┬─────────────── y
│
b → MUX ─┘
│
c → MUX ─┘
else if adds a mux to the chain. Signal a reaches y through 1 mux; signal c through 3. On wide chains (8+ priorities), the deepest path becomes a timing bottleneck.
case = Parallel Muxalways @(*) begin
case (opcode)
2'b00: result = a + b; // ADD
2'b01: result = a - b; // SUB
2'b10: result = a & b; // AND
2'b11: result = a | b; // OR
default: result = 4'b0; // always include default
endcase
end
Synthesized structure:
a+b ─┐
a-b ─┤
a&b ─┤ → 4:1 MUX → result
a|b ─┘ ↑
opcode
casez for Don't-Cares// Priority encoder for a 4-bit request bus
always @(*) begin
casez (i_req)
4'b1???: o_grant = 3'd4; // MSB wins
4'b01??: o_grant = 3'd3;
4'b001?: o_grant = 3'd2;
4'b0001: o_grant = 3'd1;
default: o_grant = 3'd0;
endcase
end
What if i_req = 4'b1011? Which grant wins?
3'd4. The MSB is 1, so the first pattern matches — the ?s in bits 2:0 are don't-care. Even though bit 0 is also 1, it never gets inspected. This is exactly priority-encoder semantics.
casez, never casex. casex also treats X as wildcard, which can hide simulation bugs where X propagation should trigger a failure. Almost every modern lint rule bans casex.
An 8-way decoder implemented two ways. Which is faster on iCE40?
// Version A: case
case (sel)
3'd0: y = i0;
3'd1: y = i1;
...
3'd7: y = i7;
endcase
// Version B: if/else if
if (sel==0) y = i0;
else if (sel==1) y = i1;
...
else if (sel==7) y = i7;
Predict: LUT count? Critical path depth?
case when you mean “parallel.”
case~5 minutes
▸ COMMANDS
cd labs/week1_day03/ex3_alu/
make sim # exhaustive per opcode
make wave
make stat
▸ EXPECTED STDOUT
PASS: ADD 5+3 = 8
PASS: SUB 5-3 = 2
PASS: AND F&A = A
PASS: OR 5|A = F
=== 16 passed, 0 failed ===
▸ GTKWAVE
Signals: opcode · a · b · result. Set opcode to enum display (2'b00=ADD, 2'b01=SUB, 2'b10=AND, 2'b11=OR). Watch result switch instantly as opcode changes — parallel mux in action.
$ yosys -p "read_verilog alu_4bit.v; synth_ice40 -top alu; stat" -q
=== alu ===
Number of wires: 14
Number of cells: 20
SB_CARRY 4 ← adder carry chain (for ADD/SUB)
SB_DFF 0
SB_LUT4 16 ← 4 per operation × 4 operations
Ask AI: “Explain the timing difference between an 8-way if/else-if and an 8-way case in Verilog after synthesis.”
TASK
Ask about timing difference, 8-way.
BEFORE
Predict: case is O(log N) depth, if/else is O(N). Difference grows with width.
AFTER
AI should mention “priority chain” and “mux tree.” If it says they're equivalent, model is weak.
TAKEAWAY
Verify: synth both, compare depth reports from opt_expr -fine.
① if/else = priority chain. case = parallel mux.
② Use case when alternatives are equal; if/else when priority matters.
③ casez for don't-cares (never casex).
④ Always include default (habit) and a final else (safety).
🔗 Transfer
Video 3 of 4 · ~12 minutes
▸ WHY THIS MATTERS NEXT
You've noticed I keep saying “always include a final else” and “always include default.” Video 3 is the why. The consequence of skipping either is an inferred latch — the single most dangerous silent bug in combinational RTL. You'll learn to spot it, prevent it three different ways, and hunt for it in existing code.