Four-state values force three comparisons. Logical == yields 0 if any known bits differ, and X when the known bits match but an X or Z bit leaves the answer open. An if treats that X as false.
Case === matches X literally and always returns 0 or 1. Wildcard ==? treats X and Z in the right operand as don't-cares.
a is 4'b1010 and b is 4'b10x0. What do a == b, a === b and a ==? b return?X, 0 and 1. == cannot decide because bit 1 of b is unknown; === sees that the bits differ literally; ==? treats the X in b as a don't-care, and the known bits match.
a | b | a == b | a === b | a ==? b |
|---|---|---|---|---|
4'b1010 | 4'b1010 | 1 | 1 | 1 |
4'b1010 | 4'b10X0 | X (false) | 0 | 1 (X in b is don't-care) |
4'b10X0 | 4'b1010 | X (false) | 0 | X (false: X in a is not a wildcard) |
4'b10X0 | 4'b10X0 | X (false) | 1 | 1 |
`==` belongs in RTL. It synthesizes, but X results read as false in conditions.
`===` belongs in testbench X checks. It never synthesizes.
`==?` belongs in masked compares. Only the right operand wildcards.
The asymmetry is deliberate. An X in the left operand at a compared position gives X, which an if treats as false, so masked checks of possibly corrupt outputs still fail.
casez treats ? as don't-care in items and selector. casex also wildcards X, so an unknown selector takes the first overlapping branch.
That first match on garbage hides uninitialized registers and bus contention. Most style guides ban casex outright.
case inside uses ==? semantics with deliberate wildcards plus ranges. An X selector falls through to default, which makes it the replacement for both.
The two decoders below give the same grants, one with casez wildcards and one with case inside ranges. Their items are disjoint; when items overlap, the first match wins, so keep intentional overlap under priority.
module grant_decode (
input logic [3:0] req,
output logic [3:0] grant
);
always_comb begin
grant = 4'b0000;
casez (req)
4'b1???: grant = 4'b1000;
4'b01??: grant = 4'b0100;
4'b001?: grant = 4'b0010;
4'b0001: grant = 4'b0001;
default: grant = 4'b0000;
endcase
end
endmodule
module inside_decode (
input logic [3:0] req,
output logic [3:0] grant
);
always_comb begin
grant = 4'b0000;
case (req) inside
[4'b1000 : 4'b1111]: grant = 4'b1000;
[4'b0100 : 4'b0111]: grant = 4'b0100;
[4'b0010 : 4'b0011]: grant = 4'b0010;
4'b0001: grant = 4'b0001;
default: grant = 4'b0000;
endcase
end
endmodule
module tb_case_inside;
logic [3:0] req, gz, gi;
int errors = 0;
grant_decode dz (.req(req), .grant(gz));
inside_decode di (.req(req), .grant(gi));
initial begin
for (int k = 0; k < 16; k++) begin
req = k[3:0]; #1;
if (gz !== gi) errors++; // both decoders agree on every value
end
req = 4'b0110; #1;
if (gi !== 4'b0100) errors++;
if (errors == 0) $display("PASS: casez and case inside gave the same grant for all 16 requests");
else $fatal(1, "FAIL: %0d requests decoded differently", errors);
$finish;
end
endmodulePASS: casez and case inside gave the same grant for all 16 requestsgot holds 4'b10X0. Does (got == 4'b1010) enter the if branch?No. The X makes the result X, and if treats X as false. Use === in the testbench to name the unknown.
req reads 4'bXXXX under the casez decoder above. Which grant results?4'b0000 through default. casez wildcards only ? and Z, so no item matches. A casex would wrongly take the first branch.
The equality table row by row, casez and casex facing an unknown selector, and a masked compare. X needs a 4-state simulator, so this bench runs on Icarus.
module tb_equality;
logic [3:0] a, b, sel, g;
logic [31:0] got;
int errors = 0;
task automatic row(input logic [3:0] x, input logic [3:0] y,
input logic e_eq, input logic e_case, input logic e_wild);
a = x; b = y;
$display("a=%b b=%b a==b: %b a===b: %b a==?b: %b", a, b, a == b, a === b, a ==? b);
if ((a == b) !== e_eq || (a === b) !== e_case || (a ==? b) !== e_wild) errors++;
endtask
initial begin
row(4'b1010, 4'b1010, 1'b1, 1'b1, 1'b1);
row(4'b1010, 4'b10x0, 1'bx, 1'b0, 1'b1); // X in b is a wildcard for ==?
row(4'b10x0, 4'b1010, 1'bx, 1'b0, 1'bx); // X in a is not
row(4'b10x0, 4'b10x0, 1'bx, 1'b1, 1'b1);
a = 4'b10x0;
if (a == 4'b1010) errors++; // X result: if takes the else branch
sel = 4'bxxxx; // an unknown selector
casez (sel)
4'b1???: g = 4'b1000;
default: g = 4'b0000;
endcase
if (g !== 4'b0000) errors++; // casez: X matches no item
casex (sel)
4'b1???: g = 4'b1000;
default: g = 4'b0000;
endcase
if (g !== 4'b1000) errors++; // casex: X matches the first item
$display("casez on X picks default; casex on X picks the first item");
got = 32'hDEAD_BE42;
if (!(got ==? 32'hDEAD_BExx)) errors++; // low byte masked: match
if (got ==? 32'hDEAD_BFxx) errors++; // a known bit differs: no match
if (errors == 0) $display("PASS: the equality table, casez and casex on X, and the masked compare all behaved as shown");
else $fatal(1, "FAIL: %0d equality checks disagreed", errors);
$finish;
end
endmodulea=1010 b=1010 a==b: 1 a===b: 1 a==?b: 1
a=1010 b=10x0 a==b: x a===b: 0 a==?b: 1
a=10x0 b=1010 a==b: x a===b: 0 a==?b: x
a=10x0 b=10x0 a==b: x a===b: 1 a==?b: 1
casez on X picks default; casex on X picks the first item
PASS: the equality table, casez and casex on X, and the masked compare all behaved as shownRun on 2026-10-18 with no warnings: the equality bench on Icarus 13 (-g2012), because X needs a 4-state simulator, and the decoder bench on Verilator 5.048 (--binary --timing), because Icarus 13 has no case inside. Each printed the PASS line shown.