SystemVerilog · Module 4
Reduction Operators
&, |, ^, ~&, ~|, ~^ — parity generation, one-hot checking, X propagation.
Module 4 · Page 4.5
Collapsing a Bus to One Bit
When you write ^data, the simulator chains XOR gates across every bit of data — bit 0 XOR bit 1 XOR bit 2, all the way to the MSB — and returns a single 1-bit result. That one bit tells you whether the number of set bits in data is odd or even. That is odd-parity generation, written in three characters.
This is the reduction operator. The same gate symbol used for bitwise operations in 4.4 — &, |, ^ — becomes a reduction operator when applied as a unary prefix to a single vector operand. The distinction is purely syntactic: one operand means reduction; two operands means bitwise.
In verification, you reach for reduction operators in three situations: checking parity on incoming data, confirming that a request bus has at least one active bit, and verifying that a one-hot encoded vector has exactly one bit set. Each of these is a single expression — no loops, no temporary variables.
The Six Reduction Operators
| Operator | Name | Returns 1 when… | Primary question answered |
|---|---|---|---|
&vec | Reduction AND | ALL bits are 1 | "Are every bit set?" |
~&vec | Reduction NAND | At least one bit is 0 | "Is any bit clear?" (complement of AND) |
|vec | Reduction OR | At least one bit is 1 | "Is any bit set?" / "Is the value nonzero?" |
~|vec | Reduction NOR | ALL bits are 0 | "Is the value zero?" (complement of OR) |
^vec | Reduction XOR | Odd number of 1 bits | Odd-count detector. Appended, it is the even-parity bit |
~^vec or ^~vec | Reduction XNOR | Even number of 1 bits | Even-count detector. Appended, it is the odd-parity bit |
How Reduction Works — The Chain Model
A reduction operator implicitly chains the operation across all bits from LSB to MSB. For a 4-bit vector 4'b1011:
| Operator | Expansion (4-bit) | Evaluation | Result |
|---|---|---|---|
&4'b1011 | 1 & 0 & 1 & 1 | has a 0 → AND gives 0 | 0 |
|4'b1011 | 1 | 0 | 1 | 1 | has a 1 → OR gives 1 | 1 |
^4'b1011 | 1 ^ 0 ^ 1 ^ 1 | 1^0=1, 1^1=0, 0^1=1 → odd number of 1s | 1 |
~&4'b1011 | ~(1 & 0 & 1 & 1) | ~0 = 1 | 1 |
~|4'b1011 | ~(1 | 0 | 1 | 1) | ~1 = 0 | 0 |
~^4'b1011 | ~(1 ^ 0 ^ 1 ^ 1) | ~1 = 0 | 0 |
&vec — all-ones check
Returns 1 only when every single bit is 1. Use to verify a bus is fully asserted — e.g., all grant signals active, all write enables set.
|vec — Any-Bit-Set Check
Returns 1 when at least one bit is 1. Equivalent to vec !== 0 for clean values. Use to detect any pending request, error, or interrupt.
^vec — odd-count detector, EVEN-parity bit
Returns 1 when the vector contains an odd number of 1 bits. Append it to the data (do not XOR it in) and the total count becomes even — so ^vec is the even-parity generator. The odd-parity bit is ~^vec.
~|vec — Zero Check
Returns 1 only when all bits are 0. Cleaner than vec == 0 in some synthesis contexts. Often seen in reset detection logic.
Syntax & Usage
logic [7:0] data = 8'hA5; // 1010_0101 — 4 ones (even count)
bit result;
// ── Six reduction operators ─────────────────────────────────────
result = &data; // AND: 0 — not all bits are 1
result = ~&data; // NAND: 1 — at least one bit is 0
result = |data; // OR: 1 — at least one bit is 1
result = ~|data; // NOR: 0 — value is not zero
result = ^data; // XOR: 0 — even number of 1s (4 ones)
result = ~^data; // XNOR: 1 — even parity (complement of XOR)
// ── Reduction on concatenation ──────────────────────────────────
logic [3:0] a = 4'hF, b = 4'h0;
result = &{a, b}; // AND all 8 bits: 1111_0000 → 0 (not all 1)
result = ^{a, b}; // XOR all 8 bits: parity of combined vector
// ── Common inline patterns ──────────────────────────────────────
if (|req_bus) handle_requests(); // any request pending?
if (&grant_bus) $display("all granted"); // all grants asserted?
if (~|error_flags) mark_pass(); // no errors?
parity_bit = ^payload; // EVEN-parity bit: appending it
// makes the total count of 1s evenReduction on Partial Vectors — Part-Select Syntax
logic [31:0] word = 32'hA5A5_A5A5;
// Reduce only a specific slice
bit upper_parity = ^word[31:16]; // parity of upper 16 bits
bit lower_any = |word[7:0]; // any bit set in lowest byte?
bit nibble_all = &word[3:0]; // all bits set in lowest nibble?
// Build even-parity byte: data + parity such that ^{data, parity} = 0
logic [7:0] payload = 8'hA5;
logic parity = ^payload; // 0 (even count of 1s in A5)
logic [8:0] protected = {parity, payload};
// Receiver checks: ^protected should = 0 for no error
$display("Parity check: %0b (0=ok)", ^protected); // 0Step-by-Step Visual Evaluation
All Six Operators on the Same Vector
Walking through every reduction operator on 8'hA5 = 1010_0101 (four 1-bits — even parity):
| Operator | Operation chain | Result | Interpretation |
|---|---|---|---|
&8'hA5 | 1&0&1&0&0&1&0&1 | 0 | Not all bits are 1 (has zeros) |
~&8'hA5 | ~(0) | 1 | At least one bit is 0 |
|8'hA5 | 1|0|1|0|0|1|0|1 | 1 | At least one bit is 1 (nonzero) |
~|8'hA5 | ~(1) | 0 | Value is NOT zero |
^8'hA5 | 1^0^1^0^0^1^0^1 = four 1s XORed | 0 | Even count of 1 bits — so the even-parity bit is 0 |
~^8'hA5 | ~(0) | 1 | Even count confirmed — and this is the odd-parity bit |
XOR Reduction Step-by-Step — Parity Computation
For 8'b1010_0101 (which is 8'hA5):
| Step | Bits processed | Running XOR |
|---|---|---|
| Start | bit[0] = 1 | 1 |
| Step 1 | bit[1] = 0 | 1 ^ 0 = 1 |
| Step 2 | bit[2] = 1 | 1 ^ 1 = 0 |
| Step 3 | bit[3] = 0 | 0 ^ 0 = 0 |
| Step 4 | bit[4] = 0 | 0 ^ 0 = 0 |
| Step 5 | bit[5] = 1 | 0 ^ 1 = 1 |
| Step 6 | bit[6] = 0 | 1 ^ 0 = 1 |
| Final | bit[7] = 1 | 1 ^ 1 = 0 — even parity |
X Propagation in Reduction Operators
| Expression | Result | Why |
|---|---|---|
&8'b1x11_1111 | X | All other bits are 1, but the X bit is unknown — result could be 0 or 1 |
&8'b0x11_1111 | 0 | The 0 bit absorbs: AND with 0 is always 0 regardless of X |
|8'b0x00_0000 | X | All other bits are 0, the X bit is unknown — could be 0 or 1 |
|8'b1x00_0000 | 1 | The 1 bit absorbs: OR with 1 is always 1 regardless of X |
^8'b0x10_0101 | X | XOR has no absorbing element — any X always propagates |
~|8'b0000_0000 | 1 | Clean zero input — NOR gives 1 (value is zero) |
Code Examples — From Basics to Production
Example 1 — Beginner: All Six Operators
module tb_reduction_basic;
logic [7:0] v_all1 = 8'hFF; // 1111_1111
logic [7:0] v_all0 = 8'h00; // 0000_0000
logic [7:0] v_mix = 8'hA5; // 1010_0101 (even parity)
logic [7:0] v_odd = 8'hA7; // 1010_0111 (odd parity)
initial begin
$display("=== ALL-ONES vector (0xFF) ===");
$display(" &v = %b (all 1?)", &v_all1); // 1
$display(" |v = %b (any 1?)", |v_all1); // 1
$display(" ^v = %b (odd par?)", ^v_all1); // 0 (8 ones = even)
$display("=== ALL-ZEROS vector (0x00) ===");
$display(" &v = %b (all 1?)", &v_all0); // 0
$display(" |v = %b (any 1?)", |v_all0); // 0
$display("~|v = %b (zero?)", ~|v_all0); // 1
$display("=== MIXED vector (0xA5) — 4 ones, even parity ===");
$display(" &v = %b", &v_mix); // 0
$display(" |v = %b", |v_mix); // 1
$display(" ^v = %b (0=even)", ^v_mix); // 0
$display("~^v = %b (1=even)", ~^v_mix); // 1
$display("=== ODD parity vector (0xA7) — 5 ones ===");
$display(" ^v = %b (1=odd)", ^v_odd); // 1
$display("~^v = %b (0=odd)", ~^v_odd); // 0
$finish;
end
endmoduleExample 2 — Intermediate: Parity Generation and Checking
Even parity: the combination of data and parity bit should have an even total count of 1s. The transmitter appends ^data as the parity bit. The receiver recomputes parity over the full received word. A zero result means clean.
module tb_parity;
logic [7:0] payload;
logic tx_parity;
logic [8:0] tx_word; // {parity, data} — 9 bits
logic [8:0] rx_word;
logic rx_parity_check;
task automatic test_parity(input logic [7:0] data, input bit inject_err);
payload = data;
tx_parity = ^payload; // odd parity bit — make total 1-count odd
tx_word = {tx_parity, payload};
$display("TX: data=%08b parity=%b word=%09b",
payload, tx_parity, tx_word);
rx_word = tx_word;
if (inject_err) rx_word[3] = ~rx_word[3]; // flip bit 3
// Receiver: ^rx_word should be 1 for odd parity (no error)
rx_parity_check = ^rx_word;
if (rx_parity_check)
$display("RX: OK — parity check passed\n");
else
$error("RX: ERROR — parity failure (even count = error)\n");
endtask
initial begin
test_parity(8'hA5, 0); // clean transmission
test_parity(8'hA5, 1); // one bit flipped — parity detects it
$finish;
end
endmoduleExpected output:
TX: data=10100101 parity=0 word=010100101
RX: OK — parity check passed
TX: data=10100101 parity=0 word=010100101
ERROR: RX: ERROR — parity failure (even count = error)Example 3 — Verification-Oriented: Reduction in Assertions and Guards
// ── 1. Any pending interrupt — OR reduction ────────────────────────
logic [7:0] irq_flags;
if (|irq_flags)
handle_interrupt(); // any bit set → at least one IRQ pending
// ── 2. All arbitration grants asserted — AND reduction ─────────────
logic [3:0] grant;
assert property (@(posedge clk)
all_grant_en |-> ##1 (&grant))
else $error("Not all grants asserted");
// ── 3. Zero check — NOR reduction ─────────────────────────────────
logic [15:0] addr_bus;
if (~|addr_bus)
$warning("Address bus is zero — is this intentional?");
// ── 4. One-hot validation — manual check using reduction ──────────
function bit is_onehot(input logic [7:0] v);
// Exactly one bit set: v != 0 AND (v & (v-1)) == 0
// (v-1) clears the lowest set bit and sets all lower bits)
return (|v) && (~|(v & (v - 8'h1)));
endfunction
// Or use the built-in system function:
if ($onehot(sel_bus))
$display("One-hot valid");
// ── 5. Parity check in scoreboard ────────────────────────────────
function void check_parity(
input logic [7:0] data,
input logic received_parity
);
logic expected_parity = ^data;
if (expected_parity !== received_parity)
$error("Parity mismatch: data=%08b exp_par=%b rcv_par=%b",
data, expected_parity, received_parity);
endfunctionExample 4 — Corner Case: X in XOR Reduction
module tb_reduction_x;
logic [7:0] sig_x = 8'bxxxx_xxxx; // all X — uninitialized
logic [7:0] sig_p = 8'b1010_x101; // partial X — one bit unknown
initial begin
// AND: 0 absorbs — if any bit is 0, result is 0
// But all X: no 0 to absorb, result is X
$display("&all_X = %b", &sig_x); // x
$display("|all_X = %b", |sig_x); // x
$display("^all_X = %b", ^sig_x); // x
// Partial X: AND checks for 0 absorbing
$display("&partial_X = %b", &sig_p); // x (no 0 bit to absorb)
// If any bit is 0, AND absorbs regardless of X
logic [7:0] sig_has0 = 8'b0xxx_xxxx; // has a 0 at bit 7
$display("&has_0 = %b", &sig_has0); // 0 — absorbed!
// XOR: NO absorption — any X propagates
$display("^partial_X = %b", ^sig_p); // x — one X contaminates
$finish;
end
endmoduleExpected output:
&all_X = x
|all_X = x
^all_X = x
&partial_X = x
&has_0 = 0
^partial_X = xWaveform & Simulation Thinking
|vec vs vec !== 0 — A Subtle Difference with X
Both |vec and vec !== 0 check whether a vector is nonzero, but they behave differently when the vector contains X bits:
| Vector value | |vec | vec !== 0 | Practical difference |
|---|---|---|---|
8'hFF (clean) | 1 | 1 | Same |
8'h00 (clean) | 0 | 0 | Same |
8'hxx (all X) | X | 1 | !== catches X as different from 0; | propagates X |
8'b1xxx_xxxx (partial X) | 1 ← 1 absorbs | 1 | Same result here, but for different reasons |
8'b0xxx_xxxx (partial X) | X | 1 | !== catches that it differs from 0x00; | is X |
In testbench guards where you want to detect that a signal is nonzero — including when it might have X — prefer vec !== 0 or $isunknown(vec) || |vec. Reserve |vec for clean RTL datapath checks where X is not expected.
Synthesis Behavior
| Operator | Synthesizes to | Typical use in RTL |
|---|---|---|
&vec | N-input AND gate tree | All-ones detection: write-enable bus, all-grant signal |
|vec | N-input OR gate tree | Any-request detection: IRQ OR, valid-OR |
^vec | XOR gate tree (balanced for timing) | Parity generator, CRC step |
~|vec | NOR gate tree | Zero detector, all-clear flag |
~^vec | XNOR gate tree | Even parity, comparator output |
Where You'll Use These in Real Projects
// ── 1. IRQ monitor — detect any active interrupt ──────────────────
always @(posedge clk) begin
if (|irq_vec && irq_en)
$display("[MON] IRQ active: vec=0x%0h", irq_vec);
end
// ── 2. Bus protocol monitor — check all valid signals asserted ────
assert property (
@(posedge clk) start_burst |-> ##1 (&{awvalid, wvalid, bready})
) else $error("AXI write burst: not all handshake signals asserted");
// ── 3. Coverage model — parity coverage ──────────────────────────
covergroup cg_parity;
cp_parity: coverpoint (^payload) {
bins even_parity = {0};
bins odd_parity = {1};
}
endgroup
// ── 4. One-hot checker in arbitration verification ────────────────
always @(posedge clk) begin
if (|grant_bus && !$onehot(grant_bus))
$error("[ARB] Multiple grants asserted: 0x%0h", grant_bus);
end
// ── 5. Post-reset X check — ensure no X after reset deassert ─────
task automatic check_no_x_post_reset(input logic [31:0] sig, input string name);
if ($isunknown(sig))
$error("[RESET] %s has X after reset: %b", name, sig);
else if (|sig) // safe to use | now that X is ruled out
$warning("[RESET] %s is nonzero after reset: 0x%0h", name, sig);
endtaskCommon Bugs & How to Debug Them
Bug 1 — X in Parity Computation: Silent Check Failure
logic [7:0] dut_data; // DUT output — may have X if undriven
logic dut_parity; // DUT parity output
// BUGGY: if dut_data has ANY X bit, ^dut_data = X
// The !== comparison then returns 1 (X !== clean bit) — looks like parity error
// But the real root cause is an X on the data bus, not a parity computation failure
if (^dut_data !== dut_parity)
$error("Parity error"); // fires on X — but misleading error message// CORRECT: screen for X first — separate root causes
if ($isunknown(dut_data)) begin
$error("X on data bus — cannot check parity: %b", dut_data);
end else if (^dut_data !== dut_parity) begin
$error("Parity MISMATCH: data=%08b exp_par=%b got_par=%b",
dut_data, ^dut_data, dut_parity);
endBug 2 — Using OR Reduction to Check Zero — Misses X
logic [7:0] status = 8'hxx; // uninitialized — all X
// BUGGY: |status = X when status is all-X
// X in if = false → branch skipped → no IRQ handled — false pass
if (|status)
handle_irq(); // never fires when status is X — wrong behavior
// CORRECT: use !== 0 to detect X as nonzero, or screen X first
if ($isunknown(status))
$error("Status register has X — check DUT reset");
else if (|status)
handle_irq();Bug 3 — AND Reduction on Wrong Width: Checking Too Few Bits
logic [7:0] grant = 8'hFF; // all 8 channels granted
// BUGGY: typo — checking only bits [3:0], not [7:0]
if (&grant[3:0])
$display("All grants active"); // fires even if grants[7:4] are 0
// CORRECT: reduce the full vector
if (&grant)
$display("All 8 grants active");Proving It — Absorbing Elements and the Parity Round Trip
Two things on this page are worth proving rather than asserting: which reductions can survive an X, and which operator is the even-parity bit. Both are three lines of code.
// reduction_semantics_proof.sv
//
// Self-checking proof of: the absorbing-element rule that decides X
// behaviour, and the parity round trip that settles odd vs even naming.
module reduction_semantics_proof;
int errors = 0;
task automatic chk(string name, logic got, logic exp);
if (got !== exp) begin
errors++;
$display("FAIL %-48s got=%b exp=%b", name, got, exp);
end else
$display("pass %-48s = %b", name, got);
endtask
initial begin
logic [7:0] v;
// ================================================================
// 1. ABSORBING ELEMENTS DECIDE X BEHAVIOUR
// AND absorbs 0. OR absorbs 1. XOR absorbs nothing.
// ================================================================
chk("&8'b0xxx_xxxx - a 0 absorbs, result is known", &8'b0xxx_xxxx, 1'b0);
chk("&8'b1xxx_xxxx - no 0 present, result is X", &8'b1xxx_xxxx, 1'bx);
chk("|8'b1xxx_xxxx - a 1 absorbs, result is known", |8'b1xxx_xxxx, 1'b1);
chk("|8'b0xxx_xxxx - no 1 present, result is X", |8'b0xxx_xxxx, 1'bx);
chk("^8'b0xxx_xxxx - XOR absorbs NOTHING", ^8'b0xxx_xxxx, 1'bx);
chk("^8'b1xxx_xxxx - still X", ^8'b1xxx_xxxx, 1'bx);
chk("^8'b1010_001x - ONE x bit is enough", ^8'b1010_001x, 1'bx);
// A single X anywhere makes parity X. That is not a defect in the
// operator - it is the correct answer, and it is why parity is the
// most X-fragile reduction and a useful accidental X detector.
// ================================================================
// 2. |v vs v != 0 - they DIVERGE on X
// ================================================================
v = 8'b1000_000x;
chk("|v on 8'b1000_000x", |v, 1'b1); // a 1 absorbs
chk("v != 0 on 8'b1000_000x", |(v != 0), 1'bx); // comparison cannot
// In an `if`, the first takes the branch and the second takes the else.
// ================================================================
// 3. THE PARITY ROUND TRIP - this settles odd vs even
// ================================================================
begin
logic even_par, odd_par;
logic [8:0] word_even, word_odd;
v = 8'hA5; // 1010_0101 - four 1 bits, an EVEN count
even_par = ^v; // odd-COUNT detector -> 0 here
odd_par = ~^v; // even-COUNT detector -> 1 here
chk("^8'hA5 (odd-count detector, count is 4)", even_par, 1'b0);
chk("~^8'hA5 (even-count detector)", odd_par, 1'b1);
// APPEND the bit - do not XOR it into the data.
word_even = {even_par, v};
word_odd = {odd_par, v};
// The defining property of each scheme:
chk("EVEN parity: ^{^v, v} == 0", ^word_even, 1'b0);
chk("ODD parity: ^{~^v, v} == 1", ^word_odd, 1'b1);
// So: ^v is the EVEN-parity generator, and ~^v is the ODD-parity
// generator, even though ^v is described as detecting an ODD count.
// Both statements are true and they are about different things.
// And it holds for an odd-count vector too:
v = 8'hA7; // 1010_0111 - five 1 bits, an ODD count
chk("EVEN parity holds for odd-count data", ^{^v, v}, 1'b0);
chk("ODD parity holds for odd-count data", ^{~^v, v}, 1'b1);
end
if (errors == 0) $display("\nreduction_semantics_proof: ALL CHECKS PASSED");
else $display("\nreduction_semantics_proof: %0d FAILURES", errors);
$finish;
end
endmoduleSection 3 is the one that resolves the naming permanently. ^v detects an odd count and generates the even-parity bit. Both descriptions are correct, they are about different things, and running the round trip once is faster than re-deriving it every time.
A parity checker was inverted, and the round trip never caught it
ODD-EVEN-PARITY-INVERTED// ❌ BUG: the link spec says ODD parity. The generator uses ^data, which
// is the EVEN-parity bit. The checker was written to match the
// generator rather than the spec, so the two agree with each other
// and disagree with the standard.
module link_tx (input logic [7:0] data, output logic [8:0] frame);
logic par;
assign par = ^data; // ❌ this is the EVEN-parity bit
assign frame = {par, data};
endmodule
module link_rx (input logic [8:0] frame, output logic par_err);
// Written by the same engineer, to match the transmitter:
assign par_err = ^frame; // ❌ 0 when even - consistent with TX
endmodule
// Internally consistent: TX makes the count even, RX checks it is even.
// Every loopback test passes. Against a spec-conformant partner that
// sends ODD parity, EVERY frame is flagged as an error.
// ✅ FIX: ODD parity means the total count of 1s is odd, so the parity
// bit is the EVEN-count detector, ~^data. The receiver then
// checks that the whole frame has an odd count.
module link_tx (input logic [7:0] data, output logic [8:0] frame);
logic par;
assign par = ~^data; // ✅ ODD-parity bit
assign frame = {par, data};
endmodule
module link_rx (input logic [8:0] frame, output logic par_err);
assign par_err = ~(^frame); // ✅ error when the count is NOT odd
endmoduleA serial link block passed its entire block-level regression, including a loopback test that ran millions of frames with randomised data and injected single-bit errors, all of which it detected correctly.
At interoperability testing against a third-party partner, every single frame was reported as a parity error. Not an occasional one — all of them.
frame from partner: 9'b1_1010_0101 data = 8'hA5 (four 1s), parity = 1
partner's rule (ODD): total 1s must be odd -> 4 + 1 = 5, odd. VALID.
our checker (^frame): ^9'b1_1010_0101 = 1 -> flagged as ERROR^data is the even-parity bit. The specification called for odd parity, which is ~^data.
The naming is where this starts. ^vec returns 1 when the vector contains an odd number of 1 bits, so it is natural to call it "the odd parity operator" — and that description is about the count of the input, not about the parity scheme it implements. Appending ^vec to the data makes the total count even. ^vec detects odd and generates even, and those two facts sit one line apart in most descriptions of the operator.
What made it survive the entire regression is more instructive than the mistake itself. The transmitter and the receiver were written from the same misunderstanding, by the same engineer, in the same afternoon. A loopback test compares the design against itself, so a consistent inversion is invisible to it — the check verifies that TX and RX agree, and they did. Single-bit error injection also passed, because inverting the parity convention does not affect the detection capability: an odd-parity checker and an even-parity checker both catch any single-bit flip. Only the polarity against an external partner differs.
That is the general lesson, and it is worth more than the parity rule: a loopback test cannot validate a convention. It validates self-consistency. Anything both endpoints get wrong in the same way — bit ordering, endianness, parity polarity, a field offset — passes loopback and fails at the first real interoperation.
Correct the generator to ~^data and the checker to match, then add the two checks that make the convention testable without a partner.
// 1. The property that DEFINES the scheme, stated once and asserted.
// Odd parity: the complete frame always has an odd number of 1 bits.
a_odd_parity_frame: assert property (@(posedge clk) disable iff (!rst_n)
frame_valid |-> (^frame == 1'b1))
else $error("frame does not carry odd parity");
// 2. A directed vector taken FROM THE SPECIFICATION, not from the design.
// One hand-computed frame is enough to pin the polarity, and it is the
// only check here that a self-consistent implementation cannot pass.
initial begin
logic [7:0] d = 8'hA5; // four 1 bits
logic [8:0] expect_frame = 9'b1_1010_0101; // parity 1 -> five 1s, odd
#1;
assert (make_frame(d) === expect_frame)
else $fatal(1, "parity polarity does not match the link specification");
endThree habits follow, and the last is the one that generalises furthest:
- Write the parity rule as a property of the whole frame, never as a formula for the bit. "The frame has an odd number of 1s" is unambiguous and directly assertable. "The parity bit is
^data" requires the reader to re-derive which scheme that produces, and half of them will get it wrong. - Keep the two facts about
^vecseparate. It detects an odd count. It generates even parity.~^vecis the odd-parity generator. If you cannot recall which, run the round trip in the proof above — it takes seconds and it is decisive. - Every convention needs at least one vector from outside the design. A hand-computed frame from the specification, a captured trace from a real partner, or a golden file. Loopback proves the two ends agree; only an external reference proves they agree with the standard.
Interview Questions
Best Practices & Coding Guidelines
Guard parity with $isunknown
Before computing ^data in a scoreboard, always check $isunknown(data). An X in the data makes parity X, which produces misleading error messages that obscure the real root cause.
|vec vs !== 0 — know the difference
For testbench guards, vec !== 0 is safer — it catches X as "not zero." Use |vec in RTL where X is not expected.
$onehot() for one-hot checks
Use the built-in $onehot() and $onehot0() system functions rather than writing manual one-hot checks. They are synthesizable and handle the edge cases correctly.
Verify reduction width
When writing &grant or |irq, confirm the vector width covers all the signals you intend to check. A silent part-select narrowing produces always-wrong results.
| Task | Best expression | Avoid |
|---|---|---|
| Any bit set (clean data) | |vec | vec != 0 for multi-bit (works but verbose) |
| Any bit set (may have X) | $isunknown(vec) || |vec | |vec alone — misses X |
| All bits set | &vec | Comparing to all-1s mask (verbose) |
| Zero check | ~|vec or vec === 0 | !vec (logical NOT — works only for 1-bit intent) |
| Odd parity bit | ^data | Manual XOR chain |
| Even parity bit | ~^data | !(^data) — logical NOT adds confusion |
| One-hot check | $onehot(vec) | Manual bit-count logic |
Summary
Reduction operators are the most compact way to answer binary questions about a bus. Six operators, six questions — and three of them are just the complements of the other three. In practice, you will use |, &, and ^ reduction most often.
Three rules to keep:
- XOR reduction is the parity generator —
^vgives the even-parity bit,~^vthe odd — but X destroys it. Any X bit in the input produces X parity. Always screen data with$isunknown()(see 2-state vs 4-state Types) before computing or comparing parity in a scoreboard, or you will get misleading error messages. - AND and OR have absorbing elements. XOR does not.
&{0, x_bits}= 0 (safe).|{1, x_bits}= 1 (safe).^{any, x_bits}= X (always propagates). Design your checks to put the absorbing bit in the right place when X is possible. - Use
$onehot()for one-hot checks in assertions. It is synthesizable, readable, and handles X correctly. The manual formula works but is harder to review.
Related Pages & References
The binary forms of the same symbols. &, |, ^ are reduction operators with one operand and bitwise operators with two. Because the unary forms bind tightest of all, ^a ^ b is (^a) ^ b — a reduction of a XORed with b — which is legal and rarely intended. Operator Precedence has the ordering. Logical Operators are reductions in disguise: a && b is (|a) & (|b), so every && on a multi-bit operand contains an OR-reduction.
Where reductions do their work. Assertions and checkers — see Concurrent Assertions for one-hot and any-active properties, and Immediate Assertions for the guard form. For the X values that decide half the behaviour on this page, 2-state vs 4-state Types. For the gate-level tree a reduction becomes — and the XNOR-depth algebra that inverts a parity tree — see Loops.
References.
- IEEE 1800 (SystemVerilog) — the unary reduction operators and their truth tables, including the X and Z rows, are in the operators-and-expressions clause. The absorbing-element behaviour proven above follows directly from those tables: AND yields a known 0 when any operand bit is 0, OR yields a known 1 when any bit is 1, and XOR has no such case.
$onehot,$onehot0and$isunknownare in the system-tasks-and-functions clause. - IEEE 1364 (Verilog) — the same operators and tables, inherited unchanged.
Requirement versus practice. The operator results, including every X row, are language requirements. Parity polarity is not — whether a link uses odd or even parity is a property of that link's specification, and this page's role is only to say which operator implements which. ^vec detects an odd count and generates the even-parity bit; ~^vec generates the odd-parity bit. Which one your design needs is in the protocol document, not in the language.
The tree structure a reduction synthesizes to is also practice rather than requirement: a balanced tree of about log₂(W) levels is what tools produce and a sound cost model, but the language specifies the value, not the topology.
Part of SystemVerilog Fundamentals·Operators & Expressions·Lesson 23 of 53
View program