Baud Mismatch: The Measured Budget

State the baud-mismatch budget of a receiver as a measured number for 8N1, 8E1 and 8N2, say which bit fails first and why, and say when a second stop bit helps.

Lesson
P6.4
Time
35 min
Runs on
Your browser
Check
5 questions

A gateway ran its UART at 115200 from a 50 MHz clock, divisor 27, error +0.47 %. Its peer ran from an internal RC oscillator. The sum was "well inside the 5 % everyone quotes", yet frames failed in the heat. The quoted number was for 8N1 and the link ran 8E1, whose longer frame drifts one bit further.

Measured sweepFrames Decoded Against Rate Error, Back to Back64 seeded frames per 0.5 % step, frames back to back, the bench receiver. Positive error is a slower transmitter.
  • 8N1
  • 8E1
  • 8N2
0102030405060Frames decoded of 64-8%-6%-4%-2%02%4%6%8%Transmitter rate error8N1 −6 %8N1 +4.5 %0204060Frames decoded of 64-5%05%Transmitter rate error8N1 −6 %8N1 +4.5 %
Read the plot as a table
Transmitter rate error8N18E18N2
-8%009
-6%0064
-4%646464
-2%646464
0%646464
2%646464
4%645264
6%745
8%012

The Sweep

The receiver counts its own ticks to the middle of each bit. A transmitter that runs slow or fast moves its bits under those samples. The sweep sends the same seeded frames at every step and counts how many decode with no flag.

PacingFormatFirst failing step, fast sideFirst failing step, slow side
back-to-back8N1−6 %+4.5 %
back-to-back8E1−5.5 %+4 %
back-to-back8N2−6.5 %+4.5 %
idle-bit8N1−6.5 %+4.5 %
idle-bit8E1−6 %+4 %
idle-bit8N2−6.5 %+4.5 %

Measured: data/P6-4.json, seed 20261108, 64 frames per step, both simulators agree. "First failing" is the first step out from 0 where fewer than 64 frames decode.

The partial steps are real, not noise. Each frame's start edge lands anywhere within a receiver tick, so at the edge of the budget some frames have room and some do not.

Drift per Bit

If the transmitter's bit is longer by a fraction e, its bit k ends 16·e·k ticks later than the receiver expects. The sample of the last bit has drifted furthest, so the stop bit fails first.

SampleBits after the startDrift at +4.5 %, ticksDrift at +6 %, ticks
data 010.71.0
data 121.41.9
data 232.22.9
data 342.93.8
data 453.64.8
data 564.35.8
data 675.06.7
data 785.87.7
stop96.58.6

Arithmetic: 16 ticks per bit, drift 16·e·k. A sample in the exact middle of its bit has 8 ticks on each side; shaded cells are past that.

A centred stop sample would leave its bit at ±5.6 %. This receiver loses frames from +4.5 % on the slow side but holds to −5.5 % on the fast side. Its sample sits early (lesson P6.3): the vote is centred one tick before the sample tick, leaving less room toward the start of the bit.

Where the Budget GoesArrows read as "leads to". A slow transmitter moves the samples toward the start of each bit, a fast one toward the end.
Where the budget goesArrows read as "leads to". A slow transmitter moves the samples toward the start of each bit, a fast one toward the end. component Rate error e TX bit = 16 × (1 + e)ticks component Drift 16·e·k ticks atsample k component Stop sample largest k, drifts most component Slow side, e > 0 room to the start ofthe bit component Fast side, e < 0 room to the end ofthe bit early samplelate sample
Read the diagram as text
  • Rate error e: TX bit = 16 × (1 + e) ticks
  • Drift: 16·e·k ticks at sample k
  • Stop sample: largest k, drifts most
  • Slow side, e > 0: room to the start of the bit
  • Fast side, e < 0: room to the end of the bit
  • Rate error e → Drift
  • Drift → Stop sample
  • Stop sample → Slow side, e > 0: early sample
  • Stop sample → Fast side, e < 0: late sample

The budget is about half a bit spread over the frame, split by where the sample sits.

