Flow Control, FIFOs and Baud Divisors

Explain what RTS and CTS buy over XON/XOFF, place a FIFO trigger, and compute a baud divisor and its error against the measured budget.

Lesson
P6.6
Time
30 min
Runs on
Your browser
Check
4 questions

A console streamed logs at 115200 into a receiver whose software read one byte per interrupt. Under load the interrupt came late, the 16-byte FIFO filled, and the hardware dropped bytes behind a flag nobody polled. The reference design routed RTS and CTS to the connector and left them unconnected, because "the link is only 115200".

Two FIFOs and the Crossed WiresSolid data flows left to right; RTS of the receiving side is CTS of the sending side.
Two FIFOs and the crossed wiresSolid data flows left to right; RTS of the receiving side is CTS of the sending side. component Sending software writes 24 bytes queue txq 16 deep; offers abyte while CTS is low component Transmitter one frame at a time component Receiver one byte per stopsample queue rxq 16 deep; RTS highfrom 12 component Slow reader reads nothing here pushsendTX → RXpushRTS → CTSpop
Read the diagram as text
  • Sending software: writes 24 bytes
  • txq: 16 deep; offers a byte while CTS is low
  • Transmitter: one frame at a time
  • Receiver: one byte per stop sample
  • rxq: 16 deep; RTS high from 12
  • Slow reader: reads nothing here
  • Sending software → txq: push
  • txq → Transmitter: send
  • Transmitter → Receiver: TX → RX
  • Receiver → rxq: push
  • rxq → txq: RTS → CTS
  • rxq → Slow reader: pop

RTS means "I can take more"; it comes from the receiving FIFO's fill level. The sender checks CTS before each frame.

RTS and CTS

Both are "ready" signals, active low. The receiving FIFO raises rts_n once its count reaches the trigger. That wire is the sender's cts_n, and the sender starts no new frame while it is high.

Honoured: 24 bytes, nothing read
0123456789101112131415161718192021222324252627rxq count123456789101112rts_ntxq count151615141312frames sent123456789101112overrun

Measured, one column per frame time (176 ticks). The count climbs to the trigger, rts_n rises, the sender stops. End: sent 12, rxq holds 12, txq holds 12, lost 0.

txq.cts_ignored = 1: the sender ignores CTS
0123456789101112131415161718192021222324252627rxq count12345678910111213141516rts_ntxq count15161514131211109876543210frames sent123456789101112131415161718192021222324overrun

Measured. rts_n rises at the same point and nothing listens. The FIFO fills to 16, overrun rises on the next push. End: sent 24, rxq holds 16, lost 8.

TriggerFrames sentrxq holdsLostOverrun
12 (default)121200
12, trigger_off_by_one131300
16161600
16, trigger_off_by_one241681

Measured. This sender checks CTS before every frame and the receiver counts a byte at its stop sample, so nothing is in flight: the count stops exactly at the trigger. A trigger that can never be reached loses bytes.

A real transmitter often loads its next byte into a shift register before the current frame ends. That byte is in flight when RTS rises, and so is anything in a cable or a bridge. The room above the trigger holds them.

XON and XOFF

Software flow control sends "stop" and "go" as characters in the data stream: XOFF is 0x13, XON is 0x11. It costs no wires.

XOFF, 0x13, one frame
startdata 0data 1data 2data 3data 4data 5data 6data 7stop 012345678910111213141516171819202122232425rxsamplerx sampled at 4.6875rx sampled at 4.8125rx sampled at 4.9375rx sampled at 6.6875rx sampled at 6.8125rx sampled at 6.9375rx sampled at 8.6875rx sampled at 8.8125rx sampled at 8.9375rx sampled at 10.6875rx sampled at 10.8125rx sampled at 10.9375rx sampled at 12.6875rx sampled at 12.8125rx sampled at 12.9375rx sampled at 14.6875rx sampled at 14.8125rx sampled at 14.9375rx sampled at 16.6875rx sampled at 16.8125rx sampled at 16.9375rx sampled at 18.6875rx sampled at 18.8125rx sampled at 18.9375rx sampled at 20.6875rx sampled at 20.8125rx sampled at 20.9375
Read phases and sample points (transmitter half bit)
  • start: columns 2 through 3.
  • data 0: columns 4 through 5.
  • data 1: columns 6 through 7.
  • data 2: columns 8 through 9.
  • data 3: columns 10 through 11.
  • data 4: columns 12 through 13.
  • data 5: columns 14 through 15.
  • data 6: columns 16 through 17.
  • data 7: columns 18 through 19.
  • stop: columns 20 through 21.
  • rx sampled at 4.688, 4.813, 4.938, 6.688, 6.813, 6.938, 8.688, 8.813, 8.938, 10.688, 10.813, 10.938, 12.688, 12.813, 12.938, 14.688, 14.813, 14.938, 16.688, 16.813, 16.938, 18.688, 18.813, 18.938, 20.688, 20.813, 20.938.

