Skip to content
VLSI Mentor

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:

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

AccumulateReduce at the end
Logicone XOR gatean XOR tree over DATA_W bits
Availablecontinuouslyonly after the last store
Timing pathone gate from the sampled bittree depth from shreg_q
Extra stateone flip-flopnone

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:

QuestionAnswerWhy
Initial value?0, loaded at the start candidateThe XOR identity. Any other value inverts the sense of every check.
When does it update?Only on a data-bit store, in S_DATAIt accumulates the payload, which is what parity protects.
Does the received parity bit enter it?NoIt is the value being compared against, not part of the protected set.
When is it read?At the parity sample, before any further updateS_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:

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

Machine-computed, for the standard test patterns at DATA_W = 8:

bytebinaryones^deven parity bitodd parity bit
0x000000_00000001
0xFF1111_11118001
0x550101_01014001
0xAA1010_10104001
0x010000_00011110
0x801000_00001110
0xA51010_01014001
0xA61010_01104001
0x530101_00114001

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rx_frame_err_o <= !rx_sync_q;      // a framing error is: stop was not mark

One comparison. The care goes into what it means.

Stop sampled at space — framing error

11 cycles
A trace of eleven sample events during reception of a frame. The start qualification succeeds and eight data bits are sampled correctly, assembling the payload A6 hexadecimal. The parity interval matches, so no parity error is raised. The final sample, at the stop interval, finds the line at the space level where a mark level is required, so the framing error flag is raised. The assembled data is nevertheless delivered together with that flag, because the data bits themselves were sampled correctly and only the validation field differed.everything correct hereeverything correct herevalidation failsvalidati…failspayload correct = 0xA6payload correct = 0xA6parity matchesparity matchesstop = space — FRAMING ERRORstop = space — FRAMINGERRORfieldSTARTd0d1d2d3d4d5d6d7PARSTOPrx_sync_qshreg_q000080C06030984CA6A6A6frame_errt0t1t2t3t4t5t6t7t8t9t10
Figure 1 — a framing error. Columns are SAMPLE EVENTS, one per bit interval centre, not fabric-clock cycles. The eight data bits are sampled correctly and the payload is assembled correctly; only the stop interval is at the wrong level. The receiver records the framing error and still delivers the byte, because whether a corrupt frame is useful is the consumer's decision and not the receiver's.

The 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 stopChapter 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.

A receiver's validation datapath split into a private region and a published region. In the private region, a parity accumulator running over the data bits feeds a comparator against the expected parity for the configured mode, whose result is stored in an internal per-frame parity error register. The stop sample feeds a level comparison against the mark level. The assembled shift register holds the payload. A single publish event, occurring only when a frame is accepted into the holding register, transfers the payload and both status results together into the output data register and the output status flags, which form the consumer's view. Because that publish event is the only path from the private region to the published region, the consumer can never observe data and status belonging to different frames.par_acc_qXOR over payloadcomparevs expected_parity()par_err_qprivate, per-frameshreg_qassembled payloadPUBLISHone edge, S_STOP onlydata + flagsthe consumer's viewaccmismatchpayloadheld resulttogether12
Figure 2 — the publish discipline. Everything to the left of the dashed boundary is computed while the frame is still arriving and is private to that frame; everything to the right is the consumer's view. The single publish event is the only path across, so data and both status flags cross together or not at all — which is what makes a torn combination unobservable rather than merely unlikely.

The rule generalises past parity:

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

FlagMeansValid whenCleared when
rx_parity_err_othe parity interval of this byte's frame did not matchrx_valid_o is highthe byte is accepted
rx_frame_err_othe stop interval of this byte's frame was not markrx_valid_o is highthe byte is accepted
rx_overrun_oat least one frame was lost while this byte was heldrx_valid_o is highthe 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:

PolicyBehaviourSuits
Deliver with flags (this receiver)data + status presented togethera consumer that can decide per byte
Suppress validcorrupt frames vanisha link where corrupt data is never useful
Deliver with metadataa wider interface carrying a reason codean 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:

Stimulusrx_data_oparity_errframe_err
0xA6, even, clean0xA600
0xA6, even, parity inverted0xA610
0x53, odd, parity inverted0x5310
0xA6, no parity, stop at space0xA601
0xA6, even, stop at space0xA601

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.

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

Where this fits

Part of the UART curriculum.