Skip to content
VLSI Mentor

UART · Module 9

Overrun, Underrun and Data Loss

Overrun is a protocol-level consequence of a full holding register; transmit starvation is an architecture-dependent system event. Both lose information an error flag records but cannot recover.

Chapter 9.1 and Chapter 9.2 covered frames that arrived and were wrong. This chapter covers a frame that arrived perfectly and was lost, because nothing read it in time.

That is a different kind of failure and it deserves different language. A framing error is a statement about the wire. An overrun is a statement about the system — about the relationship between how fast bytes arrive and how fast something consumes them — and the wire was blameless throughout.

Chapter 6.5 established the receiver's side of this and chose a policy. This chapter states the taxonomy properly, isolates the holding register so the policy can be tested on its own, and makes a point no error flag can make for itself:

A status bit records that data was lost. It does not tell you what was lost, and nothing can recover it.

1. Three Failures, Not One

These get lumped together as "data loss" and they have different mechanisms, different owners and different fixes.

Receive overrunTransmit starvationLoss in flight
What happensa completed byte cannot be storedno byte available when one could be senta byte was corrupted on the wire
Causethe consumer is behindthe producer is behindelectrical or timing
Detected bythe receiver, exactlythe transmitter, if it chooses toframing or parity — imperfectly
Is it a protocol event?the consequence is — a byte the far end sent is goneno — the line simply goes idleyes
Recoverable?nonothing was lostno
Ownerthis chapter, then Module 10§5 — architecture-dependent9.1 and 9.2

Only the first is an error in the sense the other chapters use the word. The second is usually not an error at all, and §5 is about why calling it one causes confusion.

2. Overrun, Defined

Against the one-entry holding register Chapter 6.5 established:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
OVERRUN occurs when a frame completes AND the holding register is
occupied AND it is not being emptied on that same clock edge.

Three conditions, all necessary. The third matters: a consumer accepting on the very cycle a new byte arrives frees the slot in time, and that is not an overrun — a distinction Chapter 6.5 §4 built into the ordering of its assignments and §4 below preserves.

The timing budget is generous and exact. At 115,200 baud an 8N1 frame is ten intervals — 86.81 µs (Chapter 4.1). That is how long the consumer has, from the moment a byte is presented, before the next one can complete. At a 100 MHz fabric clock that is 8,681 clock cycles.

A consumer missing that budget is almost never too slow to execute. It is blocked, inside a long interrupt handler, or polling on a timer slower than the line rate — which is why the overrun flag is a pointer at software scheduling rather than at the UART.

3. The Policy: Which Byte Survives

Two frames, one slot. Something is lost, and the design must choose what:

PolicyBehaviourConsequence
Overwritethe new byte replaces the held onethe byte the consumer was already told about disappears
Preserve and drop (this design)the held byte stays, the new one is discardedthe newer byte is lost

Both lose exactly one frame, so the choice is not about quantity. It is about which loss the consumer can reason about, and that is the argument Chapter 6.5 §4 made:

The held byte has already been announced. data_valid_o is high and the consumer may be partway through reading it. Replacing the data underneath produces a torn read the consumer cannot detect, and it violates the stability property that lets data and status be collected across several cycles.

Overwriting loses a byte the consumer knew about; dropping loses one it never saw. A consumer that reads one byte and is told a later one was lost has a coherent picture. A consumer that reads a byte different from the one it was offered has been misinformed.

The opposite policy is defensible for a consumer that prefers freshness to coherence — telemetry where the latest sample matters and history does not. What is not defensible is silently overwriting, which is what a design does when nobody decided.