Generated and decoded back. To the wire it is just a byte.

XON, 0x11, one frame
startdata 0data 1data 2data 3data 4data 5data 6data 7stop 012345678910111213141516171819202122232425rxsamplerx sampled at 4.6875rx sampled at 4.8125rx sampled at 4.9375rx sampled at 6.6875rx sampled at 6.8125rx sampled at 6.9375rx sampled at 8.6875rx sampled at 8.8125rx sampled at 8.9375rx sampled at 10.6875rx sampled at 10.8125rx sampled at 10.9375rx sampled at 12.6875rx sampled at 12.8125rx sampled at 12.9375rx sampled at 14.6875rx sampled at 14.8125rx sampled at 14.9375rx sampled at 16.6875rx sampled at 16.8125rx sampled at 16.9375rx sampled at 18.6875rx sampled at 18.8125rx sampled at 18.9375rx sampled at 20.6875rx sampled at 20.8125rx sampled at 20.9375
Read phases and sample points (transmitter half bit)
  • start: columns 2 through 3.
  • data 0: columns 4 through 5.
  • data 1: columns 6 through 7.
  • data 2: columns 8 through 9.
  • data 3: columns 10 through 11.
  • data 4: columns 12 through 13.
  • data 5: columns 14 through 15.
  • data 6: columns 16 through 17.
  • data 7: columns 18 through 19.
  • stop: columns 20 through 21.
  • rx sampled at 4.688, 4.813, 4.938, 6.688, 6.813, 6.938, 8.688, 8.813, 8.938, 10.688, 10.813, 10.938, 12.688, 12.813, 12.938, 14.688, 14.813, 14.938, 16.688, 16.813, 16.938, 18.688, 18.813, 18.938, 20.688, 20.813, 20.938.

Generated and decoded back.

Because the signals are bytes, binary data that contains 0x11 or 0x13 must be escaped. And the stop must be received, read by software and acted on, so it arrives late by the receiver's FIFO depth and the reader's delay. RTS and CTS act in hardware, before the next frame.

Baud Divisors

The receiver's tick is the system clock divided by an integer: divisor = round(clock / (16 × baud)). So the baud is quantised, and the error adds to the peer's.

Baud1.8432 MHz50 MHz100 MHz
960012: 0 %326: -0.15 %651: +0.01 %
192006: 0 %163: -0.15 %326: -0.15 %
384003: 0 %81: +0.47 %163: -0.15 %
576002: 0 %54: +0.47 %109: -0.45 %
1152001: 0 %27: +0.47 %54: +0.47 %
2304001: -50 %14: -3.12 %27: +0.47 %
4608001: -75 %7: -3.12 %14: -3.12 %
9216001: -87.5 %3: +13.03 %7: -3.12 %

Computed: divisor, then the error of the actual rate. A red cell alone would fail the measured 8N1 back-to-back sweep against an exact receiver. That sweep first fails at −6 % and +4.5 % (lesson P6.4); amber cells use more than half of it.

A 1.8432 MHz clock divides exactly to every classic rate, which is why it was a common UART crystal. A 50 MHz clock gives small errors at low rates and large ones near the top, where the divisor is small. Remember the peer: its error adds to yours.

Where You Will Meet It

Every UART block has a divisor register and a FIFO with a trigger; many have automatic RTS and CTS. A bench drives the reader slow on purpose, checks that overrun never rises with flow control on, and checks the divisor against the budget for every clock the chip supports.

Asked in interviews: flow control
  1. What do RTS and CTS mean?

    Both are "ready to receive" signals. A side drives RTS from its own FIFO level; the other side sees it as CTS and starts no new frame while it is deasserted.

  2. Why is the RTS trigger below the FIFO size?

    Bytes already started or loaded still arrive after RTS changes; the room above the trigger holds them.

  3. Why can XON/XOFF not carry binary data as is?

    The stop and go signals are the bytes 0x13 and 0x11, so binary data containing them must be escaped.

Try It

The producer writes 24 bytes into txq, the sender sends them while CTS allows, and nothing reads rxq. With flow control honoured, 12 frames go out, rxq holds 12, txq still holds 12, nothing is lost.

