Skip to content

SystemVerilog · Module 5

case, casex, casez, unique, priority

Hardware inference, don't-care matching, modifier safety.

Module 5 · Page 5.3

Plain case — The Parallel Decoder

The fundamental difference between case and if-else is what hardware they imply. A case statement tells the tool: every branch is checked in parallel — it infers a decoder (or a one-hot mux), not a priority chain. Synthesis tools exploit this to generate flatter, faster logic when conditions are known to be mutually exclusive.

In simulation, case evaluates branches top-to-bottom and picks the first match — but the hardware intent is parallel. Keep that distinction in mind.

SystemVerilog — case statement: all forms
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Form 1: case with 2-bit opcode ─────────────────────────────
always_comb begin
    result = '0;              // default prevents latch
    case (opcode)
        2'b00: result = a + b;
        2'b01: result = a - b;
        2'b10: result = a & b;
        2'b11: result = a | b;
    endcase
end
 
 
// ── Form 2: case with default clause ────────────────────────────
always_comb begin
    case (state)
        IDLE  : next = FETCH;
        FETCH : next = DECODE;
        DECODE: next = EXECUTE;
        EXECUTE: next = IDLE;
        default: next = IDLE;   // covers unreachable encodings safely
    endcase
end
 
 
// ── Form 3: multiple values per branch ──────────────────────────
always_comb begin
    case (irq)
        4'h1, 4'h2: priority_out = HIGH;
        4'h4, 4'h8: priority_out = MED;
        default:        priority_out = LOW;
    endcase
end

Figure 1 — case Infers a Parallel Decoder, Not a Priority Chain

Tutorial diagram

Figure 1 — A plain case with four branches infers a 4-to-1 decoder. All four conditions are checked simultaneously — there is no "earlier branch wins" in hardware.

🧠 The Critical Distinction: case in Simulation vs Synthesis

This is one of the most misunderstood facts about case. In simulation, the SystemVerilog simulator evaluates case branches top-to-bottom and takes the first match — exactly like an if-else if chain. If two branches could match (which plain case allows), the first one wins silently. In synthesis, the tool treats case as a declaration of parallel decode intent — it generates a flat decoder where all branches are checked simultaneously with no implied priority. This disconnect is why unique case exists: it aligns simulation behavior with synthesis intent by issuing a warning when two branches match (which should never happen if the RTL is correct).

🏗 Synthesis Concern: case vs if-else — Area and Timing

When you use case for a fully-decoded enumeration (all patterns listed, no overlap possible), the synthesis tool generates a flat N:1 decoder — essentially an AND gate per branch feeding one output mux. Critical path: one decoder gate + one mux = ~300ps in 28nm for a 4-bit opcode. When you use if-else if for the same logic, the tool generates a cascaded mux chain — each level adds ~150–200ps. For a 4-opcode decode, case is roughly 2× faster in synthesis. This is why style guides universally require case for state machine next-state decode and opcode decode.

casex & casez — Don't-Care Matching

Plain case uses 4-state exact matching: every bit in the expression must match every bit in the pattern, including X and Z. casex and casez relax this by treating certain bit values as wildcards — don't cares.

casex

Treats X and Z in both the expression and the pattern as don't cares. Powerful, but dangerous: X values in simulation (propagated unknowns) silently match patterns. Generally avoid in RTL.

casez

Treats Z and ? in the pattern (or expression) as don't cares. X values are not treated as don't cares. Safer than casex. Preferred for opcode/instruction decoding.

Plain case

Exact 4-state match. No don't cares. Use when every bit matters and you want X to propagate normally in simulation.

SystemVerilog — casez with don't-care patterns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── casez: decode a 4-bit opcode with partial patterns ──────────
// '?' in patterns = don't care (matches 0 or 1)
always_comb begin
    ctrl = '0;
    casez (instr[7:4])
        4'b1???: ctrl = BRANCH;    // MSB=1, lower 3 bits = don't care
        4'b01??: ctrl = ALU_OP;    // bits 7:6 = 01
        4'b001?: ctrl = MEM_OP;    // bits 7:5 = 001
        4'b0000: ctrl = NOP;
        default:     ctrl = ILLEGAL;
    endcase
end
 
 
// ── casex (avoid in RTL — shown for reference only) ─────────────
always_comb begin
    casex (sel)
        4'b1xxx: out = d3;     // x = don't care in pattern
        4'b01xx: out = d2;
        4'b001x: out = d1;
        4'b0000: out = d0;
        default: out = '0;
    endcase
end
// ⚠ If sel contains X bits at simulation time they silently match.
// This hides X-propagation bugs. Prefer casez in synthesisable RTL.

🔍 Debugging Insight: Why casex Is Banned in Most RTL Style Guides

Here is the exact failure mode, and it needs one precision most write-ups skip.

Suppose sel[3:0] has an X on bit 1 — a bit your patterns actually care about. With casex and pattern 4'b1x00, X in the expression is a wildcard at every position, so bit 1 matches the pattern's 0 and the branch fires. Simulation looks correct; the hardware is in an undefined state; the root cause is masked. With casez and pattern 4'b1?00, bit 1 is a care position holding 0, and an X in the expression does not match it. No branch matches, the output goes X, and the bug becomes visible.

Now the part that matters and is usually stated wrongly. casez protects you only where the pattern cares. If the X had landed on bit 2 — the position written ? — that bit is a don't-care and is excluded from the comparison entirely, so the branch fires under casez exactly as it does under casex. The X is equally masked. A ? does not "reject X"; it removes the bit from the match.

So the accurate rule is: casex masks X everywhere; casez masks X only where you wildcarded. That is still a decisive advantage — it confines the blind spot to bits you explicitly declared irrelevant, instead of every bit — and it is why ARM, Intel, Qualcomm, and virtually every ASIC methodology ban casex from synthesisable RTL. But it also means a casez decoder that wildcards a bit it genuinely depends on is no safer than casex on that bit, which §"Proving it" below demonstrates in about forty lines.

Bit value in patterncase match?casez match?casex match?
0Only if expression bit = 0Only if expression bit = 0Only if expression bit = 0
1Only if expression bit = 1Only if expression bit = 1Only if expression bit = 1
Z or ?Only if expression bit = ZAlways (wildcard)Always (wildcard)
XOnly if expression bit = XOnly if expression bit = XAlways (wildcard)