4. The RTL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  9.3 — one-entry receive holding register.
//  POLICY: preserve the held byte, DROP the newly arrived one, flag overrun.
//  Same policy as Chapter 6.5; isolated here so it can be stated and tested.
// ---------------------------------------------------------------------------
module uart_rx_holding #(
    parameter int unsigned DATA_W = 8
) (
    input  logic              clk,
    input  logic              rst_n,
    input  logic              rx_char_valid_i,   // one cycle: a frame completed
    input  logic [DATA_W-1:0] rx_char_data_i,
    output logic [DATA_W-1:0] data_o,
    output logic              data_valid_o,
    input  logic              data_ready_i,
    output logic              overrun_evt_o      // ONE cycle: a character was dropped
);
    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            data_o        <= '0;
            data_valid_o  <= 1'b0;
            overrun_evt_o <= 1'b0;
        end else begin
            overrun_evt_o <= 1'b0;

            // Accept first, so a same-cycle arrival can refill the slot.
            if (data_valid_o && data_ready_i)
                data_valid_o <= 1'b0;

            if (rx_char_valid_i) begin
                if (data_valid_o && !data_ready_i) begin
                    // Slot occupied and not being emptied this cycle: the
                    // NEW character is lost. The held one is not disturbed.
                    overrun_evt_o <= 1'b1;
                end else begin
                    data_o       <= rx_char_data_i;
                    data_valid_o <= 1'b1;
                end
            end
        end
    end
endmodule

The accept is written before the store, deliberately. Both are non-blocking assignments in the same always_ff, so the later write wins — and the later write is the store. That ordering is what makes the third condition of §2 work: a consumer accepting on the same edge that a new byte arrives frees the slot and the new byte lands, with no gap and no spurious overrun. Reversing the two would drop a byte that should have been delivered, intermittently, under exactly the traffic pattern that is hardest to reproduce.

overrun_evt_o is a one-cycle pulse, not a level — the same shape Chapter 9.1 §2 established for every error source in this module, so Chapter 9.6 can aggregate them uniformly.

The held data is not touched on the overrun path. That is the policy of §3 expressed structurally rather than by comment: the else branch is the only place data_o is written.

The hardware is DATA_W flip-flops, one valid bit, and a small amount of control. Nine flip-flops at DATA_W = 8, plus the event pulse. This is the smallest storage that makes the problem observable, and deliberately not one byte more.

The timeline, from simulation

cyclechar_validdata_ovalidreadyoverrunevent
2100000frame A completes
303C100A presented
513C100frame B completes, A still held
603C101B dropped, A untouched
903C110consumer accepts
1003C000slot free
1313C000frame C completes
1405A100C stored normally

At cycle 6 the data does not move. That single row is the policy: data_o stays at 0x3C while the overrun event fires, and frame B — a perfectly well-formed frame that arrived without a framing or parity error — is gone.

Frame B lost while frame A is held

16 cycles
A trace of sixteen fabric clock cycles from a one-entry receive holding register. At cycle two a character-valid pulse stores the byte 3C hexadecimal, which appears on the data output with valid asserted at cycle three. The consumer does not assert ready, so the byte is held. At cycle five a second character-valid pulse arrives while the register is still occupied and not being emptied, so at cycle six the overrun event pulses while the data output remains unchanged at 3C — the newly arrived byte is discarded and never appears. At cycle nine the consumer asserts ready and valid clears at cycle ten. A third character arriving at cycle thirteen is stored normally and appears as 5A hexadecimal at cycle fourteen.A held — consumer behindA held — consumer behindB completes — slot occupiedB completes — slot occupiedB DROPPED, data unchangedB DROPPED, data unchangedC stored normallyC stored normallyrx_char_valid_idata_o0000003C3C3C3C3C3C3C3C3C3C3C5A5Adata_valid_odata_ready_ioverrun_evt_ot0t1t2t3t4t5t6t7t8t9t10t11t12t13t14t15
Figure 1 — the overrun, from simulation. Columns are FABRIC CLOCK CYCLES. The held byte is visibly unchanged across the overrun event: the flat bus row through cycle 6 is the preserve-and-drop policy made observable, and frame B never appears on the output at all.
A sequence showing how a receive overrun occurs. The far end transmits the first frame on its own schedule and the receiver assembles it correctly, presenting the byte to its consumer with valid asserted. The consumer does not accept it, so the byte is held. The far end, which has no knowledge of the receiver's state, transmits a second frame. The receiver assembles that frame correctly too, and it passes its framing and parity checks, but the holding register is still occupied and is not being emptied, so the newly assembled byte is discarded and an overrun event is pulsed. The held byte is left untouched. When the consumer finally accepts, it receives the first byte and the overrun indication, and the second byte no longer exists anywhere in the design.Far endReceiverHolding regConsumerframe A — on its ownscheduleassembled, framing +parity cleandata_valid_o = 1busy — does notacceptframe B — far endknows nothingassembled, ALSOcleanslot occupied ->DROP B, keep Aoverrun_evt_o — B isgone, unrecoverable
Figure 2 — the overrun as a sequence. Time runs downward. The far end transmits on its own schedule and is never informed of anything; the consumer's failure to accept is entirely local. Frame B completes correctly on the wire and is destroyed inside the receiver, which is why the flag points at scheduling rather than at the link.

