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 overrun | Transmit starvation | Loss in flight | |
|---|---|---|---|
| What happens | a completed byte cannot be stored | no byte available when one could be sent | a byte was corrupted on the wire |
| Cause | the consumer is behind | the producer is behind | electrical or timing |
| Detected by | the receiver, exactly | the transmitter, if it chooses to | framing or parity — imperfectly |
| Is it a protocol event? | the consequence is — a byte the far end sent is gone | no — the line simply goes idle | yes |
| Recoverable? | no | nothing was lost | no |
| Owner | this chapter, then Module 10 | §5 — architecture-dependent | 9.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:
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:
| Policy | Behaviour | Consequence |
|---|---|---|
| Overwrite | the new byte replaces the held one | the byte the consumer was already told about disappears |
| Preserve and drop (this design) | the held byte stays, the new one is discarded | the 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
// ---------------------------------------------------------------------------
// 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
endmoduleThe 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
| cycle | char_valid | data_o | valid | ready | overrun | event |
|---|---|---|---|---|---|---|
| 2 | 1 | 00 | 0 | 0 | 0 | frame A completes |
| 3 | 0 | 3C | 1 | 0 | 0 | A presented |
| 5 | 1 | 3C | 1 | 0 | 0 | frame B completes, A still held |
| 6 | 0 | 3C | 1 | 0 | 1 | B dropped, A untouched |
| 9 | 0 | 3C | 1 | 1 | 0 | consumer accepts |
| 10 | 0 | 3C | 0 | 0 | 0 | slot free |
| 13 | 1 | 3C | 0 | 0 | 0 | frame C completes |
| 14 | 0 | 5A | 1 | 0 | 0 | C 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 cycles5. 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.
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:
| Stimulus | Expected | Result |
|---|---|---|
| consumer ready, one character | no overrun | pass |
| consumer stalled, first character | held, valid asserted | pass |
| consumer stalled, second character | overrun event | pass |
| — same case, data output | unchanged — held byte preserved | pass |
| consumer accepts | slot freed, valid clears | pass |
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.
// 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
Related tutorials
- Related topic
Data-Valid Generation and the Receiver Handshake
A receiver cannot tell the far end to wait. That one fact decides the shape of the output interface, forces a policy for the frame that arrives while the last is still held, and explains why the answer is not a FIFO.
- 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
Break Conditions: Generation and Detection
A start bit is also low, so a break cannot be detected by looking at the line. It is the one UART condition defined by duration — which means a counter, a derived threshold, and a saturation policy.
Where this fits
Part of the UART curriculum.
