UART · Module 10
Why UART IP Needs FIFOs
A single holding register makes the service deadline one frame time. Depth multiplies that deadline — and buys time rather than bandwidth, which is the distinction that decides whether a buffer helps at all.
Chapter 6.5 built a receiver with one holding register, and Chapter 9.3 showed exactly what that costs: a byte is lost whenever the consumer is more than one frame time behind. At 115,200 baud with 8N1 that deadline is 86.81 µs — 8,681 cycles of a 100 MHz fabric clock.
The obvious response is "add a buffer", and it is the right response. What is worth getting precise is what a buffer actually buys, because the intuition is usually wrong in a way that produces designs which fail later and more confusingly:
Depth buys time. It does not buy bandwidth.
A FIFO absorbs a consumer that is temporarily behind. It does nothing for a consumer that is permanently slower than the line, and no depth ever will. Getting that distinction right is the difference between sizing a FIFO from a latency budget and guessing at a number because sixteen sounds reasonable.
1. The Arrival Rate Is Fixed and Knowable
Unlike most producers in a system, a UART's arrival rate is exactly determined by configuration. From Chapter 4.1, an 8N1 frame is ten bit intervals for eight payload bits, so:
baud frame_bits
bytes_per_s = ───────── T_frame = ────────────── [s]
frame_bits baud| baud | frame bits | bytes/s | T_frame |
|---|---|---|---|
| 9,600 | 10 | 960.000 | 1041.667 µs |
| 115,200 | 10 | 11,520.000 | 86.806 µs |
| 1,000,000 | 10 | 100,000.000 | 10.000 µs |
| 3,000,000 | 10 | 300,000.000 | 3.333 µs |
T_frame is the consumer's deadline with one holding register, and it is the number that matters. At 115,200 baud a driver has 86.8 µs; at 3 Mbaud it has 3.33 µs — about 333 cycles of a 100 MHz clock, which is inside the range where interrupt entry alone can miss it.
That last row is why FIFOs stopped being optional. A UART at 9,600 baud gives a millisecond of grace and tolerates almost any software; the same design at 3 Mbaud gives three microseconds and tolerates almost none.
2. What Depth Buys
With D usable entries and a consumer doing nothing at all, the buffer absorbs D characters before the next one is lost:
T_buffer ≈ D × T_frame [s]Stated assumptions, because the formula is only as good as they are: the FIFO starts empty, characters arrive back-to-back at the line rate, and the consumer removes nothing. It is a worst-case bound, not a typical case.
Machine-computed:
| baud | D=1 | D=4 | D=8 | D=16 | D=32 | D=64 |
|---|---|---|---|---|---|---|
| 9,600 | 1.04 ms | 4.17 ms | 8.33 ms | 16.67 ms | 33.33 ms | 66.67 ms |
| 115,200 | 86.81 µs | 347.22 µs | 694.44 µs | 1.39 ms | 2.78 ms | 5.56 ms |
| 1,000,000 | 10.00 µs | 40.00 µs | 80.00 µs | 160.00 µs | 320.00 µs | 640.00 µs |
| 3,000,000 | 3.33 µs | 13.33 µs | 26.67 µs | 53.33 µs | 106.67 µs | 213.33 µs |
The D=1 column is Chapter 9.3's receiver, so the table reads as "what depth buys against the deadline you already have". Sixteen entries at 115,200 baud turns 86.81 µs into 1.39 ms — a factor of sixteen, which moves the requirement from "the ISR must run almost immediately" to "the ISR must run within a millisecond", and those are very different engineering problems.
At 3 Mbaud even 64 entries give only 213 µs, which is a useful reminder that depth is measured in characters while the deadline is measured in time, and the conversion factor is the line rate.
3. Burst Absorption Is Not Rate Conversion
This is the chapter's central distinction and the most common misconception in the module.
4. Where the Fluid Approximation Breaks
T = H / r treats arrival and service as continuous flows. UART arrival is not continuous — it is one character every T_frame, exactly — and service is usually burstier still: a driver that reads everything when it runs and nothing in between.
For that pattern the useful question is not a rate at all:
Will the consumer's WORST-CASE gap between service events exceed D × T_frame?That is a comparison of two times, and it is the form Chapter 10.3 uses to choose a threshold. The rate form of §3 is the right model for sustained behaviour; the deadline form is the right model for bursty service. A design usually needs both — the deadline form to size depth, the rate form to confirm the consumer can keep up at all.
This is as far into queueing theory as a UART needs to go. Arrival is deterministic and the interesting cases are worst-case rather than average, so distributions add little that the two bounds above do not already give.
5. The Structural Change
Three back-to-back characters, D=1 versus D=4
6 cycles6. A Minimal Buffer, to Make the Point Concrete
The full architecture is Chapter 10.2's. What this chapter needs is only enough to show that the receiver's interface does not change:
// Conceptual SystemVerilog — the connection, not the FIFO.
// The receiver of Module 6 is untouched: its held-valid output becomes the
// queue's push port, and the consumer pops instead of reading the register
// directly. Chapter 10.2 builds the queue itself.
assign fifo_push = rx_valid_o && !fifo_full; // accept when there is room
assign rx_ready_i = !fifo_full; // backpressure the RECEIVER
assign consumer_data = fifo_head;
assign consumer_valid = !fifo_empty;rx_ready_i = !fifo_full is the interesting line. It backpressures the receiver's holding register, which is a local handshake and works. What it cannot do is backpressure the wire — Chapter 6.5 §4 established that a UART receiver has no way to tell the far end to wait, and a FIFO does not change that. When the queue is full the receiver's register fills, and the character after that is lost exactly as before.
So a FIFO moves the overrun threshold and does not remove the overrun. Chapter 10.4 is about what happens at the new threshold, and Chapter 10.5 is about the only mechanism that can actually reach the far end.
7. Choosing a Depth
The procedure, in the order the facts become available:
1. Compute T_frame from the configured baud and frame length. It is exact.
2. Establish the consumer's worst-case service gap. Not its average — its worst case. For software this is interrupt latency plus the time the handler may be blocked by higher-priority work; for a bus master it is worst-case arbitration plus transfer time. This number is usually the hardest to obtain and the most important.
3. Require D × T_frame ≥ worst-case gap, with margin. Chapter 10.3 refines this, because a trigger level means service begins before the queue is full and the usable headroom is D − T rather than D.
4. Separately confirm μ ≥ λ on average. If it is not, stop — no depth is sufficient and §3 is the reason.
Worked, at 115,200 baud 8N1 with a 500 µs worst-case ISR latency:
T_frame = 86.806 µs
required D = 500 / 86.806 = 5.76 -> 6 entries minimum
with margin = 8 or 16 entriesAnd at 1 Mbaud with the same software:
T_frame = 10.000 µs
required D = 500 / 10 = 50 -> 50 entries minimumThe rate rose by a factor of 8.68 and the required depth rose by the same factor — depth scales linearly with line rate for a fixed software latency, which is the relationship that makes depth a system-level decision rather than a UART one.
8. Verification
Test the deadline, not just the function. A FIFO testbench that pushes and pops at a comfortable rate demonstrates nothing about the property the FIFO exists for. The useful test drives characters at the line rate and delays the consumer by a parameterised amount, then asserts that no byte is lost while the delay is below D × T_frame and that the expected byte is lost above it.
Test the sustained-mismatch case explicitly, and assert that it does overflow. A design whose suite only contains cases that pass has not characterised the boundary — and §3's whole point is that this failure is invisible until it is looked for.
Measure occupancy over time rather than only checking it at the end. The high-water mark across a run is more informative than the final level, and it is what tells you whether a chosen depth has any margin at all in practice.
9. What This Means on an FPGA
Depth is cheap until it is not. Sixteen bytes is 128 bits — trivially registers or distributed RAM on any device. A few hundred entries starts to want block RAM, which changes the read timing and the reset behaviour; Chapter 10.2 §9 covers what that implies.
The depth decision is usually made with the wrong information. It is chosen early, from a guess about software, and revisited only after data loss in the field. The arithmetic in §7 takes minutes and needs one number from the software team — worst-case service gap — that is worth asking for explicitly rather than assuming.
Instrument the high-water mark. A register holding the maximum occupancy ever reached costs a comparator and a few flip-flops, and it answers the question no amount of reasoning can: how much margin the chosen depth actually has on the real system. A high-water mark that sits at D − 1 is a design about to fail.
A FIFO does not relax the receiver's timing. The receive engine still samples at the line rate and still has the margin Chapter 5.5 computed. Buffering is entirely downstream of that, and no depth compensates for a rate mismatch on the wire.
10. Understanding Check
11. Summary
A UART's arrival rate is exactly knowable from configuration: bytes/s = baud / frame_bits, and T_frame is the consumer's deadline with one register — 86.81 µs at 115,200 8N1, falling to 3.33 µs at 3 Mbaud, which is where software stops being able to keep up.
Depth multiplies that deadline: T_buffer ≈ D × T_frame with the FIFO starting empty and the consumer doing nothing. Sixteen entries at 115,200 baud turns 86.81 µs into 1.39 ms.
Depth buys time, not bandwidth. A FIFO absorbs a consumer that is temporarily behind and does nothing for one that is permanently slow: at a 0.17% shortfall, 64 entries last 3.2 seconds and quadrupling the depth quadruples that and nothing more. Size depth from a latency budget, never from a throughput deficit.
The fluid model is right for sustained behaviour; a deadline comparison is right for bursty service. A design usually needs both.
A FIFO moves the overrun threshold and does not remove the overrun, because rx_ready_i = !fifo_full backpressures the receiver's register and cannot reach the wire.
And the depth procedure is four steps, of which the hard one is obtaining the consumer's worst-case service gap — a number that must be asked for rather than assumed.
12. What Comes Next
This chapter has treated the FIFO as a box with a depth. Chapter 10.2 opens it.
It settles the question the module's title makes tempting to get wrong — a UART FIFO is an asynchronous FIFO only when its two sides genuinely sit in different clock domains, which for a receiver whose input was already synchronised is usually not the case — then builds the pointers, the occupancy counter, and the contract that every later chapter of this module uses unchanged: what a push request means, when it is accepted, what the read port shows, and what happens when a push and a pop land on the same clock edge.
Browse the full path on the UART tutorials index. For the overrun this buffering is aimed at, read back to Chapter 9.3.
Continue learning
Related tutorials
- Related topic
Ready/Busy Handshake and Back-to-Back Frames
The write-side contract stated in cycles — what ready promises, how it differs from busy, when payload ownership transfers, and how accepting at the final boundary lets frames run with no gap and no shortened stop bit.
- Related topic
TX and RX FIFO Architecture
A UART FIFO is an asynchronous FIFO only when its two sides genuinely sit in different clock domains — which, for a receiver whose input was already synchronised, is usually not the case.
- Related topic
Thresholds, Watermarks and Trigger Levels
Full and empty are boundaries, not warnings. A trigger level is chosen by comparing the headroom it leaves against the consumer's worst-case latency — and against the interrupt load it costs.
- Related topic
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.
Where this fits
Part of the UART curriculum.