Add txq.cts_ignored = 1; to the setup, then try rxq.trigger = 16; with and without rxq.trigger_off_by_one = 1;.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: sent 12, rxq holds 12, txq holds 12, lost 0, overrun 0
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

Check Yourself

Predict the outputThe same 24 bytes with txq.cts_ignored = 1: the sender ignores CTS, nothing reads rxq. What does the summary line print?
`timescale 1ns/1ps
module tb;
  reg clk = 0, rst_n = 0, take = 1;           // the receiver hands every byte to rxq at once
  always #0.5 clk = ~clk;
  wire rx, valid, frame_err, parity_err, overrun, break_seen, frame_done;
  wire [7:0] data;
  uart_tx tx0(rx);
  uart_rx rx0(clk, rst_n, rx, take, data, valid, frame_err, parity_err, overrun, break_seen, frame_done);
  // Sending side: software pushes into txq; txq offers a byte (send) only while CTS is low.
  reg tx_push = 0, tx_pop = 0, rx_push = 0, rx_pop = 0;
  reg [7:0] tx_din = 0, rx_din = 0;
  wire rts_n, tx_send, rx_send, unused_rts_n, tx_overrun, rx_overrun;
  wire cts_n = rts_n;                          // the crossed wire: RTS of rxq is CTS of txq
  wire [7:0] tx_dout, rx_dout;
  uart_flow txq(clk, rst_n, tx_push, tx_pop, cts_n, tx_din, unused_rts_n, tx_send, tx_dout, tx_overrun);
  uart_flow rxq(clk, rst_n, rx_push, rx_pop, 1'b0, rx_din, rts_n, rx_send, rx_dout, rx_overrun);
  int pushed = 0, sent = 0;
  always @(negedge clk) begin rx_push = valid; rx_din = data; end   // one push per received byte
  // Producer: 24 bytes, written whenever txq has room.
  initial begin
    wait (rst_n);
    for (int i = 0; i < 24; i++) begin
      @(negedge clk); while (txq.count >= 16) @(negedge clk);
      tx_din = 8'h40 + i; tx_push = 1; @(negedge clk); tx_push = 0; pushed++;
    end
  end
  // Sender: between frames, take the next byte only if txq offers it (CTS low).
  initial begin
    reg [7:0] b;
    wait (rst_n);
    forever begin
      @(negedge clk);
      if (tx_send) begin b = tx_dout; tx_pop = 1; @(negedge clk); tx_pop = 0; sent++; tx0.send_byte(b); end
    end
  end
  initial begin
    txq.cts_ignored = 1;
    #4 rst_n = 1; #4;
    #(24 * 176 + 600);
    $display("sent %0d, rxq holds %0d, txq holds %0d, lost %0d, overrun %b", sent, rxq.count, txq.count, sent - rxq.count, rx_overrun);
    $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
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
Predict the outputThe divisor for 115200 baud from a 50 MHz clock with sixteen ticks per bit, rounded to the nearest integer, and the error of the rate it gives. What does the program print?
`timescale 1ns/1ps
module tb;
  reg clk = 0, rst_n = 0, take = 0;
  always #0.5 clk = ~clk;                       // one tick = one receiver clock; a bit is 16 ticks
  wire rx, valid, frame_err, parity_err, overrun, break_seen, frame_done;
  wire [7:0] data;
  uart_tx  tx0(rx);
  uart_rx  rx0(clk, rst_n, rx, take, data, valid, frame_err, parity_err, overrun, break_seen, frame_done);
  uart_mon mon(rx);
  int d; real actual;
  `include "uart_tasks.svh"
  `include "p6_lab.svh"
  initial begin
    mon.trace = 1;
    #4 rst_n = 1; #4;
    mon.trace = 0; rx_log = 0;
    d = $rtoi(50.0e6 / (16 * 115200) + 0.5);          // round to the nearest integer
    actual = 50.0e6 / (16 * d);
    $display("divisor %0d, error %+0.2f %%", d, (actual / 115200 - 1) * 100);
     $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
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
Read included file: uart_checker.sv
`timescale 1ns/1ps
module uart_checker(input wire clk,rx,valid,frame_err,parity_err,input wire [7:0] data,input wire frame_done);
  int broken=0,received=0;
  task automatic expect_byte(input byte unsigned wanted,input bit fe=0,pe=0);
    if(!valid||data!==wanted||frame_err!==fe||parity_err!==pe)begin broken++;$display("UART CHECK got=%02h valid=%b FE=%b PE=%b expected=%02h FE=%b PE=%b",data,valid,frame_err,parity_err,wanted,fe,pe);end
  endtask
  always @(negedge clk)if(frame_done)received++;
endmodule
Read included file: uart_mon.sv
`timescale 1ns/1ps
// Independent wire decoder: completion is reported after the stop sample, never at START.
module uart_mon(input wire rx);
 int bits=8,parity=0; real bit_period=16.0; int frames=0; bit trace=0;
 reg [7:0] value; bit p,pe,fe;realtime began;
 initial forever begin
  @(negedge rx);began=$realtime; #(bit_period/2); if(rx==0)begin
   value=0;p=(parity==2);pe=0;
   for(int i=0;i<bits;i++)begin #(bit_period);value[i]=rx;p=p^rx;end
   if(parity!=0)begin #(bit_period);pe=(rx!=p);end
   #(bit_period);fe=!rx;
   if(value==0&&fe)begin
    while(rx==0&&$realtime-began<11*bit_period)#(bit_period/16);
    if(rx==0)begin wait(rx==1);if(trace)$display("UART -- BREAK");end
    else begin frames++;if(trace)$display("UART 00 FE");end
   end
   else begin frames++;if(trace)$display("UART %02h %s",value,fe?"FE":pe?"PE":"ok");end
  end
 end
