Skip to content
VLSI Mentor

UART · Module 9

Parity Errors and the Limits of Detection

Parity detects every odd-weight corruption and provably misses every even-weight one — shown by exhaustive enumeration. A check that passes says a relation holds, not that the data is correct.

Chapter 3.3 built parity and stated its guarantee. Chapter 6.4 implemented the receiver's check with the accumulator discipline that keeps it attached to the right frame. This chapter attacks it.

The question is not how parity works — that is one XOR reduction and a comparison. It is what a designer is entitled to conclude from the result, in both directions:

parity_error = 1 — a specific relation over the protected bits does not hold. parity_error = 0 — that relation holds. Not that the data is correct.

The gap between the second statement and "the data arrived intact" is the entire subject, and it is wider than it looks. This chapter closes it by enumeration rather than by assertion: every corruption of every weight, counted.

1. The Relation Being Tested

Chapter 3.3 fixed the convention, and the receiver applies exactly the same rule the transmitter did:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
even parity:   p  =  ^d          the XOR reduction of the data bits
odd  parity:   p  =  ~(^d)

The receiver assembles d from the data intervals, observes p in the parity interval, and tests:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
observed p  ==  expected(mode, assembled d)     ->  relation holds

An equivalent and more revealing form is to reduce the whole protected set at once:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
even parity:   ^{d, p}  ==  0        the protected set has even weight
odd  parity:   ^{d, p}  ==  1        the protected set has odd weight

Parity is a one-bit statement about the weight of a nine-bit set. Reading it that way makes the limit immediate: a corruption that preserves the parity of the weight is invisible, whatever else it does to the data.

2. The RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  9.2 — parity relation check. The rule is Chapter 3.3's, unchanged.
// ---------------------------------------------------------------------------
module uart_parity_check #(
    parameter int unsigned DATA_W = 8
) (
    input  logic                  clk,
    input  logic                  rst_n,
    input  logic                  frame_start_i,
    input  logic                  check_en_i,        // one cycle, at the parity sample
    input  logic                  parity_odd_i,      // 0 = even, 1 = odd
    input  logic [DATA_W-1:0]     data_i,            // payload assembled so far
    input  logic                  parity_bit_i,      // the received parity interval
    output logic                  parity_error_o,
    output logic                  parity_error_evt_o
);
    // Expected parity under the configured mode. ^data_i is a reduction XOR:
    // synthesis builds a balanced tree of two-input gates.
    wire expected = parity_odd_i ? ~(^data_i) : (^data_i);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            parity_error_o     <= 1'b0;
            parity_error_evt_o <= 1'b0;
        end else begin
            parity_error_evt_o <= 1'b0;
            if (frame_start_i) begin
                parity_error_o <= 1'b0;
            end else if (check_en_i) begin
                parity_error_o     <= (parity_bit_i != expected);
                parity_error_evt_o <= (parity_bit_i != expected);
            end
        end
    end
endmodule

^data_i is a reduction XOR, not a bitwise operation between two operands. Synthesis builds a balanced tree of two-input XOR gates — three levels at DATA_W = 8 — and it is evaluated once, at the parity sample.

parity_odd_i selects the expectation, not the computation. The XOR tree is the same either way; the mode is one inverter on its output. That is why parity mode can be a runtime input at essentially no cost, which Chapter 6.1 §5 used as its example of a configuration input that selects a path through hardware that exists anyway.

The verdict/event split is Chapter 9.1 §2's, unchanged. parity_error_o is a level describing this frame, cleared at the next accepted start; parity_error_evt_o is a one-cycle pulse for the sticky aggregator of Chapter 9.6. The two chapters use the same pattern deliberately — every error source in this module produces the same pair of shapes, which is what lets 9.6 aggregate them uniformly.

3. The Detection Limit, Enumerated

The rule is usually stated as "parity catches single-bit errors". That is true and it is not the useful form. Here is the exhaustive count for 0xA6 under even parity, over the nine protected bits:

corruption weightcasesdetectedundetected
1 bit990
2 bits36036
3 bits84840
4 bits1260126

Every odd-weight corruption is detected. Every even-weight corruption is missed. Not "most", not "usually" — the split is total, and it is a consequence of the mechanism rather than a statistical property.