unique case & priority case

These are the case equivalents of unique if and priority if. They add simulation checks without changing the synthesised hardware — they are assertions built into the language.

unique case

unique case asserts two things to both the simulator and the synthesis tool:

  • Mutually exclusive: at most one branch condition can be true at any given time.
  • Complete: at least one branch condition is always true (no unhandled case).

If either assertion fails during simulation, the tool issues a violation warning. The synthesis tool uses the mutual-exclusivity guarantee to generate a parallel decoder (no priority gates), potentially reducing area and critical path.

SystemVerilog — unique case
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// unique case: patterns are mutually exclusive AND complete
always_comb begin
    unique case (grant)           // one-hot: exactly one bit set
        4'b0001: bus_out = data_a;  // if two bits set → sim warning
        4'b0010: bus_out = data_b;  // if no bits set  → sim warning
        4'b0100: bus_out = data_c;
        4'b1000: bus_out = data_d;
    endcase                         // no default needed — unique covers it
end
 
 
// ⚠ Warning: unique case without default still warns if no branch matches.
// For synthesis safety add a default that drives a known value.

priority case

priority case asserts that at least one branch is always true (completeness), but allows multiple branches to be true simultaneously — the first match wins. It does not assert mutual exclusivity. The simulator warns if no branch matches at all.

