Skip to content

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

OperatorNameReturns 1 when…Primary question answered
&vecReduction ANDALL bits are 1"Are every bit set?"
~&vecReduction NANDAt least one bit is 0"Is any bit clear?" (complement of AND)
|vecReduction ORAt least one bit is 1"Is any bit set?" / "Is the value nonzero?"
~|vecReduction NORALL bits are 0"Is the value zero?" (complement of OR)
^vecReduction XOROdd number of 1 bits"Is parity odd?" / parity bit generation
~^vec or ^~vecReduction XNOREven 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:

OperatorExpansion (4-bit)EvaluationResult
&4'b10111 & 0 & 1 & 1has a 0 → AND gives 00
|4'b10111 | 0 | 1 | 1has a 1 → OR gives 11
^4'b10111 ^ 0 ^ 1 ^ 11^0=1, 1^1=0, 0^1=1 → odd number of 1s1
~&4'b1011~(1 & 0 & 1 & 1)~0 = 11
~|4'b1011~(1 | 0 | 1 | 1)~1 = 00
~^4'b1011~(1 ^ 0 ^ 1 ^ 1)~1 = 00
  • &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

SystemVerilog — Reduction Operator Syntax
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 parity

Reduction on Partial Vectors — Part-Select Syntax

Reduction on Slices and Concatenations
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);   // 0

Step-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):

OperatorOperation chainResultInterpretation
&8'hA51&0&1&0&0&1&0&10Not all bits are 1 (has zeros)
~&8'hA5~(0)1At least one bit is 0
|8'hA51|0|1|0|0|1|0|11At least one bit is 1 (nonzero)
~|8'hA5~(1)0Value is NOT zero
^8'hA51^0^1^0^0^1^0^1 = four 1s XORed0Even number of 1 bits (even parity)
~^8'hA5~(0)1Even parity confirmed

XOR Reduction Step-by-Step — Parity Computation

For 8'b1010_0101 (which is 8'hA5):

StepBits processedRunning XOR
Startbit[0] = 11
Step 1bit[1] = 01 ^ 0 = 1
Step 2bit[2] = 11 ^ 1 = 0
Step 3bit[3] = 00 ^ 0 = 0
Step 4bit[4] = 00 ^ 0 = 0
Step 5bit[5] = 10 ^ 1 = 1
Step 6bit[6] = 01 ^ 0 = 1
Finalbit[7] = 11 ^ 1 = 0 — even parity

X Propagation in Reduction Operators

ExpressionResultWhy
&8'b1x11_1111XAll other bits are 1, but the X bit is unknown — result could be 0 or 1
&8'b0x11_11110The 0 bit absorbs: AND with 0 is always 0 regardless of X
|8'b0x00_0000XAll other bits are 0, the X bit is unknown — could be 0 or 1
|8'b1x00_00001The 1 bit absorbs: OR with 1 is always 1 regardless of X
^8'b0x10_0101XXOR has no absorbing element — any X always propagates
~|8'b0000_00001Clean zero input — NOR gives 1 (value is zero)

Code Examples — From Basics to Production

Example 1 — Beginner: All Six Operators

Example 1 — All Reduction 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
 
endmodule

Example 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.

Example 2 — Parity Generation and Error Detection
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
 
endmodule

Expected output:

Simulation 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

Example 3 — Reduction Operators in Assertions and Checks
// ── 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);
endfunction

Example 4 — Corner Case: X in XOR Reduction

Example 4 — X Propagation in Reduction Operators
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
 
endmodule

Expected output:

Simulation Output
&all_X     = x
|all_X     = x
^all_X     = x
&partial_X = x
&has_0     = 0
^partial_X = x

Waveform & 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|vecvec !== 0Practical difference
8'hFF (clean)11Same
8'h00 (clean)00Same
8'hxx (all X)X1!== catches X as different from 0; | propagates X
8'b1xxx_xxxx (partial X)1 ← 1 absorbs1Same result here, but for different reasons
8'b0xxx_xxxx (partial X)X1!== 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

OperatorSynthesizes toTypical use in RTL
&vecN-input AND gate treeAll-ones detection: write-enable bus, all-grant signal
|vecN-input OR gate treeAny-request detection: IRQ OR, valid-OR
^vecXOR gate tree (balanced for timing)Parity generator, CRC step
~|vecNOR gate treeZero detector, all-clear flag
~^vecXNOR gate treeEven parity, comparator output

Where You'll Use These in Real Projects

Real Verification Usage Patterns
// ── 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);
endtask

Common Bugs & How to Debug Them

Bug 1 — X in Parity Computation: Silent Check Failure

Bug 1 — X in Payload Corrupts Parity Check
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
Bug 1 — Fixed: Check for X Before Computing Parity
// 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);
end

Bug 2 — Using OR Reduction to Check Zero — Misses X

Bug 2 — |vec Passes X Where !== 0 Would Catch It
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

Bug 3 — Reducing a Truncated Part-Select
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.
TaskBest expressionAvoid
Any bit set (clean data)|vecvec != 0 for multi-bit (works but verbose)
Any bit set (may have X)$isunknown(vec) || |vec|vec alone — misses X
All bits set&vecComparing to all-1s mask (verbose)
Zero check~|vec or vec === 0!vec (logical NOT — works only for 1-bit intent)
Odd parity bit^dataManual 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.