Flow Control
Name the four ways a serial receiver says "wait", measure what each one loses when it is ignored or late, and size the headroom an in-band XOFF needs.
- Lesson
- P5.8
- Time
- 25 min
- Runs on
- Your browser
- Check
- 5 questions
A GPS module streamed positions into a microcontroller over a UART. When the firmware ran late, the receive buffer filled and bytes vanished. The only trace was an overrun bit that nobody read, because nothing on the link could tell the module to wait.
Measured on P6's flow bench, one column per byte-time. The receiver raises RTS_n at 12 bytes, but the sender ignores it. Byte 17 arrives at a full 16-byte FIFO and overrun rises: sent 24, receive FIFO holds 16, lost 8, overrun 1.
Four Ways to Say Wait
- A wire
RTS from the receiver is CTS at the sender. The sender checks it before every byte.
- A byte
XOFF needs no extra wire, but it is a whole frame on the return line, read by software. Bytes keep arriving while it travels.
- The clock
An I2C target holds SCL low after a byte. The controller reads SCL back and cannot send the next bit until it rises.
- On chip
A ready signal or a credit count says "wait" every cycle, before anything is sent.
Read the diagram as text
- Receiver: buffer filling up
- RTS and CTS: a wire of its own (UART)
- XON and XOFF: a byte on the return line
- Clock stretching: hold the clock low (I2C)
- Ready or credits: on-chip handshakes (P1)
- Sender: waits before its next unit
- Receiver → RTS and CTS
- Receiver → XON and XOFF
- Receiver → Clock stretching
- Receiver → Ready or credits
- RTS and CTS → Sender: CTS high
- XON and XOFF → Sender: XOFF arrives
- Clock stretching → Sender: SCL stays low
- Ready or credits → Sender: ready low, no credit
Plain SPI has none of these: the controller owns the clock. A flash that is busy ignores every instruction except Read Status (W25Q128JV §8), so the controller polls its BUSY bit.
RTS and CTS
The receiver's FIFO holds 16 bytes and raises RTS_n at 12. The sender looks at CTS before each byte, so a byte already started always finishes.
Measured, one column per byte-time: sent 12, receive FIFO holds 12, lost 0, overrun 0. RTS_n rises at 12 and the sender stops; the rest wait in its own queue.
Sender honours CTS
if (tx_send) begin // CTS low
pop; send_byte(b);
end
Measured: sent 12, receive FIFO holds 12, lost 0, overrun 0.
txq.cts_ignored = 1
txq.cts_ignored = 1;
// sends whenever it has a byte
Measured: sent 24, receive FIFO holds 16, lost 8, overrun 1.
The 4 free slots above 12 are headroom for a sender that checks late. Lesson P6.6 moves the trigger level and measures what each setting loses.
XON and XOFF Need Headroom
Read the diagram as text
- B hits its threshold: starts sending XOFF (13h)
- XOFF on the wire: one whole frame
- A's software reacts: interrupt, then a flag
- A finishes its byte: a started frame completes
- Late bytes land at B: need free slots
- B hits its threshold → XOFF on the wire
- XOFF on the wire → A's software reacts
- A's software reacts → A finishes its byte
- A finishes its byte → Late bytes land at B
Measured: with an instant reaction, A paused 152 ticks after B started XOFF, almost one byte-time of 176 ticks.
| XOFF at | A reacts in | A paused after XOFF | Bytes after XOFF | Lost |
|---|---|---|---|---|
| 12 of 16 | 0 ticks (0 byte-times) | 152 ticks | 1 | 0 |
| 12 of 16 | 176 ticks (1 byte-time) | 328 ticks | 2 | 0 |
| 12 of 16 | 528 ticks (3 byte-times) | 680 ticks | 4 | 0 |
| 15 of 16 | 528 ticks (3 byte-times) | 680 ticks | 4 | 3 |
Measured by the example program below. One byte-time is 176 ticks: an 8N1 frame plus one idle bit.
The headroom must cover every byte that can arrive after XOFF. Slow software costs bytes: 4 arrived after XOFF. With XOFF at 15 of 16 the same software lost 3. RTS on a wire, checked by hardware, needs less.
Clock Stretching: The Target Holds SCL
Read phases and sample points (quarter bit)
- START: columns 2 through 3.
- 0xa0 + ACK: columns 4 through 39.
- stretch (12 quarters): columns 40 through 51.
- 0x10 + ACK: columns 52 through 87.
- STOP: columns 88 through 91.
- SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38, 54, 58, 62, 66, 70, 74, 78, 82, 86.
- Cycle 38
The target acknowledges the address on the ninth clock.
- Cycle 40
Then it keeps SCL low (
tgt_scl_low= 1). The controller released SCL, but the wire stays low, so its next bit waits. - Cycle 52
The target lets go, SCL rises, and the controller carries on with the next byte.
Generated and decoded back. Nothing on SDA moves during the stretch, so the bytes decode the same; only the time grows.
I2C's clock is open drain too, so a target may hold SCL low after a byte until it is ready (UM10204 §3.1.9). On the bench, a 3000 ns stretch per byte turns a 96281 ns read into 101281 ns, same value.
A controller that never reads SCL back walks through the stretch: controller ignores SCL, target stretches 3000 ns: controller reads e5. I3C does not allow targets to stretch (MIPI I3C Basic §5.1.1.1, Table 4). A target there ends a read early with its T-bit (§5.1.2.3.4), and only the controller may stall SCL (§5.1.2.5).
Ready and Credits on Chip
Read the diagram as text
- Source: has data to send
- Sink: a buffer of N slots
- valid and ready: moves only when both are 1
- Credits: one per free slot, sent ahead
- Source → valid and ready: valid
- valid and ready → Sink: data
- Sink → Credits: credit returned
- Credits → Source: spend one per item
A credit is permission handed out before the data, so bytes in flight are always covered. XOFF is the opposite: a stop sent after the buffer fills. Lesson P1.3 covers valid and ready; lesson P1.6 sizes FIFOs and credits.
| Idea | Where it returns |
|---|---|
| RTS, CTS, FIFO trigger levels | P6.6 flow control, FIFOs and baud divisors |
| Overrun and the flags that report it | P6.5 errors, break and recovery |
| A busy flash that polls BUSY instead of waiting | P7.8 program, erase and status |
| Clock stretching and a controller that must read SCL | P8.5 clock stretching |
| The target ending a read; in-band interrupts | P9.2 SDR frames; P9.6 IBI and Hot-Join |
Every link answers "wait" once: with a wire, a byte, the clock or nothing at all.
Try It
run(threshold, react) streams 24 bytes from A to B. B sends XOFF when it holds threshold bytes; A notices react ticks after its receiver delivers the XOFF and starts no new byte. Each line counts the bytes that arrived after B sent XOFF.
Try run(14, 0) and run(16, 0). Then set depth = 20; before the runs and find the highest threshold that loses nothing at react = 528.
XOFF at 12 of 16, A reacts in 0 ticks: A paused 152 ticks after XOFF, 1 more arrived, lost 0 XOFF at 12 of 16, A reacts in 176 ticks: A paused 328 ticks after XOFF, 2 more arrived, lost 0 XOFF at 12 of 16, A reacts in 528 ticks: A paused 680 ticks after XOFF, 4 more arrived, lost 0 XOFF at 15 of 16, A reacts in 528 ticks: A paused 680 ticks after XOFF, 4 more arrived, lost 3Read included file: uart_tx.sv
`timescale 1ns/1ps
module uart_tx(output reg tx=1);
real error=0.0; int bits=8, parity=0; real stop_bits=1.0;
bit lsb_first=1, parity_includes_start=0, parity_includes_stop=0, parity_wrong_sense=0, short_stop=0, no_idle_between=0;
task automatic bit_time(input bit v); tx=v; #(16.0*(1.0+error)); endtask
task automatic send_byte(input byte unsigned value);
bit p; p=(parity==2);
bit_time(0);
for(int b=0;b<bits;b++) begin bit_time(value[lsb_first?b:bits-1-b]); p=p^value[b]; end
if(parity!=0) bit_time(p ^ parity_includes_stop ^ parity_wrong_sense);
tx=1; #(16.0*(1.0+error)*(short_stop?0.25:stop_bits));
// no_idle_between is a legal back-to-back frame control, not a fault.
if(!no_idle_between) #(16.0*(1.0+error));
endtask
task automatic send_break(); tx=0; #(200*16); tx=1; #32; endtask
task automatic set_error(input real e); error=e; endtask
endmodule
Read included file: uart_rx.sv
`timescale 1ns/1ps
module uart_rx(input wire clk, rst_n, rx, take,
output reg [7:0] data=0, output reg valid=0, frame_err=0, parity_err=0, overrun=0, break_seen=0, frame_done=0);
int bits=8, parity=0; real stop_bits=1.0;
bit trace=0;
bit parity_includes_start=0; // XOR of the zero-valued START is an equivalent control.
bit sample_at_edge=0, qualify_1_tick=0, no_vote=0, no_resync=0, rearm_on_low=0, stop_as_data=0, no_overrun_flag=0, parity_includes_stop=0;
int state=0, low_run=0, left_ticks=0, index=0, votes=0, frames=0, ticks=0, next_data_tick=0;
bit armed=1, sampled=0, p=0, pe=0, pending_zero_fe=0; reg [7:0] shift=0;
always @(posedge clk) begin
frame_done=0;
if(!rst_n) begin state=0;low_run=0;left_ticks=0;index=0;votes=0;valid=0;data=0;frame_err=0;parity_err=0;overrun=0;break_seen=0;armed=1;frames=0;ticks=0;next_data_tick=0;pending_zero_fe=0; end
else begin
ticks++;
if(take)valid=0;
low_run=rx?0:low_run+1;
if(rx)break_seen=0;
if(low_run>=176&&!rearm_on_low)begin break_seen=1;state=4;pending_zero_fe=0;end
if(state==4)begin if(rx)begin
if(pending_zero_fe)begin
if(valid&&!no_overrun_flag)overrun=1;
data=0;valid=1;frame_err=1;parity_err=pe;frames++;frame_done=1;pending_zero_fe=0;
end
state=0;armed=1;
end end
else if(state==0)begin
if(rx)begin armed=1;end
else if(armed&&low_run>=(qualify_1_tick?1:8))begin
state=1;left_ticks=sample_at_edge?8:16;index=0;shift=0;p=(parity==2)^parity_includes_stop;votes=0;pe=0;
// Fault: later frames keep the first frame's free-running schedule.
// At nominal back-to-back pacing it can work; a new START must normally
// reset this timing so baud error cannot accumulate across frames.
if(no_resync&&frames>0)begin
while(next_data_tick<=ticks)next_data_tick+=16*(1+bits+(parity!=0))+$rtoi(16*stop_bits);
left_ticks=next_data_tick-ticks;
end else next_data_tick=ticks+left_ticks;
next_data_tick+=16*(1+bits+(parity!=0))+$rtoi(16*stop_bits);
end
end else begin
if(left_ticks<=3)votes=votes+(rx?1:0);
left_ticks=left_ticks-1;
if(left_ticks==0)begin
sampled=no_vote?rx:(votes>=2);votes=0;left_ticks=16;
if(trace)$display("UART SAMPLE t=%0t state=%0d bit=%0d value=%0d",$time,state,index,sampled);
if(state==1)begin shift[index]=sampled;p=p^sampled;index=index+1;if(index==bits)state=parity==0?3:2;end
else if(state==2)begin pe=(sampled!=p);state=3;end
else begin
if(shift==0&&!sampled&&!rearm_on_low)pending_zero_fe=1;
else begin
if(valid&&!no_overrun_flag)overrun=1;
data=stop_as_data?{sampled,shift[7:1]}:shift;
frame_err=!sampled;parity_err=pe;valid=1;frames=frames+1;frame_done=1;
end
state=(sampled||rearm_on_low)?0:4;armed=1;
end
end
end
end
end
endmodule
Check Yourself
Remember
- A receiver says "wait" with a wire (RTS/CTS), a byte (XON/XOFF), the clock (I2C stretching) or, on chip, ready and credits; plain SPI has none, so a busy peripheral is polled.
- A sender must check the signal before every unit it sends; a sender that ignores it overruns the receiver.
- XOFF is a frame read by software, so bytes keep arriving after it: the threshold must leave that many free slots.
- An I2C target may hold SCL low; the controller must read SCL back. I3C targets may not stretch and end reads with the T-bit.