SystemVerilog — priority case
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// priority case: first match wins, multiple matches are OK
always_comb begin
    priority case (1'b1)              // case(1) idiom: test which bit is set
        req[0]: grant = 4'b0001;      // highest priority
        req[1]: grant = 4'b0010;
        req[2]: grant = 4'b0100;
        req[3]: grant = 4'b1000;      // lowest priority
    endcase                             // sim warns if all req[] = 0
end
 
// case(1'b1) — evaluates each item as a condition.
// Equivalent to priority if chain, but reads cleanly.
KeywordMutual-exclusivity checkCompleteness checkMultiple matchesHardware inferred
caseNoneNoneFirst match wins (silent)Priority encoder
unique caseYes — sim warning if violatedYes — sim warning if no matchViolation — sim warningParallel decoder (optimised)
priority caseNoneYes — sim warning if no matchFirst match wins (intentional)Priority encoder

🚀 RTL Design Insight: unique case Is the Correct Choice for All One-Hot and FSM Decodes

In production RTL, unique case is the mandatory choice for: (1) FSM next-state decode where each state encoding is unique, (2) one-hot bus grant decode, (3) ALU opcode decode where opcodes are guaranteed mutually exclusive. The simulation safety net catches encoding bugs immediately — if you accidentally assign two states the same encoding, the unique case violation fires instantly on the first simulation cycle. Without unique case, these bugs survive simulation and are only caught post-synthesis, or in silicon. The synthesis QoR improvement (parallel decoder instead of priority chain) is a bonus; the real value is the simulation-time bug detection.

💡 Senior Verification Engineer Tip: The case(1'b1) Idiom Explained

The priority case (1'b1) idiom is one of the most confusing patterns for engineers who haven't seen it before. Here's what it does: case evaluates the expression (1'b1) and compares it against each item. Each item is a signal — e.g., req[0]. The comparison is: "does req[0] equal 1'b1?" — which is true when req[0] is high. So the case fires the branch whose item equals 1. Combined with priority, this is a clean, readable priority encoder. It's exactly equivalent to a priority if chain but reads as a table — much cleaner for 8+ priority levels. Many style guides require this idiom for priority arbiters.

What each form actually synthesises to

The three decision modifiers read almost identically and infer different hardware. That difference is invisible in simulation — all three evaluate top-to-bottom and take the first match — so it is worth seeing once as structure.

Plain case without default infers a latch; unique case infers a flat parallel decoder; priority case infers a cascaded mux chain whose depth grows with arm count.4-way decisionidentical source shapecase, no defaultunmatched leaves outputsunassignedunique casearms promised exclusivepriority casearms declared orderedLatch inferredmemory in combinationallogicFlat decoderdepth ~1 decode + 1 muxMux chaindepth grows with arm count12
Figure — the same four-way decision, three ways. Plain case without a default leaves unmatched inputs unassigned and infers a latch. unique case promises exclusivity, licensing a flat decoder whose depth is one decode plus one mux regardless of arm count. priority case (and if/else-if) declares an ordered chain, inferring cascaded muxes whose depth grows with the number of arms. Simulation cannot distinguish them; only the netlist and the timing report can.

The row that costs real money is the top one. A missing default does not produce a warning or an X — it produces storage, and a latch in a path the timing tool expected to be combinational is a closure problem discovered late. The other two rows are a genuine engineering choice: a decoder is faster, a priority chain is correct when the conditions really do overlap, and claiming unique for arms that are not exclusive gives the tool permission to build hardware that does not match your simulation.

The default Clause — Always Use It

The default branch in a case statement handles every pattern not listed explicitly. It is the hardware equivalent of the final else in an if-else chain. Without it, an unmatched case expression leaves assigned signals unchanged — inferring a latch.

Even when you believe your patterns are exhaustive (e.g., a 2-bit opcode with all four patterns listed), adding a default is good practice: it documents your intent, prevents lint warnings, and guards against future code changes that add new encodings.

SystemVerilog — default clause patterns
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Pattern A: assign default before case (preferred for RTL) ──
always_comb begin
    out = '0;                  // default first — safe, clean
    case (opcode)
        ADD: out = a + b;
        SUB: out = a - b;
        // any unlisted opcode → out stays '0 from default above
    endcase
end
 
 
// ── Pattern B: explicit default branch ─────────────────────────
always_comb begin
    case (opcode)
        ADD: out = a + b;
        SUB: out = a - b;
        default: out = '0;      // explicit — synthesis generates safe mux
    endcase
end
 
 
// ── Pattern C: latch — DO NOT write this ───────────────────────
always_comb begin
    case (opcode)
        ADD: out = a + b;
        SUB: out = a - b;
        // ⚠ missing default: out holds previous value → LATCH
    endcase
end

⚠ Common Industry Mistake: Trusting "All Patterns Listed" Without a Default

Engineers often write a 2-bit case with all four patterns (00, 01, 10, 11) and skip the default, reasoning "all cases are covered." This is incorrect in two ways. First, a 4-state simulator also has X and Z states — if the expression ever contains X (during reset, startup, or from an upstream bug), none of the binary patterns match, and the output becomes a latch-held value. Second, if someone later adds a new enum value and forgets to update the case, the missing branch silently produces the held (latch) value. A default: out = '0; line costs zero logic gates when all patterns are listed — but it eliminates both failure modes permanently.

Quick Reference

ConstructX in patternZ / ? in patternBest used for
caseExact match onlyExact match onlyEnumerations, state machines, opcodes
casezExact match onlyWildcard (don't care)Instruction decoding with partial patterns
casexWildcardWildcardAvoid in RTL (use casez instead)
unique caseExact matchExact matchOne-hot decode, mutually exclusive conditions
priority caseExact matchExact matchPriority arbiter (case(1) idiom)

🏗 FSM Design with case — The Industry Standard Pattern

The state encodings below are enums, and the transition logic is the always_comb half of the two-block FSM style; the sequential half uses non-blocking assignment.

The case statement is the backbone of finite state machine implementation in RTL. Every production SoC has dozens to hundreds of FSMs — controllers, arbiters, protocol handlers, power managers. The 3-block FSM pattern using always_ff + always_comb with case is the universal industry standard.

SystemVerilog — Production-Grade 3-Block FSM with unique case
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── AXI-Lite Slave Controller FSM — Real Project Style ───────────
typedef enum logic [2:0] {
    IDLE    = 3'b000,
    RD_ADDR = 3'b001,
    RD_DATA = 3'b010,
    WR_ADDR = 3'b011,
    WR_DATA = 3'b100,
    WR_RESP = 3'b101
} axi_st_t;
 
module axi_lite_slave_ctrl (
    input  logic clk, rst_n,
    input  logic awvalid, wvalid, bready, arvalid, rready,
    output logic awready, wready, bvalid, arready, rvalid
);
    axi_st_t state, next;
 
    // ── Block 1: State register ───────────────────────────────────
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) state <= IDLE;
        else        state <= next;
    end
 
    // ── Block 2: Next-state logic (combinational) ────────────────
    always_comb begin
        next = state;       // default: hold state
        unique case (state)
            IDLE:    begin
                         if      (arvalid) next = RD_ADDR;
                         else if (awvalid) next = WR_ADDR;
                     end
            RD_ADDR: next = RD_DATA;
            RD_DATA: if (rready) next = IDLE;
            WR_ADDR: if (wvalid) next = WR_DATA;
            WR_DATA: next = WR_RESP;
            WR_RESP: if (bready) next = IDLE;
            default: next = IDLE;    // safety: unreachable encodings → IDLE
        endcase
    end
 
    // ── Block 3: Output logic (Moore — combinational from state) ─
    always_comb begin
        {awready, wready, bvalid, arready, rvalid} = 5'b00000;
        unique case (state)
            IDLE:    begin arready = arvalid; awready = awvalid; end
            RD_ADDR: arready = 1'b1;
            RD_DATA: rvalid  = 1'b1;
            WR_ADDR: awready = 1'b1;
            WR_DATA: wready  = 1'b1;
            WR_RESP: bvalid  = 1'b1;
            default: {awready, wready, bvalid, arready, rvalid} = 5'b00000;
        endcase
    end
endmodule

Waveform — AXI Write Transaction FSM State Traceclk_‾‾_‾awvalid0 1 1 0 0 0wvalid0 0 1 1 0 0bready0 0 0 0 1 0stateIDLE IDLE WR_ADDR WR_DATA WR_RESP IDLEawready0 1 0 0 0 0wready0 0 0 1 0 0bvalid0 0 0 0 1 0 ↑ ↑ ↑ ↑ ↑ ↑ idle aw wr wr wr idle V A D R

FSM Encoding StyleState BitsNext-State LogicBest ForModifier to Use
Binary⌈log₂N⌉ bitsDense decoder logicSmall FSMs (≤8 states), area-criticalunique case
One-hotN bits (1 per state)One FF per state; simpler logicHigh-speed FSMs, FPGA-friendlyunique case (parallel)
Gray⌈log₂N⌉ bitsOnly 1 bit changes per transitionAsync domain crossing state syncPlain case
Sequential⌈log₂N⌉ bitsCounter-styleLinear pipelinesPlain case

⚙ Instruction Decoder with casez — Partial Opcode Matching

This is the pattern the Bug 2 Debug Lab dissects — worth reading together, because the wildcard that makes partial matching convenient is the same wildcard that creates the X blind spot.

Every processor has an instruction decoder. Modern ISAs (RISC-V, ARM) use hierarchical opcode fields — the top bits identify the instruction class, and lower bits qualify it. casez with ? wildcards is the natural implementation: match the bits that matter, ignore the rest.

SystemVerilog — RISC-V Style Instruction Decoder using casez
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── Simplified RISC-V 32-bit instruction decoder ─────────────────
// Opcode field: instr[6:0]  — identifies instruction type
// Funct3 field: instr[14:12] — qualifies within the type
 
typedef enum logic [4:0] {
    OP_ADD, OP_SUB, OP_AND, OP_OR, OP_XOR, OP_SLL, OP_SRL,
    OP_LW, OP_SW, OP_BEQ, OP_BNE, OP_JAL, OP_LUI, OP_AUIPC,
    OP_ECALL, OP_ILLEGAL
} alu_op_t;
 
module riscv_decoder (
    input  logic [31:0] instr,
    output alu_op_t       alu_op,
    output logic          reg_write, mem_read, mem_write, branch
);
    always_comb begin
        // Safe defaults — prevent latches and ensure known state
        alu_op    = OP_ILLEGAL;
        reg_write = 1'b0;
        mem_read  = 1'b0;
        mem_write = 1'b0;
        branch    = 1'b0;
 
        casez ({instr[14:12], instr[6:0]})  // {funct3, opcode}
            // ── R-type (opcode = 0110011) ─────────────────────────────
            10'b000_0110011: begin alu_op = OP_ADD; reg_write = 1'b1; end
            10'b000_0110011: begin alu_op = OP_SUB; reg_write = 1'b1; end
            10'b111_0110011: begin alu_op = OP_AND; reg_write = 1'b1; end
            10'b110_0110011: begin alu_op = OP_OR;  reg_write = 1'b1; end
            10'b100_0110011: begin alu_op = OP_XOR; reg_write = 1'b1; end
            // ── I-type Load (opcode = 0000011, funct3 = 010 = LW) ────
            10'b010_0000011: begin alu_op = OP_LW; mem_read = 1'b1; reg_write = 1'b1; end
            // ── S-type Store (opcode = 0100011, funct3 = 010 = SW) ───
            10'b010_0100011: begin alu_op = OP_SW; mem_write = 1'b1; end
            // ── B-type Branch (opcode = 1100011) ─────────────────────
            10'b000_1100011: begin alu_op = OP_BEQ; branch = 1'b1; end
            10'b001_1100011: begin alu_op = OP_BNE; branch = 1'b1; end
            // ── U-type (opcode only — funct3 is don't-care) ──────────
            10'b???_0110111: begin alu_op = OP_LUI;   reg_write = 1'b1; end
            10'b???_0010111: begin alu_op = OP_AUIPC; reg_write = 1'b1; end
            // ── SYSTEM (opcode = 1110011) ─────────────────────────────
            10'b000_1110011: alu_op = OP_ECALL;
            // ── All others → illegal ──────────────────────────────────
            default: alu_op = OP_ILLEGAL;
        endcase
    end
endmodule
 
// ── Key: casez matches '?' against any bit (0 or 1)
// 10'b???_0110111 fires for ALL funct3 values with opcode=0110111
// Without casez, you'd need 8 separate case items (funct3=000..111)

🚀 RTL Design Insight: casez ? vs Separate Bit Checks — Synthesis Impact

When you write casez with ? wildcards, the synthesis tool understands the don't-care semantics and applies it to logic minimization. For a pattern like 4'b1???, the synthesizer knows only bit[3] matters — it generates a single gate checking bit[3], not a 4-bit comparator. Without casez, you'd need to write all 8 combinations (1000, 1001, 1010, ..., 1111) as separate case items, and the synthesis tool would generate a wider comparator before optimizing it. The casez version communicates don't-care intent directly to the tool, enabling cleaner minimization and smaller logic cones.

🔬 Simulation vs Synthesis — What the Simulator and Tool Each See

Understanding the gap between how the simulator processes case and how synthesis interprets it is the key to writing correct, synthesizable RTL. The gap is small but critical.

AspectSimulator (VCS/Questa/Xcelium)Synthesis Tool (DC/Genus)
Branch evaluation orderTop to bottom — first match winsAll branches in parallel — no implied order
Multiple matching branchesFirst one fires (silent with plain case)Undefined — tool may generate incorrect logic
X in expression (casex)Matches any x/z in pattern (wildcard)X never exists in gates — treated as 0 or 1
X in expression (casez)X does NOT match ? (Z does)Z treated as don't-care (DC optimization)
No-match (no default)Signal holds old value (latch behavior)Latch cell inferred — STA treats as memory
unique case with overlapWarning issued, first match takenTool assumes exclusive — parallel decoder
priority case orderingFirst match wins (guaranteed)Priority encoder inferred
❌ Sim/Synth mismatch — overlapping case items// Two case items match when grant=4'b0011 always_comb begin bus_out = '0; case (grant) 4'b0001: bus_out = data_a; // ← sim: wins when grant=1 4'b0011: bus_out = data_b; // ← this can also match grant=1 if // casez? No, plain case is exact. // But if programmer made typo: 4'b00?1: bus_out = data_c; // ← casez: matches 0001 AND 0011! endcase end // Sim: first match (data_a) wins // Synth: may generate data_c for 0001✅ Use unique case to catch the overlap// unique case exposes the overlap in simulation always_comb begin bus_out = '0; unique case (grant) 4'b0001: bus_out = data_a; 4'b0010: bus_out = data_b; 4'b0100: bus_out = data_c; 4'b1000: bus_out = data_d; default: bus_out = '0; endcase end // Sim warns if any two branches match // Synth: clean parallel one-hot decoder

📊 Waveform Analysis — case Evaluation in Action

Watching how a case statement behaves in a waveform viewer gives you the intuition to debug case-related issues instantly. The key insight: case inside always_comb re-evaluates every time any input changes — including X values at startup. Waveform — 4-opcode ALU: case evaluation at each opcode transitionTime →T0 T1 T2 T3 T4 T5 T6opcodeXX 00 01 10 11 01 00a08 08 08 08 08 08 10b03 03 03 03 03 03 05resultXX 0B 05 08 0B 05 15 ↑ ↑ ↑ ↑ ↑ ↑ ↑ X at ADD SUB AND OR SUB ADD T=0 8+3 8-3 8&3 8|3 8-3 16+5 (rst) =0Bh =05h =08h =0Bh =05h =15h At T0: opcode is X (undriven before reset). The case finds no exact match for XX — result becomes X. This is correct simulation behavior: X propagates to alert you to an uninitialized input. At T1 after reset: opcode=00, case fires the ADD branch instantly (combinational, no clock delay). Each subsequent opcode change immediately re-evaluates the case and updates result. Waveform — FSM case: state transitions over clock edgesclk_‾‾rst_n0 1 1 1 1 1start0 0 1 0 0 0done0 0 0 0 1 0stateIDLE IDLE IDLE BUSY BUSY DONEnextIDLE IDLE BUSY BUSY DONE IDLE ↑ ↑ ↑ ↑ ↑ ↑ rst held comb start comb done IDLE sees low sees cond start done

🧠 How to Read an FSM Waveform — next vs state

In a 3-block FSM, next (combinational) and state (registered) are both visible in the waveform. next changes immediately when any input changes (combinational — always_comb re-evaluates). state changes only at the posedge clock (registered — always_ff captures next). The 1-cycle lag between next changing and state updating is correct flip-flop behavior. If they ever appear to change at the same time (without a clock edge), you have a blocking assignment in the always_ff — a race condition.

⚙ Advanced Code Examples — Industry Patterns

Example A — 8-bit One-Hot Bus Arbiter (unique case)

SystemVerilog — One-Hot Bus Arbiter with unique case
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 8-master bus arbiter: grant is always one-hot ─────────────────
module bus_arbiter (
    input  logic clk, rst_n,
    input  logic [7:0] req,
    output logic [7:0] grant,
    output logic [2:0] grant_id,
    output logic         bus_active
);
    always_comb begin
        grant      = 8'h00;
        grant_id   = 3'd0;
        bus_active = 1'b0;
 
        // Fixed-priority arbiter: req[7] highest, req[0] lowest
        priority case (1'b1)
            req[7]: begin grant = 8'b1000_0000; grant_id = 3'd7; bus_active = 1'b1; end
            req[6]: begin grant = 8'b0100_0000; grant_id = 3'd6; bus_active = 1'b1; end
            req[5]: begin grant = 8'b0010_0000; grant_id = 3'd5; bus_active = 1'b1; end
            req[4]: begin grant = 8'b0001_0000; grant_id = 3'd4; bus_active = 1'b1; end
            req[3]: begin grant = 8'b0000_1000; grant_id = 3'd3; bus_active = 1'b1; end
            req[2]: begin grant = 8'b0000_0100; grant_id = 3'd2; bus_active = 1'b1; end
            req[1]: begin grant = 8'b0000_0010; grant_id = 3'd1; bus_active = 1'b1; end
            req[0]: begin grant = 8'b0000_0001; grant_id = 3'd0; bus_active = 1'b1; end
        endcase
        // priority case(1): sim warns if no req active (bus idle unexpectedly)
    end
 
    // ── Verification: scoreboard checks grant is always one-hot ──────
    always_comb begin
        if (bus_active) begin
            assert ($countones(grant) == 1)
                else $error("GRANT NOT ONE-HOT: %b", grant);
        end
    end
endmodule

Example B — Gray Code Counter Decoder (casez for don't-care)

SystemVerilog — Gray Code to Binary Converter using casez
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 4-bit Gray code → binary decode via casez ─────────────────────
// Used in async FIFO pointers, rotary encoders, ADC thermometer codes
module gray_to_bin (
    input  logic [3:0] gray,
    output logic [3:0] binary
);
    always_comb begin
        binary = 4'h0;
        casez (gray)
            4'b0000: binary = 4'd0;
            4'b0001: binary = 4'd1;
            4'b0011: binary = 4'd2;
            4'b0010: binary = 4'd3;
            4'b0110: binary = 4'd4;
            4'b0111: binary = 4'd5;
            4'b0101: binary = 4'd6;
            4'b0100: binary = 4'd7;
            4'b1100: binary = 4'd8;
            4'b1101: binary = 4'd9;
            4'b1111: binary = 4'd10;
            4'b1110: binary = 4'd11;
            4'b1010: binary = 4'd12;
            4'b1011: binary = 4'd13;
            4'b1001: binary = 4'd14;
            4'b1000: binary = 4'd15;
            default: binary = 4'hX;   // impossible (all covered), but defensive
        endcase
    end
 
    // Note: casez here is actually plain case — no ? wildcards are used.
    // The important thing is the default: outputs X for unexpected inputs,
    // making simulation bugs visible rather than silently outputting 0.
endmodule

Example C — Verification Scoreboard using case for Opcode Reference Model

SystemVerilog — Testbench Reference Model with unique case
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── ALU reference model: used in scoreboard to generate expected output
module alu_scoreboard;
    typedef enum logic [3:0] {
        ADD=4'h0, SUB=4'h1, AND=4'h2, OR=4'h3,
        XOR=4'h4, NOT=4'h5, SHL=4'h6, SHR=4'h7
    } opcode_t;
 
    function automatic logic [8:0] alu_ref(
        input logic [7:0] a, b,
        input opcode_t op
    );
        unique case (op)
            ADD: return {1'b0, a} + {1'b0, b};
            SUB: return {1'b0, a} - {1'b0, b};
            AND: return {1'b0, a & b};
            OR:  return {1'b0, a | b};
            XOR: return {1'b0, a ^ b};
            NOT: return {1'b0, ~a};
            SHL: return {a[7], a, 1'b0};     // MSB spills to carry
            SHR: return {1'b0, 1'b0, a[7:1]};  // LSB lost
            default: return 9'hX;           // illegal op — propagate X
        endcase
    endfunction
 
    // ── Coverage group: hit every opcode in simulation ────────────────
    opcode_t cov_op;
    covergroup opcode_cg @(posedge clk);
        cp_op: coverpoint cov_op {
            bins all_ops[] = {ADD, SUB, AND, OR, XOR, NOT, SHL, SHR};
        }
    endgroup
endmodule

🔬 Debugging Academy — 8 Real case/casez Bugs from the Field

Every one of these bugs has appeared in production RTL reviews or verification campaigns. The symptoms look confusing until you understand exactly how case matching, X propagation, and latch inference interact.

Bug 1 — Missing default in an FSM case: unknown state after a reset glitch

Category: FSM / latch inference. Buggy code:

Bug 1 — FSM case Missing default Leaves State Machine Stuck
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: 3-state FSM with 3-bit state register — 5 unreachable encodings
typedef enum logic [2:0] {IDLE=3'd0, FETCH=3'd1, EXEC=3'd2} st_t;
st_t state, next;
 
always_comb begin
    next = state;
    case (state)        // ❌ no default!
        IDLE:  next = FETCH;
        FETCH: next = EXEC;
        EXEC:  next = IDLE;
        // state = 3'd3..7: no match → next holds → LATCH on next!
        // If state glitches to 3'd5 (reset noise, power event):
        // → next never leaves 3'd5 → FSM locked forever
    endcase
end
 
// ✅ FIX: always include default → IDLE as safety net
always_comb begin
    next = IDLE;         // safe default at top
    case (state)
        IDLE:  next = FETCH;
        FETCH: next = EXEC;
        EXEC:  next = IDLE;
        default: next = IDLE;  // safety: illegal encodings → IDLE
    endcase
end

Root cause / waveform symptom / prevention. Waveform symptom: the FSM appears stuck in an unrecognized state — the state register shows a binary value (e.g., 3'd5) that doesn't correspond to any declared state. The state never transitions. Outputs are all at their default (zero) values since no output case branch fires.How It Happens in SiliconPower-on glitch or reset noise can briefly corrupt the state register to an illegal encoding. Without a default: next = IDLE;, the FSM has no recovery path — it stays locked in the illegal state forever. In simulation, this scenario is often missed because testbenches apply clean resets. In silicon, power integrity issues create exactly this scenario.Industry RuleEvery FSM case statement must have a default that returns to a safe state. No exceptions. Most RTL style guides make this a P1 (blocker) lint rule. Tools like Spyglass flag missing FSM defaults as "FSM_NO_DEFAULT_STATE" — a must-fix before tape-out.2casex Hides X-Propagation — Reset Bug Survives SimulationX-Propagation / casexBuggy Code

Bug 2 — casex masks an X, and the "obvious" casez fix does not help

2

Swapping casex for casez leaves the X exactly as hidden, because the X landed on a wildcarded bit

CASEZ-WILDCARD-BLIND-SPOT
Observed Symptom

An instruction decoder produces plausible control signals at startup, but the design behaves randomly on the bench. Simulation is clean. opcode[3:2] is X out of reset — an upstream register is never initialised — and the decoder never notices.

A reviewer applies the standard rule, replaces casex with casez, and the simulation is still clean. The X is still invisible.

Expected vs Actual
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The original. casex treats X in the EXPRESSION as a wildcard everywhere.
always_comb begin
  ctrl = '0;
  casex (opcode)            // opcode = 4'bXX00
    4'bxx00: ctrl = LOAD;   // fires - X masked
    4'bxx01: ctrl = STORE;
    4'bxx10: ctrl = ALU;
    4'bxx11: ctrl = BRANCH;
  endcase
end
 
// The "fix" that changes nothing for THIS opcode.
always_comb begin
  ctrl = '0;
  casez (opcode)            // opcode = 4'bXX00
    4'b??00: ctrl = LOAD;   // STILL fires - bits 3:2 are '?', so they are
    4'b??01: ctrl = STORE;  // excluded from the comparison entirely. The X
    4'b??10: ctrl = ALU;    // sits exactly where the pattern stopped caring.
    4'b??11: ctrl = BRANCH;
    default: ctrl = '0;
  endcase
end

Expected (per the usual rule): casez refuses to match an X, output goes X, bug visible. Actual: bits 3:2 are ?. A ? does not reject an X — it removes that bit from the match. Both decoders return LOAD for an opcode nobody knows.

Diagnostic

The question that resolves it is not "casex or casez" but where is the X relative to the pattern's care bits?

  1. Locate the X. Which bits of the expression are unknown? Here, 3:2.
  2. Read the pattern at those positions. 4'b??00 — both are ?. The comparison never looks at them, under either keyword.
  3. Predict, then confirm. If every X bit sits on a wildcard, no case variant will expose it; if any X bit sits on a 0/1, casez will. That prediction is testable in seconds with the proof below, and it is faster than re-reading the decoder.

The tell that separates this from an ordinary decode bug: swapping casexcasez changed nothing. That is not the fix failing to apply — it is evidence the X is on a don't-care bit.

Root Cause

Two distinct faults, and the review caught only the second.

The real fault is upstream: opcode is X out of reset. The decoder is downstream of a missing initialisation.

The masking fault is that the decoder wildcards bits 3:2 while its four patterns are distinguished solely by bits 1:0 — so the decoder genuinely does not depend on 3:2, and an X there is invisible by construction. casex additionally masks X on bits 1:0; casez does not. Replacing one with the other fixed a blind spot the design did not have and left the one it did.

Fix

casez is still right — it removes the wildcard blind spot on the care bits — but it is not sufficient. Check the operand explicitly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
always_comb begin
  ctrl = '0;
  // The X check the case statement structurally cannot perform: a wildcarded
  // bit is excluded from matching, so no case variant can flag it.
  if ($isunknown(opcode))
    ctrl = ILLEGAL;                 // or leave '0 and let an assertion fire
  else
    casez (opcode)
      4'b??00: ctrl = LOAD;
      4'b??01: ctrl = STORE;
      4'b??10: ctrl = ALU;
      4'b??11: ctrl = BRANCH;
      default: ctrl = '0;
    endcase
end
 
// Better still, make it a verification failure rather than a silent recode:
a_opcode_known: assert property (@(posedge clk) disable iff (!rst_n)
    !$isunknown(opcode))
  else $error("[%0t] opcode has X bits: %b", $time, opcode);
Prevention

Do not delegate X-detection to the pattern matcher. A case statement decides which branch, not whether the input is meaningful; a wildcard is a statement that a bit is irrelevant, and an irrelevant bit cannot also be a bit you want checked. Use $isunknown or an assertion for the second job.

Then treat the rule with its real scope: "use casez not casex" is about the care bits. It is correct and worth following, but quoting it as blanket X-protection is what let this reviewer close the ticket with the bug still in the design. Where a decoder truly must depend on a bit, do not wildcard that bit — a ? there converts a decode bug into silence.

Proving it — when casez helps and when it cannot

Forty lines settle the argument permanently. The same X is placed once on a care bit and once on a wildcard bit, and run through both keywords.

casez_x_proof.sv — self-checking; the result is not what the usual rule predicts
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
module casez_x_proof;
 
  typedef enum logic [1:0] {LOAD, STORE, ALU, BRANCH} ctrl_e;
 
  // Decoder wildcarding bits 3:2, deciding on bits 1:0.
  function automatic ctrl_e dec_casex(input logic [3:0] op);
    casex (op)
      4'bxx00: return LOAD;    4'bxx01: return STORE;
      4'bxx10: return ALU;     4'bxx11: return BRANCH;
      default: return LOAD;
    endcase
  endfunction
 
  function automatic ctrl_e dec_casez(input logic [3:0] op);
    casez (op)
      4'b??00: return LOAD;    4'b??01: return STORE;
      4'b??10: return ALU;     4'b??11: return BRANCH;
      default: return LOAD;
    endcase
  endfunction
 
  // A match is "exposed" when no specific arm fires and we fall to default.
  // Encode that by returning a sentinel from default instead: simpler here to
  // test the arms directly with a known-good reference.
  initial begin
    logic [3:0] x_on_wildcard = 4'bxx00;   // X on bits 3:2 - the '?' positions
    logic [3:0] x_on_care     = 4'b00x0;   // X on bit 1   - a '0'/'1' position
 
    $display("  opcode        casex          casez");
    $display("  ----------------------------------------");
    $display("  4'bxx00   ->  %-12s   %-12s", dec_casex(x_on_wildcard).name(),
                                              dec_casez(x_on_wildcard).name());
    $display("  4'b00x0   ->  %-12s   %-12s", dec_casex(x_on_care).name(),
                                              dec_casez(x_on_care).name());
    $display("");
    $display("  Row 1: X sits on the wildcarded bits. BOTH decoders return LOAD.");
    $display("         casez did NOT help - the '?' excluded those bits.");
    $display("  Row 2: X sits on a care bit. casex still matches an arm;");
    $display("         casez matches none and falls through to default.");
    $display("");
    $display("  Conclusion: casez removes the blind spot on CARE bits only.");
    $finish;
  end
endmodule
simulation log
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  opcode        casex          casez
  ----------------------------------------
  4'bxx00   ->  LOAD           LOAD
  4'b00x0   ->  LOAD           LOAD
 
  Row 1: X sits on the wildcarded bits. BOTH decoders return LOAD.
         casez did NOT help - the '?' excluded those bits.
  Row 2: X sits on a care bit. casex still matches an arm;
         casez matches none and falls through to default.
 
  Conclusion: casez removes the blind spot on CARE bits only.

Read row 2 carefully, because it exposes a second trap. Both columns print LOAD — but for opposite reasons. casex matched the 4'bxx00 arm (X is a wildcard, so bit 1 matched 0). casez matched nothing and fell through to default: return LOAD. The decoder returns the same value either way, so the X is invisible again — this time because the default silently supplies a plausible answer.

That is the practical lesson the keyword debate obscures: casez moves an unmatched X into the default arm, and a default that returns a valid-looking value hides it just as effectively as casex did. Make the default distinguishable — ILLEGAL, an X, or an assertion — or the improvement exists only on paper.

Bug 3 — unique case fires spurious warnings: the one-hot bus has an idle state

Category: unique case misuse. Buggy code:

Bug 3 — unique case Without default on a Bus That Can Be Idle
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: grant can be 4'b0000 (bus idle) — unique case warns
always_comb begin
    unique case (grant)
        4'b0001: bus_out = data_a;
        4'b0010: bus_out = data_b;
        4'b0100: bus_out = data_c;
        4'b1000: bus_out = data_d;
        // grant=4'b0000 (idle): no branch → "unique case: no match" warning
        // Simulation log: thousands of warnings during bus idle cycles
    endcase
end
 
// ✅ FIX A: add default for the idle case
always_comb begin
    unique case (grant)
        4'b0001: bus_out = data_a;
        4'b0010: bus_out = data_b;
        4'b0100: bus_out = data_c;
        4'b1000: bus_out = data_d;
        default: bus_out = '0;   // covers idle — no more warnings
    endcase
end
 
// ✅ FIX B: add a zero assignment before case (alternative)
always_comb begin
    bus_out = '0;          // idle state covered here
    unique case (grant)
        4'b0001: bus_out = data_a;
        4'b0010: bus_out = data_b;
        4'b0100: bus_out = data_c;
        4'b1000: bus_out = data_d;
    endcase
end

Bug 4 — Overlapping casez patterns: the wrong branch fires for some inputs

Category: casez pattern overlap. Buggy code:

Bug 4 — casez Patterns Overlap: First Match Wins Silently
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: overlapping casez patterns — opcode 4'b1100 matches BOTH
always_comb begin
    ctrl = ILLEGAL;
    casez (opcode)
        4'b1???: ctrl = BRANCH;  // matches 1000..1111 (8 patterns)
        4'b1100: ctrl = JUMP;    // ❌ also matches 1100 — but BRANCH already took it!
        4'b0???: ctrl = ALU;
    endcase
end
// opcode=4'b1100: BRANCH fires (first match in sim)
// JUMP branch is UNREACHABLE — dead code in simulation AND synthesis
// Lint: "unreachable case item" warning
 
// ✅ FIX: put more specific patterns BEFORE general wildcard patterns
always_comb begin
    ctrl = ILLEGAL;
    casez (opcode)
        4'b1100: ctrl = JUMP;    // ✅ specific first — catches 1100 before wildcard
        4'b1???: ctrl = BRANCH;  // wildcard last — catches remaining 1xxx
        4'b0???: ctrl = ALU;
        default: ctrl = ILLEGAL;
    endcase
end
// Rule: in casez, order from MOST SPECIFIC to LEAST SPECIFIC pattern.

Bug 5 — case inside always_ff with blocking assignment: the pipeline collapses

Category: blocking assignment in sequential logic. Buggy code:

Bug 5 — Blocking = in always_ff case: Multi-Stage Pipeline Collapses
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: blocking = in always_ff case — 2-stage pipeline becomes 1-stage
always_ff @(posedge clk) begin
    case (opcode)
        ADD: begin
            stage1 = a + b;    // blocking: stage1 updates immediately
            stage2 = stage1;   // blocking: reads NEW stage1 — same cycle!
        end
        SUB: begin
            stage1 = a - b;
            stage2 = stage1;   // stage2 = a-b in same cycle → 1-stage not 2
        end
    endcase
end
// Expected: stage2 = stage1 from PREVIOUS cycle (2-cycle latency)
// Actual:   stage2 = stage1 from THIS cycle (1-cycle latency)
 
// ✅ FIX: use non-blocking <= — evaluates RHS before updating LHS
always_ff @(posedge clk) begin
    case (opcode)
        ADD: begin stage1 <= a + b; stage2 <= stage1; end  // ✅ old stage1
        SUB: begin stage1 <= a - b; stage2 <= stage1; end
    endcase
end

Bug 6 — case expression width mismatch: synthesis generates the wrong comparison

Category: width mismatch. Buggy code:

Bug 6 — case Expression Width Mismatch Causes Silent Comparison Error
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: opcode is 4 bits, but patterns are only 2 bits
logic [3:0] opcode;
 
always_comb begin
    ctrl = '0;
    case (opcode)
        2'b00: ctrl = ADD;    // ❌ 2-bit literal vs 4-bit expression
        2'b01: ctrl = SUB;    // simulator: zero-extends to 4'b0000, 4'b0001
        2'b10: ctrl = AND;    // so opcode=4'b0100 also matches 2'b00 after zero-extend?
        2'b11: ctrl = OR;     // No — zero-extension means 2'b00→4'b0000 only
        // But: opcode=4'b0100 → no match → latch!
    endcase
end
 
// The patterns ONLY match opcode[3:0] = 0000, 0001, 0010, 0011
// opcode[3:0] = 0100..1111: no match → latch on ctrl
// Lint: "case item width mismatch" WARNING — treat as ERROR
 
// ✅ FIX: match expression width in all patterns
always_comb begin
    ctrl = '0;         // default covers missing patterns
    case (opcode)
        4'b0000: ctrl = ADD;   // ✅ 4-bit literals match 4-bit expression
        4'b0001: ctrl = SUB;
        4'b0010: ctrl = AND;
        4'b0011: ctrl = OR;
    endcase
end

Bug 7 — Partial output assignment in case: some outputs latch, others do not

Category: partial latch inference. Buggy code:

Bug 7 — Some Outputs Latch, Some Don't: Partial Coverage
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: mem_write is only assigned in one branch, not all
always_comb begin
    case (opcode)
        LD:  begin reg_write = 1'b1; mem_read  = 1'b1; end
        ST:  begin reg_write = 1'b0; mem_write = 1'b1; end  // mem_read not assigned!
        ALU: begin reg_write = 1'b1; end                   // mem_read, mem_write not assigned!
        default: begin reg_write = 1'b0; end               // mem_read, mem_write not assigned!
    endcase
end
// Result: reg_write has no latch (assigned in every branch via default)
// mem_read: LATCH inferred (only assigned in LD branch)
// mem_write: LATCH inferred (only assigned in ST branch)
// Tool ERROR: "Latch inferred on mem_read, mem_write"
 
// ✅ FIX: assign ALL outputs at top before case
always_comb begin
    {reg_write, mem_read, mem_write} = 3'b000;  // ← covers ALL paths
    case (opcode)
        LD:  begin reg_write = 1'b1; mem_read  = 1'b1; end
        ST:  begin                     mem_write = 1'b1; end
        ALU: begin reg_write = 1'b1; end
        default:; // all held at '0 from top assignment
    endcase
end

Bug 8 — priority case (1'b1) with no active request: simulation warns, output undefined

Category: no-match warning. Buggy code:

Bug 8 — priority case(1) No-Match: What Happens When req Is All Zero
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: what if req = 4'b0000 (no interrupt pending)?
always_comb begin
    priority case (1'b1)
        req[3]: irq_id = 2'd3;
        req[2]: irq_id = 2'd2;
        req[1]: irq_id = 2'd1;
        req[0]: irq_id = 2'd0;
        // req=4'b0000 → no item equals 1'b1 → no match
        // priority case: "no condition true" WARNING fires
        // irq_id is NOT assigned → LATCH holds last value
    endcase
end
 
// ✅ FIX: gate with irq_active, or add default
always_comb begin
    irq_id = 2'd0;        // default: irq 0 (or idle encoding)
    irq_active = 1'b0;
    priority case (1'b1)
        req[3]: begin irq_id = 2'd3; irq_active = 1'b1; end
        req[2]: begin irq_id = 2'd2; irq_active = 1'b1; end
        req[1]: begin irq_id = 2'd1; irq_active = 1'b1; end
        req[0]: begin irq_id = 2'd0; irq_active = 1'b1; end
    endcase
    // Default assignment at top covers req=0000 silently — no warning, no latch
end

💡 Senior Verification Engineer Tip: Make casez Pattern Overlap Visible

When you use casez in RTL, always pair it with a lint rule that detects overlapping case items. Synopsys Spyglass and Cadence JasperGold have specific rules like STARC-2.1.4.5 (overlapping case items in casez). Many teams miss this because casez overlaps are legal SystemVerilog — the tool silently takes the first match. Discovering an unreachable case item in post-synthesis simulation is far more expensive than catching it during RTL lint review. Set the lint rule severity to ERROR for casez overlap detection.

Interview Q&A — From Fresher to Principal Engineer

case tells the synthesis tool the branches are mutually exclusive parallel conditions, so it infers a decoder — flat logic whose critical path is roughly one decode level plus one mux level. if / else if implies priority: each condition matters only when all earlier ones are false, so the tool infers a chain of cascaded 2:1 muxes whose depth grows linearly with the branch count.

In simulation both evaluate top-to-bottom and take the first match, which is why the difference is invisible in a testbench and shows up only in timing reports. That asymmetry — identical simulation, different hardware — is the reason this question is asked.

Where This Is Specified

case, casez, and casex are defined in IEEE Std 1800 (SystemVerilog), clause 12 — Procedural programming statements — the case statement and its wildcard variants in §12.5, and the unique/priority decision modifiers with their violation-reporting semantics in §12.4–12.5. The IEEE Standards Association listing is the primary source.

The sentence worth reading in the clause rather than taking from a summary is the definition of the wildcard behaviour: casez treats z (and its ? alias) as don't-care in either the expression or the case item, and casex extends that to x as well. Read precisely, that says a wildcard bit is excluded from the comparison — it does not "reject" anything. That single distinction is what makes the Bug 2 blind spot above possible, and it is why casez is a narrowing of the X hazard rather than a cure.

Equally worth noting is what the standard does not promise: unique and priority are decision modifiers whose violation reports are simulation behaviour. They convey intent to synthesis, but a unique case whose arms overlap in silicon is a lie the tool is entitled to believe — the standard does not make the tool check it for you.

Related lessons. The conditional counterpart is if-else with unique and priority; the enum types these decode are enums, and the procedural context is procedural blocks. For the X behaviour Bug 2 turns on, see 4-state vs 2-state types, and for what an X does once it reaches gates, where X comes from and X through muxes and flops.

Part of SystemVerilog Fundamentals·Procedural Statements·Lesson 31 of 53

View program

Continue learning