endmodule
Read included file: uart_tasks.svh
task automatic send_byte(input byte unsigned b); tx0.send_byte(b); endtask
task automatic send_break(); tx0.send_break(); endtask
task automatic set_error(input real e); tx0.set_error(e); endtask
task automatic consume(); @(negedge clk);take=1;@(negedge clk);take=0;endtask
Read included file: p6_lab.svh
// p6_lab.svh: lesson helpers. The wire monitor follows the transmitter's format and rate, so its
// UART line shows what was sent; the RX line shows what the receiver under test delivered.
bit rx_log = 1, sample_log = 0;
int ticks = 0, edge_tick = 0, last_left = 0, last_state = 0, last_index = 0;
always @(posedge clk) ticks++;
always @(negedge rx) if (rx0.state == 0) edge_tick = ticks;
always @(tx0.parity) mon.parity = tx0.parity;
always @(tx0.bits) mon.bits = tx0.bits;
always @(tx0.error) mon.bit_period = 16.0 * (1.0 + tx0.error);
always @(negedge clk) begin
  if (sample_log && last_left == 1 && rx0.left_ticks == 16) begin
    if (last_state == 1) $display("tick %0d: data %0d sample reads %b", ticks - edge_tick, last_index, rx0.sampled);
    else if (last_state == 2) $display("tick %0d: parity sample reads %b", ticks - edge_tick, rx0.sampled);
    else $display("tick %0d: stop sample reads %b", ticks - edge_tick, rx0.sampled);
  end
  last_left = rx0.left_ticks; last_state = rx0.state; last_index = rx0.index;
end
// The RX line is printed one bit-time after the receiver's stop sample, after the monitor's line.
always @(rx0.frames) if (rx_log) begin
  #16;
  if (overrun) $display("RX %02h %s overrun", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
  else $display("RX %02h %s", data, frame_err ? "FE" : parity_err ? "PE" : "ok");
end
always @(posedge break_seen) if (rx_log) begin wait (rx == 1); #16 $display("RX break"); end
// A low pulse of the given number of ticks on the idle line.
task automatic glitch(input int width); tx0.tx = 0; #(width); tx0.tx = 1; endtask
// Send a byte and pull the line low for `width` ticks ending at receiver tick `at` after its start edge
// (tick n is the n-th clock edge after the edge), then give the line back its level.
int hit_at = 0, hit_width = 0; reg hit_level = 1; event hit_go;
always @(hit_go) begin
  #(hit_at - 0.5 - hit_width + 0.25); hit_level = tx0.tx; tx0.tx = 0; #(hit_width); tx0.tx = hit_level;
end
task automatic send_hit(input byte unsigned b, input int at, input int width);
  hit_at = at; hit_width = width; -> hit_go; send_byte(b);
endtask
ConceptOn a real link, why is the RTS trigger usually set below the receiving FIFO's capacity?
ConceptWhat does XON/XOFF flow control cost compared with RTS and CTS?

Remember

  • RTS and CTS are "ready" signals: the receiving FIFO drives RTS, the sender sees it as CTS and starts no new frame.
  • Ignoring CTS fills the FIFO and every byte after it is lost behind an overrun flag.
  • The trigger sits below full so bytes already in flight have room.
  • XON/XOFF needs no wires but uses two byte values and acts late, through software.
  • The divisor quantises the baud; its error adds to the peer's and must fit the measured budget.

Practice