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.
Standards & specifications
- Governing standard
- IEEE Std 1364 (Verilog)(opens IEEE in a new tab)
Defines the Verilog language and its simulation semantics, including the event scheduling model. Synthesis support is defined by tools, not by this standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the Verilog HDL curriculum.