5. Underrun Is Not a UART Error

This is where the taxonomy earns its place, because "underrun" is applied to two very different things.

On the transmit side of a basic UART, running out of data is not an error at all. Chapter 7.4 built a transmitter that asserts tx_ready_o when it can accept a byte; if the producer offers none, the transmitter finishes the frame in flight, returns to idle, and holds the line at mark. That is the correct steady state of an idle link. Nothing was lost, nothing was corrupted, and the far end sees exactly what a healthy quiet line looks like.

Calling that an underrun creates a false alarm on every link that is not saturated — which is most of them.

A starvation event becomes meaningful only when a system contract says the stream must be continuous. A DMA-fed transmitter that is supposed to deliver a fixed-rate stream, or a protocol layer above UART that requires back-to-back frames, has such a contract — and then "the producer failed to supply a byte before the transmitter needed one" is a real, reportable system event.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
protocol violation      : a byte the far end sent was destroyed  (receive overrun)
system availability     : a byte our own producer failed to supply (transmit starvation)

The first is about the link. The second is about us. They deserve different names, different status bits and different responses, and a design that reports both through one "underrun/overrun" pair will confuse the two in the field.

Where it does apply, the smallest honest definition is: the transmitter reached a point where it could have begun a frame, the system contract said one should have been available, and none was. That requires the contract to exist — which is why this cannot be a universal UART status bit, and why the base transmitter of Module 7 has none.

6. A Flag Cannot Recover the Data

Worth stating plainly because the presence of a status bit invites the opposite assumption.

overrun_evt_o records that a byte was lost. It does not record which byte, how many, or anything about their content — and no amount of status can, because the information was never stored. Frame B in §4's trace existed on the wire, was assembled correctly, passed its framing and parity checks, and was then discarded because there was nowhere to put it.

The consequences follow directly:

A byte stream with an overrun has a hole of unknown size. If the consumer stalls for three frame times, three bytes are lost and the flag looks identical to a single loss. A counter distinguishes those and is strictly more useful than a flag — one reason Chapter 9.6 treats aggregation as a design decision rather than a formality.

A protocol above UART cannot resynchronise from a byte stream with silent holes unless it carries its own framing. This is the practical argument for message-level structure — length fields, delimiters, sequence numbers — on any UART link that matters, and it is the same argument Chapter 6.2 §7 made when a mid-frame reset fabricated a byte.

Buffering moves the threshold; it does not remove it. Chapter 6.5 §7 made this point and it is worth repeating with numbers: one register overruns when the consumer is more than 86.81 µs behind; a sixteen-deep FIFO overruns when it is more than 1.39 ms behind. If the consumer's average rate is below the line rate, every depth eventually overflows — buffering converts a hard real-time requirement into an average-rate requirement, which is a genuine and large improvement and is not a solution. Module 10 chooses the depth with the facts that decide it.

7. Verification

The positive control is the test that matters most here. An overrun detector that fires on every character passes every negative test:

StimulusExpectedResult
consumer ready, one characterno overrunpass
consumer stalled, first characterheld, valid assertedpass
consumer stalled, second characteroverrun eventpass
— same case, data outputunchanged — held byte preservedpass
consumer acceptsslot freed, valid clearspass

The fourth row is the policy under test, not the flag. Asserting that overrun_evt_o rose proves something was noticed; asserting that data_o did not change proves which byte survived, which is the design decision §3 made.