The reason is one line: XOR is addition modulo two, so flipping k bits changes the reduction by k mod 2. An even k leaves the relation exactly as it was, and the receiver's comparison is against that relation and nothing else.

4. A Concrete Undetected Corruption

Abstract statements about weight are easy to nod along to. Here is one that a receiver reports as clean, machine-verified:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
transmitted    0xA6 = 1010_0110        even parity bit = 0
corrupted      0xA5 = 1010_0101        bits 0 and 1 both flipped
received       0xA5 with parity 0

receiver's check:  ^0xA5 = 0,  expected 0,  relation HOLDS
reported:          parity_error = 0        <-- no error
delivered:         0xA5                    <-- wrong byte, flagged clean

0xA6 and 0xA5 are both members of the standard test-pattern set used throughout this curriculum, which makes the example uncomfortably realistic: a two-bit disturbance turns one perfectly ordinary byte into another perfectly ordinary byte, and every check the receiver performs passes.

Two more, from the same enumeration:

transmittedflipped bitsreceivedparity relation
0xA60 and 10xA5holds — undetected
0xA60 and 70x27holds — undetected
0xA63 and 50x8Eholds — undetected

The third is worth noting: 0xA6 → 0x8E is not a subtle change, and parity is silent about it.

One flipped bit detected, two flipped bits invisible

11 cycles
A comparison across eleven bit intervals of three received frames. The first shows the transmitted reference carrying A6 hexadecimal with an even parity bit of zero. The second shows the same frame with a single data bit flipped, which changes the exclusive-or reduction of the data and therefore violates the parity relation, so a parity error is raised. The third shows the same frame with two data bits flipped, producing the byte A5 hexadecimal; because two flips leave the reduction unchanged, the parity relation still holds and no parity error is raised even though the delivered byte is wrong.protected data intervalsprotected data intervalsparity interval IDENTICAL in all threeparity interval IDENTICALin all three1 flip -> detected1 flip -> detected2nd flip -> now invisible2nd flip -> now invisiblefieldSTARTd0d1d2d3d4d5d6d7PARSTOPsent 0xA61 flip2 flipsparity_error_ot0t1t2t3t4t5t6t7t8t9t10
Figure 1 — the same frame under three corruptions. Columns are BIT INTERVALS. The first row is the transmitted reference; the second flips one data bit and is detected; the third flips two and is not. Note that the third row's payload differs from the reference in two positions and the receiver's parity check reports no error at all — the parity interval itself is identical in all three cases.

5. What a Passing Check Licenses

This is the distinction that matters most in practice.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
parity_error == 0    means:  the parity relation over the protected set holds.
parity_error == 0    does NOT mean:  the data is correct.

The gap contains every even-weight corruption — which, for a disturbance that flips bits independently, is about half of all possible corruption events. A design that treats a clean parity check as an integrity guarantee has adopted a mechanism that is wrong roughly half the time it matters.

What parity is legitimately good for:

Cheap detection that something is wrong at all. One interval of overhead and an XOR tree. On a link that is mostly clean, a parity error is a genuine signal that conditions have changed.

Distinguishing corruption from misalignment, when read alongside the framing error. That combination is diagnostically sharp, and §7 develops it.

What it is not good for: anything requiring assurance that delivered data is intact. That needs a checksum or CRC over a larger unit, which is a layer above UART — Ethernet's frame check sequence is the same argument made for a mechanism with a much stronger guarantee, and the contrast is instructive.

And parity costs a real interval. Chapter 4.1 computed the overhead: 8E1 is eleven intervals against 8N1's ten, so parity costs 9.1% of throughput and lowers the clock-mismatch tolerance because N_frame grows (Chapter 5.5). It is not free, and "enable parity for safety" is a decision that should be made with its guarantee and its price both on the table.

6. Verification

The undetected case is the most important test in the chapter, and it is the one most often missing. A suite that only injects single-bit errors verifies a property parity has and never exercises the boundary of what it does not.

From this module's simulation, with a positive control for every negative case:

StimulusExpectedResult
clean frame, even parityno errorpass
clean frame, odd parityno errorpass
one data bit flippeddetectedpass
parity bit itself flippeddetectedpass
two data bits flipped, 0xA6 → 0xA5NOT detectedpass
three data bits flippeddetectedpass

The fifth row asserts that the checker does not fire. Writing a test whose pass condition is silence feels wrong and is exactly right: it pins the mechanism's boundary, and it fails loudly if someone later "improves" the checker into something that cannot be a parity check.

