Skip to content

SystemVerilog · Module 4

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 the right operand = don't-care bit. X/Z in the left operand is a literal value0 or 1 onlyPattern/mask matching
!=?Wildcard inequalitySame asymmetric rule — right operand supplies the wildcards0 or 1 onlyPattern/mask matching

==? always returns a clean 0 or 1. It does this by treating X and Z in the right operand as don't-care bits that match anything in the left. 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 one question: "is the RIGHT operand's bit an X or Z here?" If yes, that position is a wildcard and counts as equal regardless of what the left operand holds — including if the left operand's bit is itself X or Z. If the right operand's bit is 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 every bit position where the right operand is known agrees with the left. X/Z in the right operand masks that position; X/Z in the left is a literal value. Always returns 0 or 1.

!=? Wildcard Inequality

Logical inverse of ==?. Returns 1 when the operands do NOT wildcard-match. Same asymmetric rule — the right operand supplies the wildcards.

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Syntax: value ==? pattern           <-- ORDER MATTERS
// Returns: 1-bit - always 0 or 1, NEVER X
// Rule: for each bit - if the RIGHT operand's bit is X or Z, that position
//       is a wildcard and matches anything on the left. Otherwise the bits
//       must match exactly, with X and Z on the LEFT treated as literal
//       values (as `===` does), not as don't-cares.
 
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 is NOT a wildcard ──────────────────────
//    This is where the symmetric intuition fails. The pattern on the
//    right is fully known, so nothing is masked, and the X on the left
//    is matched literally - exactly as === would match it.
logic [3:0] val = 4'bx101;
val ==? 4'b0101   // 0 - bit3: left X vs right 0 -> literal mismatch
val ==? 4'b1101   // 0 - bit3: left X vs right 1 -> literal mismatch
val ==? 4'b0100   // 0 - bit0 also mismatches on a known bit
val ==? 4'bx101   // 1 - NOW bit3 is a wildcard: it is on the RIGHT
 
// ── Swapping the operands changes the answer ─────────────────────
4'b0101 ==? val   // 1 - val is now the pattern; its X masks bit 3
val ==? 4'b0101   // 0 - val is now the value; its X is literal
//                       Same two values. Opposite results.
 
