Skip to content

Verilog · Chapter 10.8 · Operators & Operands

Equality Operators in Verilog — == != vs === !== (and the X Trap)

The equality operators ask whether two values are the same, but Verilog has two kinds, and the difference is one of the most misunderstood points in the language. Logical equality compares values the way you expect, with one catch. If either operand contains an unknown or high-impedance bit, the result is x, because the comparison genuinely cannot tell. Case equality compares the exact four-state bit pattern, matching unknown to unknown and high-impedance to itself, and always returns a clean 0 or 1. This has two big consequences. Logical equality is synthesizable and builds a comparator, while case equality is simulation-only because real hardware has no unknown state, and a mismatch check written with logical not-equal can silently miss an unknown output. This lesson drills all four operators, the logical-versus-case distinction, and where each belongs.

Foundation18 min readVerilogEquality OperatorsCase EqualityX PropagationVerificationRTL Design

Chapter 10 · Section 10.8 · Operators & Operands

1. The Engineering Problem

A self-checking testbench counts mismatches between the DUT's output and the expected value:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (actual != expected)          // mismatch → flag an error
    errors = errors + 1;

It catches wrong values correctly. But one day the DUT output is all x — an unreset register left it unknown, a real and serious bug. The scoreboard reports PASS, error count zero. The check that exists precisely to catch broken outputs let the worst kind through.

The reason is the operator. != is logical inequality, and when either operand contains an x bit, its result is x, not 1. So actual != expected with actual all-x evaluates to x — and in the if condition, x is treated as false, so no error is counted. The mismatch check is blind to unknowns.

The fix is case inequality, which compares the exact bits including x:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
if (actual !== expected)         // case inequality → definite 0 or 1
    errors = errors + 1;         // x != known → differs → 1 → error counted  ✓

!== returns a clean 1 when actual (unknown) differs from expected (known), so the x output is flagged.

Logical equality (==, !=) returns x when operands contain x/z — it cannot be sure — and x in a condition reads as false. Case equality (===, !==) compares the exact 4-state bits and always returns a definite 0 or 1. Using the wrong one lets a testbench silently pass an unknown output.

But there is a catch the other way: === and !== are simulation-only — they cannot be synthesized, because hardware has no x. This page teaches both kinds, when each is right, and the synthesizable line between them.

2. Mental Model — Two Kinds of Equality: Value vs Exact Bits

Visual A — logical vs case equality on unknown operands

Logical versus case equality with x bits4'b1x10 vs 4'b1x10operands with an x== → 1'bxuncertain (has x)=== → 1'b1exact bits match12
Comparing two operands that contain an x bit. Logical equality == returns x — it cannot tell whether the unknown bits are equal. Case equality === compares the exact 4-state pattern, sees the x positions match, and returns a definite 1. On all-known operands the two agree; they diverge exactly when x or z is involved.

3. The Hardware View — A Comparator vs a Simulation-Only Check

== builds an equality comparator: XNOR each bit pair (1 where the bits match), then AND-reduce ("all bits match?"). It is real, cheap hardware — the same XNOR/reduction logic from 10.4/10.5. This is why == is synthesizable and why a == b is effectively &(a ~^ b).

=== has no hardware. It asks whether bits are x or z, and those values do not exist in silicon — every real wire is 0 or 1. A synthesizer cannot build a circuit that detects "this bit is unknown," because unknown is a simulation concept (it models "we don't know" or "uninitialized"). So ===/!== are confined to simulation: testbenches, assertions, debug. Writing them in synthesizable RTL is rejected or warned by synthesis (and risks a simulation/synthesis mismatch). The hardware boundary is the deepest reason to keep === out of design code.

4. The Four Operators

equality-operators.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   a == b      // LOGICAL equality    — 1 if equal, 0 if not, x if either has x/z
   a != b      // LOGICAL inequality  — complement of ==  (x if either has x/z)
   a === b     // CASE equality       — exact 4-state match incl. x/z; always 0/1
   a !== b     // CASE inequality     — complement of ===; always 0/1
  • All four are binary, return a single bit, and zero-extend the narrower operand to match widths.
  • == / != are logical (value) equality — synthesizable, x-uncertain.
  • === / !== are case equality — exact-bit, always definite, simulation-only.
  • The mnemonic: the triple === is the "exact / case" one (more = = stricter, x-aware match); the double == is the everyday value comparison.

5. The X/Z Behaviour — Where They Diverge

On all-known operands, == and === give the same answer. They diverge exactly when an x or z is present:

equality-x-behaviour.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   4'b1010 == 4'b1010    // = 1     equal, no unknowns
   4'b1010 == 4'b1011    // = 0     not equal
   4'b1x10 == 4'b1x10    // = 1'bx  ⚠ has x → uncertain (NOT 1!)
   4'b1x10 == 4'b1010    // = 1'bx  uncertain
 
   4'b1x10 === 4'b1x10   // = 1     exact bits match (x matches x)
   4'b1x10 === 4'b1010   // = 0     x does NOT match 0 → not identical
   4'bxxxx === 4'bxxxx   // = 1     all-x matches all-x exactly

