Serial Links in One Picture

Sort the five off-chip serial links by two questions, read one byte on four of them, and say what each answer costs the receiver.

Lesson
P6.1
Time
20 min
Runs on
Your browser
Check
4 questions

A board passed bring-up on the bench, then lost bytes from a sensor in the field. The sensor spoke UART, and the firmware had set its baud divisor "close enough" without measuring how close. Nobody could say what the link tolerated, because nobody had drawn a frame against the receiver's sample points.

0xA5 on a UART
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. A start bit, eight data bits least significant first, a stop bit: 10 bit-times for one byte. The dots are where a receiver samples. Module P6 is this picture with its tolerance measured.

Five links leave a typical SoC. Ask each one two questions. Does a clock wire travel with the data? How is a 1 driven: pushed high, or released to a pull-up? The first answer sets the receiver's job, the second sets speed and sharing.

One SoC, Five Serial LinksEach edge carries the two answers: clock wire or none, and the drive style of a 1.
One SoC, five serial linksEach edge carries the two answers: clock wire or none, and the drive style of a 1. component SoC five off-chip links component Debug console UART: TX and RX, noclock component Boot flash QSPI: clock, select, 4data component Sensor SPI: clock, select, 2data component Power controller I2C: SCL and SDA component Sensor hub I3C: SCL and SDA no clock · push-pullclock · push-pullclock · push-pullclock · open drainclock · both styles
  1. No clock wire

    Only UART sends no clock. Its receiver must find every bit from one falling edge per frame (lesson P6.3).

  2. A clock, pushed

    SPI and QSPI send a clock and drive both levels. Fast, but one driver per wire at a time (module P7).

  3. Released to a pull-up

    I2C only ever pulls low; a resistor brings the line high, slowly. Any device can pull, so many share two wires (module P8). I3C mixes both styles (module P9).

Read the diagram as text
  • SoC: five off-chip links
  • Debug console: UART: TX and RX, no clock
  • Boot flash: QSPI: clock, select, 4 data
  • Sensor: SPI: clock, select, 2 data
  • Power controller: I2C: SCL and SDA
  • Sensor hub: I3C: SCL and SDA
  • SoC → Debug console: no clock · push-pull
  • SoC → Boot flash: clock · push-pull
  • SoC → Sensor: clock · push-pull
  • SoC → Power controller: clock · open drain
  • SoC → Sensor hub: clock · both styles

No clock wire means the receiver recovers timing from the data. Open drain means the rise is slow but the wire can be shared.

One Byte, Four Shapes

The same byte, 0xA5, on four of the links. Each figure is generated and read back by its own decoder before the page is built. Count the bits and note who drives each lane.

0xA5 on SPI, mode 0
mode 0 012345678910111213141516171819CS_nCLKMOSIMISOMOSI sampled at 3MOSI sampled at 5MOSI sampled at 7MOSI sampled at 9MOSI sampled at 11MOSI sampled at 13MOSI sampled at 15MOSI sampled at 17MISO sampled at 3MISO sampled at 5MISO sampled at 7MISO sampled at 9MISO sampled at 11MISO sampled at 13MISO sampled at 15MISO sampled at 17
Read phases and sample points (half clock)
  • mode 0: columns 1 through 18.
  • MOSI sampled at 3, 5, 7, 9, 11, 13, 15, 17.
  • MISO sampled at 3, 5, 7, 9, 11, 13, 15, 17.

8 clocks, most significant bit first, framed by chip select going low. The controller drives CLK and MOSI; the peripheral drives MISO in the same clocks.

0xA5 in a quad read (EBh)
commandaddressmodedummydata 0123456789101112131415161718192021222324252627282930313233343536373839404142434445CS_nCLKIO0IO1IO2IO3dirreleasedcontrollerturnaroundflashreleased
Read phases and sample points (half clock)
  • command: columns 1 through 16.
  • address: columns 17 through 28.
  • mode: columns 29 through 32.
  • dummy: columns 33 through 40.
  • data: columns 41 through 44.

A quad read: command, address, mode and dummy clocks first, then the data at a nibble per clock on four pins. The byte itself takes 2 clocks. One command, four lanes, not four links.

0xA5 on I2C, after the address
START0xa0 + ACK0xa5 + ACKSTOP 012345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879SCLSDActrl_lowtgt_lowSAAPSDA 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 42SDA sampled at 46SDA sampled at 50SDA sampled at 54SDA sampled at 58SDA sampled at 62SDA sampled at 66SDA sampled at 70SDA sampled at 74
Read phases and sample points (quarter bit)
  • START: columns 2 through 3.
  • 0xa0 + ACK: columns 4 through 39.
  • 0xa5 + ACK: columns 40 through 75.
  • STOP: columns 76 through 79.
  • SDA sampled at 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58, 62, 66, 70, 74.

After START and the address byte, the data byte takes 9 clocks: eight data bits, most significant first, and a ninth in which the receiver pulls SDA low to acknowledge. The two driver lanes show who pulls the line.

