Wildcard Equality — ==? and !=?
X/Z don't-care matching, mask-based pattern matching, comparison with casex/casez.
Module 4 · Page 4.9
Four Equality Operators — One Confusing Family
SystemVerilog has four equality operators, and getting them confused is one of the most common sources of silent simulation bugs. The difference matters most when X or Z values are involved — which is constantly: reset sequences, uninitialized signals, Z-state buses, and partial-drive scenarios all generate X/Z.
| Operator | Name | X/Z in operand | Result type | Primary use |
|---|---|---|---|---|
== | Logical equality | Propagates X → result can be X | 0, 1, or X | General comparisons |
!= | Logical inequality | Propagates X → result can be X | 0, 1, or X | General comparisons |
=== | Case equality | X and Z are literal values — must match exactly | 0 or 1 only | X/Z detection in testbench |
!== | Case inequality | X and Z are literal values | 0 or 1 only | X/Z detection in testbench |
==? | Wildcard equality | X/Z in either operand = don't-care bit | 0 or 1 only | Pattern/mask matching |
!=? | Wildcard inequality | X/Z in either operand = don't-care bit | 0 or 1 only | Pattern/mask matching |
==? is the only equality operator that always returns a clean 0 or 1. It does this by treating any bit position where either operand has X or Z as automatically equal — a don't-care match. This makes it ideal for opcode pattern matching, bus mask comparisons, and anywhere you want to classify a signal without caring about specific bit positions.
The Don't-Care Mechanism — Bit by Bit
For each bit position, ==? asks: "is either operand's bit an X or Z here?" If yes — don't-care, that position is considered equal regardless of what the other operand holds. If both bits are known (0 or 1), it falls back to standard equality for that position. Only when all non-masked positions agree does the operator return 1.
The practical use: write a mask pattern on the right operand where X marks the bits you don't care about. cmd ==? 8'b1XXX_XXXX matches any command where bit 7 is 1, regardless of the other seven bits. This is exactly the same matching rule as casex.
- ==? Wildcard Equality — Returns 1 if all non-X/Z bit positions agree between operands. X/Z in either operand masks that bit position. Always returns 0 or 1.
- !=? Wildcard Inequality — Logical inverse of ==?. Returns 1 when the operands do NOT wildcard-match. Same X/Z masking rules apply.
- vs === (Case Equality) — === treats X and Z as literal values — X === X is 1. Used to detect unknown states. ==? masks them — used to ignore specific bits.
Syntax and the Bit-Level Rule
// Syntax: expression ==? pattern
// Returns: 1-bit — always 0 or 1, NEVER X
// Rule: for each bit — if either operand has X or Z → that bit matches
// if both are known → standard equality applies
logic [7:0] cmd = 8'b1010_0011;
// ── Pattern matching: X marks don't-care bits ─────────────────────
cmd ==? 8'b1XXX_XXXX // 1 — bit7=1 matches; all other bits are X (don't-care)
cmd ==? 8'b1010_XXXX // 1 — bits[7:4]=1010 match; lower nibble is X
cmd ==? 8'b1010_0011 // 1 — exact match (no wildcards needed)
cmd ==? 8'b0XXX_XXXX // 0 — bit7=1 in cmd, pattern requires bit7=0
// ── Inequality ───────────────────────────────────────────────────
cmd !=? 8'b1XXX_XXXX // 0 — they wildcard-match, so !=? is 0
cmd !=? 8'b0XXX_XXXX // 1 — they don't match, so !=? is 1
// ── X in the LEFT operand (value being tested) ────────────────────
logic [3:0] val = 4'bX101;
val ==? 4'b0101 // 1 — val bit3 is X → don't-care, other bits 101 match
val ==? 4'b1101 // 1 — val bit3 is X → don't-care, other bits 101 match
val ==? 4'b0100 // 0 — bit0: val=1 vs pattern=0 → mismatch on known bit
// ── Use in if statement ───────────────────────────────────────────
if (opcode ==? 8'b1010_XXXX)
$display("ALU class opcode");
// ── Use in casex — same semantics as ==? ─────────────────────────
casex (opcode)
8'b1010_XXXX: /* ALU */
8'b1100_XXXX: /* LOAD */
default: /* ILLEGAL */
endcaseBit-Level Visual — Exactly What Gets Compared
Complete Equality Operator Truth Table
For a single bit position, comparing left operand bit (L) against right operand bit (R):
| L bit | R bit | == result | === result | ==? result |
|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 1 |
| 0 | 1 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 1 | 1 |
| X | 0 | X | 0 | 1 (don't-care) |
| X | 1 | X | 0 | 1 (don't-care) |
| X | X | X | 1 (exact match) | 1 (don't-care) |
| 0 | X | X | 0 | 1 (don't-care) |
| 1 | X | X | 0 | 1 (don't-care) |
| Z | 0 | X | 0 | 1 (don't-care) |
| Z | Z | X | 1 (exact match) | 1 (don't-care) |
Multi-Bit Pattern Match — Step by Step
cmd = 8'b1010_0011, pattern = 8'b1010_XXXX
| Bit | cmd bit | Pattern bit | ==? for this bit | Reason |
|---|---|---|---|---|
| [7] | 1 | 1 | Match | Both known, equal |
| [6] | 0 | 0 | Match | Both known, equal |
| [5] | 1 | 1 | Match | Both known, equal |
| [4] | 0 | 0 | Match | Both known, equal |
| [3] | 0 | X | Match (don't-care) | Pattern has X → ignore |
| [2] | 0 | X | Match (don't-care) | Pattern has X → ignore |
| [1] | 1 | X | Match (don't-care) | Pattern has X → ignore |
| [0] | 1 | X | Match (don't-care) | Pattern has X → ignore |
| Overall ==? result | 1 — all bits match or are masked |
All Four Operators on the Same Values — Side by Side
| Expression | == | === | ==? | Key takeaway |
|---|---|---|---|---|
4'b1010 == 4'b1010 | 1 | 1 | 1 | All agree on clean match |
4'b1010 == 4'b1011 | 0 | 0 | 0 | All agree on clean mismatch |
4'b1X10 == 4'b1010 | X | 0 | 1 | == propagates X; === rejects; ==? masks |
4'bXXXX == 4'b0000 | X | 0 | 1 | ==? masks all bits — matches anything |
4'bXXXX === 4'bXXXX | X | 1 | 1 | === requires exact X match; ==? masks |
4'b1010 ==? 4'b1X1X | — | — | 1 | Bits 3,1 match; bits 2,0 masked by X in pattern |
4'b1000 ==? 4'b1X1X | — | — | 0 | Bit 1: val=0, pattern=X (masked), bit 2: val=0, pattern=1 → mismatch |
Code Examples — Pattern Matching to Protocol Checking
Example 1 — Beginner: All Equality Operators Compared
module tb_equality_compare;
logic [3:0] clean = 4'b1010;
logic [3:0] dirty = 4'b1X10; // bit2 is X
logic [3:0] pattn = 4'b1X1X; // wildcard pattern: care about bits 3,1 only
initial begin
$display("--- Clean vs clean ---");
$display("1010 == 1010 : %0b", clean == clean); // 1
$display("1010 === 1010 : %0b", clean === clean); // 1
$display("1010 ==? 1010 : %0b", clean ==? clean); // 1
$display("--- Dirty (1X10) vs clean (1010) ---");
$display("1X10 == 1010 : %0b", dirty == clean); // X — X propagates
$display("1X10 === 1010 : %0b", dirty === clean); // 0 — X != 0 literally
$display("1X10 ==? 1010 : %0b", dirty ==? clean); // 1 — X in dirty masks bit2
$display("--- Pattern (1X1X) matching ---");
$display("1010 ==? 1X1X : %0b", clean ==? pattn); // 1 — bit3=1✓ bit1=1✓
$display("1000 ==? 1X1X : %0b", 4'b1000 ==? pattn); // 0 — bit1: 0≠1
$display("1110 ==? 1X1X : %0b", 4'b1110 ==? pattn); // 1 — bit3=1✓ bit1=1✓
$display("--- Detecting X with === ---");
$display("1X10 === 1X10 : %0b", dirty === dirty); // 1 — X matches X exactly
$display("1X10 !== 1010 : %0b", dirty !== clean); // 1 — they are not case-equal
$finish;
end
endmoduleExpected output:
--- Clean vs clean ---
1010 == 1010 : 1
1010 === 1010 : 1
1010 ==? 1010 : 1
--- Dirty (1X10) vs clean (1010) ---
1X10 == 1010 : x
1X10 === 1010 : 0
1X10 ==? 1010 : 1
--- Pattern (1X1X) matching ---
1010 ==? 1X1X : 1
1000 ==? 1X1X : 0
1110 ==? 1X1X : 1
--- Detecting X with === ---
1X10 === 1X10 : 1
1X10 !== 1010 : 1Example 2 — Intermediate: Instruction Class Decoder with ==?
// 8-bit opcode classification by bit pattern:
// ALU: 1010_XXXX (bit7:4 = 1010, lower nibble = don't-care)
// LOAD/STORE: 110X_XXXX (bit7:5 = 110, bit4 = R/W, lower = don't-care)
// BRANCH: 0001_XXXX
// NOP: 0000_0000
module tb_pattern_decoder;
function automatic string classify(input logic [7:0] op);
if (op ==? 8'b1010_XXXX) return "ALU";
else if (op ==? 8'b110X_XXXX) return "LD/ST";
else if (op ==? 8'b0001_XXXX) return "BRANCH";
else if (op ==? 8'b0000_0000) return "NOP";
else return "ILLEGAL";
endfunction
initial begin
$display("0xA0 → %s", classify(8'hA0)); // ALU (1010_0000)
$display("0xAF → %s", classify(8'hAF)); // ALU (1010_1111)
$display("0xC5 → %s", classify(8'hC5)); // LD/ST (1100_0101)
$display("0xD3 → %s", classify(8'hD3)); // LD/ST (1101_0011)
$display("0x17 → %s", classify(8'h17)); // BRANCH(0001_0111)
$display("0x00 → %s", classify(8'h00)); // NOP
$display("0x55 → %s", classify(8'h55)); // ILLEGAL
$finish;
end
endmoduleExpected output:
0xA0 → ALU
0xAF → ALU
0xC5 → LD/ST
0xD3 → LD/ST
0x17 → BRANCH
0x00 → NOP
0x55 → ILLEGALExample 3 — Verification: Scoreboard X Detection vs Pattern Match
module tb_scoreboard_operators;
logic [7:0] dut_out;
logic [7:0] expected;
task automatic check(input logic [7:0] got, exp);
// Step 1: ALWAYS detect X first using === before comparing values
// If DUT output has X bits, that is always a bug — flag it immediately
if (got === 8'hXX || got !=? 8'hXX == 0) // simpler: check for X
if (^got === 1'bX) // reduction XOR: X if any bit is X
$error("DUT output has X bits: %08b", got);
// Step 2: Compare known bits — use === for exact match
if (got !== exp)
$error("MISMATCH: got=0x%02h exp=0x%02h", got, exp);
else
$display("PASS: 0x%02h", got);
endtask
// Protocol checker: verify opcode class using ==?
function automatic void check_opcode_class(input logic [7:0] op);
// Use ==? when you only care about WHICH class the opcode belongs to
if (!(op ==? 8'b1010_XXXX || op ==? 8'b110X_XXXX ||
op ==? 8'b0001_XXXX || op == 8'h00))
$error("Illegal opcode received from DUT: 0x%02h", op);
endfunction
initial begin
// Normal pass
dut_out = 8'hAB; expected = 8'hAB;
check(dut_out, expected); // PASS
// X in output — always an error
dut_out = 8'hXX; expected = 8'hAB;
check(dut_out, expected); // ERROR: X bits detected
// Opcode class check
check_opcode_class(8'hA5); // ALU class: passes
check_opcode_class(8'h55); // ILLEGAL: error
$finish;
end
endmoduleExample 4 — Corner Case: ==? in casex and Priority Matching
module tb_casex_wildcard;
logic [3:0] op;
logic [1:0] class_out;
// casex uses ==? semantics — X in case item = don't-care
always_comb begin
casex (op)
4'b1XXX: class_out = 2'b11; // any op with bit3=1
4'b01XX: class_out = 2'b10; // bit3=0, bit2=1
4'b001X: class_out = 2'b01; // bit3=0, bit2=0, bit1=1
default: class_out = 2'b00;
endcase
end
// Equivalent using ==? chained if-else
function automatic logic [1:0] classify_wc(input logic [3:0] v);
if (v ==? 4'b1XXX) return 2'b11;
else if (v ==? 4'b01XX) return 2'b10;
else if (v ==? 4'b001X) return 2'b01;
else return 2'b00;
endfunction
initial begin
$display("4'b1010 → %02b (expect 11)", classify_wc(4'b1010)); // 11
$display("4'b0110 → %02b (expect 10)", classify_wc(4'b0110)); // 10
$display("4'b0011 → %02b (expect 01)", classify_wc(4'b0011)); // 01
$display("4'b0000 → %02b (expect 00)", classify_wc(4'b0000)); // 00
// Priority trap: 4'b1100 matches BOTH 4'b1XXX and 4'b01XX patterns
// casex/if-else takes the FIRST match — this is priority encoding
// 4'b1100: bit3=1 → matches 4'b1XXX first → class_out = 2'b11
$display("4'b1100 → %02b (expect 11 — first match wins)",
classify_wc(4'b1100)); // 11, NOT 10
$finish;
end
endmoduleSimulation Behavior and Synthesis Notes
==? Always Returns 0 or 1 — Never X
This is the defining property that makes ==? useful in checking logic. Because X/Z bits are masked as don't-care matches, the operator can never produce an X result — every comparison resolves to clean 0 or 1. This means you can use it in if conditions without worrying about X-triggered conditional X-propagation (the trap that makes ordinary == dangerous during reset and initialization phases).
==? vs casex/casez — Relation
| Construct | Don't-care character | Which operand | Notes |
|---|---|---|---|
==? | X or Z | Either operand | Explicit wildcard equality operator |
casex | X or Z | Either expression or case item | Equivalent to ==? per case item |
casez | Z only (? also accepted) | Either expression or case item | X is NOT a don't-care — stricter than casex |
unique casex | X or Z | Either | Adds uniqueness check — tool warns on overlapping patterns |
Synthesis
==? with a constant pattern synthesizes to a masked comparator — only the non-X/Z bit positions generate comparison logic. The X/Z positions are literally removed from the comparison tree by the synthesizer. A pattern like 8'b1010_XXXX generates a 4-bit comparator on bits [7:4] only, and the lower nibble produces no gates at all. This is more efficient than writing (op >> 4) == 4'b1010 and is the preferred form for instruction decode logic.
Where You'll Use This in Real Projects
// ── 1. MONITOR: classify received AXI transaction ─────────────────
function automatic string axi_op_type(input logic [7:0] awid);
if (awid ==? 8'b0000_XXXX) return "MGMT";
else if (awid ==? 8'b0001_XXXX) return "DATA";
else if (awid ==? 8'b1XXX_XXXX) return "DMA";
else return "UNKNOWN";
endfunction
// ── 2. SCOREBOARD: detect X in DUT output (use ===, not ==?) ──────
function automatic void assert_no_x(input logic [31:0] data, input string sig);
if (^data === 1'bX) // XOR reduction: 1'bX if any bit is X
$error("%s contains X bits: %08h", sig, data);
endfunction
// ── 3. SVA ASSERTION: pattern-based property ──────────────────────
// assert property (@(posedge clk)
// cmd_valid |-> cmd_opcode ==? 8'b1010_XXXX || cmd_opcode ==? 8'b0001_XXXX);
// ── 4. PROTOCOL CHECKER: bus mask verification ─────────────────────
// AHB: HSIZE must be 3'b000, 3'b001, or 3'b010 — upper bits always 0
function automatic void check_hsize(input logic [2:0] hsize);
if (!(hsize inside {3'b000, 3'b001, 3'b010}))
$error("Illegal HSIZE: 3'b%03b", hsize);
endfunction
// ── 5. COVERAGE: sample by pattern class ──────────────────────────
// covergroup cg_opcode;
// cp_class: coverpoint {
// (opcode ==? 8'b1010_XXXX), // ALU
// (opcode ==? 8'b110X_XXXX) // LD/ST
// }
// endgroup
// ── 6. DRIVER: choose payload based on address pattern ────────────
logic [31:0] addr;
logic [31:0] write_data;
// Peripheral region: addr[31:28] = 4'hA
write_data = (addr ==? 32'hAXXX_XXXX) ? 32'h0000_0001 // reg init value
: 32'hCAFE_BABE; // memory fillBugs Engineers Actually Hit
Bug 1 — Using == Instead of === to Detect X: Silent Pass
logic [7:0] dut_out = 8'hXX; // DUT output corrupted with X
// BUGGY: == with X on left operand → result is X, not 0 or 1
// if (dut_out == 8'hXX) evaluates to X — the if-body never runs!
if (dut_out == 8'hXX)
$error("X detected"); // NEVER FIRES — condition is X, treated as false
// ALSO BUGGY: != doesn't detect X either
if (dut_out != 8'hAB)
$error("Mismatch"); // also X → doesn't fire! Bug passes silently
// CORRECT: use === (case equality) to detect X
if (dut_out === 8'hXX)
$error("X detected"); // FIRES correctly
// CORRECT: use !== (case inequality) for mismatch check
if (dut_out !== 8'hAB)
$error("Mismatch"); // FIRES correctly — X !== AB is true
// BEST PRACTICE: always use !== in scoreboard comparisons
// This catches both value mismatches AND unexpected X/Z valuesBug 2 — ==? Masking an Unexpected X in the Value
logic [7:0] opcode = 8'bXXXX_1010; // upper nibble is X!
// BUGGY: engineer checks if opcode is an ALU instruction
// But upper nibble of opcode is X, not 1010
if (opcode ==? 8'b1010_XXXX)
$display("ALU instruction"); // FIRES — but upper nibble is unknown!
// X in opcode[7:4] + X in pattern[3:0] = ALL bits masked = always matches!
// The engineer doesn't notice the DUT sent garbage in the upper nibble
// CORRECT: first verify no X in the value, THEN pattern-match
if (^opcode === 1'bX) // reduction XOR detects any X bit
$error("Opcode has X bits");
else if (opcode ==? 8'b1010_XXXX)
$display("ALU instruction"); // now safe to pattern-matchBug 3 — casex Overlapping Patterns: Wrong Priority
logic [3:0] op = 4'b1100;
logic [1:0] result;
// BUGGY: both 4'b1XXX and 4'b11XX match op=1100
// casex picks the FIRST matching arm — order matters!
casex (op)
4'b1XXX: result = 2'b01; // matches 4'b1100 FIRST
4'b11XX: result = 2'b10; // never reached for op=1100
default: result = 2'b00;
endcase
// result = 2'b01 — engineer expected 2'b10 for the more specific pattern
// CORRECT: put more specific patterns FIRST
casex (op)
4'b11XX: result = 2'b10; // more specific — check first
4'b1XXX: result = 2'b01; // less specific — fallthrough
default: result = 2'b00;
endcase
// result = 2'b10 — correct
// BETTER: use unique casex to get a warning if patterns overlap
// unique casex (op) — tool reports overlap if any two patterns can both matchBug 4 — Using ==? in Constraint Instead of ==
class txn;
rand logic [7:0] opcode;
// INTENT: constrain opcode to ALU class (upper nibble = 4'b1010)
// BUGGY: ==? is NOT valid in a constraint expression
// Constraint solver works on 2-state values — no X/Z
// Most simulators will error or ignore this
// constraint alu_c { opcode ==? 8'b1010_XXXX; } // ILLEGAL / undefined
// CORRECT option 1: constrain the specific bits directly
constraint alu_c { opcode[7:4] == 4'b1010; }
// Solver picks any opcode where upper nibble is exactly 1010
// CORRECT option 2: use inside with explicit values
// constraint alu_c { opcode inside {[8'hA0:8'hAF]}; }
endclassInterview Questions
Beginner Level
Q1: What is the difference between ==, ===, and ==? when one operand has X bits?== propagates X — the result is X, which makes any if condition false. === treats X as a literal value — A === X_val is 1 only if A also has X in the same positions, 0 otherwise. Always returns 0 or 1. ==? treats X/Z as don't-cares — any bit position where either operand has X/Z is automatically considered a match. Always returns 0 or 1. Q2: Can ==? ever return X? No. ==? always returns a clean 0 or 1. X and Z bits in either operand are masked as don't-cares rather than propagated. This is its key advantage over == in comparison logic during simulation phases where signals may be partially unknown.
Intermediate Level
Q3: What is the result of 4'bXXXX ==? 4'b0000? 1. All four bits of the left operand are X — so all four bit positions are masked as don't-cares. With no non-masked bits to compare, all positions match → result is 1. This means any signal that is completely unknown will wildcard-match any pattern. This is why you should check for X using === before applying ==? pattern matching. Q4: A scoreboard uses if (got != expected) to detect mismatches. Why is this dangerous? If got contains X bits, got != expected evaluates to X (logical inequality propagates X from operands). In an if statement, X is treated as false — so the mismatch error block never executes. The DUT can output X values and the scoreboard silently passes them all. Use !== (case inequality) instead — it treats X as a literal value and returns 1 if got and expected differ in any bit including X/Z positions.
Experienced Engineer Level
Q5: In casex, X in the expression being switched (not the case items) also acts as a don't-care. How can this cause silent bugs, and what is the safe alternative? If the switched expression has X bits — common during reset before registers are initialized — those X positions match every case item equally. The first matching arm executes, potentially executing the wrong branch entirely. The DUT may appear to function correctly post-reset but was actually triggering the wrong state during initialization. The safe alternative: use an explicit if-else chain with ==? expressions on clean (registered) values, or use casez which only treats Z (not X) as don't-care. Best practice: for RTL, avoid casex entirely and use unique case with known-clean inputs from registers. For testbench, use if-else with ==?.
Best Practices & Coding Guidelines
- Use !== in all scoreboard comparisons — Never use != in a scoreboard. It silently passes X values. !== catches both value mismatches and unexpected X/Z — always.
- Check for X before ==? matching — Use ^val === 1'bX (XOR reduction) to detect X bits before applying ==? pattern matching. X in the left operand masks positions silently.
- Prefer ==? over casex in testbench — Explicit if-else with ==? is more readable and avoids the casex trap where X in the switched expression causes unintended matches.
- More specific patterns first — In casex and ==? chains, always put the most specific (fewest X bits) pattern first. Less specific patterns are fall-through matches.
| Task | Right operator | Wrong operator & why |
|---|---|---|
| Detect X/Z in a signal | === 'X or ^val === 1'bX | == 'X — returns X, not 1 |
| Scoreboard mismatch check | !== | != — silent on X output |
| Opcode class / bit-pattern match | ==? with X-mask pattern | == — need full bit match, can't mask |
| Exact X/Z position match | === | ==? — masks X, won't detect it |
| Constraint: restrict to bit pattern class | Field slice equality: val[7:4] == 4'b1010 | val ==? 8'b1010_XXXX — not valid in constraints |
Summary
==? and !=? fill a specific gap in SystemVerilog's equality toolkit. They always return 0 or 1, they enable concise bit-pattern matching without cascading bit slices, and they are the mechanism behind both inside and casex. Understanding when to use them — and when === is the right choice instead — is what separates engineers who write solid scoreboards from those who ship bugs that pass silently.
==?always returns 0 or 1. X/Z in either operand is a don't-care for that bit. The result never propagates X.- Use
===to detect X, use==?to ignore specific bits. These are opposite intents and opposite tools. - Every scoreboard comparison should use
!==. It catches X values in DUT output.!=lets them through silently — arguably the most common verification correctness bug. - Check for X before pattern-matching with
==?. A fully-X value matches any pattern. Validate cleanliness first with reduction XOR. - More specific patterns first in
casexchains. Priority is top-to-bottom — a broad wildcard first will shadow every specific pattern below it.