Skip to content
VLSI Mentor

UART · Module 10

Overflow, Overrun and Backpressure

At the boundary the buffer is full and the line does not stop. Two events describe the same loss at different points, and the ready signal that backpressures a local producer has no path to the far end.

Chapter 10.3 built the warning. This chapter is about what happens when the warning is not enough — because the consumer is genuinely too slow, because the burst was larger than any headroom, or because nobody chose the threshold from a latency budget.

Two things need to be precise, and they are usually blurred together.

Which byte is lost. The FIFO is full and a character completes. Something must go, and which thing determines whether the consumer ends up with a coherent picture or a misleading one.

Why ready cannot help. A full FIFO drives ready low, which stops a local producer immediately and completely. It has no effect whatsoever on the far end, and understanding exactly why is what motivates the two chapters after this one:

ready = !full is a wire between two blocks in the same design. The transmitter that is about to overrun you is in a different device, on the other end of a cable, and has never heard of it.

1. Terminology, Stated

Datasheets use overflow and overrun inconsistently — sometimes interchangeably, sometimes for different events, occasionally with the senses reversed. The model used throughout Module 10:

TermMeaning hereProduced by
FIFO overflowa push was requested and not acceptedoverflow_evt_o, Chapter 10.2
RX overruna completed character could not be stored anywherethe receive path as a whole
FIFO underflowa pop was requested and not acceptedunderflow_evt_o

In a buffered receiver these are two views of one event. The character completes, the receiver tries to hand it to the FIFO, the FIFO is full and rejects the push — so overflow_evt_o fires. The same instant, a byte the far end really transmitted has been destroyed, which is an overrun in Chapter 9.3's sense.

They are not redundant. overflow_evt_o is a statement about the queue and is useful for sizing and instrumentation; the overrun is a statement about the link and is what software needs to know. A design that reports only the first tells software the buffer was busy; a design that reports only the second loses the ability to distinguish a queue that is merely full from one that is chronically undersized.

2. Exactly When Overflow Occurs

From Chapter 10.2 §4, the acceptance condition is:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
push_fire = push_i && (!full_o || pop_fire)

so the overflow event is its complement:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
overflow_evt_o  =  push_i && !push_fire
                =  push_i && full_o && !pop_fire

The !pop_fire term is the one that matters. A full FIFO with a consumer popping on the same edge accepts the push — the slot is free by the end of the cycle — and no overflow is reported, correctly. A design that defines overflow as push_i && full_o reports a loss that did not happen, on every cycle where producer and consumer coincide at the boundary, which is the steady state under sustained load.

This is the same boundary Chapter 10.2 §6 argued for on the acceptance side, and the two must agree: if the push is accepted, there is no overflow; if it is rejected, there is. Deriving one from the other rather than writing them independently is what keeps them consistent.

3. Which Byte Is Lost

The FIFO holds DEPTH bytes and a new one arrives. Two policies:

PolicyBehaviourWhat the consumer sees
Drop newest (this design)queue untouched, arriving byte discardeda contiguous run, then a gap
Overwrite oldestthe oldest queued byte is replaceda gap inside what looked like a contiguous run

This design drops the newest, and it is the same policy the one-entry receiver of Chapter 9.3 §3 chose, for a reason that scales:

The queued bytes are already committed. Everything in the FIFO has been accepted, ordered, and — in the RX case — paired with its per-character status. Overwriting the oldest destroys a byte the consumer was going to receive next, and it does so silently within the ordered stream, so the consumer reads a sequence that looks contiguous and is not.

Dropping the newest keeps the invariant that survives. What the consumer reads is a correct, in-order prefix followed by a reported gap. That is a picture a protocol layer can reason about; the alternative is not.

Overwrite-oldest is defensible where freshness beats history — a telemetry stream where the latest sample matters and older ones are worthless. The requirement is that it be chosen, because a design that overwrites by accident looks identical from the outside to one that chose to.

Two characters rejected at a full FIFO