The UART frame in the opening figure has no clock lane at all. Its start bit is the only edge the receiver is promised, and its stop bit guarantees the next start is an edge too.

The Comparison

LinkWiresClock fromA 1 isPer payload byteAt a common rate
UARTTX, RXnobody: each side has its owndriven high10 bit-times86.8 µs at 115200 baud
SPICLK, CS_n, MOSI, MISOthe controllerdriven high8 clocks60.2 ns at 133 MHz
QSPICLK, CS_n, IO0-IO3the controllerdriven high2 clocks (data phase)15.0 ns at 133 MHz
I2CSCL, SDAthe controllerreleased to a pull-up9 clocks22.5 µs at 400 kHz
I3CSCL, SDAthe controllerdriven or releasedmodule P9module P9

Counts from the four figures. The rates are the usual top rates: a common console baud, the flash's 133 MHz maximum clock, I2C Fast-mode. Command, address and acknowledge overheads differ per link.

The table also gives the module order. UART first, because it has no clock and every bit is found by counting. Then SPI and QSPI, where edges and phases matter. Then I2C, where two devices can drive one wire.

Module P6, Lesson by LessonArrows show the order. The RTL track builds the same blocks from the design side.
Module P6, lesson by lessonArrows show the order. The RTL track builds the same blocks from the design side. component P6.2 The frame start, data, parity,stop component P6.3 Thereceiver sixteen ticks per bit component P6.4 Baudmismatch the measured budget component P6.5 Errors andbreak which flag, whichcause component P6.6 Flow control RTS, CTS, FIFOs,divisors component P6.7 Capstone monitor, checker, bughunt component RTL R8.2 a UART with FIFOs design side
Read the diagram as text
  • P6.2 The frame: start, data, parity, stop
  • P6.3 The receiver: sixteen ticks per bit
  • P6.4 Baud mismatch: the measured budget
  • P6.5 Errors and break: which flag, which cause
  • P6.6 Flow control: RTS, CTS, FIFOs, divisors
  • P6.7 Capstone: monitor, checker, bug hunt
  • RTL R8.2: a UART with FIFOs
  • P6.2 The frame → P6.3 The receiver
  • P6.3 The receiver → P6.4 Baud mismatch
  • P6.4 Baud mismatch → P6.5 Errors and break
  • P6.5 Errors and break → P6.6 Flow control
  • P6.6 Flow control → P6.7 Capstone
  • P6.7 Capstone → RTL R8.2: design side

Every page after this one runs the same bench: a transmitter, a sixteen-tick receiver and a wire monitor.

Where You Will Meet It

Every chip has a UART console, usually the first thing that talks at bring-up. A flash boots it over QSPI, sensors sit on SPI or I2C, and the power controller is on I2C. A verification engineer meets all four in one SoC bench.

Asked in interviews: the serial family
  1. Which serial link has no clock wire, and what does that cost?

    UART. The receiver must find the bit times from the start edge and its own clock, so both ends must agree on the rate closely.

  2. Why is I2C slower than SPI?

    A 1 on I2C is a released line pulled up by a resistor, so the rise is slow. SPI drives both levels and runs far faster.

  3. Is QSPI four SPI links?

    No. One controller sends one command and uses four data lanes together; a byte takes two clocks.

Try It

Two lines. UART a5 ok is the wire monitor: it watches the line and decodes what was sent. RX a5 ok is the receiver on the bench: what it delivered to its holding register. They agree, and neither saw an error.

Change the byte and run it again. Then add tx0.parity = 1; before send_byte: the monitor follows the transmitter, the receiver still expects no parity, and you will see what a mismatched setting looks like. Lesson P6.2 explains the result.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: UART a5 ok RX a5 ok
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

Check Yourself

Predict the outputsend_byte(8'hA5) with the bench defaults: 8 data bits, no parity, one stop bit. The program times the frame from the start edge to the end of the stop bit. What does it 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 t0;
  `include "uart_tasks.svh"
  `include "p6_lab.svh"
  initial begin
    mon.trace = 1;
    #4 rst_n = 1; #4;
    mon.trace = 0; rx_log = 0;
    tx0.no_idle_between = 1;     // send_byte returns at the end of the stop bit
    t0 = $time;                  // send_byte drives the start edge at once
    send_byte(8'hA5);
    $display("%0d bit-times", ($time - t0) / 16);
     $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
ConceptWhich of these links needs no clock wire, and what does it pay for that?
Read the waveformThis is the opening figure: 0xA5 as one UART frame. Which statement about it is right?
UART on the wires
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.

The generated 0xA5 frame, one column per half bit.

ConceptA colleague says "8N1 and I2C are both two-wire serial, so they behave alike." What is wrong with that?

Remember

  • Two questions sort the serial links: is there a clock wire, and is a 1 driven or released to a pull-up.
  • UART alone has no clock wire, so its receiver finds every bit by counting from the start edge.
  • SPI and QSPI drive both levels with a controller clock; I2C only pulls low and shares the line.
  • One 8N1 UART byte is ten bit-times on the wire; SPI takes eight clocks, QSPI two, I2C nine.

Practice