UART · Module 6
Parity Check, Stop-Bit Validation and Error Status
The receiver decides whether a frame was good, then faces the harder question: which frame does each status flag describe? Getting that wrong lets an incoming frame rewrite the status of a byte the consumer has not yet read.
Chapter 6.3 left the receiver able to assemble a byte and unable to say whether the byte is any good.
Two checks answer that, and both are mechanically trivial. Parity is an XOR reduction and a comparison (Chapter 3.3 built it). Stop validation is reading one bit and testing it against a constant. Neither is where the difficulty lies.
The difficulty is the question that follows: which frame does each status flag describe?
A flag without a stated lifetime is not an interface. And the natural implementation — latch the parity result when you compute it — has a defect that only appears when frames arrive faster than the consumer reads them, which is exactly when a receiver's status matters most. The implementation in Chapter 6.6 had to be corrected for it during development, and §6 shows both versions.
1. The Parity Accumulator
Chapter 3.3 established the rule at the transmitter: for payload d, the quantity that matters is ^d, the XOR reduction, which is 1 when the payload holds an odd number of ones.
A receiver could assemble the byte and then apply ^shreg_q at the parity sample. Reusing the transmitter's generator that way is what Chapter 3.3 §4 recommended, and it remains correct. The alternative is to accumulate as the bits arrive:
// One XOR, updated on the same event that stores the bit — Chapter 6.3 §8.
par_acc_q <= par_acc_q ^ rx_sync_q;Both produce the same value. They differ in when the value is available and in what they cost:
| Accumulate | Reduce at the end | |
|---|---|---|
| Logic | one XOR gate | an XOR tree over DATA_W bits |
| Available | continuously | only after the last store |
| Timing path | one gate from the sampled bit | tree depth from shreg_q |
| Extra state | one flip-flop | none |
At DATA_W = 8 neither choice matters — an eight-input XOR tree is three levels and closes at any frequency a UART runs at. The accumulator is used here because it keeps the parity path identical in depth regardless of DATA_W, and because it makes the four semantic questions below explicit rather than implicit.
Four questions the accumulator must answer, and a design that leaves any of them to the reader has a defect waiting:
| Question | Answer | Why |
|---|---|---|
| Initial value? | 0, loaded at the start candidate | The XOR identity. Any other value inverts the sense of every check. |
| When does it update? | Only on a data-bit store, in S_DATA | It accumulates the payload, which is what parity protects. |
| Does the received parity bit enter it? | No | It is the value being compared against, not part of the protected set. |
| When is it read? | At the parity sample, before any further update | S_PARITY performs no store, so the value is stable. |
The third is the one most often got wrong, and the symptom is distinctive: including the received parity bit makes the accumulator's final value the XOR of payload and parity, which is 0 for a good even-parity frame and 1 for a good odd one. A receiver that then compares that against the received bit reports a parity error on every correctly-formed frame, and flipping the comparison to "fix" it reports success on every frame including corrupt ones.
2. The Expected-Parity Relation
Chapter 3.3's conventions, applied at the receiver:
// Synthesizable SystemVerilog — the receiver's expectation, one function.
// Mirrors uart_parity_gen of Chapter 3.3 §4 exactly: the check must apply
// the SAME rule as the transmitter, and the way to guarantee that is to
// state it once.
function automatic logic expected_parity(input parity_mode_t m, input logic acc);
case (m)
PARITY_EVEN: expected_parity = acc; // total ones must be even
PARITY_ODD: expected_parity = ~acc; // total ones must be odd
default: expected_parity = 1'b1; // MARK — constant, no detection
endcase
endfunctionMachine-computed, for the standard test patterns at DATA_W = 8:
| byte | binary | ones | ^d | even parity bit | odd parity bit |
|---|---|---|---|---|---|
0x00 | 0000_0000 | 0 | 0 | 0 | 1 |
0xFF | 1111_1111 | 8 | 0 | 0 | 1 |
0x55 | 0101_0101 | 4 | 0 | 0 | 1 |
0xAA | 1010_1010 | 4 | 0 | 0 | 1 |
0x01 | 0000_0001 | 1 | 1 | 1 | 0 |
0x80 | 1000_0000 | 1 | 1 | 1 | 0 |
0xA5 | 1010_0101 | 4 | 0 | 0 | 1 |
0xA6 | 1010_0110 | 4 | 0 | 0 | 1 |
0x53 | 0101_0011 | 4 | 0 | 0 | 1 |
Note what this table shows about test selection: seven of these nine bytes have the same parity. A parity test set built from 0x00, 0xFF, 0x55 and 0xAA exercises exactly one of the two branches, and a receiver with PARITY_ODD and PARITY_EVEN swapped passes every one of them. 0x01, 0x80 and any other odd-weight byte are what make the test set complete.
PARITY_MARK performs no detection, as Chapter 3.3 §3 established — it is a constant 1 and the comparison is against a constant. It is supported because real configurations use it for framing compatibility, and the receiver reports a mismatch if the constant is wrong, which detects a gross framing error and nothing finer.
3. Stop Validation
The stop interval is validated at the sample Chapter 6.3 scheduled for it — 10.5 UI from the candidate in an 8E1 frame, 9.5 UI in 8N1:
rx_frame_err_o <= !rx_sync_q; // a framing error is: stop was not markOne comparison. The care goes into what it means.
Stop sampled at space — framing error
11 cyclesThe payload is still correct, and that is not an accident of this example. A framing error says the stop interval was wrong; it says nothing about the data intervals, which were sampled at their own centres and stored normally. Chapter 5.5 §3 showed the most common cause — accumulated clock mismatch, which displaces the last interval furthest — and in that scenario the data is genuinely correct and only the final sample has drifted past its boundary. A receiver that discarded data on a framing error would throw away good bytes.
4. Which Frame Owns the Status
Now the part that is actually hard.
The receiver has one holding register. A byte sits in it from the moment its frame completes until the consumer takes it. During that window, the line does not stop — Chapter 6.5 develops why — so another frame can be arriving, and completing, while the previous byte is still being held.
That produces a question every status flag must answer: does rx_parity_err_o describe the byte currently in rx_data_o, or the frame currently on the wire?
Only the first is usable. A consumer reads data and status together and must be able to trust that they belong to each other. If status can be rewritten by a later frame, then a byte that arrived clean can be read with an error flag set by a frame the consumer has never seen and will never receive.
The rule generalises past parity:
Compute status wherever it is convenient.
PUBLISH status only where the data it describes is published.Framing error happens to be computed in S_STOP, at the same moment data is published, so it needs no internal holding register — but it is published in the same branch, under the same condition, for the same reason. The symmetry is deliberate: a reviewer should be able to see at a glance that every output belonging to a frame is written in one place.
5. The Status Set, and Its Stated Lifetime
Three flags, each with a lifetime the consumer can rely on without reading the implementation — the test Chapter 6.1 §1 set:
| Flag | Means | Valid when | Cleared when |
|---|---|---|---|
rx_parity_err_o | the parity interval of this byte's frame did not match | rx_valid_o is high | the byte is accepted |
rx_frame_err_o | the stop interval of this byte's frame was not mark | rx_valid_o is high | the byte is accepted |
rx_overrun_o | at least one frame was lost while this byte was held | rx_valid_o is high | the byte is accepted |
Three properties hold across all of them, and they are what make the set usable:
They are meaningless when rx_valid_o is low. There is no byte, so there is nothing for them to describe. A consumer polling status without checking valid is reading a stale frame's result.
They are published together with the data, on one clock edge. No consumer can observe a torn combination.
They describe exactly one frame. Not a running history — that is the sticky-flag behaviour real UART IP exposes in a status register, and Module 9 owns it, built on top of these per-frame flags rather than instead of them.
The last distinction is worth stating plainly because production UARTs blur it. A 16550-style status register has bits that accumulate until read, which is a different contract serving a different consumer: software polling occasionally wants to know whether anything went wrong since it last looked. Both contracts are legitimate and they are not interchangeable. This receiver provides the per-frame one because it is the primitive; a sticky register is built from it by OR-accumulating on each accepted byte, and cannot be recovered from a sticky register going the other way.
6. The Delivery Policy for a Bad Frame
A frame with a parity or framing error still produces data. That is a choice, and the protocol does not make it — Chapter 3.3 was explicit that parity's guarantee is about detection, not about what to do next.
Three policies are defensible:
| Policy | Behaviour | Suits |
|---|---|---|
| Deliver with flags (this receiver) | data + status presented together | a consumer that can decide per byte |
| Suppress valid | corrupt frames vanish | a link where corrupt data is never useful |
| Deliver with metadata | a wider interface carrying a reason code | an IP with a register model — Module 13 |
This receiver delivers with flags, for a reason worth stating: suppressing valid makes a corrupt frame indistinguishable from no frame at all. A link that is failing then looks exactly like a link that is idle, and the most common real symptom — "the device stopped responding" — loses the information that would have diagnosed it in seconds. Delivering the byte with rx_frame_err_o set turns a silent failure into a reported one, and the consumer that genuinely wants corrupt frames discarded can discard them in one line.
What is not defensible is leaving the policy implicit. A receiver that suppresses valid on framing errors but delivers on parity errors — a combination that arises naturally from writing the two checks at different times — has a policy no one chose and no one documented.
7. Verification
Corrupt each field independently. Parity and stop are separate checks and must be shown to be independent: a parity error must not raise rx_frame_err_o, and a framing error must not raise rx_parity_err_o. Simulation of this receiver confirms both, and confirms the payload is delivered correctly in each case:
| Stimulus | rx_data_o | parity_err | frame_err |
|---|---|---|---|
0xA6, even, clean | 0xA6 | 0 | 0 |
0xA6, even, parity inverted | 0xA6 | 1 | 0 |
0x53, odd, parity inverted | 0x53 | 1 | 0 |
0xA6, no parity, stop at space | 0xA6 | 0 | 1 |
0xA6, even, stop at space | 0xA6 | 0 | 1 |
Every row delivers the correct payload, which is the point §3 made: the data intervals were sampled correctly and only the validation field differed.
Test both parity branches. §2's table shows that seven of the nine standard patterns share a parity, so a set built from 0x00/0xFF/0x55/0xAA leaves the odd-weight branch entirely unexercised and a mode-swap defect undetected.
Stall the consumer, then corrupt the next frame. This is the test that catches §4's defect and the only one that does. Hold a clean byte, drive a frame with bad parity, and assert that the held byte's status is still clean. A testbench that reads promptly cannot fail this test because it never creates the condition.
// Assertion — status is only meaningful alongside a byte.
property p_status_implies_valid;
@(posedge clk) disable iff (!rst_n)
(rx_parity_err_o || rx_frame_err_o || rx_overrun_o) |-> rx_valid_o;
endproperty
assert property (p_status_implies_valid);
// Assertion — THE property that fails on the §4 defect. While a byte is
// held and unaccepted, its status cannot change.
property p_status_stable_while_held;
@(posedge clk) disable iff (!rst_n)
(rx_valid_o && !rx_ready_i) |=>
$stable(rx_parity_err_o) && $stable(rx_frame_err_o);
endproperty
assert property (p_status_stable_while_held);
// Assertion — a parity error is impossible when no parity interval exists.
property p_no_parity_err_without_parity;
@(posedge clk) disable iff (!rst_n)
(rx_valid_o && parity_mode_i == PARITY_NONE) |-> !rx_parity_err_o;
endproperty
assert property (p_no_parity_err_without_parity);The second property is the chapter in one line, and it is worth writing even in a design believed correct — it costs nothing and it fails loudly on a class of defect that directed tests systematically miss.
8. Debugging
9. What This Means on an FPGA
The parity path is one XOR and a flip-flop, and it does not grow with DATA_W. The end-of-frame reduction alternative grows logarithmically, which still closes timing easily — this is not a place where the choice is made on timing.
Probe the three flags alongside rx_valid_o, never alone. §5's first property is the reason: the flags are meaningless when valid is low, and a logic analyser capture that shows them without it will produce an hour of confusion over a flag that was simply stale.
Do not build the sticky register here. It is tempting during bring-up to OR the flags into a persistent set so nothing is missed between captures. That is genuinely useful and it is Module 9's, because doing it in the receiver destroys the per-frame contract that everything above depends on. Build it as an observer alongside, reading the same accepted-byte event.
10. Understanding Check
11. Summary
Both checks are mechanically trivial. Parity is an accumulator — initialised to 0 at the frame's start, updated only on data-bit stores, never including the received parity bit, and read at the parity sample where no store occurs. The expected value is acc for even, ~acc for odd, and a constant for mark. Stop validation is one comparison against the mark level at the scheduled stop sample.
The stop bit does not resynchronise anything. It supplies a required level, frame separation and a validation opportunity. The next frame is timed from the next accepted start, and two stop bits therefore cost timing tolerance rather than improving it.
A framing error does not imply corrupt data — the data intervals were sampled at their own centres, and the usual cause displaces only the frame's last interval.
The chapter's real content is status ownership. The natural implementation writes the parity result to the output where it is computed, and that lets an incoming frame rewrite the status of a byte the consumer has not yet read — attributing an error to the one byte that survived while the frame that actually failed is dropped. The fix is a register discipline: compute anywhere, publish only where the data is published. This defect was in the first version of this receiver, and no testbench that reads promptly can expose it.
Each flag has a stated lifetime: meaningless without rx_valid_o, published on one edge with the data, describing exactly one frame. The sticky accumulation real UART IP exposes is a different contract built on top — Module 9 owns it.
And bad frames are delivered with their flags, because suppressing valid makes a failing link indistinguishable from an idle one.
12. What Comes Next
Every section above has leaned on a behaviour not yet built: that a byte is held until the consumer takes it, and that the line keeps moving while it waits.
Chapter 6.5 builds that interface. It compares a one-cycle valid pulse against a held handshake and justifies the choice rather than asserting it, establishes when a byte becomes architecturally valid, and confronts the consequence this chapter kept deferring: a UART receiver cannot tell the far end to wait. From that follows the overrun — what a single holding register must do when a frame completes and the previous byte is still there, why preserve and drop is chosen over overwrite, and why the answer is not a FIFO.
Browse the full path on the UART tutorials index. For parity's detection guarantee and the exact class of corruption it provably misses, read back to Chapter 3.3.
Continue learning
Related tutorials
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
- Related topic
Framing Errors
A framing error is one sampled bit at one instant. It proves the line was not at mark where the receiver expected mark — and nothing about why, which is what makes the diagnosis interesting.
- Related topic
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.
- Related topic
Stop Bits, Frame Boundaries and Back-to-Back Frames
The stop interval is a required mark condition the receiver checks at a known position — not a pause, not recovery time, and not a resynchronisation. What a framing error reports, what it cannot tell you, and why two frames may follow with no idle between them.
Where this fits
Part of the UART curriculum.
