Errors, Break and Recovery
Tell a framing error from a parity error from an overrun by the trace that caused it, decode a break once, and say what the three-tick vote can and cannot reject.
- Lesson
- P6.5
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A bootloader entered its update mode on a break. Its receiver flagged a framing error on the first frame of the break and then, line still low, started counting a new frame, and another. Each break became dozens of 0x00 bytes with framing errors, the firmware filtered them all out, and updates in the field never started.
- A good stop
The stop sample reads 1: the byte goes to the holding register and the receiver looks for the next start.
- A bad stop
The stop sample reads 0. The byte is delivered with FE, and the receiver waits for the line to go high before it looks for a start.
- Frame or break?
A frame of all zeros with a 0 stop is held back. If the line rises soon, it was a frame: 00 with FE. If it stays low for 176 ticks, it is a break, reported once.
Read the diagram as text
- Stop sample: the last bit of the frame
- Reads 1: deliver the byte
- Reads 0, data not all 0: deliver it with FE
- Reads 0, data all 0: hold it back: frame or break?
- Wait for a high: never start on a low line
- High within 176 ticks: deliver 00 with FE
- 176 low ticks: break_seen; drop the held frame
- Look for a start: eight low ticks again
- Stop sample → Reads 1
- Stop sample → Reads 0, data not all 0
- Stop sample → Reads 0, data all 0
- Reads 1 → Look for a start
- Reads 0, data not all 0 → Wait for a high
- Reads 0, data all 0 → High within 176 ticks
- Reads 0, data all 0 → 176 low ticks
- Wait for a high → Look for a start
Waiting for a high before the next start is what keeps a break from becoming a stream of bytes. The 176-tick threshold (11 bit-times) is this bench's rule.
Three Flags, Three Places
Each flag points at a different place on the frame and a different kind of fault. A framing error is the stop sample. A parity error is the parity sample. An overrun is not on the wire at all: a frame finished while the last byte was still unread.
| Cause | On the wire | Flag | Measured | What the consumer does |
|---|---|---|---|---|
| Stop cut short, next frame behind it | the stop sample reads 0 | FE | RX a5 FE | drop the byte; distrust the next one |
| One flipped bit | the parity sample disagrees | PE | RX a5 PE | drop the byte |
| Byte not read in time | nothing wrong | overrun | RX 22 ok overrun | read faster, or add a FIFO (lesson P6.6) |
| Line held low | longer than a frame | break | RX break | a message in itself, often "reset" or "attention" |
Measured: the RX lines of this lesson's example and of the break run below.
Look at the frame after the short stop in the example: UART fe ok, RX fe ok. The receiver waited for a high, which came inside the next frame, then took a later 0 for a start bit. A byte after a framing error can be wrong without any flag.
Overrun
The holding register is one byte deep. valid rises at the stop sample and take clears it. If the next frame's stop sample comes first, overrun rises and one byte is gone.
Measured, one column per bit-time. 0x11 lands and nobody reads it; at the second stop sample overrun rises and the holding register now shows 0x22.
Bench defaults
send_byte(8'h11);
send_byte(8'h22);
Measured: overrun = 1, data = 22. The newer byte replaced the older.
rx0.no_overrun_flag = 1
rx0.no_overrun_flag = 1;
send_byte(8'h11);
send_byte(8'h22);
Measured: overrun = 0, data = 22. 0x11 is lost and nothing says so.
A Break
A break is the line held low for longer than a frame. The receiver first sees a start, eight zeros and a stop of 0. It must not start again on a line that is still low.
| Tick after the edge | What the receiver does |
|---|---|
| 8 | start accepted |
| 152 | stop sample reads 0 on a frame of zeros: held back |
| 176 | 176 low ticks, break_seen |
| 3200 | line high again |
Measured for send_break(), 200 bit-times low, then 0x55. The monitor prints UART -- BREAK, UART 55 ok; the receiver RX break, RX 55 ok.
Bench defaults
send_break();
send_byte(8'h55);
Measured: RX break, RX 55 ok.
rx0.stop_as_data = 1
rx0.stop_as_data = 1;
send_break();
send_byte(8'h55);
Measured: RX break, RX aa ok. The break is still caught, but the stop sample becomes data bit 7 and bit 0 is lost on every frame.
What the Vote Buys
Each sample is a vote of the three ticks ending on the sample tick. A glitch that covers one of them is outvoted. One that covers two wins.
Measured, one column per tick for 0x55. Ticks 86 and 87 count two 1s; tick 88 reads 0 and is the sample tick. Two of three is a 1, and the counter restarts at 16.
| Glitch | Bench defaults (vote) | rx0.no_vote = 1 |
|---|---|---|
| 1 tick low on data 4's sample tick (tick 88) | RX 55 ok | RX 45 ok |
| 2 ticks low, ticks 87 and 88 | RX 45 ok | RX 45 ok |
0x55 sent each time; the wire monitor reads 0x55 every time. Losing data 4 turns 0x55 into 0x45, with no flag.
The vote buys one tick and no more. Real protection against noise is electrical: filtering, termination and edge rates. A bench that calls the vote a noise filter will not test a two-tick glitch.
Where You Will Meet It
Every UART driver reads a status register with these flags, and every UART bench injects each cause on purpose. Bootloaders and debug consoles use the break as a signal, so a receiver that turns it into bytes breaks real products.
Asked in interviews: UART errors
What is the difference between a framing error and an overrun?
A framing error is on the wire: the stop sample read 0. An overrun is in the receiver: a byte arrived before the last one was read.
How should a receiver handle a break?
Report it once, deliver no bytes for it, and look for the next start only after the line has gone high again.
Does a majority vote make a receiver noise-proof?
No. A vote of three outvotes a one-tick glitch; a two-tick glitch on the right ticks still wins.
Try It
Three faults in a row. The short stop gives FE on 0xA5, and the frame behind it is read from the wrong edge. The inverted parity bit gives PE on 0xA5. The two bytes nobody reads give overrun, and the holding register keeps the newer one.
Put consume(); between the last two send_byte calls and run again: the overrun goes away. Then replace the whole body with send_break(); send_byte(8'h55);.
UART a5 FE RX a5 FE UART fe ok RX fe ok UART a5 PE RX a5 PE UART 11 ok RX 11 ok UART 22 ok RX 22 ok overrun overrun = 1, data = 22Read 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 framing error is the stop sample, a parity error the parity sample, an overrun a byte nobody read in time.
- After a stop bit of 0 the receiver waits for a high, and the byte after a framing error may be wrong without a flag.
- A break is reported once; the receiver delivers nothing for it and never starts on a low line.
- The three-tick vote outvotes a one-tick glitch and loses to a two-tick one.
- Each flag has its own fix: resynchronise, drop the byte, read faster or add a FIFO.
Practice
- The Missing Overrun Flag Fix · Medium
- Check UART Errors Without False Alarms Verify · Hard