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.
- 8N1
- 8E1
- 8N2
Read the plot as a table
| Transmitter rate error | 8N1 | 8E1 | 8N2 |
|---|---|---|---|
| -8% | 0 | 0 | 9 |
| -6% | 0 | 0 | 64 |
| -4% | 64 | 64 | 64 |
| -2% | 64 | 64 | 64 |
| 0% | 64 | 64 | 64 |
| 2% | 64 | 64 | 64 |
| 4% | 64 | 52 | 64 |
| 6% | 7 | 4 | 5 |
| 8% | 0 | 1 | 2 |
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.
| Pacing | Format | First failing step, fast side | First failing step, slow side |
|---|---|---|---|
| back-to-back | 8N1 | −6 % | +4.5 % |
| back-to-back | 8E1 | −5.5 % | +4 % |
| back-to-back | 8N2 | −6.5 % | +4.5 % |
| idle-bit | 8N1 | −6.5 % | +4.5 % |
| idle-bit | 8E1 | −6 % | +4 % |
| idle-bit | 8N2 | −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.
| Sample | Bits after the start | Drift at +4.5 %, ticks | Drift at +6 %, ticks |
|---|---|---|---|
| data 0 | 1 | 0.7 | 1.0 |
| data 1 | 2 | 1.4 | 1.9 |
| data 2 | 3 | 2.2 | 2.9 |
| data 3 | 4 | 2.9 | 3.8 |
| data 4 | 5 | 3.6 | 4.8 |
| data 5 | 6 | 4.3 | 5.8 |
| data 6 | 7 | 5.0 | 6.7 |
| data 7 | 8 | 5.8 | 7.7 |
| stop | 9 | 6.5 | 8.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.
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.
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.
| Sample | Tick | Sent | Read |
|---|---|---|---|
| data 0 | 24 | 1 | 1 |
| data 1 | 40 | 0 | 0 |
| data 2 | 56 | 1 | 1 |
| data 3 | 72 | 0 | 0 |
| data 4 | 88 | 1 | 1 |
| data 5 | 104 | 0 | 0 |
| data 6 | 120 | 1 | 0 |
| data 7 | 136 | 0 | 1 |
| stop | 152 | 1 | 0 |
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.
- 8N1
- 8E1
- 8N2
Read the plot as a table
| Transmitter rate error | 8N1 | 8E1 | 8N2 |
|---|---|---|---|
| -8% | 9 | 5 | 9 |
| -6% | 64 | 43 | 64 |
| -4% | 64 | 64 | 64 |
| -2% | 64 | 64 | 64 |
| 0% | 64 | 64 | 64 |
| 2% | 64 | 64 | 64 |
| 4% | 64 | 48 | 64 |
| 6% | 5 | 4 | 7 |
| 8% | 2 | 0 | 2 |
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
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.
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.
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.
UART 55 ok RX 55 ok UART 55 ok RX 55 ok UART 55 ok RX 95 FE UART 55 ok RX d5 FERead 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
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
- A Receiver That Survives the Sweep Implement · Hard
- Locate the First Displaced Sample Debug · Hard