0x55 at +4 %, with the receiver’s three-tick vote
startdata 0data 1data 2data 3data 4data 5data 6data 7stopnext startnext data 0next data 1next data 2next data 3next data 4next data 5next data 6next data 7next stop 0123456789101112131415161718192021222324252627282930313233343536373839404142434445rxsamplerx sampled at 4.584134615384615rx sampled at 4.704326923076923rx sampled at 4.824519230769231rx sampled at 6.507211538461538rx sampled at 6.627403846153846rx sampled at 6.747596153846153rx sampled at 8.430288461538462rx sampled at 8.55048076923077rx sampled at 8.670673076923077rx sampled at 10.353365384615385rx sampled at 10.473557692307692rx sampled at 10.59375rx sampled at 12.276442307692307rx sampled at 12.396634615384615rx sampled at 12.516826923076923rx sampled at 14.19951923076923rx sampled at 14.319711538461538rx sampled at 14.439903846153845rx sampled at 16.122596153846153rx sampled at 16.24278846153846rx sampled at 16.362980769230766rx sampled at 18.045673076923077rx sampled at 18.165865384615383rx sampled at 18.286057692307693rx sampled at 19.96875rx sampled at 20.088942307692307rx sampled at 20.209134615384613rx sampled at 24.53605769230769rx sampled at 24.65625rx sampled at 24.776442307692307rx sampled at 26.459134615384613rx sampled at 26.579326923076923rx sampled at 26.69951923076923rx sampled at 28.382211538461537rx sampled at 28.502403846153847rx sampled at 28.622596153846153rx sampled at 30.30528846153846rx sampled at 30.425480769230766rx sampled at 30.545673076923077rx sampled at 32.22836538461539rx sampled at 32.34855769230769rx sampled at 32.46875rx sampled at 34.15144230769231rx sampled at 34.27163461538461rx sampled at 34.39182692307692rx sampled at 36.074519230769226rx sampled at 36.19471153846154rx sampled at 36.31490384615385rx sampled at 37.99759615384615rx sampled at 38.11778846153846rx sampled at 38.23798076923077rx sampled at 39.92067307692307rx sampled at 40.04086538461539rx sampled at 40.16105769230769
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.
  • next start: columns 22 through 23.
  • next data 0: columns 24 through 25.
  • next data 1: columns 26 through 27.
  • next data 2: columns 28 through 29.
  • next data 3: columns 30 through 31.
  • next data 4: columns 32 through 33.
  • next data 5: columns 34 through 35.
  • next data 6: columns 36 through 37.
  • next data 7: columns 38 through 39.
  • next stop: columns 40 through 41.
  • rx sampled at 4.584, 4.704, 4.825, 6.507, 6.627, 6.748, 8.43, 8.55, 8.671, 10.353, 10.474, 10.594, 12.276, 12.397, 12.517, 14.2, 14.32, 14.44, 16.123, 16.243, 16.363, 18.046, 18.166, 18.286, 19.969, 20.089, 20.209, 24.536, 24.656, 24.776, 26.459, 26.579, 26.7, 28.382, 28.502, 28.623, 30.305, 30.425, 30.546, 32.228, 32.349, 32.469, 34.151, 34.272, 34.392, 36.075, 36.195, 36.315, 37.998, 38.118, 38.238, 39.921, 40.041, 40.161.

Generated and decoded back with the receiver’s three vote ticks (21.5, 22.5, 23.5 ticks after START for data 0). A second 0x55 starts immediately after the stop. At +4 %, the last step where every frame decoded, the dots have walked toward the start of each bit and the stop vote sits near the start of the stop bit.

SampleTickSentRead
data 02411
data 14000
data 25611
data 37200
data 48811
data 510400
data 612010
data 713601
stop15210

Measured at +6 % with sample_log = 1, 0x55 sent. The ticks are the receiver's own and do not move; the wire does. From data 6 on, each sample reads the bit before.

Parity Makes the Frame Longer

8E1 adds a bit before the stop, so the stop sample is one bit further from the start and drifts further. Measured back to back: 8E1 fails from +4 % where 8N1 fails from +4.5 %, and from −5.5 % where 8N1 fails from −6 %.

At the first failing step the first wrong bit is the stop bit for 8N1 and the stop bit for 8E1. The quoted 5 % is not one number: it depends on the frame, the receiver and the pacing.

Two Stop Bits

On the fast side the stop sample drifts late. If the next frame follows at once, it lands in the next start bit and reads 0, a framing error. The receiver then waits for the line to go high, so it starts the next frame late.

8N1 back to back, −6 %