The rule: == returns x the moment either operand has any x/z bit — it refuses to claim equality it cannot verify. === always resolves, treating x and z as ordinary values to match exactly. This is why:

  • Detecting an unknown needs ===/!== (or $isunknown, §6). signal == 1'bx is always x (never 1), so it never fires — you cannot test for x with ==. signal === 1'bx correctly reports whether the bit is x.
  • A != mismatch check can miss an x output (the §1 bug): x != known is x, treated as false, so no mismatch is flagged. !== gives a definite 1.

Visual B — the synthesizable boundary

Which equality goes where

data flow
Which equality goes where== / != (logical)value compare · synthesizable→ design RTLbuilds a comparator=== / !== (case)exact bits · simulation-only→ testbench /assertionsx/z detection & exact compare
Logical equality (== / !=) is synthesizable and belongs in design RTL — it builds an equality comparator. Case equality (=== / !==) is simulation-only and belongs in testbenches and assertions, where comparing or detecting x/z exactly is needed. Crossing the line — === in RTL — is a synthesis error or a sim/synth-mismatch risk.

6. When to Use Each — Design vs Testbench

The synthesizable boundary maps cleanly onto where each operator belongs:

usage.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // SYNTHESIZABLE RTL — value comparison with ==
   assign match    = (addr == BASE);        // address decode
   if (state == IDLE)        ...            // FSM state compare
   if (count != 0)           ...            // nonzero check
 
   // TESTBENCH / ASSERTIONS — exact compare and x-detection with ===
   if (actual !== expected)  errors = errors + 1;   // catches x outputs (§1)
   if (out === 1'bx)         $error("output went X");
   if ($isunknown(bus))      $error("bus has unknown bits");  // any-x detector
  • In design RTL, use == / !=. You are comparing values that will be real 0s and 1s in silicon; the x-uncertainty never arises in hardware, and == synthesizes. Never use ===/!== here.
  • In testbenches and assertions, use === / !== when the comparison must be exact across x/z — self-checking scoreboards (so an x output is caught), and x/z detection. For "does this vector contain any unknown bit?", the idiomatic check is the $isunknown(...) system function, which is cleaner than constructing an all-x pattern.

This is the same design/testbench discipline from Chapter 9.4: synthesizable constructs in the DUT, simulation-only constructs (including ===) in the harness.

7. Width and Relationship to XOR-Reduction

Like the other comparison operators, equality zero-extends the narrower operand to the wider width before comparing, and returns one bit. And == connects directly to the bitwise/reduction operators of 10.4/10.5:

equality-as-reduction.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   (a == b)  is equivalent to  ~|(a ^ b)     // XOR → nonzero only where bits differ;
                                             // NOR-reduce → 1 only if NO bits differ
   (a != b)  is equivalent to   |(a ^ b)     // any differing bit → not equal

a ^ b is zero exactly where the operands match (10.4), so OR-reducing it (|(a^b), 10.5) gives "they differ" and NOR-reducing it gives "they are equal." This is the comparator == synthesizes to, and it ties the equality operator back to the bit-level operators that build it. (=== has no such reduction equivalent, because there is no synthesizable way to compare x bits.)

8. Worked Examples

8.1 Example 1 — value comparison in RTL (==)

rtl-equality.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
module decode (
    input  [15:0] addr,
    output        sel_rom,
    output        sel_ram
);
    assign sel_rom = (addr == 16'h0000);     // exact address match
    assign sel_ram = (addr != 16'h0000);     // everything else
endmodule

Synthesizable address decode using == / != — value comparison that builds an equality comparator. In real hardware addr is always known 0s and 1s, so the x-uncertainty of == never arises; this is exactly where == belongs. Using === here would be a synthesis error.

8.2 Example 2 — exact compare in a scoreboard (!==)

scoreboard.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // Self-checking testbench: catch ANY difference, including x outputs.
   task check(input [7:0] actual, input [7:0] expected);
       if (actual !== expected) begin      // case inequality → catches x
           $display("MISMATCH: actual=%h expected=%h", actual, expected);
           errors = errors + 1;
       end
   endtask

The §1 fix in working form: !== flags a mismatch whenever actual differs from expected including when actual is x — because x !== known is a definite 1. A != here would let an unknown output slip through as a non-mismatch. Scoreboards use case inequality precisely for this.

8.3 Example 3 — detecting an unknown (=== / $isunknown)

x-detect.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
   // In a testbench/assertion — flag an output that went unknown:
   if (dut_out === 8'hxx)                    // exact match against an all-x pattern
       $error("dut_out is fully unknown at t=%0t", $time);
 
   if ($isunknown(dut_out))                  // ANY bit unknown — the idiomatic check
       $error("dut_out has unknown bit(s) at t=%0t", $time);

You cannot detect an x with == (dut_out == 8'hxx is always x, never true). === against an x pattern, or the $isunknown system function for "any unknown bit," is how a testbench catches unreset or contended signals. All simulation-only — these never appear in synthesizable RTL.

9. Industry Perspective

  • Design RTL uses == / != exclusively. FSM state compares, address decodes, tag matches — all value comparisons that synthesize to equality comparators. ===/!== in synthesizable code is flagged by lint and rejected or warned by synthesis.
  • Scoreboards use === / !== for robustness. A self-checking testbench that compares with != can silently pass an x (unreset) output; mature verification uses !== so unknowns are always caught — a standard practice and a frequent code-review point.
  • X-detection guards are everywhere in verification. Assertions that fire on unknown control signals ($isunknown, === against x) catch reset and initialization bugs early. Hardware has no x, so these live only in the testbench/assertion layer.
  • The ==-vs-=== distinction is a top interview question. It tests whether a candidate understands 4-state simulation, the synthesizable boundary, and the silent-x-pass failure mode — all core verification literacy.

10. Common Mistakes

  1. === / !== in synthesizable RTL — simulation-only; synthesis rejects/warns and risks a sim/synth mismatch (§3, DebugLab 2).
  2. != mismatch check that misses an x outputx != known is x (false), so the error is never counted; use !== (§1, DebugLab 1).
  3. Detecting x with ==signal == 1'bx is always x, never true; use === or $isunknown (§5, DebugLab 3).
  4. Forgetting == returns x on unknown operands — and that x in a condition reads as false.
  5. Confusing the double and triple forms== is value (synthesizable), === is exact-bit (simulation-only).

11. Debugging Lab

Three equality-operator debug post-mortems

12. Interview Q&A

13. Exercises

Exercise 1 — Predict logical vs case

Give the result of each: (a) 4'b1100 == 4'b1100; (b) 4'b1100 == 4'b1101; (c) 4'b1x00 == 4'b1x00; (d) 4'b1x00 === 4'b1x00; (e) 4'b1x00 === 4'b1000.

Exercise 2 — Pick the operator

For each, choose ==, !=, ===, or !==: (a) synthesizable FSM state compare; (b) testbench scoreboard that must catch an x output; (c) address decode in RTL; (d) assertion checking a bus is exactly all-x.

Exercise 3 — Fix the checks

Each line states intent. Identify the bug and fix it.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
assign go = (state === IDLE);          // synthesizable RTL FSM compare
if (actual != expected) errors++;      // scoreboard must catch x outputs
if (sig == 1'bx) $error("unknown");    // detect an unknown signal

Exercise 4 — Reason about x

(a) Why does if (a != b) in a scoreboard miss an x in a, but if (a !== b) catch it? (b) Write two ways to detect that an 8-bit bus contains any unknown bit. (c) Why can't a synthesizer build ===?

14. Summary

The equality operators come in two kinds. Logical equality (==, !=) compares values and is synthesizable, but returns x on unknown operands. Case equality (===, !==) compares the exact 4-state bits, always returns a definite 0/1, and is simulation-only.

The core ideas:

  • == / != — value comparison, synthesizable — builds an equality comparator (a == b is ~|(a ^ b)). Returns x if either operand has any x/z bit, and x in a condition reads as false.
  • === / !== — exact 4-state comparison, simulation-only — matches x to x, z to z; always resolves to 0/1; cannot synthesize (hardware has no x).
  • The split: design RTL uses ==/!=; testbenches and assertions use ===/!== for x/z-exact comparison.
  • Two silent failures: a != mismatch check misses an x output (use !==); signal == 1'bx never detects x (use === or $isunknown).

The discipline this page instils:

  • Use == / != in synthesizable RTL; never === / !== (synthesis error / sim-synth mismatch).
  • Use !== in self-checking scoreboards so unknown outputs are caught.
  • Detect unknowns with === against x or $isunknown — never with ==.

You can now compare values and exact bits correctly across both design and verification. The chapter turns next from comparing to constructing vectors: Chapter 10.9 Replication Operator drills {n{...}} — repeating a value n times to build wide buses, fill patterns, and sign-extension constants — the first of the two brace operators (with concatenation, 10.10).

  • Relational Operators — Chapter 10.6; the ordering comparators (the previous comparison family).
  • Bitwise Operators — Chapter 10.4; the XOR that == reduces over.
  • Reduction Operators — Chapter 10.5; the NOR-reduction that completes the == comparator.
  • reg — Chapter 5.2.1; the 4-state values whose x/z bits distinguish == from ===.