UART · Module 9
Error Status, Sticky Flags and Receiver Re-Synchronisation
Transient events become persistent state, and the design decision is what happens when a clear and a new event land on the same clock edge. Detection and recovery are different problems.
Five chapters have produced events: a framing verdict, a parity verdict, an overrun pulse, a break declaration and a release. Each is correct, each is transient, and none of them is what software reads.
This chapter closes the module by answering the two questions the events leave open.
How do transient events become stable state? A one-cycle pulse on a 100 MHz clock is invisible to anything that samples less often than every 10 ns, which is everything. Something must hold it — and how it holds it, particularly when a clear collides with a new event, is a design decision with a defensible answer rather than an accident of statement ordering.
How does the receiver get back to a trustworthy frame boundary? This is a genuinely different problem from detection, and the distinction is the chapter's second half:
An implementation can detect every error correctly and still recover badly — turning one malformed frame into a burst of them.
1. Event and State
The distinction runs through every error source in this module, which is why they were all built to produce the same two shapes.
| Event | State | |
|---|---|---|
| Example | frame_error_evt_o | frame_error_sticky_o |
| Shape | one-cycle pulse | level, held |
| Answers | did something happen on this clock? | has something happened since I last looked? |
| Lifetime | one cycle | until cleared or reset |
| Consumer | hardware — counters, aggregators | software, or a slow observer |
| Loses information? | no | yes — how many, and which |
Software cannot observe events. A driver polling a status register every millisecond sees one sample in every hundred thousand clock cycles; a one-cycle pulse is invisible with certainty. Every event in this module therefore needs something that holds it, and that something is the sticky flag.
But the flag loses information the event had. A sticky bit that is set tells you at least one error occurred since the last clear — not how many, not when, not which frame. Chapter 9.3 §8 made the same point about overrun specifically, and it generalises: a counter is strictly more informative than a flag, for the cost of a few flip-flops, and a design that only ever needs "did anything go wrong" is choosing the weaker primitive deliberately rather than by default.
There is a third shape, and Chapter 9.4 produced the only instance of it: break_active_o is a level that is neither an event nor a sticky flag. It describes the line's present condition rather than a past occurrence, so it clears itself when the condition ends. A design that made break sticky and only sticky would be unable to answer "is the link broken right now?", which is usually the more urgent question.
2. The RTL
// ---------------------------------------------------------------------------
// 9.6 — sticky status, SET-DOMINANT, with an explicit clear input.
// ---------------------------------------------------------------------------
module uart_err_status (
input logic clk,
input logic rst_n,
input logic frame_error_evt_i,
input logic parity_error_evt_i,
input logic overrun_evt_i,
input logic break_evt_i,
input logic clear_i, // software clear, one cycle
output logic frame_error_sticky_o,
output logic parity_error_sticky_o,
output logic overrun_sticky_o,
output logic break_sticky_o,
output logic any_error_o
);
// SET-DOMINANT: when a clear and a new event land on the same cycle the
// flag STAYS SET. Losing a real event to a clear that raced it is the
// worse failure for diagnosis — §5 of the chapter argues this.
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_error_sticky_o <= 1'b0;
parity_error_sticky_o <= 1'b0;
overrun_sticky_o <= 1'b0;
break_sticky_o <= 1'b0;
end else begin
if (clear_i) begin
frame_error_sticky_o <= 1'b0;
parity_error_sticky_o <= 1'b0;
overrun_sticky_o <= 1'b0;
break_sticky_o <= 1'b0;
end
// Written AFTER the clear, so the set wins on a collision.
if (frame_error_evt_i) frame_error_sticky_o <= 1'b1;
if (parity_error_evt_i) parity_error_sticky_o <= 1'b1;
if (overrun_evt_i) overrun_sticky_o <= 1'b1;
if (break_evt_i) break_sticky_o <= 1'b1;
end
end
assign any_error_o = frame_error_sticky_o | parity_error_sticky_o
| overrun_sticky_o | break_sticky_o;
endmodule3. The Same Status Block in Three Languages
//===========================================================================
// uart_err_status_v — Synthesizable Verilog-2001
//
// Sticky status, SET-DOMINANT, with an explicit software clear.
//
// The collision case is the whole design decision: when clear_i and a new
// event land on the SAME clock, the flag stays SET. Losing a real error to
// a clear that raced it is the worse failure, because the condition has
// already happened and the only record of it is this bit.
//
// In Verilog the priority is expressed by WRITE ORDER inside one always
// block: the later non-blocking assignment to the same register wins.
//===========================================================================
module uart_err_status_v (
input wire clk,
input wire rst_n,
input wire frame_error_evt_i,
input wire parity_error_evt_i,
input wire overrun_evt_i,
input wire break_evt_i,
input wire clear_i,
output reg frame_error_sticky_o,
output reg parity_error_sticky_o,
output reg overrun_sticky_o,
output reg break_sticky_o,
output wire any_error_o
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_error_sticky_o <= 1'b0;
parity_error_sticky_o <= 1'b0;
overrun_sticky_o <= 1'b0;
break_sticky_o <= 1'b0;
end else begin
if (clear_i) begin
frame_error_sticky_o <= 1'b0;
parity_error_sticky_o <= 1'b0;
overrun_sticky_o <= 1'b0;
break_sticky_o <= 1'b0;
end
// Written AFTER the clear: the set wins on a collision.
if (frame_error_evt_i) frame_error_sticky_o <= 1'b1;
if (parity_error_evt_i) parity_error_sticky_o <= 1'b1;
if (overrun_evt_i) overrun_sticky_o <= 1'b1;
if (break_evt_i) break_sticky_o <= 1'b1;
end
end
assign any_error_o = frame_error_sticky_o | parity_error_sticky_o
| overrun_sticky_o | break_sticky_o;
endmodule--==========================================================================
-- uart_err_status -- Synthesizable VHDL-2008
--
-- Sticky status, SET-DOMINANT, with an explicit software clear.
--
-- On a same-cycle collision between clear_i and a new event the flag STAYS
-- SET. In VHDL the priority is expressed by SIGNAL ASSIGNMENT ORDER inside
-- one process -- the last assignment to a signal before the process
-- suspends is the one that takes effect -- which is the same mechanism
-- Verilog uses, spelled differently.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
entity uart_err_status is
port (
clk : in std_logic;
rst_n : in std_logic;
frame_error_evt_i : in std_logic;
parity_error_evt_i : in std_logic;
overrun_evt_i : in std_logic;
break_evt_i : in std_logic;
clear_i : in std_logic;
frame_error_sticky_o : out std_logic;
parity_error_sticky_o : out std_logic;
overrun_sticky_o : out std_logic;
break_sticky_o : out std_logic;
any_error_o : out std_logic
);
end entity uart_err_status;
architecture rtl of uart_err_status is
signal frame_q : std_logic;
signal parity_q : std_logic;
signal over_q : std_logic;
signal break_q : std_logic;
begin
frame_error_sticky_o <= frame_q;
parity_error_sticky_o <= parity_q;
overrun_sticky_o <= over_q;
break_sticky_o <= break_q;
any_error_o <= frame_q or parity_q or over_q or break_q;
process (clk, rst_n)
begin
if rst_n = '0' then
frame_q <= '0';
parity_q <= '0';
over_q <= '0';
break_q <= '0';
elsif rising_edge(clk) then
if clear_i = '1' then
frame_q <= '0';
parity_q <= '0';
over_q <= '0';
break_q <= '0';
end if;
-- Assigned AFTER the clear: the set wins on a collision.
if frame_error_evt_i = '1' then frame_q <= '1'; end if;
if parity_error_evt_i = '1' then parity_q <= '1'; end if;
if overrun_evt_i = '1' then over_q <= '1'; end if;
if break_evt_i = '1' then break_q <= '1'; end if;
end if;
end process;
end architecture rtl;| Property | Verilog-2001 | SystemVerilog | VHDL-2008 | Agree? |
|---|---|---|---|---|
| flags | four sticky + any_error | same | same | yes |
| set source | one-cycle event inputs | same | same | yes |
| clear source | clear_i, one cycle | same | same | yes |
| collision policy | SET-DOMINANT | SET-DOMINANT | SET-DOMINANT | yes |
| how priority is expressed | write order in one block | same | signal assignment order | yes — same mechanism |
any_error_o | combinational OR | combinational OR | combinational OR | yes |
| reset | clears all four | same | same | yes |
| reading the flags internally | reads the reg | reads the logic | internal *_q | no — language rule |
All three express SET-DOMINANT the same way: by ORDER. The clear is written first and the sets are written after it, so when both fire on one edge the later assignment is the one that survives. Verilog and VHDL are using the identical underlying rule — the last assignment to a signal before the process suspends wins — which is a rare case where the two languages' semantics line up exactly rather than merely agreeing in effect.
This is also why the block is not written with a ternary. A reader can verify "set beats clear" by looking at two adjacent statements; verifying it inside a nested conditional expression is harder, and §3 argues the policy needs to be obvious rather than merely correct.
4. The Self-Checking Testbenches
The collision is tested three ways, because it is the one behaviour this block exists to get right and the one a casual test never reaches:
// 1. clear and a new event on the same edge, flag previously clear
@(negedge clk) clr = 1'b1; fe_evt = 1'b1;
@(negedge clk) clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
check(fe_st === 1'b1, "SET-DOMINANT: collision leaves the flag SET");then again with another flag already set — to prove the other flags still clear normally during a collision — and once more with reset asserted, to prove reset outranks both.
//===========================================================================
// tb_uart_err_status_v — self-checking Verilog-2001 testbench
//
// The interesting property is not "a flag sets". It is the COLLISION: what
// happens when a software clear and a new hardware event land on the same
// clock. This design is SET-DOMINANT, and that is a contract to be tested,
// not an accident to be discovered.
//
// INDEPENDENCE: the checker tracks which events it injected and which
// clears it issued, and derives the expected flag state from that history
// alone.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_err_status_v;
reg clk = 1'b0, rst_n = 1'b0;
always #5 clk = ~clk;
reg fe_evt = 1'b0, pe_evt = 1'b0, ov_evt = 1'b0, bk_evt = 1'b0, clr = 1'b0;
wire fe_st, pe_st, ov_st, bk_st, any_err;
uart_err_status_v dut (
.clk(clk), .rst_n(rst_n),
.frame_error_evt_i(fe_evt), .parity_error_evt_i(pe_evt),
.overrun_evt_i(ov_evt), .break_evt_i(bk_evt), .clear_i(clr),
.frame_error_sticky_o(fe_st), .parity_error_sticky_o(pe_st),
.overrun_sticky_o(ov_st), .break_sticky_o(bk_st),
.any_error_o(any_err)
);
integer checks = 0, failures = 0;
task check;
input cond;
input [8*60-1:0] name;
begin
checks = checks + 1;
if (cond) $display(" PASS %0s", name);
else begin failures = failures + 1; $display(" FAIL %0s", name); end
end
endtask
task pulse_fe; begin @(negedge clk) fe_evt = 1'b1; @(negedge clk) fe_evt = 1'b0; end endtask
task pulse_pe; begin @(negedge clk) pe_evt = 1'b1; @(negedge clk) pe_evt = 1'b0; end endtask
task pulse_ov; begin @(negedge clk) ov_evt = 1'b1; @(negedge clk) ov_evt = 1'b0; end endtask
task pulse_bk; begin @(negedge clk) bk_evt = 1'b1; @(negedge clk) bk_evt = 1'b0; end endtask
task pulse_clr;begin @(negedge clk) clr = 1'b1; @(negedge clk) clr = 1'b0; end endtask
initial begin
#200_000;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: VERILOG STATUS TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_err_status_v : self-checking Verilog testbench ==");
rst_n = 1'b0;
repeat (4) @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "reset: all flags clear");
check(any_err === 1'b0, "reset: any_error clear");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- each event sets ONLY its own flag ---------------------------
pulse_fe; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1000, "framing event sets only framing");
check(any_err === 1'b1, "any_error follows");
pulse_clr; @(negedge clk);
pulse_pe; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0100, "parity event sets only parity");
pulse_clr; @(negedge clk);
pulse_ov; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0010, "overrun event sets only overrun");
pulse_clr; @(negedge clk);
pulse_bk; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0001, "break event sets only break");
pulse_clr; @(negedge clk);
check(any_err === 1'b0, "clear drops any_error");
// ---- sticky: the flag outlives the event by design ---------------
pulse_fe;
repeat (20) @(negedge clk);
check(fe_st === 1'b1, "sticky: flag survives long after the event");
// ---- accumulation across different error types -------------------
pulse_pe; pulse_ov; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1110, "three distinct errors accumulate");
pulse_clr; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "one clear clears all four");
// ---- THE COLLISION: clear and a new event on the same clock ------
@(negedge clk) clr = 1'b1; fe_evt = 1'b1;
@(negedge clk) clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
check(fe_st === 1'b1, "SET-DOMINANT: collision leaves the flag SET");
// ---- and the same collision on a flag with nothing new -----------
pulse_pe; @(negedge clk);
@(negedge clk) clr = 1'b1; fe_evt = 1'b1;
@(negedge clk) clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
check(fe_st === 1'b1, "collision: the colliding flag stays set");
check(pe_st === 1'b0, "collision: other flags still clear normally");
pulse_clr; @(negedge clk);
// ---- a clear with no event clears cleanly ------------------------
pulse_bk; @(negedge clk);
check(bk_st === 1'b1, "break sticky set");
pulse_clr; @(negedge clk);
check(bk_st === 1'b0, "clear with no collision clears the flag");
// ---- reset beats everything --------------------------------------
pulse_fe; pulse_pe; pulse_ov; pulse_bk; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1111, "all four set before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "reset clears all sticky flags");
check(any_err === 1'b0, "reset clears any_error");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- reset DURING a collision ------------------------------------
@(negedge clk) rst_n = 1'b0; clr = 1'b1; fe_evt = 1'b1;
repeat (2) @(negedge clk);
check(fe_st === 1'b0, "reset wins over a set/clear collision");
@(negedge clk) rst_n = 1'b1; clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL VERILOG STATUS TESTS PASSED");
else $display(" RESULT: VERILOG STATUS TESTS FAILED");
$finish;
end
endmodule//===========================================================================
// tb_uart_err_status_sv — self-checking Verilog-2001 testbench
//
// The interesting property is not "a flag sets". It is the COLLISION: what
// happens when a software clear and a new hardware event land on the same
// clock. This design is SET-DOMINANT, and that is a contract to be tested,
// not an accident to be discovered.
//
// INDEPENDENCE: the checker tracks which events it injected and which
// clears it issued, and derives the expected flag state from that history
// alone.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_err_status_sv;
logic clk = 1'b0, rst_n = 1'b0;
always #5ns clk = ~clk;
logic fe_evt = 1'b0, pe_evt = 1'b0, ov_evt = 1'b0, bk_evt = 1'b0, clr = 1'b0;
logic fe_st, pe_st, ov_st, bk_st, any_err;
uart_err_status dut (
.clk(clk), .rst_n(rst_n),
.frame_error_evt_i(fe_evt), .parity_error_evt_i(pe_evt),
.overrun_evt_i(ov_evt), .break_evt_i(bk_evt), .clear_i(clr),
.frame_error_sticky_o(fe_st), .parity_error_sticky_o(pe_st),
.overrun_sticky_o(ov_st), .break_sticky_o(bk_st),
.any_error_o(any_err)
);
int unsigned checks = 0, failures = 0;
task automatic check(input bit cond, input string name);
checks++;
if (cond) $display(" PASS %0s", name);
else begin failures++; $display(" FAIL %0s", name); end
endtask
task pulse_fe; begin @(negedge clk) fe_evt = 1'b1; @(negedge clk) fe_evt = 1'b0; end endtask
task pulse_pe; begin @(negedge clk) pe_evt = 1'b1; @(negedge clk) pe_evt = 1'b0; end endtask
task pulse_ov; begin @(negedge clk) ov_evt = 1'b1; @(negedge clk) ov_evt = 1'b0; end endtask
task pulse_bk; begin @(negedge clk) bk_evt = 1'b1; @(negedge clk) bk_evt = 1'b0; end endtask
task pulse_clr;begin @(negedge clk) clr = 1'b1; @(negedge clk) clr = 1'b0; end endtask
initial begin
#200us;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: SYSTEMVERILOG STATUS TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_err_status : self-checking Verilog testbench ==");
rst_n = 1'b0;
repeat (4) @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "reset: all flags clear");
check(any_err === 1'b0, "reset: any_error clear");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- each event sets ONLY its own flag ---------------------------
pulse_fe; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1000, "framing event sets only framing");
check(any_err === 1'b1, "any_error follows");
pulse_clr; @(negedge clk);
pulse_pe; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0100, "parity event sets only parity");
pulse_clr; @(negedge clk);
pulse_ov; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0010, "overrun event sets only overrun");
pulse_clr; @(negedge clk);
pulse_bk; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0001, "break event sets only break");
pulse_clr; @(negedge clk);
check(any_err === 1'b0, "clear drops any_error");
// ---- sticky: the flag outlives the event by design ---------------
pulse_fe;
repeat (20) @(negedge clk);
check(fe_st === 1'b1, "sticky: flag survives long after the event");
// ---- accumulation across different error types -------------------
pulse_pe; pulse_ov; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1110, "three distinct errors accumulate");
pulse_clr; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "one clear clears all four");
// ---- THE COLLISION: clear and a new event on the same clock ------
@(negedge clk) clr = 1'b1; fe_evt = 1'b1;
@(negedge clk) clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
check(fe_st === 1'b1, "SET-DOMINANT: collision leaves the flag SET");
// ---- and the same collision on a flag with nothing new -----------
pulse_pe; @(negedge clk);
@(negedge clk) clr = 1'b1; fe_evt = 1'b1;
@(negedge clk) clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
check(fe_st === 1'b1, "collision: the colliding flag stays set");
check(pe_st === 1'b0, "collision: other flags still clear normally");
pulse_clr; @(negedge clk);
// ---- a clear with no event clears cleanly ------------------------
pulse_bk; @(negedge clk);
check(bk_st === 1'b1, "break sticky set");
pulse_clr; @(negedge clk);
check(bk_st === 1'b0, "clear with no collision clears the flag");
// ---- reset beats everything --------------------------------------
pulse_fe; pulse_pe; pulse_ov; pulse_bk; @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b1111, "all four set before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check({fe_st,pe_st,ov_st,bk_st} === 4'b0000, "reset clears all sticky flags");
check(any_err === 1'b0, "reset clears any_error");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- reset DURING a collision ------------------------------------
@(negedge clk) rst_n = 1'b0; clr = 1'b1; fe_evt = 1'b1;
repeat (2) @(negedge clk);
check(fe_st === 1'b0, "reset wins over a set/clear collision");
@(negedge clk) rst_n = 1'b1; clr = 1'b0; fe_evt = 1'b0;
@(negedge clk);
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL SYSTEMVERILOG STATUS TESTS PASSED");
else $display(" RESULT: SYSTEMVERILOG STATUS TESTS FAILED");
$finish;
end
endmodule--==========================================================================
-- tb_uart_err_status -- self-checking VHDL-2008 testbench
-- Same 20 checks as the Verilog and SystemVerilog testbenches. The
-- collision case -- clear and a new event on the same clock -- is the one
-- this block exists to get right, so it is tested three different ways.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;
entity tb_uart_err_status is
end entity tb_uart_err_status;
architecture sim of tb_uart_err_status is
constant CLK_PERIOD : time := 10 ns;
signal clk, rst_n, done : std_logic := '0';
signal fe_evt, pe_evt, ov_evt, bk_evt, clr : std_logic := '0';
signal fe_st, pe_st, ov_st, bk_st, any_err : std_logic;
begin
clk <= not clk after CLK_PERIOD / 2 when done = '0' else '0';
dut : entity work.uart_err_status
port map (clk => clk, rst_n => rst_n,
frame_error_evt_i => fe_evt, parity_error_evt_i => pe_evt,
overrun_evt_i => ov_evt, break_evt_i => bk_evt, clear_i => clr,
frame_error_sticky_o => fe_st, parity_error_sticky_o => pe_st,
overrun_sticky_o => ov_st, break_sticky_o => bk_st,
any_error_o => any_err);
watchdog : process
begin
wait for 200 us;
report "watchdog: simulation did not finish" severity error;
report " RESULT: VHDL STATUS TESTS FAILED (timeout)" severity failure;
wait;
end process watchdog;
stim : process
variable checks, failures : natural := 0;
procedure check(cond : boolean; name : string) is
begin
checks := checks + 1;
if cond then report " PASS " & name;
else failures := failures + 1; report " FAIL " & name severity error; end if;
end procedure;
procedure pulse(signal s : out std_logic) is
begin
wait until falling_edge(clk); s <= '1';
wait until falling_edge(clk); s <= '0';
end procedure;
procedure step is begin wait until falling_edge(clk); end procedure;
begin
report "== uart_err_status (VHDL) : self-checking testbench ==";
rst_n <= '0';
for i in 0 to 3 loop step; end loop;
check(fe_st = '0' and pe_st = '0' and ov_st = '0' and bk_st = '0',
"reset: all flags clear");
check(any_err = '0', "reset: any_error clear");
rst_n <= '1'; step;
pulse(fe_evt); step;
check(fe_st = '1' and pe_st = '0' and ov_st = '0' and bk_st = '0',
"framing event sets only framing");
check(any_err = '1', "any_error follows");
pulse(clr); step;
pulse(pe_evt); step;
check(fe_st = '0' and pe_st = '1' and ov_st = '0' and bk_st = '0',
"parity event sets only parity");
pulse(clr); step;
pulse(ov_evt); step;
check(fe_st = '0' and pe_st = '0' and ov_st = '1' and bk_st = '0',
"overrun event sets only overrun");
pulse(clr); step;
pulse(bk_evt); step;
check(fe_st = '0' and pe_st = '0' and ov_st = '0' and bk_st = '1',
"break event sets only break");
pulse(clr); step;
check(any_err = '0', "clear drops any_error");
pulse(fe_evt);
for i in 0 to 19 loop step; end loop;
check(fe_st = '1', "sticky: flag survives long after the event");
pulse(pe_evt); pulse(ov_evt); step;
check(fe_st = '1' and pe_st = '1' and ov_st = '1' and bk_st = '0',
"three distinct errors accumulate");
pulse(clr); step;
check(fe_st = '0' and pe_st = '0' and ov_st = '0' and bk_st = '0',
"one clear clears all four");
-- THE COLLISION
wait until falling_edge(clk); clr <= '1'; fe_evt <= '1';
wait until falling_edge(clk); clr <= '0'; fe_evt <= '0';
step;
check(fe_st = '1', "SET-DOMINANT: collision leaves the flag SET");
pulse(pe_evt); step;
wait until falling_edge(clk); clr <= '1'; fe_evt <= '1';
wait until falling_edge(clk); clr <= '0'; fe_evt <= '0';
step;
check(fe_st = '1', "collision: the colliding flag stays set");
check(pe_st = '0', "collision: other flags still clear normally");
pulse(clr); step;
pulse(bk_evt); step;
check(bk_st = '1', "break sticky set");
pulse(clr); step;
check(bk_st = '0', "clear with no collision clears the flag");
pulse(fe_evt); pulse(pe_evt); pulse(ov_evt); pulse(bk_evt); step;
check(fe_st = '1' and pe_st = '1' and ov_st = '1' and bk_st = '1',
"all four set before reset");
wait until falling_edge(clk); rst_n <= '0';
for i in 0 to 1 loop step; end loop;
check(fe_st = '0' and pe_st = '0' and ov_st = '0' and bk_st = '0',
"reset clears all sticky flags");
check(any_err = '0', "reset clears any_error");
wait until falling_edge(clk); rst_n <= '1'; step;
wait until falling_edge(clk); rst_n <= '0'; clr <= '1'; fe_evt <= '1';
for i in 0 to 1 loop step; end loop;
check(fe_st = '0', "reset wins over a set/clear collision");
wait until falling_edge(clk); rst_n <= '1'; clr <= '0'; fe_evt <= '0';
step;
report "== " & integer'image(checks) & " checks, " &
integer'image(failures) & " failures ==";
if failures = 0 then report " RESULT: ALL VHDL STATUS TESTS PASSED";
else report " RESULT: VHDL STATUS TESTS FAILED" severity error; end if;
done <= '1'; finish;
end process stim;
end architecture sim;$ iverilog -g2001 -o sim uart_err_status_v.v tb_uart_err_status_v.v && ./sim
== uart_err_status_v : self-checking Verilog testbench ==
PASS reset: all flags clear
PASS framing event sets only framing
PASS parity event sets only parity
PASS overrun event sets only overrun
PASS break event sets only break
PASS sticky: flag survives long after the event
PASS three distinct errors accumulate
PASS one clear clears all four
PASS SET-DOMINANT: collision leaves the flag SET
PASS collision: other flags still clear normally
PASS clear with no collision clears the flag
PASS reset clears all sticky flags
PASS reset wins over a set/clear collision
== 20 checks, 0 failures ==
RESULT: ALL VERILOG STATUS TESTS PASSED20 checks in each language, 0 failures.
Proving these checks can fail
| Mutation | What it breaks | Failures |
|---|---|---|
| flag follows the event (not sticky) | §1's lifetime contract | 7 / 20 |
| made CLEAR-dominant | §3's collision policy | 2 / 20 |
The second row is the number to sit with. Inverting the collision policy — the single decision this block is built around — is caught by two checks out of twenty. Every other check still passes, because a clear-dominant design sets correctly, accumulates correctly, stays sticky correctly and resets correctly. It is wrong only on the one edge where a clear and an event coincide, which in a real system happens rarely and non-deterministically.
That is the profile of the bugs that reach silicon: correct almost always, wrong on a race, and invisible to any test that does not deliberately construct the race. The two checks exist because the policy was written down first.
5. The Collision: Clear and Set on the Same Edge
This is the decision the block exists to make, and it is the one most often left to accident.
Software reads the status register, sees an error, and writes a clear. On the very clock edge that clear takes effect, a new error event pulses. Two behaviours are possible and both are implementable in one line:
| Set-dominant | Clear-dominant | |
|---|---|---|
| Result | flag stays set | flag goes clear |
| Loses | nothing | the new event, silently |
| Software sees | the error again on its next read | nothing — as if it never happened |
| Risk | a duplicate report of one error | an error that is never reported at all |
// SET-DOMINANT — the clear is written first, the set after, so the set wins.
if (clear_i) err_q <= 1'b0;
if (error_event_i) err_q <= 1'b1; // later write wins on a collision
// CLEAR-DOMINANT — reverse the order.
if (error_event_i) err_q <= 1'b1;
if (clear_i) err_q <= 1'b0; // later write wins on a collisionThis design is set-dominant, and the argument is asymmetric consequences.
Clear and event on the same edge
8 cyclesThat figure is not drawn, it is measured. Both policies were built and driven with exactly the stimulus the figure shows — an event at cycle 1, and an event colliding with a clear at cycle 4:
cyc evt clr SET-dom CLEAR-dom
0 0 0 0 0
1 1 0 0 0
2 0 0 1 1 <- both set, one cycle after the event
3 0 0 1 1
4 1 1 1 1 <- the collision
5 0 0 1 0 <- and here the policies diverge
6 0 0 1 0
7 0 0 1 0Cycle 5 is the entire argument. Under the clear-dominant policy the flag is low and the error that occurred at cycle 4 has left no trace anywhere in the design — not in a register, not in a pulse, nowhere. Under set-dominant it is still set, and the software that issued the clear will see it on its next read and can clear it again. One of those is recoverable and the other is not.
6. Per-Character Status and Historical Status Are Different
Two consumers want two different things, and a design that provides only one is inadequate for the other.
| Per-character | Historical (sticky) | |
|---|---|---|
| Question | was this byte good? | has anything gone wrong since I looked? |
| Scope | exactly one frame | many frames |
| Travels with | the data | nothing — it is global |
| Built by | Chapter 6.4's publish discipline | this chapter's OR accumulation |
| Consumer | logic deciding what to do with a byte | software polling occasionally |
The per-character form is the primitive; the sticky form is derived from it. Chapter 6.4 §5 argued this and it is worth restating as a direction: given per-frame flags you can build a sticky register by ORing each accepted byte's status into a held bit. Given only a sticky register you cannot recover which byte was bad, because the association was destroyed when several frames were merged into one bit.
So a design that exposes only sticky status has made per-byte decisions impossible for anything downstream. A design that exposes only per-character status forces software to catch every byte, which defeats the purpose of a status register.
Both are cheap and they answer different questions, which is why real UART IP exposes both — and why this module built the per-frame flags first (Module 6) and the sticky ones last.
Where per-character status travels with buffered data, each FIFO entry carries its own flags alongside its byte. That is Module 10's architecture, and it is only possible because the per-character primitive exists.
7. Verification
The collision test is the one that matters and the one that is missing from most suites, because it requires deliberately aligning a clear with an event — something no nominal traffic produces.
From this module's simulation:
| Stimulus | Expected | Result |
|---|---|---|
| event, no clear | sticky set | pass |
| clear, no event | sticky clears | pass |
any_error_o with any flag set | asserted | pass |
| clear and event on the same cycle | sticky STAYS SET | pass |
The fourth row is the architecture under test. Written as a property it is unambiguous, and it fails immediately if someone reorders the two assignments during a refactor — which is exactly the kind of change that looks harmless:
// Assertion — SET-DOMINANT. A colliding clear must not destroy an event.
// This is the policy of §3 made executable; it fails on a reordering.
property p_set_dominant;
@(posedge clk) disable iff (!rst_n)
(clear_i && frame_error_evt_i) |=> frame_error_sticky_o;
endproperty
assert property (p_set_dominant);
// Assertion — a sticky flag never sets without a cause.
property p_sticky_needs_event;
@(posedge clk) disable iff (!rst_n)
$rose(frame_error_sticky_o) |-> $past(frame_error_evt_i);
endproperty
assert property (p_sticky_needs_event);
// Assertion — and never clears without a clear or a reset.
property p_sticky_holds;
@(posedge clk) disable iff (!rst_n)
$fell(frame_error_sticky_o) |-> $past(clear_i);
endproperty
assert property (p_sticky_holds);
// Assertion — the aggregate is exactly the OR of its inputs.
property p_any_error_is_or;
@(posedge clk) disable iff (!rst_n)
any_error_o == (frame_error_sticky_o | parity_error_sticky_o
| overrun_sticky_o | break_sticky_o);
endproperty
assert property (p_any_error_is_or);The third property is the one that catches an accidentally clear-dominant flag under sustained errors, where the flag would appear to flicker rather than hold.
8. Recovery Is a Different Problem
Detection asks "did something violate the frame?". Recovery asks "where is the next trustworthy boundary?". They are independent, and a receiver can be excellent at the first and poor at the second.
The failure mode to avoid is amplification: one malformed frame producing a burst of errors because the receiver kept trying to decode from a position that was never valid.
Chapter 6.2 already made the single most important recovery decision, and it is worth recognising as one:
S_STOPexits unconditionally. A bad stop is reported, not retried.
That one choice is what prevents the worst amplification. A receiver that waited in S_STOP for the line to return to mark would be permanently disabled by a break — never idle, never detecting a candidate, never receiving again even after the line recovered. Leaving unconditionally means the receiver is back in S_IDLE watching, and a line still low simply produces no candidate because there is no mark to leave.
So the base receiver already recovers correctly from a framing error. What it does not do is recover from the cases Chapter 9.5 identified, where the receiver's timing origin is wrong rather than one field.
The recovery model
Three conditions, and they use signals this module has already built:
| Condition | Recovery requirement | Signal |
|---|---|---|
| Framing or parity error | none — report and continue | — |
| Break | wait for release, then for proven idle | break_released_o, line_idle_o |
| Suspected mis-framing | wait for proven idle | line_idle_o |
// Conceptual SystemVerilog — recovery as a gate on start acceptance,
// not as new states inside the receive FSM. Chapter 6.1 §3's argument:
// the frame's state graph must not depend on line history.
//
// The receiver of Module 6 is unchanged; this qualifies its input.
assign may_accept_start = start_cand_i
&& !break_active_o // not during a break
&& (!recovery_armed_q || line_idle_o);Waiting for line_idle_o is the strong form of recovery and it carries Chapter 9.5 §7's cost exactly: on a saturated link it never completes. A design that arms it after every error on a busy link stops receiving. That is why the gate above arms it only for the cases that need it — a break, or a suspected timing-origin failure — and not for an ordinary framing error, which needs no recovery at all.
9. Debugging
10. What This Means on an FPGA
The whole status block is four flip-flops and an OR. There is no version of this that is too expensive, and the collision policy costs nothing at all — it is the order of two statements.
Add counters, not just flags. Four bits per error class turns "something went wrong" into a rate. It is the single highest-value addition to a bring-up build and it is usually absent.
Expose break_active_o separately from break_sticky_o. They answer different questions — is it broken now versus was it ever — and a design that merges them cannot answer the first.
Probe the FSM state alongside the flags. Chapter 6.2 §10 noted that three bits of state make the receiver's behaviour legible; combined with the status set it distinguishes a receiver that is detecting errors from one that has stopped receiving altogether, which the flags alone cannot.
Check that clear_i is a pulse, not a level. A clear held high permanently — easy to produce with a mis-decoded register write — makes every sticky flag permanently clear under set-dominance and permanently unreadable. The assertion in §5 that a flag never falls without a clear will not catch it, because there genuinely is a clear.
11. Assertions Across the Module
Six blocks, and the properties worth asserting are the ones whose violation is silent — where the design keeps working and only the meaning is wrong.
// Published as SystemVerilog Assertions. Icarus Verilog 13.0 supports
// neither named properties nor $past, so what was EXECUTED is the
// procedural equivalent inside each testbench; both forms are shown so the
// intent is portable and the evidence is real.
// p_event_is_one_cycle -- catches an error "event" that is really a level.
// A held event double-counts in any consumer that edge-detects it, and the
// symptom is an error rate that scales with how long the frame took.
property p_event_is_one_cycle;
@(posedge clk) disable iff (!rst_n) frame_error_evt_o |=> !frame_error_evt_o;
endproperty
// p_event_only_at_sample -- catches a verdict latched at the wrong instant.
// The design still reports the right answer for every frame; only the
// TIMING is wrong, and nothing downstream notices until something else
// starts depending on the sample point.
property p_event_only_at_sample;
@(posedge clk) disable iff (!rst_n) frame_error_evt_o |-> sample_stop_i;
endproperty
// p_sticky_holds -- catches a flag that decays. A status bit that clears
// itself loses the only record of a condition that has already passed.
property p_sticky_holds;
@(posedge clk) disable iff (!rst_n)
(frame_error_sticky_o && !clear_i) |=> frame_error_sticky_o;
endproperty
// p_set_dominant -- catches the collision policy being inverted. This is
// the property that two checks in twenty catch, and nothing else does.
property p_set_dominant;
@(posedge clk) disable iff (!rst_n)
(clear_i && frame_error_evt_i) |=> frame_error_sticky_o;
endproperty
// p_break_declared_once -- catches a wrapping break counter. See 9.4's
// callout: this property is protected by TWO mechanisms, and the assertion
// is what makes the protection explicit rather than incidental.
property p_break_declared_once;
@(posedge clk) disable iff (!rst_n) break_evt_o |-> !break_active_o;
endproperty
// p_idle_needs_mark -- catches idle asserted on a line that is not at mark.
// The negative property; no positive test reaches it.
property p_idle_needs_mark;
@(posedge clk) disable iff (!rst_n) line_idle_o |-> rx_sync_i;
endproperty| Property | Defect it catches | Why it would otherwise be missed |
|---|---|---|
p_event_is_one_cycle | event held as a level | consumers double-count; the error rate is wrong, not the error |
p_event_only_at_sample | verdict latched late | every per-frame verdict is still correct |
p_sticky_holds | status decays on its own | the condition has already passed; nothing else records it |
p_set_dominant | collision policy inverted | only wrong on a race, rarely and non-deterministically |
p_break_declared_once | break re-declared | a stream of events where there was one condition |
p_idle_needs_mark | idle asserted too readily | no positive test can reach a too-permissive qualifier |
Every row names a defect that leaves the design working. That is the selection rule — a property whose violation is already obvious does not need an assertion, it needs a testbench.
12. UVM Mapping
The full environment is Module 16's subject. What is worth fixing here is which part of the error contract each component owns, because those boundaries follow from this module rather than from UVM.
| Error-contract element | UVM component | What it carries |
|---|---|---|
| a frame to transmit, correct or corrupt | sequence item | data, parity_mode, corrupt_parity, corrupt_stop, inject_false_start, break_bits |
| producing that frame on the wire | driver | bit-level serialisation at the configured baud |
observing rx_i | monitor | reconstructs frames from the line alone |
| what the frame should have produced | predictor | error type and payload, from the item |
| comparison | scoreboard | error type, payload validity, status lifetime |
Three things in that table are decided by this module, not by UVM.
The driver drives the wire, and that is not a stylistic preference. A
driver that corrupted a frame by calling into the DUT — forcing
frame_error_o, or poking a sample input — would be testing the status
plumbing while bypassing the detection that Module 9 is about. Every
corruption in this module is expressible as a serial waveform: a stop
interval held low, a flipped parity bit, a short low pulse in idle, a
sustained low. If a corruption cannot be expressed that way, it is not a
line-level error and does not belong in this agent.
The monitor reconstructs frames from rx_i and nothing else. It must
not read the receiver's FSM, bit counter, sample counter or error helpers —
because the defect it is most likely to be catching is precisely a receiver
that has mis-framed, and a monitor that asked the receiver where the frame
boundary was would mis-frame with it. This is the same rule the six
testbenches above follow, which is why they are usable as the basis for a
monitor rather than thrown away.
The scoreboard must model status LIFETIME, not just status value. §1 established that an event, a per-character verdict and a sticky flag are three different contracts. A scoreboard that compares "was there an error" against "is the flag set" will report false mismatches every time software has not yet cleared a sticky bit. It has to predict when the flag should still be set, which means modelling the clears it issued.
Coverage worth collecting, from the axes this module established:
| Axis | Bins | Why |
|---|---|---|
| error type | none, framing, parity, overrun, break, false start | the module's own taxonomy |
| parity mode | none, even, odd | parity errors only exist in two of the three |
| payload | all-zero, all-one, alternating, single-one | all-zero is the one a break can be confused with |
| break duration | below threshold, at threshold, far above | the boundary is the interesting part |
| recovery result | next frame clean / next frame also bad | an error that prevents recovery is a different defect |
| status at clear | no collision / clear races an event | §3's policy |
The cross that earns its keep is error_type x recovery_result. A
receiver that detects every error correctly and then fails to receive the
next frame has a recovery defect, and no single-error test can see it —
the axis only exists when an error is followed by a good frame.
What is deliberately not here: no uvm_component boilerplate, no
factory registration, no config-db plumbing. None of it teaches anything
about UART error handling.
13. The Module 9 Error Matrix
Every error this module owns, end to end. Read the "Detection point" column first — it is the one that distinguishes these failures from one another, and the one most summaries omit.
| Error | Stimulus | Detection point | Status | Data validity | Recovery |
|---|---|---|---|---|---|
| Framing | stop interval driven low | the stop sample of that frame | per-frame verdict + one-cycle event; sticky in 9.6 | payload already shifted; suspect, not discarded | next accepted start re-establishes phase |
| Parity | parity bit flipped | the parity sample, before the stop | per-frame verdict + event | payload intact and delivered; relation failed | none needed — framing was fine |
| Parity, undetected | two data bits flipped | never | none | payload wrong, reported good | — the known limit (9.2 §5) |
| Overrun | a frame completes while the slot is full | the arrival of the new character | one-cycle event; sticky | new character lost, held one intact | automatic once the consumer reads |
| Break | line held low past FRAME_BITS x OVERSAMPLE ticks | the threshold tick | level + declare-once event + release pulse | no character; the frame is not data | on release to mark, then normal framing |
| False start | short low pulse in idle | the start validation sample | none — nothing happened | no character produced | receiver never left idle |
| Mid-frame attach | receiver enabled during someone else's character | idle qualification withholds consent | none | no character produced | after a full frame time of mark |
Two rows deliberately have no status. A rejected false start and a withheld mid-frame attach are the system working, not failing — reporting them would train software to ignore the register. §1 of 9.5 makes that argument.
One row has no detection point at all, and it is the most important row in the table: a two-bit corruption satisfies the parity relation and is delivered as good data. No amount of receiver design changes that. It is why protocols that need integrity put a CRC above the UART rather than trusting the parity bit.
14. The Module 9 Verification Matrix
| Requirement | Directed test | Corner test | Assertion | Coverage | Waveform |
|---|---|---|---|---|---|
| stop low at the stop sample -> framing error | 9.1 §5, 15 checks x3 | back-to-back errors | p_event_only_at_sample | error type | 9.1 Fig 1 |
| verdict cleared by a new frame | good frame after bad | start with no stop sample | — | error -> recovery | 9.1 Fig 1 |
| event exactly one clock | whole-run observer | — | p_event_is_one_cycle | — | — |
| parity relation, both modes | 32-frame sweep x3 | all-zero, all-one | — | parity mode x payload | 9.2 Fig 1 |
| two-bit corruption is not detected | 0xA5 -> 0xA6 | — | — | — | — |
| collision keeps held byte, drops new | 9.3 §6, 22 checks x3 | same-cycle hand-off | — | — | 9.3 Fig 1 |
| consumer keeping up -> no overrun | 8 back-to-back | ready high throughout | — | — | — |
| break at threshold, not before | one tick short / at | 9 low bit times | p_break_declared_once | break duration | 9.4 Fig 1 |
| break declared once | long break | counter wrap point | p_break_declared_once | — | — |
| threshold scales with config | 16x/10 vs 8x/7 | both simultaneously | — | — | — |
| idle earned, never assumed | reset check | busy line, 20 cycles | p_idle_needs_mark | — | 9.5 Fig 1 |
| sticky survives until clear | 20-cycle hold | — | p_sticky_holds | — | §5 Fig 1 |
| set beats clear on a collision | 3 collision variants | reset during collision | p_set_dominant | status at clear | §5 Fig 1 |
| reset clears all error state | every block | reset mid-error | — | — | — |
Two rows have no waveform and that is honest: "event exactly one clock" and "reset clears all error state" are whole-run invariants rather than scenarios, and a figure of one instant would suggest they hold only there. They are checked continuously instead.
One row has no assertion: the two-bit parity limit is a property of the algorithm, not of the implementation. There is nothing to assert — the design is behaving correctly by failing to notice.
15. Module Evidence
Every block in Module 9 exists in three languages with a self-checking testbench in each. Every number quoted anywhere in this module comes from these runs.
| Block | Chapter | Verilog | SystemVerilog | VHDL |
|---|---|---|---|---|
uart_frame_check | 9.1 | 15 | 15 | 15 |
uart_parity_check | 9.2 | 45 | 45 | 45 |
uart_rx_holding | 9.3 | 22 | 22 | 22 |
uart_break_detect | 9.4 | 19 | 19 | 19 |
uart_idle_qualify | 9.5 | 14 | 14 | 14 |
uart_err_status | §4 above | 20 | 20 | 20 |
| total | 135 | 135 | 135 |
405 checks across three languages, 0 failures.
| Language | Tool | Compile | Simulate |
|---|---|---|---|
| Verilog-2001 | Icarus Verilog 13.0 (-g2001) | clean | yes |
| SystemVerilog | Icarus Verilog 13.0 (-g2012) | clean | yes |
| VHDL-2008 | NVC 1.23.0 (LLVM 23.1.1) | clean | yes |
The per-block counts are identical in all three languages, and that is the acceptance criterion rather than a coincidence. A module where SystemVerilog runs two hundred checks and VHDL runs three has verified one design and compiled two others.
The whole mutation campaign
Eleven deliberate defects, each run against the unmodified testbench for its block:
| Mutation | Block | Failures |
|---|---|---|
| parity polarity reversed | parity | 42 / 45 |
| parity ignores the configured mode | parity | 17 / 45 |
| framing polarity reversed | frame | 10 / 15 |
| sticky flag follows the event | status | 7 / 20 |
| framing verdict sampled one cycle late | frame | 6 / 15 |
| overrun overwrites the held byte | holding | 3 / 22 |
| break threshold off by one | break | 3 / 19 |
| idle assumed out of reset | idle | 3 / 14 |
| status made clear-dominant | status | 2 / 20 |
| verdict not cleared on a new start | frame | 1 / 15 |
| break counter wraps instead of saturating | break | 0 — equivalent, see 9.4 |
Read it bottom-up. The defects at the top are loud and a thin test finds them. The defects at the bottom are the ones that ship: a verdict that leaks onto the next character, a collision policy inverted, a counter that wraps where a second mechanism happens to cover for it. Each is caught by one or two checks, and those checks exist only because the property was written down before the code was.
16. Understanding Check
17. Summary
Events are one-cycle pulses; software cannot see them. Sticky flags hold them and lose what the event had — count, timing and frame association — which is why a counter is strictly more informative than a flag. break_active_o is a third shape: a condition level that clears itself, because "is it broken now" is a different question from "was it ever".
The collision policy is the decision this block exists to make. When a clear and an event land on the same edge, set-dominance keeps the error and clear-dominance destroys it. The failures are asymmetric: a duplicate report is cosmetic, a lost error defeats the register's purpose. Set-dominant here; clear-dominant where a flag gates behaviour rather than reporting it. Never by accident of statement ordering — the difference appears only in a race no nominal test produces.
Per-character and historical status answer different questions, and the per-character form is the primitive. Sticky is derived from it; the reverse is impossible, because merging frames destroys the association.
Recovery is not detection. The base receiver already recovers from framing and parity errors, because Chapter 6.2 made S_STOP exit unconditionally — the choice that prevents a break from permanently disabling the link.
Recovery belongs outside the frame FSM, as a gate on start acceptance built from the break and idle detectors. That keeps the state graph enumerable and verifiable without line activity — the module's most transferable idea, now made three times: conditions depending on line history belong alongside the frame machine, never inside it.
And the strong recovery has a cost: waiting for proven idle never completes on a saturated link, so it is armed for breaks and suspected mis-framing, not for ordinary errors.
18. Where Module 9 Leaves You
Chapter 9.1 established that a framing error is one sample at one instant and consistent with five different causes. Chapter 9.2 proved by exhaustive enumeration that parity catches every odd-weight corruption and misses every even-weight one, and that a passing check licenses nothing about the data. Chapter 9.3 separated a receive overrun from transmit starvation and showed that a flag records a loss it cannot recover or even count. Chapter 9.4 built the only duration-based detector in the curriculum, with the saturation policy and the +1 width that a power-of-two threshold demands. Chapter 9.5 mapped the disturbance window — 4.34 µs to 86.81 µs — in which a glitch becomes a frame nothing reports, and built the idle detection that closes the mid-frame case at a stated price. This chapter turned all of it into state and defined the response.
The theme, stated once: every status bit in a UART receiver is a statement about an observation, not about the world. A framing error says one sample was low. A parity error says one relation failed. An overrun says a slot was full. None of them says "the data is wrong", and a fabricated frame satisfies all of them. An engineer who holds that distinction reads a status register correctly and knows when to stop trusting it.
19. What Comes Next
Module 10 takes the problem this module could only describe. Chapter 9.3 showed a one-entry holding register overrunning when the consumer is more than one frame time behind, and showed that buffering moves the threshold rather than removing it. Module 10 chooses the depth with the facts that decide it — interrupt latency, polling interval, whether DMA is present — builds the TX and RX FIFOs, adds the thresholds and watermarks that let a consumer be warned before data is lost, and implements the flow control that lets a receiver do what no mechanism in this module could: ask the far end to stop.
After that: Module 11 assembles an IP from both halves, the generator and the buffering; Module 12 returns to the clock-domain and reset questions; Module 13 builds the register interface these flags have been waiting for.
Browse the full path on the UART tutorials index. For the per-frame status this chapter aggregates, read back to Chapter 6.4.
Continue learning
Related tutorials
- Related topic
Parity Check, Stop-Bit Validation and Error Status
The receiver decides whether a frame was good, then faces the harder question: which frame does each status flag describe? Getting that wrong lets an incoming frame rewrite the status of a byte the consumer has not yet read.
- Related topic
Framing Errors
A framing error is one sampled bit at one instant. It proves the line was not at mark where the receiver expected mark — and nothing about why, which is what makes the diagnosis interesting.
- Related topic
Parity Errors and the Limits of Detection
Parity detects every odd-weight corruption and provably misses every even-weight one — shown by exhaustive enumeration. A check that passes says a relation holds, not that the data is correct.
- Related topic
Overrun, Underrun and Data Loss
Overrun is a protocol-level consequence of a full holding register; transmit starvation is an architecture-dependent system event. Both lose information an error flag records but cannot recover.
Where this fits
Part of the UART curriculum.
