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 | "Is parity odd?" / parity bit generation |
~^vec or ^~vec | Reduction XNOR | Even number of 1 bits | "Is parity even?" (complement of XOR) |
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 Parity — The standard parity generator. Returns 1 when an odd number of bits are 1. XOR the data with this bit to produce even-parity protected data.
- ~|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; // generate odd parityReduction 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 number of 1 bits (even parity) |
~^8'hA5 | ~(0) | 1 | Even parity confirmed |
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");Interview Questions
Beginner Level
Q1: What is the difference between ^data (reduction) and a ^ b (bitwise)?^data is a unary reduction operator — it takes one vector operand, chains XOR across every bit from LSB to MSB, and returns a single 1-bit result (odd parity of the vector). a ^ b is a binary bitwise operator — it takes two operands of the same width, XORs corresponding bit pairs, and returns a result of the same width. The symbol is identical; the number of operands determines which form applies. Q2: How do you generate even parity for an 8-bit data byte in one expression?parity = ^data. This is odd parity — it returns 1 when an odd number of bits are 1. For even parity encoding (the parity bit makes the total count even), append this bit to the data. The receiver recomputes ^{parity_bit, data} — a result of 0 means clean (even count), 1 means error (odd count, bit flipped). Alternatively, ~^data gives even parity directly (1 when count is already even).
Intermediate Level
Q3: What does &8'b1xxx_xxxx evaluate to? It evaluates to X. The AND reduction chains across all bits. Bit 7 is 1, and the remaining bits are X. Since AND has an absorbing element of 0 (not 1), the 1 bits cannot absorb the unknowns. The result depends on whether the X bits are 0 or 1 — unknown. Compare this to &8'b0xxx_xxxx, which evaluates to 0 because the explicit 0 bit absorbs the entire AND reduction. Q4: Write a one-expression check to verify that a 4-bit vector is one-hot encoded.(|v) && (~|(v & (v - 4'h1))) The first condition |v ensures the vector is nonzero (at least one bit set). The second condition uses the trick: v & (v-1) clears the lowest set bit — if v is one-hot, this gives 0. NOR-reduction ~| confirms the result is all-zeros. Together: exactly one bit set. Alternatively, use the built-in $onehot(v) system function which handles this cleanly.
Experienced Engineer Level
Q5: In a scoreboard parity check, the assertion fires unexpectedly. Upon inspection, the parity result is X. What are the two possible root causes and how do you distinguish them? Two causes: (1) The data bus has X — an undriven or uninitialized signal in the DUT. The XOR reduction propagates the X to the parity output. (2) The parity signal itself from the DUT is X — driven but uninitialized. Distinguish them by checking $isunknown(data_bus) separately from $isunknown(parity_signal). If the data bus is clean but the parity signal is X, the DUT's parity generator is undriven. If the data bus has X, the parity check is not meaningful until the data bus is initialized — typically a reset or clock-enable issue in the DUT.
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 standard parity generator — but X destroys it. Any X bit in the input produces X parity. Always screen data with
$isunknown()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.