// ── Use in if statement ───────────────────────────────────────────
if (opcode ==? 8'b1010_XXXX)
  $display("ALU class opcode");
 
// ── casex is NOT the same as ==?  It is SYMMETRIC: an X in the
//    expression `opcode` is also a don't-care and can match the
//    wrong item. Prefer casez with `?`, or an if-else on ==?.
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--- 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 ==?
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

Proving It — The Asymmetry, and the Three-Operator Matrix

Everything above rests on one claim: that ==? wildcards its right operand and not its left. That claim is directly testable, and the test takes ten seconds.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// wildcard_equality_proof.sv
//
// Self-checking proof that ==? is ASYMMETRIC, plus the canonical
// ==  /  ===  /  ==?  matrix on shared operands.
module wildcard_equality_proof;
 
  int errors = 0;
 
  task automatic chk(string name, logic got, logic exp);
    if (got !== exp) begin
      errors++;
      $display("FAIL %-52s got=%b exp=%b", name, got, exp);
    end else
      $display("pass %-52s = %b", name, got);
  endtask
 
  initial begin
    logic [3:0] val = 4'bx101;      // an X in the DATA
    logic [3:0] pat = 4'bx101;      // the same bits, used as a PATTERN
 
    // ================================================================
    // 1. THE ASYMMETRY - the whole point of the page
    //    Identical bit patterns. Opposite results. Operand order decides.
    // ================================================================
    chk("val ==? 4'b0101   (left X is LITERAL -> mismatch)",
        val ==? 4'b0101, 1'b0);
    chk("4'b0101 ==? pat   (right X is WILDCARD -> match)",
        4'b0101 ==? pat, 1'b1);
 
    // With no wildcards on the right, ==? degenerates to ===:
    chk("4'bxxxx ==? 4'b0000  (right known -> like ===)",
        4'bxxxx ==? 4'b0000, 1'b0);
    chk("4'b0000 ==? 4'bxxxx  (right all wildcards)",
        4'b0000 ==? 4'bxxxx, 1'b1);
    chk("4'bxxxx ==? 4'b0000  equals  4'bxxxx === 4'b0000",
        (4'bxxxx ==? 4'b0000), (4'bxxxx === 4'b0000));
 
    // A wildcard matches ANY left value, including X and Z:
    chk("4'bxxxx ==? 4'bxxxx  (wildcards match X too)",
        4'bxxxx ==? 4'bxxxx, 1'b1);
    chk("4'bzzzz ==? 4'bxxxx  (wildcards match Z too)",
        4'bzzzz ==? 4'bxxxx, 1'b1);
 
    // ================================================================
    // 2. THE CANONICAL MATRIX - one pair of operands, three operators
    //    a = 8'b1010_01xz   (X and Z in the DATA)
    //    b = 8'b1010_01xz   (the identical literal, as a PATTERN)
    // ================================================================
    begin
      logic [7:0] a = 8'b1010_01xz;
      logic       r;
 
      r = (a ==  8'b1010_01xz); chk("a ==  same-bits  -> X (propagates)", r, 1'bx);
      r = (a === 8'b1010_01xz); chk("a === same-bits  -> 1 (literal match)", r, 1'b1);
      r = (a ==? 8'b1010_01xz); chk("a ==? same-bits  -> 1 (right wildcards)", r, 1'b1);
 
      // Now compare against a fully-KNOWN value. The X/Z are on the left
      // only, so ==? has nothing to wildcard and behaves like ===.
      r = (a ==  8'b1010_0100); chk("a ==  known      -> X", r, 1'bx);
      r = (a === 8'b1010_0100); chk("a === known      -> 0", r, 1'b0);
      r = (a ==? 8'b1010_0100); chk("a ==? known      -> 0  (NOT masked!)", r, 1'b0);
 
      // And a pattern that deliberately wildcards the low nibble:
      r = (a ==? 8'b1010_xxxx); chk("a ==? 8'b1010_xxxx -> 1 (masked)", r, 1'b1);
    end
 
    // ================================================================
    // 3. THE CHECKER CONSEQUENCE
    //    ==? does NOT hide an X in the value under test.
    // ================================================================
    begin
      logic [7:0] dut_out  = 8'hxx;
      logic [7:0] expected = 8'hA5;
 
      chk("dut_out ==? expected  -> 0  (X is caught)",
          dut_out ==? expected, 1'b0);
      chk("expected ==? dut_out  -> 1  (X MASKS - operands reversed!)",
          expected ==? dut_out, 1'b1);
      // ^ The second line is the bug in Debug Lab 5. Same two signals,
      //   written the other way round, and the checker stops working.
    end
 
    if (errors == 0) $display("\nwildcard_equality_proof: ALL CHECKS PASSED");
    else             $display("\nwildcard_equality_proof: %0d FAILURES", errors);
    $finish;
  end
endmodule

The matrix, as a table

One pair of operands, a = 8'b1010_01xz, compared three ways. This is the mental model worth memorising; everything else on this page follows from it.

Right operanda == ra === ra ==? rWhy ==? behaves that way
8'b1010_01xz (identical)X11The right operand's X and Z are wildcards; the rest matches
8'b1010_0100 (fully known)X00Nothing to wildcard — ==? reduces to ===, and X ≠ 0
8'b1010_xxxx (masked nibble)X01Low nibble wildcarded; high nibble matches exactly

Read the middle row twice. ==? returns 0, not 1, when the value under test carries X and the pattern does not. That is the row the symmetric reading gets wrong, and it is the row that decides whether a checker works.

For each bit position, wildcard equality first inspects the right operand: an X or Z there makes the position a wildcard matching anything; otherwise both bits are compared literally as case equality does, so an X on the left is matched as a value.One bit positionleft bit, right bitIs the RIGHT bit X orZ?the only question askedYes: wildcardmatches 0, 1, X or ZNo: literal 4-statecomparesame rule as ===Left X here fails tomatchit is a value, not a mask12
Figure 1 - the asymmetry, one bit position at a time. The right operand is inspected first: if its bit is X or Z the position is a wildcard and matches whatever the left operand holds, including X or Z. Otherwise both bits are compared as literal four-state values exactly as case equality does, so an X on the left is a value to be matched rather than a don't-care. Neither path can yield X, which is why the operator always returns a clean 0 or 1.
5

A scoreboard written with the operands reversed passed on every X the DUT produced

WILDCARD-ON-THE-WRONG-SIDE
Buggy Code & Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ BUG: the operands are the wrong way round. The author believed ==?
//         was symmetric, so the order "did not matter".
class packet_scoreboard extends uvm_scoreboard;
 
  function void compare(bit [63:0] got, bit [63:0] exp);
    // Reads naturally as "expected matches got". It does not mean that.
    if (EXP_MASK ==? got)                    // ❌ got is the RIGHT operand
      `uvm_info("SB", "payload matched", UVM_HIGH)
    else
      `uvm_error("SB", $sformatf("payload mismatch: got %0h exp %0h", got, exp))
  endfunction
 
endclass
 
// Every X bit in `got` is now a WILDCARD, because got is on the right.
// A fully-X payload wildcards all 64 positions and matches anything:
//   EXP_MASK ==? 64'hxxxx_xxxx_xxxx_xxxx   ->  1   (silent pass)
 
// ✅ FIX: data on the left, pattern on the right. Then an X in the data
//         is a literal value and fails to match, as it should.
function void compare(bit [63:0] got, bit [63:0] exp);
  if (got ==? EXP_MASK)                      // ✅ pattern on the right
    `uvm_info("SB", "payload matched", UVM_HIGH)
  else
    `uvm_error("SB", $sformatf("payload mismatch: got %0h exp %0h", got, exp))
endfunction
Symptom

A payload scoreboard reported clean for six weeks. A silicon bring-up team then found that one DMA descriptor path was returning an all-X payload in simulation whenever a particular back-pressure sequence occurred — and the scoreboard had recorded every one of those transactions as a match.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
transaction 41,882
  got      = 64'hxxxx_xxxx_xxxx_xxxx      <-- DUT drove nothing
  EXP_MASK = 64'hFFFF_FFFF_0000_0000
  scoreboard verdict: MATCH

The scoreboard's own coverage showed the comparison executing four million times with a 100% match rate — which nobody questioned, because a 100% match rate is what a passing scoreboard looks like.

Root Cause

==? is asymmetric, and the operands were reversed.

IEEE 1800 wildcards X and Z in the right operand only. Writing EXP_MASK ==? got puts the DUT's data on the wildcarding side, so every X bit the DUT produces becomes a don't-care that matches whatever the expected value holds. A fully-X payload wildcards all sixty-four positions and matches unconditionally.

Written the other way — got ==? EXP_MASK — the DUT's X bits sit on the left, where they are literal four-state values compared exactly as === compares them. x against 1 is a mismatch, the check fails, and the bug is reported on the first transaction.

Three things made this survive six weeks, and each is worth recognising separately.

The wrong form reads more naturally in English. "Expected matches got" is how the sentence forms in the head, and it produces the operand order that breaks the check. The correct order reads as "got matches the pattern", which is a slightly less natural sentence and the correct comparison.

It only differs on X. For every clean transaction the two orders give identical results, so the check was genuinely working for the 99.9% of traffic that was healthy. It failed exactly on the transactions that mattered.

The failure is a silent pass, and the metric that would reveal it looks like success. A comparison that always matches produces a 100% match rate. There is no error to triage, no mismatch to investigate, and the coverage report says the check ran.

The belief underneath all of it — that ==? treats X in either operand as a don't-care — is widespread enough to be worth stating as a correction rather than a nuance. It is not what the standard says, and the sibling inside chapter has always documented the asymmetry correctly.

Fix

Put the data on the left and the pattern on the right, then add the two guards that make the class of error detectable rather than reliant on remembering the rule.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 1. The fix: data left, pattern right.
if (got ==? EXP_MASK)
 
// 2. Screen for X explicitly, and report it as its own failure rather
//    than folding it into a value mismatch. An all-X payload is a
//    different bug from a wrong payload, and deserves a different message.
if ($isunknown(got)) begin
  `uvm_error("SB", $sformatf("DUT drove X/Z on payload: %0h", got))
end else if (got ==? EXP_MASK) begin
  ...
end
 
// 3. Cover the mismatch outcome. A check whose failing branch has never
//    been taken has never been shown to discriminate - whatever the cause.
covergroup cg_compare;
  cp_verdict : coverpoint verdict {
    bins matched  = {1'b1};
    bins mismatch = {1'b0};   // zero hits after a full regression means
  }                           // this comparison never said no
endgroup

Three habits, and the third is the one that generalises past this operator:

  • Data on the left, pattern on the right — always, with ==? and with inside. The set members of an inside are the right operand of an implied ==? and inherit the same asymmetry.
  • Never put runtime data on the wildcarding side. If the right operand of a ==? is a signal rather than a literal, its X bits are wildcards you did not write. The right operand should be a constant or a parameter that a reader can see.
  • A check should get stricter when data is unknown, never looser. That single principle rules out != for scoreboard comparisons (it returns X and takes the else), rules out casex on an expression that can carry X, and rules out this operand order. The same question decides ?: against if/else for a mux — see Concatenation, Replication & Conditional. When choosing a comparison operator, ask what it does on an all-X input; if the answer is "passes", choose something else.

Simulation Behavior and Synthesis Notes

==? Always Returns 0 or 1 — Never X

==? never returns X, and the reason is worth stating precisely because it is not "X bits are masked". It is that the comparison is built on case equality: for every bit position, either the right operand is a wildcard (that position matches, unconditionally) or both bits are compared as literal 4-state values, exactly as === compares them. Neither path can produce X, so the result is always a clean 0 or 1.

That makes ==? safe in an if condition, where ordinary == is not — an == that returns X takes the else branch and silently disables a check. But note what it does not buy you: because an X in the left operand is matched literally rather than masked, dut_out ==? PATTERN fails when the DUT drives X. That is the behaviour you want in a checker, and it is the opposite of what the symmetric reading predicts.

==? vs casex/casez — Relation

ConstructDon't-care characterWhere the don't-care may appearSymmetric?Notes
==?X or ZRight operand onlyNoX/Z on the left is a literal value, matched as === does
!=?X or ZRight operand onlyNoLogical inverse of ==?, same asymmetry
casexX or ZBoth the case expression and the case itemsYesNot equivalent to ==? — an X in the expression is also a don't-care
casezZ and ? onlyBoth expression and case itemsYesX is not a don't-care — stricter than casex, and the safer of the two
inside {…}X or ZSet members only (they are the right operand of an implied ==?)NoInherits ==?'s asymmetry — see inside
===nonen/aX and Z are literal values everywhere; the reference point for all of the above

The row that is most often stated wrongly is casex versus ==?. They are not equivalent. casex is symmetric — an X arriving on the case expression during reset is a don't-care and can match a case item it has nothing to do with. ==? is asymmetric, so the same X on the left is matched literally and simply fails to match. ==? is the safer of the two, and that is the opposite of the usual assumption.

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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

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, and 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, because every bit position either matches unconditionally (right operand is a wildcard) or is compared as a literal 4-state value, as === does. Neither path yields X.
  • ==? is asymmetric. X/Z in the right operand is a wildcard; X/Z in the left is a literal value. a ==? b and b ==? a are different comparisons. Put the pattern on the right, always.
  • 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.

The rest of the equality family. Relational & Equality Operators owns ==, !=, ===, !== and the relational operators — including why != returning X silently disables a scoreboard check. This page owns only ==? and !=?, and the matrix above is the bridge between them.

The constructs that look like ==? and are not. case, casex & casezcasex is symmetric where ==? is asymmetric, which makes casex the more dangerous of the two, not the equivalent. inside compares each set member with an implied ==?, with the member as the right operand, so it inherits this page's asymmetry exactly.

Where the X values come from. 2-state vs 4-state Types for what X and Z mean and where they originate; Logical Operators for why an X condition takes the else branch, which is the mechanism behind the != scoreboard trap. For where ==? sits among the other operators — at equality level, above the bitwise operators — see Operator Precedence.

References.

  • IEEE 1800 (SystemVerilog) — the wildcard equality operators are defined in the operators-and-expressions clause. The definition is explicit and is the basis for every correction on this page: X and Z values in a given bit position of the right operand are treated as wildcards, and a wildcard bit matches any value — 0, 1, X or Z — in the corresponding bit of the left operand. Nothing in the definition wildcards the left operand. The casex and casez don't-care rules, which are symmetric across the case expression and the case items, are in the case-statement clause.
  • IEEE 1364 (Verilog)casex and casez originate here; ==? and !=? were introduced in SystemVerilog.

Requirement versus practice. The asymmetry, the never-X result, and the casex/casez don't-care rules are language requirements. The conventions built on them — data on the left and pattern on the right, patterns as literals rather than signals, and the principle that a check should get stricter rather than looser on unknown data — are engineering practice. They are not optional in the sense that violating them produces the silent-pass failure in Debug Lab 5, but the language will compile either order without complaint.

Part of SystemVerilog Fundamentals·Operators & Expressions·Lesson 27 of 53

View program

Continue learning