Verilog · Chapter 14.5.2 · Behavioural Modeling
Case Statements in Verilog — case, casez, casex & the default Rule
The case statement is behavioural logic's parallel branch. It selects among the values of an expression, such as an opcode, an FSM state, or a mux select, with cases that are normally mutually exclusive, synthesizing to a parallel mux or decoder. Three variants exist: plain case for exact matches, casez which treats z and question mark as don't-cares and is the safe way to write priority and range patterns, and casex which treats both x and z as don't-cares and is dangerous because an x in the expression silently matches and can mask real bugs. This page drills all three, the all-important default that prevents an inferred latch, the use of casez for don't-care patterns, and why casex should be avoided in favour of casez. The case statement is the backbone of FSM state decoding and value-based selection.
Foundation14 min readVerilogcasecasezcasexdefaultFSM
Chapter 14 · Section 14.5.2 · Behavioural Modeling
1. The Engineering Problem
case selects on a value and is the natural construct for opcodes, states, and selects — but it carries the same latch hazard as if, plus a variant trap:
A
casewithout adefaultinfers a latch (incomplete assignment); andcasex(which treatsxas a don't-care) can silently mask bugs — prefercasezwith?for don't-cares.
This page drills case/casez/casex, the default rule, and the casex hazard.
2. Mental Model — Parallel Value Selection; default Completes It
3. The case Statement
always @(*)
case (op)
2'b00: y = a + b;
2'b01: y = a - b;
2'b10: y = a & b;
2'b11: y = a | b;
default: y = 8'h00; // covers any unexpected value (incl. x/z)
endcase- The expression
opis matched against each case value (exact match); the first matching case executes. - The
defaultcovers all unlisted values — and even a fully-enumerated case should include adefaultto handlex/zon the expression and to avoid a latch. casereads more clearly than a longif/else ifchain for value selection, and synthesizes to a parallel structure.
Visual A — case is parallel selection; if/else is priority
case → one parallel mux; if/else → a priority cascade
data flow4. The default Rule — Latch Avoidance
As with if, a combinational case must assign every output on every path. A missing default (with unlisted values, or x/z inputs) leaves an unassigned path → a latch:
// LATCH — no default, sel=2'd3 unassigned:
always @(*)
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase
// FIX — default:
always @(*)
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = 0; // covers 2'd3 and x/z → no latch
endcaseAlways include a default in a combinational case. Even when all values are enumerated, the default handles x/z on the expression and documents the intent. (Or use the default-first pattern: assign y before the case.)
5. casez and casex — Don't-Cares
casez and casex allow don't-care bits in the case values — but they differ critically:
// casez — z and ? are don't-cares (the SAFE way to write patterns):
casez (req)
4'b1???: grant = 2'd3; // req[3]=1, others don't-care (priority)
4'b01??: grant = 2'd2;
4'b001?: grant = 2'd1;
4'b0001: grant = 2'd0;
default: grant = 2'd0;
endcase
// casex — x AND z are don't-cares (DANGEROUS):
casex (sel)
2'b1x: ... // an x in 'sel' also matches → masks bugs
endcasecaseztreatszand?(the preferred don't-care symbol) as don't-cares in the case values — useful for priority encoders and range matching (the4'b1???pattern). Safe and common.casextreats bothxandzas don't-cares — and crucially, anxin the case expression (a real unknown, often a bug) will match acasexpattern, silently hiding the unknown. This masks bugs, socasexis avoided in modern RTL; usecasez(with?) instead.
The discipline: casez with ? for don't-cares, never casex. (Note casez/casex build priority structures when patterns overlap, unlike plain case.)
Visual B — case variants
case / casez / casex
data flow6. Common Mistakes
- Missing
default— infers a latch (and mishandlesx/z); always include it (§4, DebugLab 1). - Using
casex— its x-matching masks bugs; usecasezwith?(§5, DebugLab 2). - Using
if/elsefor value selection —caseis clearer for selecting on a value (§3). - Overlapping
caseitems — plaincaseexpects mutual exclusion; overlap makes it priority-like (§5). full_case/parallel_casepragmas — promise synthesis something the simulator ignores, creating a sim/synth mismatch; make the case genuinely complete instead (DebugLab 3, 14.5.3).
7. Debugging Lab
Three case-statement debug post-mortems
Pitfall 1 — case without default infers a latch
module mux3 (input [1:0] sel, [7:0] a, b, c, output reg [7:0] y);
// Intent: combinational 3:1 mux. But no default for sel=2'd3.
always @(*)
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
endcase // no default → sel=2'd3 leaves y unassigned
endmodule
// When sel=2'd3 (or x/z), no case matches and y is not assigned, so it
// holds its previous value — a LATCH is inferred in combinational logic.A combinational mux synthesizes with a 'latch inferred' warning and holds a stale output for the unhandled select value (2'd3). Lint flags an unintended latch.
Incomplete assignment. The case covers sel = 0, 1, 2 but not 3 (and not x/z), and there is no default — so for sel=2'd3 no branch assigns y, and y must hold its previous value, inferring a latch. A combinational case must assign every output on every path, which means covering ALL values, either by enumerating them or with a default.
The fix is to add a default that assigns y, covering the uncovered value(s) and any x/z on sel.
module mux3 (input [1:0] sel, [7:0] a, b, c, output reg [7:0] y);
always @(*)
case (sel)
2'd0: y = a;
2'd1: y = b;
2'd2: y = c;
default: y = 8'h00; // covers 2'd3 and x/z → no latch
endcase
endmodule
// Always include a default in a combinational case. (Or assign y a default
// before the case.)Pitfall 2 — casex masks an unknown input
module decode (input [3:0] req, output reg [1:0] grant);
// Intent: priority decode with don't-cares. But casex is used.
always @(*)
casex (req)
4'b1xxx: grant = 2'd3; // intends 'req[3]=1, rest don't-care'
4'b01xx: grant = 2'd2;
default: grant = 2'd0;
endcase
endmodule
// casex treats x AND z as don't-cares. If 'req' has a REAL x bit (e.g. from
// an unreset source), that x MATCHES the patterns, so a genuine unknown is
// silently treated as a valid request — masking the bug instead of
// propagating the x.A priority decoder behaves correctly in normal operation but hides unknown-input bugs: when 'req' contains a real x (from an unreset or contended source), the decoder produces a definite grant instead of an x, so the unknown never surfaces in simulation.
casex treats both x AND z in the CASE EXPRESSION as don't-cares. So a real x bit in 'req' — which usually indicates a bug (unreset, contention) — matches the case patterns and is silently accepted as a valid value. The unknown is masked rather than propagated, hiding the defect. The intent (don't-cares in the PATTERNS) is correctly expressed by casez with '?', which treats only z/? as don't-cares and lets a real x fail to match (so the x propagates and the bug is visible).
The fix is to use casez with '?' instead of casex.
module decode (input [3:0] req, output reg [1:0] grant);
always @(*)
casez (req)
4'b1???: grant = 2'd3; // '?' don't-cares; a real x does NOT match
4'b01??: grant = 2'd2;
default: grant = 2'd0;
endcase
endmodule
// casez with '?' expresses don't-cares safely; a genuine x in 'req'
// propagates instead of being masked. Avoid casex.Pitfall 3 — full_case / parallel_case create a sim/synth mismatch
module dec (input [1:0] sel, output reg [7:0] y);
// Pragmas tell SYNTHESIS to assume the case is full and parallel —
// but the SIMULATOR ignores the pragmas and behaves literally.
always @(*)
case (sel) // synopsys full_case parallel_case
2'd0: y = 8'hA0;
2'd1: y = 8'hB1;
2'd2: y = 8'hC2;
// no default, and 2'd3 is unhandled
endcase
endmodule
// 'full_case' tells synthesis "every value is covered, treat the missing
// 2'd3 as don't-care" — so synthesis optimizes away the latch and outputs
// a DON'T-CARE for sel=2'd3. But SIMULATION ignores the pragma: with no
// branch for 2'd3 and no default, y HOLDS its previous value (latch-like).
// So for sel=2'd3, the gate-level netlist and the RTL simulation DISAGREE —
// the classic full_case/parallel_case sim/synth mismatch. 'parallel_case'
// similarly forces a parallel mux even if the items overlap, diverging from
// the priority the simulator would apply.A design passes RTL simulation but gate-level simulation (or silicon) behaves differently for an unhandled select value: RTL holds the old output while the netlist drives a different, optimized value. The pragmas made the two views of the same code disagree.
full_case / parallel_case are SYNTHESIS PRAGMAS (comments) that the SIMULATOR ignores. 'full_case' promises synthesis that all expression values are covered, so it removes the latch and treats uncovered values as don't-cares — but the simulator, ignoring the promise, still infers hold/latch behaviour for the uncovered value. 'parallel_case' promises the items are mutually exclusive so synthesis builds a parallel mux, but the simulator applies first-match priority if they actually overlap. Either way the RTL simulation and the synthesized hardware can diverge — a mismatch that is invisible until gate-level sim or silicon.
The fix is to make the case ACTUALLY full and parallel in the source, so no pragma is needed: add a default (real completeness) and write non-overlapping items. Then RTL and synthesis agree by construction.
module dec (input [1:0] sel, output reg [7:0] y);
always @(*)
case (sel) // no pragmas needed
2'd0: y = 8'hA0;
2'd1: y = 8'hB1;
2'd2: y = 8'hC2;
default: y = 8'h00; // REAL completeness — sim and synth agree
endcase
endmodule
// Make the case genuinely complete (a default) and its items genuinely
// mutually exclusive. Then there is nothing for a pragma to "promise," and
// no sim/synth gap. Avoid full_case/parallel_case in modern RTL. (The
// pragmas and their dangers are drilled in 14.5.3.)8. Interview Q&A
9. Exercises
Exercise 1 — Add the default
Add a default to a 4-state FSM case that decodes state into out, so it is latch-free.
Exercise 2 — casez vs casex
For a priority decoder with don't-care patterns, which variant do you use and why? What does the other one do with a real x input?
Exercise 3 — case or if?
For (a) selecting on a 3-bit opcode and (b) a prioritized error/interrupt decision, which construct (case or if/else) fits each?
10. Summary
The case statement is parallel value selection:
- Parallel — mutually-exclusive cases; synthesizes to a parallel mux/decoder.
default— covers unlisted values andx/z; required for latch avoidance in combinational blocks.casez—z/?don't-cares; the safe way to write patterns/priority encoders.casex—xandzdon't-cares; avoid (a realxmatches, masking bugs).
The discipline: always a default; casez with ? for don't-cares, never casex.
The last branching sub-topic covers the synthesis pragmas and advanced patterns: Chapter 14.5.3 Multiway Advanced Techniques drills full_case/parallel_case (and their dangers), don't-care handling, and priority vs parallel structures.
Related Tutorials
- if/else Statements — Chapter 14.5.1; the priority-branching alternative.
- Multiway Branching — Chapter 14.5; the branching overview and latch discipline.
- Equality Operators — Chapter 10.8; the case-equality (
===) semantics behindcasematching. - Generate case — Chapter 14.7.3; the elaboration-time counterpart — a runtime
casebuilds all branches, agenerate casebuilds only the selected structure. - Dataflow Practical Examples — Chapter 13.2; muxes and decoders at the dataflow level.