Skip to content

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

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 bitsOdd-count detector. Appended, it is the even-parity bit
~^vec or ^~vecReduction XNOREven number of 1 bitsEven-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:

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

SystemVerilog — Reduction Operator Syntax
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 even

Reduction on Partial Vectors — Part-Select Syntax

Reduction on Slices and Concatenations
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 count of 1 bits — so the even-parity bit is 0
~^8'hA5~(0)1Even count confirmed — and this is the odd-parity bit

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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
TX: data=10100101  parity=0  word=010100101
RX: OKparity check passed
 
TX: data=10100101  parity=0  word=010100101
ERROR: RX: ERRORparity failure (even count = error)

Example 3 — Verification-Oriented: Reduction in Assertions and Guards

Example 3 — Reduction Operators in Assertions and Checks
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
&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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
endmodule

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

AND reduction absorbs a zero so an X input can still give a known result; OR reduction absorbs a one the same way; XOR reduction has no absorbing element so any X input makes the result X.AND reduction &vabsorbing element: 0OR reduction |vabsorbing element: 1XOR reduction ^vno absorbing elementAny bit is 0result forced to 0Any bit is 1result forced to 1Every bit mattersnothing can force itKnown result despiteXX bits are irrelevantOtherwise Xno bit forced the resultOne X makes parity Xmost X-fragile12
Figure 1 - why the three reductions behave differently on X. AND has an absorbing element of 0, so a single 0 anywhere forces the result and the X bits are irrelevant. OR absorbs 1 the same way. XOR has no absorbing element - every bit affects the output, so one X anywhere makes the whole result X. This one property explains the entire X column of the truth tables, and why parity is the first thing to fail on a bus with an undriven lane.
1

A parity checker was inverted, and the round trip never caught it

ODD-EVEN-PARITY-INVERTED
Buggy Code & Fix
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ❌ 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
endmodule
Symptom

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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
Root Cause

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

Fix

Correct the generator to ~^data and the checker to match, then add the two checks that make the convention testable without a partner.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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");
end

Three 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 ^vec separate. It detects an odd count. It generates even parity. ~^vec is 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.

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 parity generator — ^v gives the even-parity bit, ~^v the 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.

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, $onehot0 and $isunknown are 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

Continue learning