8 cycles
A trace of eight events at a four-entry receive FIFO. The first four characters, A through D, are accepted and fill the queue. The fifth and sixth characters, E and F, arrive while the queue is full and no pop is occurring, so each push is rejected and an overflow event is pulsed; the queue contents are unchanged and both characters are destroyed. When the consumer finally pops, it receives A, the oldest surviving byte, and occupancy falls to three. The next arriving character is then accepted normally. The consumer therefore sees a correct in-order prefix followed by a reported gap, rather than a gap hidden inside an apparently contiguous run.full — newest droppedfull — newestdroppedE rejected — queue untouchedE rejected — queueuntouchedF rejected tooF rejected tooconsumer gets A, the OLDESTconsumer gets A, the OLDESTeventpoplevel12344434acceptedoverflow_evt_oqueue headAAAAAABBt0t1t2t3t4t5t6t7
Figure 1 — the boundary. Columns are CHARACTER ARRIVALS into a full four-entry FIFO with no consumer activity, then one pop. Characters E and F are rejected and the queue's contents never change; when the consumer finally pops, it receives A — the oldest surviving byte — and the gap is between D and whatever arrives next.

4. Underflow Is a Different Kind of Statement

underflow_evt_o fires when a pop is requested on an empty FIFO. Unlike overflow, nothing is lost — the consumer asked for a byte that does not exist, and was correctly given nothing.

On the RX side it usually indicates a consumer bug: software reading a data register without first checking that data is available. It is worth reporting for exactly that reason, and it is diagnostic rather than a link event.

On the TX side it is not an error at all, and this is where Chapter 9.3 §5's distinction matters. A transmit engine that pops an empty FIFO has simply run out of data to send; the correct response is to finish the frame in flight and let the line go idle, which is the steady state of a link with nothing to say. Reporting that as an error produces an alarm on every link that is not saturated.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
RX underflow : the consumer read something that was not there   -> consumer bug
TX underflow : there was nothing to send                        -> normal idle

The same FIFO signal, two meanings, determined entirely by which side the engine is on. A UART IP that wires both to one "underflow" status bit conflates a software defect with ordinary quiet operation. The base FIFO produces the event; deciding what it means belongs to whatever instantiates it.

5. Backpressure, and Exactly Where It Stops

A full FIFO can stop its local producer completely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Synthesizable SystemVerilog — local backpressure. This works, and its
// reach is precisely the boundary of the design.
assign producer_ready = !fifo_full;

For the TX FIFO that is the whole story. The producer is software or a bus master inside the same design; ready low stalls it, nothing is lost, and the mechanism is complete. A TX FIFO genuinely cannot overflow unless its producer ignores ready — which is a protocol violation on a local interface, not a link event.

For the RX FIFO it is not the whole story at all, and the gap is the entire reason Module 10 continues past this chapter.

A sequence showing the limit of local backpressure in a UART receive path. The far end's transmitter sends characters according to its own schedule, having no knowledge of the receiver's state. Each character is assembled by the receive engine and offered to the receive FIFO. While the FIFO has space, the push is accepted. When the FIFO becomes full it drives its ready signal low, which successfully stalls the receive engine's holding register — a local handshake between two blocks in the same design. That signal has no path out of the device: the far end's transmitter never observes it and continues transmitting on its original schedule, so each further character is assembled correctly and then discarded for want of space. Reaching the far end requires a physical flow-control signal or an in-band control character, which are the subjects of the next two chapters.Far end TXRX engineRX FIFOConsumercharacter — its ownschedulepush — acceptedwhile space remainsblocked, not poppingfull -> ready low:STALLS the enginenext character — farend heard nothingpush REJECTED ->overflow, bytedestroyedready has no pathoff this device
Figure 2 — where backpressure reaches, and where it does not. Every arrow inside the local device is a wire the designer controls. The far end's transmitter is in a different device with its own clock and its own schedule, and the only path back to it is a physical signal the next chapter adds — which is why the receiver's ready has no effect on what arrives.

The asymmetry is worth stating plainly, because it is the module's pivot:

TX FIFORX FIFO
Producerlocal — software or a busremote — another device
ready reaches it?yes, completelyno, not at all
Can overflow?only if ready is violatedyes, whenever the consumer is late
Remedynone neededflow control — 10.5, 10.6

A TX FIFO's backpressure is a solved problem. An RX FIFO's is not, and no amount of internal signalling changes that: the producer is on the other side of a cable, and the only way to influence it is to send it something it can observe.

6. Verification

The positive control is the same-cycle pop. A design that reports overflow whenever push_i && full_o passes every test that pushes into a full FIFO with an idle consumer, and is wrong exactly when the consumer is active — the case that matters.

From this module's simulation of the FIFO contract:

