case, casez, casex and their cousins
Every HDL needs a way to say “pick a behaviour based on this value”. That’s one hardware idea, a multiplexer tree, but across Verilog, SystemVerilog and VHDL it grew a small family of spellings. Two of them you’ll use weekly, a few exist to make your intent checkable, and one is banned by most style guides for good reason. This page is the family portrait.
Verilog: a family of three
case is an exact, four-state comparison. Each item is checked
literally, and that includes x and z: an item written 2'b1x matches
only when the selector is literally 1x in simulation, which essentially
never describes hardware you meant to build. In practice: plain case, real
values only, and always a default (a case that assigns nothing on some
path builds a latch, which has its own guide).
casez treats z in an item as “don’t care”, and since ? is a
synonym for z, you get readable priority tables. Each row reads “this bit
set, lower bits clear, higher bits irrelevant”:
reg [1:0] idx;
always @(*) begin
casez (req[3:0])
4'b???1: idx = 2'd0; // ? = don't care: any high bits, bit 0 wins
4'b??10: idx = 2'd1;
4'b?100: idx = 2'd2;
4'b1000: idx = 2'd3;
default: idx = 2'd0; // no requests: park at 0 (and no latch)
endcase
end
That’s a priority encoder, live below. Try swapping two rows and watch the
priority move (a small aside: the [3:0] is there because this is a bare
snippet and RapidRTL has to infer the ports; the part-select is what tells
the inference engine how wide req is):
Interactive schematic by RapidRTL
casez is how the table-style takes in the
priority arbiter guide are written.
One convention makes it safe: write ? for don’t-care, never a literal z,
so a reader can’t mistake a wildcard for a tri-state comparison.
casex goes one step further and treats x as don’t-care too, in both
directions, and that direction is the problem. If the selector carries an
x in simulation (uninitialized register, unconnected wire), it matches the
first item as if nothing were wrong. Your testbench passes; the synthesized
hardware, where that x is a real 0 or 1, does something else. A construct
that can hide exactly the bugs simulation exists to catch is why most style
guides simply say: never casex. If you feel you need it, casez with ?
almost certainly expresses what you meant.
SystemVerilog: say what you’re claiming
SystemVerilog kept the three above and added qualifiers that turn your assumptions into checked claims:
unique caseclaims that exactly one item matches, and that items don’t overlap. Simulation flags a violation at runtime, and synthesis is free to build a flat (non-priority) mux because you promised order doesn’t matter.priority caseclaims that at least one item always matches, so the tool knows the statement is complete without adefault.
These two exist to replace the old // synopsys full_case parallel_case
pragmas, which made the same promises to the synthesizer without anything
ever checking them, a famous source of simulation/synthesis mismatches. The
qualifiers are the honest version: same optimization, but a lie now fails in
simulation instead of in hardware.
case insidebrings set membership: items can be ranges and lists ([8:15],{3, 5, 7}) and wildcard patterns, using asymmetric matching that doesn’t let an unknown selector match everything. Where you’re tempted bycasex,case insidewith explicit patterns is the modern answer.
VHDL: strict by construction
VHDL’s case doesn’t negotiate: the choices must cover every possible
value of the selector or the code doesn’t compile. That’s why when others
appears at the end of nearly every VHDL case. The subtlety worth knowing is
that VHDL checks coverage, not assignment: an arm that exists but
assigns nothing (when others => null;) satisfies the compiler and still
infers a latch, as covered in the
VHDL latch guide.
Here’s a classic complete case, an operation selector, with every arm assigning:
Interactive schematic by RapidRTL
Two relatives round out the VHDL side:
with sel selectis the expression-level spelling, acasefor a single concurrent assignment outside a process. Same coverage rules.case?(VHDL-2008) is the don’t-care version:'-'in a choice matches anything, which finally gives VHDL a native way to write priority tables. It’s stricter thancasezin a useful way: choices must not overlap, so two rows that could both match the same value are a compile error rather than a silent priority you didn’t notice.
A note on standards, since VHDL projects pin them: plain case behaves the
same from VHDL-87 through 2008. case?, like process (all), needs
VHDL-2008, so check what your flow is set to before reaching for either
(the embed above runs with the 2008 standard for exactly that reason).
The whole family at a glance
| Construct | Language | Don’t-cares | Checked for you | Verdict |
|---|---|---|---|---|
case | V / SV | none (x/z literal) | nothing | default choice, add default |
casez | V / SV | ?/z in items | nothing | fine for priority tables |
casex | V / SV | ?, z and x, both ways | nothing | don’t use it |
unique case | SV | as base construct | exactly one match, no overlap | use when order doesn’t matter |
priority case | SV | as base construct | at least one match | use when you mean priority |
case inside | SV | ranges, lists, patterns | safe asymmetric matching | the modern wildcard case |
case | VHDL | none | full coverage, at compile time | assignment still your job |
case? | VHDL-2008 | '-' in choices | coverage and no overlap | the safest wildcard case here |
What to actually use
- Verilog:
casewith adefault,casezwith?when encoding priority,casexnever. - SystemVerilog: the same, plus
unique/prioritywhenever you’re making that claim anyway (free checking), andcase insidefor ranges. - VHDL:
caseis already safe on coverage; your job is making every arm assign. On a 2008 flow,case?for don’t-care tables. - All three: a case statement in combinational logic that doesn’t assign every output on every path is a latch. When in doubt, default first, then override.