Test both modes with odd-weight data. Chapter 6.6 §8 noted that six of the eight standard patterns have even weight, so a parity suite drawn from them exercises one branch of the mode select. 0x01 and 0x80 are the two that exercise the other.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — a mismatch against the configured expectation always fires.
property p_mismatch_detected;
    @(posedge clk) disable iff (!rst_n)
        (check_en_i && (parity_bit_i != expected)) |=> parity_error_evt_o;
endproperty
assert property (p_mismatch_detected);

// Assertion — and a match never does. The positive control, formalised.
property p_match_silent;
    @(posedge clk) disable iff (!rst_n)
        (check_en_i && (parity_bit_i == expected)) |=> !parity_error_evt_o;
endproperty
assert property (p_match_silent);

// Assertion — the verdict is per-frame: it changes only at a check or a start.
property p_verdict_scope;
    @(posedge clk) disable iff (!rst_n)
        $changed(parity_error_o) |-> $past(check_en_i) || $past(frame_start_i);
endproperty
assert property (p_verdict_scope);

There is no assertion that parity detects corruption, because it is not true and cannot be written. The properties above are about the relation, which is what the hardware actually implements — and the discipline of only asserting what the mechanism guarantees is the same one Chapter 5.4 §7 applied to majority voting.

7. Debugging

8. What This Means on an FPGA

The check is an XOR tree and a comparator, evaluated once per frame at the parity sample. Three levels of logic at eight bits, nowhere near any critical path, and it never sits between a register and a pin.

Instrument the pair, not the bit. A parity-error counter alongside a framing-error counter — four bits each — turns §7's table from a qualitative reading into a measurement. Their ratio is the diagnostic, and a single sticky bit cannot express it.

Do not enable parity reflexively. It costs an interval per frame — 9.1% of throughput at 8N1 versus 8E1 — and it reduces the clock-mismatch tolerance because the frame is longer. On a link whose real risk is rate drift rather than bit corruption, enabling parity makes the dominant failure mode more likely while adding a check that misses half of what it is aimed at.

If integrity actually matters, add a check above the byte layer. A CRC over a multi-byte message costs a few bytes per message rather than 9% of every frame, and its guarantee is qualitatively different rather than marginally better.

9. Understanding Check

10. Summary

Parity tests one relation: the weight parity of the protected set — the data bits plus the parity bit. The receiver applies the same rule the transmitter did, evaluated once at the parity sample.

The detection limit, established exhaustively rather than asserted: all 9 single-bit corruptions detected, all 36 two-bit corruptions missed, all 84 three-bit detected, all 126 four-bit missed. Every odd weight caught, every even weight invisible — because XOR is addition modulo two and flipping k bits changes the reduction by k mod 2.

This has nothing to do with severity. The smallest corruption is caught and a four-bit one is not. 0xA6 → 0xA5 — two flipped bits, both bytes in the standard test set — passes every check the receiver performs and is delivered as clean.

A passing check licenses one statement: the relation holds. Not that the data is correct. The gap is roughly half of all corruption events.

Parity is a cheap indicator, not an integrity mechanism, and it costs 9.1% of throughput plus a reduction in clock-mismatch tolerance because the frame grows.

Read it against the framing error: parity without framing means corruption or a mode mismatch; framing without parity means drift with good data; both means the receiver is sampling in the wrong places; neither, with wrong data, means the corruption was even-weight and nothing in a bare UART frame was watching.

A parity error rate near 50% on random data is a mode mismatch, because exactly the odd-weight bytes fail.

11. What Comes Next

Framing and parity both describe a frame that arrived. Neither says anything about a frame that arrived correctly and was then lost because nobody read it.

Chapter 9.3 takes that failure. It defines overrun precisely against the one-entry holding register, shows which byte survives under the policy Chapter 6.5 chose and why, distinguishes it from the transmit-side starvation that is architecture-dependent rather than a protocol event, and makes the point an error flag cannot: a status bit records that data was lost and cannot recover it.

Browse the full path on the UART tutorials index. For parity's construction and its cost in overhead, read back to Chapter 3.3; for a check with a genuinely stronger guarantee, see Why CRC Exists.

Continue learning

Where this fits

Part of the UART curriculum.