FPGA

Verilog Case Statement

By CourseTron Team ·  7 min read ·  Updated Oct 2026 💬 𝕏 in f
Table of Contents
    ? Print / Save as PDF

    In short: case matches exactly (including X and Z), casez treats Z/? as wildcards, casex also treats X as a wildcard and can hide bugs, and any case inside combinational logic that does not assign every output on every path synthesizes a latch.

    What a Verilog case statement really does

    A case statement compares one expression against a list of items from top to bottom and executes the first branch that matches. In simulation this is a strictly ordered, priority-style evaluation. In hardware, whether you get a priority chain or a flat multiplexer depends on how the items overlap and on what the synthesis tool can prove about them.

    The plain case uses the case-equality operator (===), so 0, 1, X and Z must all match literally. If the selector carries an X because a flop was never reset, case will skip every defined item and fall into default. That is usually what you want: the unknown is visible rather than silently swallowed.

    Key fact: plain case is a bit-for-bit 4-state comparison. Only casez and casex introduce wildcards.

    case vs casez vs casex

    The three keywords differ only in which bit values are treated as "don't care" during the compare, and the wildcard works on both sides, the selector and the item.

    KeywordWildcard bitsTypical use
    caseNoneDecoders, FSM next-state, ALU opcodes
    casezZ and ? Priority encoders, address decoding
    casexX, Z and ?Avoid in RTL
    Does item 2'b1x match the selector? Selector case casez casex 2'b10 NO NO YES 2'b1x YES YES YES 2'b1z NO YES YES casex matches an unknown 1x or 1z selector against any item: bugs disappear. CourseTron · coursetron.com
    Match behaviour of the three keywords for the item 2'b1x. Green is a match, red is no match.

    Why casex is dangerous: if an uninitialized register drives an X into a casex selector, the first item matches no matter what. Simulation shows plausible behaviour while real silicon, which has no X, does something else. Most lint rule sets (Synopsys SpyGlass, Cadence HAL) flag it, and SystemVerilog recommends case ... inside instead.

    Priority encoder with casez

    The legitimate use of wildcards is a priority encoder. Below, the highest set request bit wins, and the ? marks bits we do not care about once a higher bit is set.

    always @* begin
      casez (req)
        4'b1???: grant = 2'd3;
        4'b01??: grant = 2'd2;
        4'b001?: grant = 2'd1;
        4'b0001: grant = 2'd0;
        default: grant = 2'd0;
      endcase
    end

    Notice the items are written so that no input matches two of them. Hardware-wise this collapses to a small AND-OR network; with overlapping items the tool would build a priority chain, costing extra logic levels and delay.

    Full case and parallel case

    Two properties matter to synthesis. A case is full when every possible selector value hits some item. It is parallel when no value can hit more than one item. A full, parallel case maps to a clean mux; a non-parallel case needs priority logic; a non-full case needs either a default or memory.

    Older flows used the pragmas // synopsys full_case parallel_case to tell Design Compiler to assume both. The trouble is that only the synthesis tool reads them. Simulation still follows the code, so the pre- and post-synthesis behaviour can differ. A wrong parallel_case on overlapping items removes priority logic the RTL relied on; a wrong full_case lets outputs go unspecified. Clifford Cummings' well-known paper calls them the "evil twins" for this reason (see references).

    SystemVerilog fixes the problem in the language. unique case asserts items are parallel and full, priority case asserts full with ordered matching, and the simulator reports a run-time violation if reality disagrees. Synthesis tools from Synopsys, Cadence Genus and Siemens Questa/Precision honour the same intent without a mismatch.

    How incomplete cases create latches

    In a combinational always @* block, every output must be assigned on every path. If a branch skips an output, Verilog semantics say it keeps its old value, and the only way hardware can remember is a level-sensitive latch. Say you decode a 2-bit opcode but only write three items and no default. For the fourth opcode, y holds, and the tool inserts a transparent latch whose enable is the "some item matched" logic.

    No default: latch With default: mux case (op)3 of 4 items LATCHholds y D = new value, EN = "an item matched" op = 2'b11 keeps the old y extra area, timing check, DFT problem case (op)+ default MUXpure logic every op value drives y no state, no enable clean timing and test CourseTron · coursetron.com
    Left: a missing branch makes the tool add a latch. Right: assigning every output removes it.

    Tools tell you when this happens. Vivado prints "inferring latch for variable" (Synth 8-327), Quartus issues warning 10240, and Design Compiler reports latches in report_area and its inference messages. Treat any combinational latch warning as an error in sign-off.

    Three reliable fixes exist. Put a default branch that assigns every output. Assign a default value to every output before the case. Or use unique case plus a lint rule that fails the build. The second approach is the most robust because adding a new item later can never reintroduce a hole.

    always @* begin
      y = 8'h00;           // default first
      case (op)
        2'b00: y = a + b;
        2'b01: y = a - b;
        2'b10: y = a & b;
        default: ;         // op = 2'b11 keeps y = 0
      endcase
    end

    Latches also break scan-based DFT flows, because ATPG tools from Synopsys TetraMAX and Siemens Tessent must treat them as special sequential cells. A single accidental latch can cost coverage.

    Watching it happen

    The animation below steps a 2-bit selector through all four values. Only the first matching row lights up, and the last value falls through to the default.

    case (sel) evaluated top to bottom sel = 2'b00 2'b01 2'b10 2'b11 2'b00: y = a + b; 2'b01: y = a - b; 2'b10: y = a & b; default: y = 8'h00; CourseTron · coursetron.com
    Animation: the selector steps through four values, highlighting the branch that executes.

    Coding guidelines worth remembering

    • Use case by default, casez only for true priority or address patterns, and avoid casex entirely.
    • Always include default in combinational logic, and in FSMs assign the next state there to a known recovery state.
    • Use blocking assignments in combinational blocks, as covered in blocking vs non-blocking in Verilog.
    • Remember that a variable assigned in always must be a reg; see reg vs wire in Verilog for the reasoning.
    • Check selector widths: a 3-bit selector against 2-bit items zero-extends the items silently, a cousin of the problem in sign extension in Verilog.

    Interview tip: when asked "how do you avoid latches?", say "assign defaults before the case, add a default branch, and run lint plus the synthesis log check." That answer covers design, review and tool flow.

    If you are targeting devices, the choice between mux-based and priority-based structures affects LUT depth, which differs across vendors; the Xilinx vs Intel FPGA comparison and our guide on what an FPGA is explain the underlying fabric. Teams that want a code review of their RTL can also talk to Avecas design services.

    Want to write synthesizable RTL that passes lint on the first try? Explore the hands-on Verilog and FPGA tracks in our CourseTron courses.

    FAQ

    Is casez synthesizable?

    Yes. All three forms synthesize, but casez with ? wildcards is the style most tools and lint checkers accept for priority logic.

    Does a default branch always prevent a latch?

    Only if the default assigns every output the block drives. An empty default: ; without a prior assignment still leaves a latch.

    Should I use full_case and parallel_case pragmas?

    No. They change synthesis only and cause simulation mismatches. Use SystemVerilog unique or priority instead.

    Why is casex discouraged?

    Because X in the selector matches any item, simulation can pass while hardware misbehaves.

    Advertisement