Skip to content
VLSI Mentor

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

Azvya Education Pvt. Ltd.VLSI Mentor
case.v
   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:

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

Azvya Education Pvt. Ltd.VLSI Mentor
casez-casex.v
   // 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 selection — case 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.
  • casez — z/? don't-cares; the safe way to write patterns/priority encoders.
  • casex — x 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.

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.