StimulusExpectedResult
fill to DEPTH, then pushoverflow event, level unchangedpass
full + simultaneous push and popboth accepted, no overflowpass
pop from emptyunderflow eventpass
empty + simultaneous push and poppush accepted, underflow reportedpass
randomised traffic, six configurationsevents fire only on rejected requestspass

The overflow and underflow counts in the randomised run were non-zero in every configuration — 19 to 82 overflows and 85 to 316 underflows depending on depth — which confirms the boundaries were genuinely exercised rather than avoided.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Assertion — overflow is reported exactly when a push is rejected.
// Derived from the acceptance condition rather than written independently,
// so the two cannot drift apart.
property p_overflow_iff_rejected_push;
    @(posedge clk) disable iff (!rst_n)
        (push_i && !push_fire) |=> overflow_evt_o;
endproperty
assert property (p_overflow_iff_rejected_push);

// Assertion — and NOT reported when the push is accepted, including the
// same-cycle-pop case. This is the positive control, formalised.
property p_no_overflow_on_accepted_push;
    @(posedge clk) disable iff (!rst_n)
        push_fire |=> !overflow_evt_o;
endproperty
assert property (p_no_overflow_on_accepted_push);

// Assertion — the retention policy. A rejected push never disturbs the
// queue: neither the head nor the occupancy may move.
property p_overflow_preserves_queue;
    @(posedge clk) disable iff (!rst_n)
        overflow_evt_o |-> $stable(level_o);
endproperty
assert property (p_overflow_preserves_queue);

The third is the policy under test rather than the flag. Asserting that overflow_evt_o rose proves a rejection was noticed; asserting that occupancy did not move proves which byte survived, which is the design decision §3 made.

Test the sustained case and assert that it does overflow. A suite containing only cases that pass has not characterised the boundary, and Chapter 10.1 §3 showed that sustained mismatch is the failure that hides longest.

7. Debugging

8. What This Means on an FPGA

Overflow detection is three AND terms on signals the FIFO already computes. There is no version of this worth omitting.

Add a saturating loss counter. Four bits alongside a 16-entry FIFO is negligible and turns "we sometimes lose data" into a rate that can be correlated with load, temperature and configuration. It is the single most useful addition to a bring-up build in this module.

Wire RX and TX underflow to different status, per §4. Merging them reports normal idle operation as an error on every link that is not saturated.

Probe full_o, push_fire and pop_fire together. Those three answer the whole question: was the queue full, was a push attempted, and was a pop freeing a slot at the same moment. full_o alone is ambiguous and push_i alone is misleading.

The TX FIFO needs none of this vigilance. Its producer honours ready or violates a local contract, and neither is a link condition. Instrumentation effort belongs on the receive side.

9. Understanding Check

10. Summary

Overflow occurs when push_i && full_o && !pop_fire. The third term is essential: a full FIFO with a simultaneous pop accepts the push, and a definition omitting it reports losses that never happened under exactly the traffic that matters.

FIFO overflow and RX overrun are two views of one event — one about the queue, one about the link — and both are worth reporting because they support different decisions.

This design drops the newest byte, matching the one-entry policy of Chapter 9.3. Both policies lose one byte; dropping the newest leaves a correct prefix and a reported gap, while overwriting the oldest hides a gap inside an apparently contiguous run. A flag cannot say how many were lost — a counter can, for four flip-flops.

Underflow loses nothing. On the receive side it indicates a consumer reading without checking; on the transmit side it is normal idle operation, and merging the two into one status bit reports a quiet link as a fault.

Local backpressure is complete for TX and useless for RX. ready = !full stops a local producer entirely and has no path off the device. The far end's transmitter has its own clock, its own schedule, and no knowledge of this design — which is the module's pivot and the reason two chapters follow.

Verified across six configurations with overflow and underflow genuinely exercised in every one, including the full-plus-simultaneous-pop case that the naive definition gets wrong.

11. What Comes Next

The gap this chapter identified has exactly one kind of solution: send the far end something it can observe.

Chapter 10.5 takes the hardware form. A dedicated signal, out of band, that says stop. The interesting part is not the wire — it is that asserting it when the FIFO is full is already too late: the far end may be mid-frame, may have another frame committed, and takes time to notice at all. That chapter derives the headroom required to absorb the bytes still in flight, and shows why the threshold that triggers flow control is a different number from the one that triggers an interrupt.

Browse the full path on the UART tutorials index. For the one-entry version of this boundary, read back to Chapter 9.3.

Continue learning

Where this fits

Part of the UART curriculum.