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:
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:
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 (
==,!=) returnsxwhen operands containx/z— it cannot be sure — andxin 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
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
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:
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 exactlyThe 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'bxis alwaysx(never 1), so it never fires — you cannot test forxwith==.signal === 1'bxcorrectly reports whether the bit isx. - A
!=mismatch check can miss anxoutput (the §1 bug):x != knownisx, treated as false, so no mismatch is flagged.!==gives a definite 1.
Visual B — the synthesizable boundary
Which equality goes where
data flow6. When to Use Each — Design vs Testbench
The synthesizable boundary maps cleanly onto where each operator belongs:
// 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 acrossx/z— self-checking scoreboards (so anxoutput 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-xpattern.
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:
(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 equala ^ 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 (==)
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
endmoduleSynthesizable 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 (!==)
// 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
endtaskThe §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)
// 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 anx(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,===againstx) catch reset and initialization bugs early. Hardware has nox, 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
===/!==in synthesizable RTL — simulation-only; synthesis rejects/warns and risks a sim/synth mismatch (§3, DebugLab 2).!=mismatch check that misses anxoutput —x != knownisx(false), so the error is never counted; use!==(§1, DebugLab 1).- Detecting
xwith==—signal == 1'bxis alwaysx, never true; use===or$isunknown(§5, DebugLab 3). - Forgetting
==returnsxon unknown operands — and thatxin a condition reads as false. - 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.
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 signalExercise 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 == bis~|(a ^ b)). Returnsxif either operand has anyx/zbit, andxin a condition reads as false.===/!==— exact 4-state comparison, simulation-only — matchesxtox,ztoz; always resolves to 0/1; cannot synthesize (hardware has nox).- The split: design RTL uses
==/!=; testbenches and assertions use===/!==for x/z-exact comparison. - Two silent failures: a
!=mismatch check misses anxoutput (use!==);signal == 1'bxnever detectsx(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
===againstxor$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).
Related Tutorials
- 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/zbits distinguish==from===.