Skip to content

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 case without a default infers a latch (incomplete assignment); and casex (which treats x as a don't-care) can silently mask bugs — prefer casez with ? 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

case.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   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 op is matched against each case value (exact match); the first matching case executes.
  • The default covers all unlisted values — and even a fully-enumerated case should include a default to handle x/z on the expression and to avoid a latch.
  • case reads more clearly than a long if/else if chain 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 flow
case → one parallel mux; if/else → a priority cascadecase (op)all items comparedagainst op at once→ parallel mux /decoderone select, allbranches equalif / else if …conditions checked inorder→ priority muxcascadeearliest true wins(deeper path)
case is not just syntax — it is a SELECTION structure. Because its items are mutually exclusive matches against one expression, it synthesizes to a single parallel mux/decoder where every branch is equal depth. An if/else chain, by contrast, is a priority cascade (earliest true wins) that builds a deeper mux chain. Choose case when selecting on one value; if/else when the conditions have a precedence.

4. 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:

case-default.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
       endcase

Always 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-casex.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // 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
   endcase
  • casez treats z and ? (the preferred don't-care symbol) as don't-cares in the case values — useful for priority encoders and range matching (the 4'b1??? pattern). Safe and common.
  • casex treats both x and z as don't-cares — and crucially, an x in the case expression (a real unknown, often a bug) will match a casex pattern, silently hiding the unknown. This masks bugs, so casex is avoided in modern RTL; use casez (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 flow
case / casez / casexcaseexact match · parallelselectcasezz / ? don't-care ·SAFE patternscasexx AND z don't-care ·DANGEROUS
Plain case matches exactly (parallel value selection). casez treats z and ? as don't-cares — the safe way to write patterns and priority encoders. casex treats x AND z as don't-cares, so a real x (a bug) silently matches a pattern, masking the unknown — which is why casex is avoided in favour of casez.

6. Common Mistakes

  1. Missing default — infers a latch (and mishandles x/z); always include it (§4, DebugLab 1).
  2. Using casex — its x-matching masks bugs; use casez with ? (§5, DebugLab 2).
  3. Using if/else for value selectioncase is clearer for selecting on a value (§3).
  4. Overlapping case items — plain case expects mutual exclusion; overlap makes it priority-like (§5).
  5. full_case/parallel_case pragmas — 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
Buggy Code
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.
Symptom

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.

Root Cause

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.

Fix
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
Buggy Code
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.
Symptom

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.

Root Cause

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.

Fix
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
Buggy Code
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.
Symptom

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.

Root Cause

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.

Fix
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 and x/z; required for latch avoidance in combinational blocks.
  • casezz/? don't-cares; the safe way to write patterns/priority encoders.
  • casexx and z don't-cares; avoid (a real x matches, 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.

  • 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 behind case matching.
  • Generate case — Chapter 14.7.3; the elaboration-time counterpart — a runtime case builds all branches, a generate case builds only the selected structure.
  • Dataflow Practical Examples — Chapter 13.2; muxes and decoders at the dataflow level.