Skip to content

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.

OperatorNameX/Z in operandResult typePrimary use
==Logical equalityPropagates X → result can be X0, 1, or XGeneral comparisons
!=Logical inequalityPropagates X → result can be X0, 1, or XGeneral comparisons
===Case equalityX and Z are literal values — must match exactly0 or 1 onlyX/Z detection in testbench
!==Case inequalityX and Z are literal values0 or 1 onlyX/Z detection in testbench
==?Wildcard equalityX/Z in either operand = don't-care bit0 or 1 onlyPattern/mask matching
!=?Wildcard inequalityX/Z in either operand = don't-care bit0 or 1 onlyPattern/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

SystemVerilog — Wildcard Equality Syntax
// 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 */
endcase

Bit-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 bitR bit== result=== result==? result
00111
01000
10000
11111
X0X01 (don't-care)
X1X01 (don't-care)
XXX1 (exact match)1 (don't-care)
0XX01 (don't-care)
1XX01 (don't-care)
Z0X01 (don't-care)
ZZX1 (exact match)1 (don't-care)

Multi-Bit Pattern Match — Step by Step

cmd = 8'b1010_0011, pattern = 8'b1010_XXXX

Bitcmd bitPattern bit==? for this bitReason
[7]11MatchBoth known, equal
[6]00MatchBoth known, equal
[5]11MatchBoth known, equal
[4]00MatchBoth known, equal
[3]0XMatch (don't-care)Pattern has X → ignore
[2]0XMatch (don't-care)Pattern has X → ignore
[1]1XMatch (don't-care)Pattern has X → ignore
[0]1XMatch (don't-care)Pattern has X → ignore
Overall ==? result1 — all bits match or are masked

All Four Operators on the Same Values — Side by Side

Expression=======?Key takeaway
4'b1010 == 4'b1010111All agree on clean match
4'b1010 == 4'b1011000All agree on clean mismatch
4'b1X10 == 4'b1010X01== propagates X; === rejects; ==? masks
4'bXXXX == 4'b0000X01==? masks all bits — matches anything
4'bXXXX === 4'bXXXXX11=== requires exact X match; ==? masks
4'b1010 ==? 4'b1X1X1Bits 3,1 match; bits 2,0 masked by X in pattern
4'b1000 ==? 4'b1X1X0Bit 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

Example 1 — ==, ===, ==? Side by Side
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
 
endmodule

Expected output:

Simulation 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 : 1

Example 2 — Intermediate: Instruction Class Decoder with ==?

Example 2 — Opcode Pattern Matching
// 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
 
endmodule

Expected output:

Simulation Output
0xA0 → ALU
0xAF → ALU
0xC5 → LD/ST
0xD3 → LD/ST
0x17 → BRANCH
0x00 → NOP
0x55 → ILLEGAL

Example 3 — Verification: Scoreboard X Detection vs Pattern Match

Example 3 — Scoreboard: When to Use === vs ==?
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
 
endmodule

Example 4 — Corner Case: ==? in casex and Priority Matching

Example 4 — casex vs ==? and Priority Traps
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
 
endmodule

Simulation 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

ConstructDon't-care characterWhich operandNotes
==?X or ZEither operandExplicit wildcard equality operator
casexX or ZEither expression or case itemEquivalent to ==? per case item
casezZ only (? also accepted)Either expression or case itemX is NOT a don't-care — stricter than casex
unique casexX or ZEitherAdds 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

Real Verification Patterns
// ── 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 fill

Bugs Engineers Actually Hit

Bug 1 — Using == Instead of === to Detect X: Silent Pass

Bug 1 — Scoreboard Uses == to Check for X
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 values

Bug 2 — ==? Masking an Unexpected X in the Value

Bug 2 — ==? Hides X in Left Operand
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-match

Bug 3 — casex Overlapping Patterns: Wrong Priority

Bug 3 — Overlapping casex Patterns
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 match

Bug 4 — Using ==? in Constraint Instead of ==

Bug 4 — ==? in Constraint Context
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]}; }
 
endclass

Interview 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.
TaskRight operatorWrong 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 classField slice equality: val[7:4] == 4'b1010val ==? 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 casex chains. Priority is top-to-bottom — a broad wildcard first will shadow every specific pattern below it.