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.

A sender that ignores the receiver
01234567891011121314151617FIFO count12345678910111213141516RTS_nbytes sent123456789101112131415161718overrun

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

How a Receiver Holds off a SenderArrows point from the receiver that needs time to the sender it holds off.
How a receiver holds off a senderArrows point from the receiver that needs time to the sender it holds off. component Receiver buffer filling up component RTS and CTS a wire of its own(UART) component XON and XOFF a byte on the returnline component Clock stretching hold the clock low(I2C) component Ready or credits on-chip handshakes(P1) component Sender waits before its nextunit CTS highXOFF arrivesSCL stays lowready low, no credit
  1. A wire

    RTS from the receiver is CTS at the sender. The sender checks it before every byte.

  2. 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.

  3. 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.

  4. 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.

A sender that honours CTS
01234567891011121314151617FIFO count123456789101112RTS_nbytes sent123456789101112overrun

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

Why Bytes Keep Coming After XOFFArrows show time order. Each delay lets more bytes leave the sender.
Why bytes keep coming after XOFFArrows show time order. Each delay lets more bytes leave the sender. component B hits itsthreshold starts sending XOFF(13h) component XOFF on the wire one whole frame component A's softwarereacts interrupt, then a flag component A finishes itsbyte a started framecompletes component Late bytes landat B need free slots
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 atA reacts inA paused after XOFFBytes after XOFFLost
12 of 160 ticks (0 byte-times)152 ticks10
12 of 16176 ticks (1 byte-time)328 ticks20
12 of 16528 ticks (3 byte-times)680 ticks40
15 of 16528 ticks (3 byte-times)680 ticks43

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

The Target Holds SCL Low After Its ACKOne column per quarter bit. tgt_scl_low is the target pulling SCL low; the controller has let go and waits.
START0xa0 + ACKstretch (12 quarters)0x10 + ACKSTOP 012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091SCLSDActrl_lowtgt_lowtgt_scl_lowSAtarget stretches SCLAPSDA sampled at 6SDA sampled at 10SDA sampled at 14SDA sampled at 18SDA sampled at 22SDA sampled at 26SDA sampled at 30SDA sampled at 34SDA sampled at 38SDA sampled at 54SDA sampled at 58SDA sampled at 62SDA sampled at 66SDA sampled at 70SDA sampled at 74SDA sampled at 78SDA sampled at 82SDA sampled at 86
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.
  1. Cycle 38

    The target acknowledges the address on the ninth clock.

  2. 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.

  3. 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

Permission Before the Data, or a Stop After ItArrows show which way each signal travels between a source and a sink.
Permission before the data, or a stop after itArrows show which way each signal travels between a source and a sink. component Source has data to send component Sink a buffer of N slots component valid and ready moves only when bothare 1 component Credits one per free slot,sent ahead validdatacredit returnedspend one per item
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.

IdeaWhere it returns
RTS, CTS, FIFO trigger levelsP6.6 flow control, FIFOs and baud divisors
Overrun and the flags that report itP6.5 errors, break and recovery
A busy flash that polls BUSY instead of waitingP7.8 program, erase and status
Clock stretching and a controller that must read SCLP8.5 clock stretching
The target ending a read; in-band interruptsP9.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.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: 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 3
Read 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