Sweep the consumer's latency across a frame time. Accepting at 0, half, one and two frame times after valid covers the comfortable case, the marginal case, the first overrun and sustained loss. The boundary at exactly one frame time is where a design with extra delivery latency begins failing.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — an overrun is reported ONLY when the slot was genuinely
// unavailable. This is the positive control expressed formally.
property p_overrun_only_when_blocked;
    @(posedge clk) disable iff (!rst_n)
        overrun_evt_o |-> $past(rx_char_valid_i && data_valid_o && !data_ready_i);
endproperty
assert property (p_overrun_only_when_blocked);

// Assertion — the held byte is never disturbed by an overrun.
// This is the retention policy, stated as a property.
property p_overrun_preserves_data;
    @(posedge clk) disable iff (!rst_n)
        overrun_evt_o |-> $stable(data_o);
endproperty
assert property (p_overrun_preserves_data);

// Assertion — a character arriving into a free slot is always stored.
// Nothing accepted may be silently dropped.
property p_free_slot_always_stores;
    @(posedge clk) disable iff (!rst_n)
        (rx_char_valid_i && (!data_valid_o || data_ready_i)) |=> data_valid_o;
endproperty
assert property (p_free_slot_always_stores);

The third is the one that catches an over-eager overrun condition: a detector that treats any arrival while data_valid_o is high as an overrun drops the same-cycle-accept case, and this property fails on it immediately.

8. Debugging

9. What This Means on an FPGA

The block is nine flip-flops plus control, and the overrun detection is three AND terms. There is no version of this that is too expensive to include.

Probe data_valid_o and data_ready_i together. Valid high and never falling is a consumer that is not accepting — immediately distinguishable from every line-side fault. Adding overrun_evt_o to the capture says whether data has already been lost.

Bring out a saturating overrun counter during bring-up. Four bits is enough to tell "one hiccup" from "continuously losing", and it costs nothing next to a UART.

Consider where the consumer actually is. If the consuming logic is software, the 86.81 µs budget is an interrupt-latency requirement and belongs in the system's timing analysis rather than the UART's. If it is hardware, it is usually a handshake that stalls — and a hardware consumer that cannot accept within thousands of cycles is doing something that deserves examination on its own.

10. Understanding Check

11. Summary

Three failures, not one. A receive overrun destroys a byte the far end really sent; transmit starvation is usually not an error at all; loss in flight is what 9.1 and 9.2 cover.

Overrun requires three conditions: a frame completes, the register is occupied, and it is not being emptied on that edge. The third makes a same-cycle accept legal, and a detector that omits it drops bytes under prompt back-to-back traffic.

The deadline is one frame time — 86.81 µs at 115,200 8N1, or 8,681 cycles at 100 MHz. A consumer missing that is blocked, not slow.

This design preserves the held byte and drops the new one, because the held byte was already announced and overwriting it produces an undetectable torn read. Simulation shows data_o flat at 0x3C across the overrun event while frame B disappears entirely.

Underrun is not a universal UART error. A transmitter with nothing to send goes idle, which is correct. Starvation is meaningful only where a system contract demands a continuous stream — a statement about our producer, not about the link.

A flag records that data was lost and cannot recover it, or even count it. A byte stream with silent holes of unknown size is why a UART link carrying structured data needs its own framing above the byte layer.

Buffering moves the threshold: one register tolerates 86.81 µs, sixteen entries tolerate 1.39 ms, and no depth survives a consumer whose average rate is below the line rate.

12. What Comes Next

Every failure so far has been a frame that went wrong. The next one is a line that stops carrying frames at all.

Chapter 9.4 takes the break condition: a line held at the start-bit level past a full frame. It is the one condition that cannot be defined by a single sample — a start bit is also low — so detection requires duration, which means a counter, a threshold derived from the frame configuration, and a saturation policy so a long break cannot wrap and un-declare itself. It also separates a deliberately generated break from a stuck wire, which look identical to the receiver and are not the same problem.

Browse the full path on the UART tutorials index. For the receiver interface this chapter isolates, read back to Chapter 6.5.

Continue learning

Where this fits

Part of the UART curriculum.