tx0.no_idle_between = 1;
set_error(-0.06);
repeat (8) send_byte(8'h55);

Measured: 7 RX lines for 8 frames, 3 of them RX 55 ok; the rest are RX 55 FE.

8N2 back to back, −6 %

tx0.no_idle_between = 1;
tx0.stop_bits = 2;
set_error(-0.06);
repeat (8) send_byte(8'h55);

Measured: 8 of 8 RX 55 ok. The drifted stop sample lands in the second stop bit, a 1.

Measured sweepThe Same Sweep with an Idle Bit Between FramesOne idle bit plus a seeded fraction of a tick before every frame. Compare 8N1 and 8N2 on the fast side.
  • 8N1
  • 8E1
  • 8N2
0102030405060Frames decoded of 64-8%-6%-4%-2%02%4%6%8%Transmitter rate error8N1 −6.5 %8N1 +4.5 %0204060Frames decoded of 64-5%05%Transmitter rate error8N1 −6.5 %8N1 +4.5 %
Read the plot as a table
Transmitter rate error8N18E18N2
-8%959
-6%644364
-4%646464
-2%646464
0%646464
2%646464
4%644864
6%547
8%202

With an idle bit between frames the second stop bit buys nothing: 8N1 and 8N2 both first fail at −6.5 %, the same as 8N2 back to back. It helps only on the fast side, and only when frames stream back to back. The plain 8N1 run with the idle bit at −6 % decodes 8 of 8.

Where You Will Meet It

Every UART link budget adds two errors: this end's divisor and the peer's oscillator (lesson P6.6). A bench for a UART receiver runs this sweep, in both directions and in every format the design supports, and prints the first failing step.

Asked in interviews: baud tolerance
  1. Which bit fails first under a rate mismatch, and why?

    The stop bit, or the last data bit before it: the drift grows with every bit after the start edge, which is the only point both ends agree on.

  2. Why does parity reduce the tolerance?

    It adds a bit before the stop, so the stop sample is one bit further from the start and drifts further.

  3. Does a second stop bit help?

    Only when the transmitter is fast and frames come back to back: the late stop sample then lands in idle instead of the next start bit.

Try It

Two frames with the transmitter's bits 4 % long, then two at 6 %. At 4 % both decode. At 6 % the last samples have drifted past the start of their bits: the last data bits read their neighbours, and the stop sample reads data 7, a 0, so both frames carry FE.

The two 6 % lines differ because each frame starts at a different fraction of a receiver tick. Try set_error(0.045); and set_error(-0.06);, then add tx0.no_idle_between = 1; and watch the fast side get worse.

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: UART 55 ok RX 55 ok UART 55 ok RX 55 ok UART 55 ok RX 95 FE UART 55 ok RX d5 FE
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 outputBack-to-back 8N1 (tx0.no_idle_between = 1), set_error(-0.06), eight frames of 0x55, every byte read at once. The program counts the frames delivered as 0x55 with no flag. 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 good = 0;
  `include "uart_tasks.svh"
  `include "p6_lab.svh"
  initial begin
    mon.trace = 1;
    #4 rst_n = 1; #4;
    take = 1; rx_log = 0; mon.trace = 0;
    tx0.no_idle_between = 1;
    set_error(-0.06);
    fork forever begin @(rx0.frames); #1 if (data == 8'h55 && !frame_err) good++; end join_none
    repeat (8) send_byte(8'h55);
    #200 $display("%0d of 8 frames decoded", good);
     $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
ConceptBack-to-back 8E1 at +4 %, its first failing step on the slow side. Which bit is the first one wrong in the failing frames?
ConceptThree statements are true of the measured sweep. Which one is false?
ConceptWhy does a receiver restart its tick count on every start edge instead of counting from the first one?
Read the waveformThe figure of 0x55 at +4 %, sampled at the receiver's three vote ticks. Which sample dot sits closest to the start of its bit?
UART on the wires
startdata 0data 1data 2data 3data 4data 5data 6data 7stop 012345678910111213141516171819202122232425rxsamplerx sampled at 4.584134615384615rx sampled at 4.704326923076923rx sampled at 4.824519230769231rx sampled at 6.507211538461538rx sampled at 6.627403846153846rx sampled at 6.747596153846153rx sampled at 8.430288461538462rx sampled at 8.55048076923077rx sampled at 8.670673076923077rx sampled at 10.353365384615385rx sampled at 10.473557692307692rx sampled at 10.59375rx sampled at 12.276442307692307rx sampled at 12.396634615384615rx sampled at 12.516826923076923rx sampled at 14.19951923076923rx sampled at 14.319711538461538rx sampled at 14.439903846153845rx sampled at 16.122596153846153rx sampled at 16.24278846153846rx sampled at 16.362980769230766rx sampled at 18.045673076923077rx sampled at 18.165865384615383rx sampled at 18.286057692307693rx sampled at 19.96875rx sampled at 20.088942307692307rx sampled at 20.209134615384613
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.584, 4.704, 4.825, 6.507, 6.627, 6.748, 8.43, 8.55, 8.671, 10.353, 10.474, 10.594, 12.276, 12.397, 12.517, 14.2, 14.32, 14.44, 16.123, 16.243, 16.363, 18.046, 18.166, 18.286, 19.969, 20.089, 20.209.

One column per half bit; dots are the three vote ticks for each sample.

Remember

  • A rate error moves sample k by 16·e·k ticks, so the stop bit fails first.
  • The budget is about half a bit over the frame, split by where the receiver's sample sits; this receiver's early sample makes it lopsided.
  • Parity lengthens the frame and fails one step sooner on each side.
  • A second stop bit helps only on the fast side with frames back to back.
  • Quote the budget with its format, pacing and receiver, as a measured step.

Practice