Predict the outputXOFF bench: B sends XOFF when it holds 12 bytes, and A reacts 352 ticks (two byte-times) after its receiver delivers the XOFF. The program prints how many more bytes arrived at B after B started sending XOFF. What does it print?
`timescale 1ns/1ps
// A streams bytes to B. B's buffer is not read during the test, so it fills; when it holds `xoff_at`
// bytes, B sends XOFF (13h) back on its own line. A's software notices `react` ticks after its receiver
// delivers the XOFF and starts no new byte; a byte already on the wire finishes. One tick is one receiver
// clock; one byte is 176 ticks (8N1 plus one idle bit).
module tb;
  reg clk = 0, rst_n = 0;
  always #0.5 clk = ~clk;
  wire a_to_b, b_to_a;
  wire [7:0] b_data, a_data;
  wire b_valid, b_fe, b_pe, b_ovr, b_brk, b_done, a_valid, a_fe, a_pe, a_ovr, a_brk, a_done;
  uart_tx tx_a(a_to_b);
  uart_rx rx_b(clk, rst_n, a_to_b, 1'b1, b_data, b_valid, b_fe, b_pe, b_ovr, b_brk, b_done);
  uart_tx tx_b(b_to_a);
  uart_rx rx_a(clk, rst_n, b_to_a, 1'b1, a_data, a_valid, a_fe, a_pe, a_ovr, a_brk, a_done);

  int depth = 16, xoff_at = 12, react = 0;       // B's buffer, B's XOFF threshold, A's reaction in ticks
  int held = 0, arrived = 0, lost = 0, sent = 0;
  bit paused = 0, short_report = 0; time xoff_time = 0, pause_time = 0;

  // B: each byte goes into the buffer; at the threshold B sends XOFF once.
  always @(negedge clk) if (b_done) begin
    arrived++;
    if (held < depth) held++; else lost++;
    if (arrived == xoff_at) begin xoff_time = $time; fork tx_b.send_byte(8'h13); join_none end
  end
  // A's software: pauses `react` ticks after its receiver delivers an XOFF.
  always @(negedge clk) if (a_done && a_data == 8'h13) fork begin #(react); paused = 1; pause_time = $time; end join_none

  // One run: A offers 24 bytes, checking `paused` before each one.
  task automatic run(input int threshold, input int r);
    int i;
    xoff_at = threshold; react = r; held = 0; arrived = 0; lost = 0; sent = 0; paused = 0;
    i = 0;
    while (i < 24 && !paused) begin tx_a.send_byte(8'h40 + i); sent++; i++; end
    #(5 * 176);                                    // everything in flight lands
    if (short_report) $display("%0d more arrived after XOFF", arrived - threshold);
    else $display("XOFF at %0d of %0d, A reacts in %0d ticks: A paused %0d ticks after XOFF, %0d more arrived, lost %0d",
                  threshold, depth, r, pause_time - xoff_time, arrived - threshold, lost);
  endtask

  initial begin
    #4 rst_n = 1; #4;
    short_report = 1;  // one line: how many more bytes arrived after XOFF
    run(12, 352);      // A reacts two byte-times later
    $finish;
  end
endmodule
Read 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
Spot the bugEach option is a sender loop for 24 bytes. cts_n is the receiver's RTS_n, raised when its 16-byte FIFO holds 12. The bench passes if nothing overruns. Which loop fails?
Read 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
Read included file: uart_flow.sv
`timescale 1ns/1ps
module uart_flow(input wire clk,rst_n,push,pop,cts_n,input wire [7:0] din,
 output wire rts_n,output wire send,output wire [7:0] dout,output reg overrun=0);
  reg [7:0] fifo[0:15]; int head=0,count=0,trigger=12;
  bit cts_ignored=0,trigger_off_by_one=0;
  assign rts_n=count>=(trigger+(trigger_off_by_one?1:0));
  assign send=count>0&&(!cts_n||cts_ignored);
  assign dout=count>0?fifo[head]:8'h00;
  always @(posedge clk)begin
    if(!rst_n)begin head=0;count=0;overrun=0;end
    else begin
      if(pop&&send)begin head=(head+1)%16;count=count-1;end
      if(push)begin if(count==16)overrun=1;else begin fifo[(head+count)%16]=din;count=count+1;end end
    end
  end
endmodule
ConceptB has a 16-byte buffer, and A's software may take three byte-times to react to XOFF. From the measured table, where should B send XOFF so nothing is lost?
ConceptAn I3C target needs more time before it can return more data during a read. What may it do?
Read the waveformThis is the opening trace: the sender ignores RTS_n (measured, one column per byte-time). Which byte is the first one lost?
01234567891011121314151617FIFO count12345678910111213141516RTS_nbytes sent123456789101112131415161718overrun

Measured on the flow bench; FIFO count and bytes sent in decimal.

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.