UART · Module 9
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.
Module 6 built a receiver that emits rx_frame_err_o, and Chapter 6.4 established the register discipline that keeps that flag attached to the frame it describes. What neither chapter did was say what the flag means.
It is worth being precise, because the usual gloss — "a framing error means the baud rate is wrong" — is a guess dressed as a definition. The actual content of the flag is much narrower:
At the receiver's configured stop-sample position, the synchronised line was not at the mark level.
That is one bit, observed at one instant, measured against one expectation. Everything else — the baud rate, the far end's configuration, noise, a disconnected wire — is inference from that single observation, and this chapter is largely about how much inference the observation actually supports.
1. The Assumption Being Violated
From Chapter 3.1 and Chapter 3.4, a well-formed frame ends with the line held at mark for at least the configured stop duration. The receiver, having anchored its timing to the accepted start edge, schedules one sample inside that interval — 9.5 UI from the candidate in 8N1, 10.5 UI in 8E1 (Chapter 6.3 §7).
expected at the stop sample: MARK (1)
observed: SPACE (0) -> framing errorThe check is a comparison against a constant, and its entire information content is the level of one sample. The receiver does not measure the stop interval's width, does not compare it against the bit period, and does not look at the line before or after that instant.
That is a deliberate architecture, not a limitation to be fixed. Measuring the stop interval's duration would require a second timing reference, and the receiver has only the one it derived from the start edge — the reference whose accuracy is in question whenever a framing error occurs.
2. Event and Verdict Are Different Signals
Two things are wanted from the check, and conflating them is the most common structural mistake:
frame_error_o — the verdict | frame_error_evt_o — the event | |
|---|---|---|
| Shape | a level | a one-cycle pulse |
| Means | this frame's stop sample failed | a stop sample failed on this clock |
| Valid | from the stop sample until the next frame starts | for exactly one cycle |
| Cleared by | the next accepted start | nothing — it is an edge |
| Consumed by | the byte's status, alongside the data | the sticky aggregator of Chapter 9.6 |
The verdict answers a question about a byte. The event answers a question about a moment. A sticky flag built by ORing the verdict would stay set for as long as the verdict is held, which is most of a frame time — so it would be indistinguishable from a flag that was never cleared. Built from the event, it accumulates discrete occurrences, which is what a diagnostic register should count.
Chapter 3.4 published the verdict half of this and explicitly deferred the rest here:
How the flag is latched, presented and cleared is Module 9's.
3. The RTL
// ---------------------------------------------------------------------------
// 9.1 — framing verdict. Chapter 3.4 published the per-frame clear; this adds
// the event pulse and the explicit lifetime Module 9 owns.
// ---------------------------------------------------------------------------
module uart_frame_check
import uart_line_pkg::*;
(
input logic clk,
input logic rst_n,
input logic frame_start_i, // a start has been ACCEPTED (Chapter 5.2)
input logic sample_stop_i, // the stop position, one cycle (Module 5)
input logic rx_sampled_i, // the synchronised line at that instant
output logic frame_error_o, // per-frame verdict, valid after the stop sample
output logic frame_error_evt_o // ONE cycle, on the stop sample, when low
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_error_o <= 1'b0;
frame_error_evt_o <= 1'b0;
end else begin
frame_error_evt_o <= 1'b0; // default: one-cycle pulse
if (frame_start_i) begin
frame_error_o <= 1'b0; // new frame, new verdict
end else if (sample_stop_i) begin
frame_error_o <= (rx_sampled_i != UART_MARK);
frame_error_evt_o <= (rx_sampled_i != UART_MARK);
end
end
end
endmoduleThe hardware is two flip-flops and a comparator. frame_error_o holds the verdict; frame_error_evt_o is default-assigned low every cycle so it can only ever be a one-cycle pulse — the same pattern the receiver used for sample_now in Chapter 6.3. The comparison against UART_MARK is a single inverter once synthesis resolves the constant.
frame_start_i clears the verdict, not the event. Each frame gets its own verdict, so a framing error reports on the frame that just ended rather than persisting from an earlier one. The event has no lifetime to clear.
Reset leaves the verdict clear. A receiver out of reset has not yet judged any frame, and reporting an error it never observed would be worse than reporting nothing.
Valid stop versus framing error
11 cycles4. The Same Block in Three Languages
§3's listing is SystemVerilog. The contract it implements is not, and the whole of it survives translation: a verdict cleared by an accepted start, a verdict latched at the stop sample, and a one-cycle event coincident with that sample.
//===========================================================================
// uart_frame_check_v — Synthesizable Verilog-2001
//
// The framing verdict. Verilog-2001 has no packages, so the line convention
// is a localparam here rather than an import; the VALUE is identical to
// uart_line_pkg::UART_MARK and must stay that way.
//
// Contract (identical in all three languages):
// frame_start_i clears the verdict — a new frame gets a new verdict
// sample_stop_i is ONE cycle at the stop sample point, produced by the
// receiver's sample generator (Module 5). The verdict is
// latched at that instant and not before: this is the exact
// sample event at which a framing error becomes knowable.
// frame_error_o LEVEL, valid from the stop sample until the next start
// frame_error_evt_o ONE cycle, coincident with the stop sample
//===========================================================================
module uart_frame_check_v (
input wire clk,
input wire rst_n,
input wire frame_start_i,
input wire sample_stop_i,
input wire rx_sampled_i,
output reg frame_error_o,
output reg frame_error_evt_o
);
localparam UART_MARK = 1'b1; // == uart_line_pkg::UART_MARK
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
frame_error_o <= 1'b0;
frame_error_evt_o <= 1'b0;
end else begin
frame_error_evt_o <= 1'b0; // default: one-cycle pulse
if (frame_start_i) begin
frame_error_o <= 1'b0; // new frame, new verdict
end else if (sample_stop_i) begin
frame_error_o <= (rx_sampled_i != UART_MARK);
frame_error_evt_o <= (rx_sampled_i != UART_MARK);
end
end
end
endmodule--==========================================================================
-- uart_line_pkg -- the line-state convention, as VHDL-2008.
--
-- The same two constants SystemVerilog publishes in uart_line_pkg. They are
-- LOGICAL states at the controller's pin; what they become electrically is
-- the transceiver's business (Chapter 1.4) -- an RS-232 driver inverts both.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
package uart_line_pkg is
constant UART_MARK : std_logic := '1'; -- idle, and the stop condition
constant UART_SPACE : std_logic := '0'; -- the start interval
end package uart_line_pkg;The VHDL needs the package published too, and that is not a formality.
Chapter 3.2 publishes
uart_line_pkg as SystemVerilog; VHDL cannot import that, so the same two
constants exist as a VHDL package with the same values. Three languages, one
convention, three places it is written down — which is precisely the
duplication the localparam note below warns about, one level up.
--==========================================================================
-- uart_frame_check -- Synthesizable VHDL-2008
--
-- The framing verdict, latched at the stop sample and not before.
--
-- frame_error_o is a LEVEL that survives until the next accepted start;
-- frame_error_evt_o is ONE cycle coincident with the stop sample. They are
-- different contracts and Chapter 9.6 is where that distinction is spent.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use work.uart_line_pkg.all;
entity uart_frame_check is
port (
clk : in std_logic;
rst_n : in std_logic;
frame_start_i : in std_logic;
sample_stop_i : in std_logic;
rx_sampled_i : in std_logic;
frame_error_o : out std_logic;
frame_error_evt_o : out std_logic
);
end entity uart_frame_check;
architecture rtl of uart_frame_check is
begin
process (clk, rst_n)
begin
if rst_n = '0' then
frame_error_o <= '0';
frame_error_evt_o <= '0';
elsif rising_edge(clk) then
frame_error_evt_o <= '0'; -- default: one-cycle pulse
if frame_start_i = '1' then
frame_error_o <= '0'; -- new frame, new verdict
elsif sample_stop_i = '1' then
if rx_sampled_i /= UART_MARK then
frame_error_o <= '1';
frame_error_evt_o <= '1';
else
frame_error_o <= '0';
end if;
end if;
end if;
end process;
end architecture rtl;| Property | Verilog-2001 | SystemVerilog | VHDL-2008 | Agree? |
|---|---|---|---|---|
| line convention | localparam | uart_line_pkg import | use work.uart_line_pkg | yes — same value |
| reset | async assert, active low | same | same | yes |
| verdict cleared by | frame_start_i | same | same | yes |
| verdict latched at | sample_stop_i | same | same | yes |
| event width | one clock | one clock | one clock | yes |
| event instant | the stop sample | same | same | yes |
frame_start vs sample_stop priority | start wins | start wins | start wins | yes |
Verilog-2001 has no packages, so UART_MARK becomes a localparam in the
module. That is the one structural difference, and it carries a real risk the
other two do not have: the constant is now duplicated, and a project that
changed the convention in the package would have to remember this copy.
Chapter 12 treats shared constants as an integration concern for exactly this
reason.
The priority between frame_start_i and sample_stop_i is not
arbitrary. All three write the clear first and the latch in the else
branch, so a start arriving on the same cycle as a stop sample clears rather
than latches. That combination should not occur in a correct receiver — but
if it does, the safe reading is "a new frame began", not "the old frame
failed", because the latter would attach an error to a character that no
longer exists.
5. The Self-Checking Testbenches
All three drive named scenarios and assert the verdict each scenario is
defined to produce. None of them recomputes rx_sampled != MARK — a
checker that did would be the DUT's own line copied across, and would bless
an implementation that sampled at the wrong instant just as happily.
Two of the fifteen checks are whole-run properties rather than per-scenario ones, and they are the ones that catch a mis-timed sample:
// Computed from the OUTPUT alone, every clock, for the whole run.
if (frame_error_evt && !stop_prev) evt_off_sample = evt_off_sample + 1;evt_off_sample must stay zero: the verdict is knowable at the stop sample
and at no other instant. A design that latched one cycle late still produces
the right verdict for every frame — and fails this check.
//===========================================================================
// tb_uart_frame_check_v — self-checking Verilog-2001 testbench
//
// Independence: the checker never recomputes the DUT's expression. It drives
// a NAMED SCENARIO ("frame with a good stop", "frame with a stop held low")
// and asserts the verdict that scenario is defined to produce, plus the
// timing contract around it. A checker that re-derived
// `rx_sampled != MARK` would agree with any implementation of that same
// expression, including one that sampled at the wrong instant.
//
// Mandatory timeout: the watchdog turns a stalled DUT into a reported
// failure rather than a hung regression.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_frame_check_v;
reg clk = 1'b0, rst_n = 1'b0;
always #5 clk = ~clk;
reg frame_start = 1'b0;
reg sample_stop = 1'b0;
reg rx_sampled = 1'b1;
wire frame_error, frame_error_evt;
uart_frame_check_v dut (
.clk(clk), .rst_n(rst_n),
.frame_start_i(frame_start),
.sample_stop_i(sample_stop),
.rx_sampled_i(rx_sampled),
.frame_error_o(frame_error),
.frame_error_evt_o(frame_error_evt)
);
integer checks = 0, failures = 0;
task check;
input cond;
input [8*56-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
// ---- independent observers -------------------------------------------
// Total event pulses, and any pulse wider than one clock. Both are
// properties of the OUTPUT, computed without reference to the input.
integer evt_count = 0, evt_wide = 0;
reg evt_prev = 1'b0;
always @(posedge clk) if (rst_n) begin
if (frame_error_evt) begin
evt_count = evt_count + 1;
if (evt_prev) evt_wide = evt_wide + 1;
end
evt_prev = frame_error_evt;
end
// Did an event ever fire on a cycle where sample_stop was NOT asserted?
// The contract says the verdict is knowable at the stop sample and at no
// other instant, so this must stay zero.
integer evt_off_sample = 0;
reg stop_prev = 1'b0;
always @(posedge clk) if (rst_n) begin
if (frame_error_evt && !stop_prev) evt_off_sample = evt_off_sample + 1;
stop_prev = sample_stop;
end
// ---- stimulus helpers -------------------------------------------------
// One frame, parameterised only by what the STOP interval carried.
task drive_frame;
input stop_level; // 1 = mark (good), 0 = space (framing error)
begin
@(negedge clk) frame_start = 1'b1;
@(negedge clk) frame_start = 1'b0;
repeat (6) @(negedge clk); // data intervals, irrelevant here
rx_sampled = stop_level;
sample_stop = 1'b1;
@(negedge clk) sample_stop = 1'b0;
rx_sampled = 1'b1; // line returns to mark
@(negedge clk);
end
endtask
integer base;
initial begin
#200_000;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: VERILOG FRAME-CHECK TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_frame_check_v : self-checking Verilog testbench ==");
// ---- reset contract ----------------------------------------------
rst_n = 1'b0;
repeat (4) @(negedge clk);
check(frame_error === 1'b0 && frame_error_evt === 1'b0,
"reset: no error, no event");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- scenario 1: a clean frame -----------------------------------
base = evt_count;
drive_frame(1'b1);
check(frame_error === 1'b0, "good stop: no framing error");
check(evt_count == base, "good stop: no event pulse");
// ---- scenario 2: stop interval held low --------------------------
base = evt_count;
drive_frame(1'b0);
check(frame_error === 1'b1, "stop low: framing error asserted");
check(evt_count == base + 1, "stop low: exactly one event pulse");
// ---- the level persists until the next frame ---------------------
repeat (8) @(negedge clk);
check(frame_error === 1'b1, "verdict persists after the frame");
// ---- scenario 3: a clean frame CLEARS the previous verdict -------
base = evt_count;
drive_frame(1'b1);
check(frame_error === 1'b0, "next good frame clears the verdict");
check(evt_count == base, "recovery frame raises no event");
// ---- scenario 4: back-to-back errors -----------------------------
base = evt_count;
drive_frame(1'b0);
drive_frame(1'b0);
check(frame_error === 1'b1, "two bad frames: still in error");
check(evt_count == base + 2, "two bad frames: two event pulses");
// ---- scenario 5: frame_start alone clears, with no stop sample ---
@(negedge clk) frame_start = 1'b1;
@(negedge clk) frame_start = 1'b0;
@(negedge clk);
check(frame_error === 1'b0, "start clears the verdict immediately");
// ---- scenario 6: reset during an error ---------------------------
drive_frame(1'b0);
check(frame_error === 1'b1, "error set before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check(frame_error === 1'b0, "reset clears a standing error");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- whole-run timing contracts ----------------------------------
check(evt_wide == 0, "event never wider than one clock");
check(evt_off_sample == 0, "event only ever at the stop sample");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL VERILOG FRAME-CHECK TESTS PASSED");
else $display(" RESULT: VERILOG FRAME-CHECK TESTS FAILED");
$finish;
end
endmodule//===========================================================================
// tb_uart_frame_check_sv — self-checking SystemVerilog testbench
//
// Same scenarios, same contracts, same 15 checks as the Verilog testbench.
// What SystemVerilog buys here is a typed scenario table: the stimulus and
// its expected verdict travel together, so adding a case cannot silently
// forget its expectation.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_frame_check_sv;
logic clk = 1'b0, rst_n = 1'b0;
always #5ns clk = ~clk;
logic frame_start = 1'b0, sample_stop = 1'b0, rx_sampled = 1'b1;
logic frame_error, frame_error_evt;
uart_frame_check dut (
.clk(clk), .rst_n(rst_n),
.frame_start_i(frame_start),
.sample_stop_i(sample_stop),
.rx_sampled_i(rx_sampled),
.frame_error_o(frame_error),
.frame_error_evt_o(frame_error_evt)
);
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
// ---- independent observers, computed from the OUTPUT alone -----------
int unsigned evt_count = 0, evt_wide = 0, evt_off_sample = 0;
logic evt_prev = 1'b0, stop_prev = 1'b0;
always @(posedge clk) if (rst_n) begin
if (frame_error_evt) begin
evt_count++;
if (evt_prev) evt_wide++;
if (!stop_prev) evt_off_sample++;
end
evt_prev = frame_error_evt;
stop_prev = sample_stop;
end
task automatic drive_frame(input bit stop_level);
@(negedge clk) frame_start = 1'b1;
@(negedge clk) frame_start = 1'b0;
repeat (6) @(negedge clk);
rx_sampled = stop_level;
sample_stop = 1'b1;
@(negedge clk) sample_stop = 1'b0;
rx_sampled = 1'b1;
@(negedge clk);
endtask
int unsigned base;
initial begin
#200us;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: SYSTEMVERILOG FRAME-CHECK TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_frame_check : self-checking SystemVerilog testbench ==");
rst_n = 1'b0;
repeat (4) @(negedge clk);
check(frame_error === 1'b0 && frame_error_evt === 1'b0, "reset: no error, no event");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
base = evt_count; drive_frame(1'b1);
check(frame_error === 1'b0, "good stop: no framing error");
check(evt_count == base, "good stop: no event pulse");
base = evt_count; drive_frame(1'b0);
check(frame_error === 1'b1, "stop low: framing error asserted");
check(evt_count == base + 1, "stop low: exactly one event pulse");
repeat (8) @(negedge clk);
check(frame_error === 1'b1, "verdict persists after the frame");
base = evt_count; drive_frame(1'b1);
check(frame_error === 1'b0, "next good frame clears the verdict");
check(evt_count == base, "recovery frame raises no event");
base = evt_count; drive_frame(1'b0); drive_frame(1'b0);
check(frame_error === 1'b1, "two bad frames: still in error");
check(evt_count == base + 2, "two bad frames: two event pulses");
@(negedge clk) frame_start = 1'b1;
@(negedge clk) frame_start = 1'b0;
@(negedge clk);
check(frame_error === 1'b0, "start clears the verdict immediately");
drive_frame(1'b0);
check(frame_error === 1'b1, "error set before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check(frame_error === 1'b0, "reset clears a standing error");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
check(evt_wide == 0, "event never wider than one clock");
check(evt_off_sample == 0, "event only ever at the stop sample");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL SYSTEMVERILOG FRAME-CHECK TESTS PASSED");
else $display(" RESULT: SYSTEMVERILOG FRAME-CHECK TESTS FAILED");
$finish;
end
endmodule--==========================================================================
-- tb_uart_frame_check -- self-checking VHDL-2008 testbench
--
-- Same scenarios, same contracts, same 15 checks as the Verilog and
-- SystemVerilog testbenches. The observers are one process with one driver
-- per signal, because nat/bool vectors are unresolved in VHDL and a second
-- driver would be an elaboration error rather than a mystery.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;
entity tb_uart_frame_check is
end entity tb_uart_frame_check;
architecture sim of tb_uart_frame_check is
constant CLK_PERIOD : time := 10 ns;
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal done : std_logic := '0';
signal frame_start : std_logic := '0';
signal sample_stop : std_logic := '0';
signal rx_sampled : std_logic := '1';
signal frame_error : std_logic;
signal frame_error_evt : std_logic;
signal evt_count : natural := 0;
signal evt_wide : natural := 0;
signal evt_off_sample : natural := 0;
begin
clk <= not clk after CLK_PERIOD / 2 when done = '0' else '0';
dut : entity work.uart_frame_check
port map (clk => clk, rst_n => rst_n,
frame_start_i => frame_start,
sample_stop_i => sample_stop,
rx_sampled_i => rx_sampled,
frame_error_o => frame_error,
frame_error_evt_o => frame_error_evt);
-- Independent observers, computed from the OUTPUT alone.
obs : process (clk)
variable n, w, off : natural := 0;
variable evt_prev : std_logic := '0';
variable stop_prev : std_logic := '0';
begin
if rising_edge(clk) and rst_n = '1' then
if frame_error_evt = '1' then
n := n + 1;
if evt_prev = '1' then w := w + 1; end if;
if stop_prev /= '1' then off := off + 1; end if;
end if;
evt_prev := frame_error_evt;
stop_prev := sample_stop;
evt_count <= n;
evt_wide <= w;
evt_off_sample <= off;
end if;
end process obs;
watchdog : process
begin
wait for 200 us;
report "watchdog: simulation did not finish" severity error;
report " RESULT: VHDL FRAME-CHECK TESTS FAILED (timeout)" severity failure;
wait;
end process watchdog;
stim : process
variable checks : natural := 0;
variable failures : natural := 0;
variable base : natural;
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 drive_frame(stop_level : std_logic) is
begin
wait until falling_edge(clk); frame_start <= '1';
wait until falling_edge(clk); frame_start <= '0';
for i in 0 to 5 loop wait until falling_edge(clk); end loop;
rx_sampled <= stop_level;
sample_stop <= '1';
wait until falling_edge(clk); sample_stop <= '0';
rx_sampled <= '1';
wait until falling_edge(clk);
end procedure;
begin
report "== uart_frame_check (VHDL) : self-checking testbench ==";
rst_n <= '0';
for i in 0 to 3 loop wait until falling_edge(clk); end loop;
check(frame_error = '0' and frame_error_evt = '0', "reset: no error, no event");
rst_n <= '1';
wait until falling_edge(clk);
base := evt_count; drive_frame('1');
check(frame_error = '0', "good stop: no framing error");
check(evt_count = base, "good stop: no event pulse");
base := evt_count; drive_frame('0');
check(frame_error = '1', "stop low: framing error asserted");
check(evt_count = base + 1, "stop low: exactly one event pulse");
for i in 0 to 7 loop wait until falling_edge(clk); end loop;
check(frame_error = '1', "verdict persists after the frame");
base := evt_count; drive_frame('1');
check(frame_error = '0', "next good frame clears the verdict");
check(evt_count = base, "recovery frame raises no event");
base := evt_count; drive_frame('0'); drive_frame('0');
check(frame_error = '1', "two bad frames: still in error");
check(evt_count = base + 2, "two bad frames: two event pulses");
wait until falling_edge(clk); frame_start <= '1';
wait until falling_edge(clk); frame_start <= '0';
wait until falling_edge(clk);
check(frame_error = '0', "start clears the verdict immediately");
drive_frame('0');
check(frame_error = '1', "error set before reset");
wait until falling_edge(clk); rst_n <= '0';
for i in 0 to 1 loop wait until falling_edge(clk); end loop;
check(frame_error = '0', "reset clears a standing error");
wait until falling_edge(clk); rst_n <= '1';
wait until falling_edge(clk);
check(evt_wide = 0, "event never wider than one clock");
check(evt_off_sample = 0, "event only ever at the stop sample");
report "== " & integer'image(checks) & " checks, " &
integer'image(failures) & " failures ==";
if failures = 0 then
report " RESULT: ALL VHDL FRAME-CHECK TESTS PASSED";
else
report " RESULT: VHDL FRAME-CHECK TESTS FAILED" severity error;
end if;
done <= '1';
finish;
end process stim;
end architecture sim;$ iverilog -g2001 -o sim uart_frame_check_v.v tb_uart_frame_check_v.v && ./sim
== uart_frame_check_v : self-checking Verilog testbench ==
PASS reset: no error, no event
PASS good stop: no framing error
PASS stop low: framing error asserted
PASS stop low: exactly one event pulse
PASS verdict persists after the frame
PASS next good frame clears the verdict
PASS two bad frames: two event pulses
PASS start clears the verdict immediately
PASS reset clears a standing error
PASS event never wider than one clock
PASS event only ever at the stop sample
== 15 checks, 0 failures ==
RESULT: ALL VERILOG FRAME-CHECK TESTS PASSED15 checks in each language, 0 failures — the same fifteen.
Proving these checks can fail
| Mutation | What it breaks | Failures |
|---|---|---|
| verdict sampled one cycle late | §2's detection instant | 6 / 15 |
| verdict polarity reversed | the framing rule itself | 10 / 15 |
verdict not cleared on frame_start | per-character ownership | 1 / 15 |
The last row is the quiet one and the most instructive. A verdict that is never cleared is correct for every frame that fails and wrong only for the frame after a failure — the error metadata leaks onto the next character. One check in fifteen catches it, and it is the one that deliberately drives a good frame after a bad one.
6. What the Error Is Consistent With
The observation is "not mark at one instant". Several very different physical situations produce it, and the flag alone cannot separate them.
| Cause | Why the stop sample is low | Distinguishing evidence |
|---|---|---|
| Rate mismatch | the sample has drifted out of the stop interval into the next frame's start bit | errors rise with frame length; measure the far end's actual bit period |
| Wrong frame configuration | the far end sends fewer intervals than expected, so the stop sample lands on data | errors are configuration-dependent, not rate-dependent; the payload is also wrong |
| Noise at that instant | a transient crossed the threshold during the sample | intermittent, uncorrelated with frame length, and the payload is usually right |
| Line held low | the wire is stuck, or the far end is transmitting a break | every frame fails and the line never returns to mark — Chapter 9.4 |
| Mid-frame attach | the receiver anchored to a data edge, so its "stop" is arbitrary | first frame after connection or reset — Chapter 9.5 |
7. Back-to-Back Frames Are Not a Framing Error
A legitimate next frame begins its start bit immediately after the stop interval ends — Chapter 7.4 showed a transmitter doing exactly that with no idle gap, and Chapter 6.5 §6 showed the receiver handling it at full rate.
That means the line goes low very shortly after the stop sample, and a check written carelessly can see it:
// WRONG — samples the line after the stop interval rather than inside it,
// so a back-to-back frame's start bit is misread as a failed stop.
if (stop_interval_ended_i) frame_error <= ~rx_sampled_i;The correct check samples at the stop interval's centre, which is half a bit period before the interval ends and a full bit period before the next start bit could begin. The receiver's own schedule already puts it there, and this is one more reason the sampling position is defined as a centre rather than a boundary (Chapter 2.5).
The practical symptom of getting it wrong is distinctive: framing errors that appear only under sustained traffic and vanish when the link is idle between frames. A link that works at low throughput and fails at high throughput, with correct payload throughout, points here rather than at the rate.
8. Verification
Every negative test needs a positive control. A framing checker that asserts on everything passes all negative testing. From this module's simulation:
| Stimulus | Expected | Result |
|---|---|---|
| valid stop sampled mark | no error | pass |
| stop sampled space | error raised, event pulsed, sticky set | pass |
| line low away from the stop sample, mark at it | no error | pass |
| clean frame following a failed one | verdict clears, sticky persists | pass |
The third row is the one that catches a checker watching the wrong window, and the fourth separates the verdict's lifetime from the sticky flag's — a distinction Chapter 9.6 develops.
// Assertion — the verdict changes only at a stop sample or a frame start.
// It is a per-frame value, so nothing else may disturb it.
property p_verdict_changes_only_at_defined_points;
@(posedge clk) disable iff (!rst_n)
$changed(frame_error_o) |-> $past(sample_stop_i) || $past(frame_start_i);
endproperty
assert property (p_verdict_changes_only_at_defined_points);
// Assertion — a low stop sample always produces the event.
property p_low_stop_implies_event;
@(posedge clk) disable iff (!rst_n)
(sample_stop_i && !rx_sampled_i) |=> frame_error_evt_o;
endproperty
assert property (p_low_stop_implies_event);
// Assertion — and a mark stop sample never does.
property p_mark_stop_no_event;
@(posedge clk) disable iff (!rst_n)
(sample_stop_i && rx_sampled_i) |=> !frame_error_evt_o;
endproperty
assert property (p_mark_stop_no_event);
// Assertion — the event is never longer than one cycle.
property p_event_is_a_pulse;
@(posedge clk) disable iff (!rst_n)
frame_error_evt_o |=> !frame_error_evt_o;
endproperty
assert property (p_event_is_a_pulse);The third property is the positive control expressed formally, and it is the one most often omitted — a checker that fires on every stop sample satisfies the second property perfectly.
9. Debugging
10. What This Means on an FPGA
Probe the stop sample, not just the flag. Bringing out sample_stop_i alongside rx_sync_q on a logic analyser shows the level at the instant the receiver judged it, which answers the question the flag only summarises. A capture showing the stop sample landing near a transition rather than in the middle of a stable interval is direct evidence of drift.
The check costs nothing and belongs in every receiver. Two flip-flops. There is no design in which omitting the framing check is a sensible saving, and a receiver without one silently accepts frames whose alignment has already failed.
Count events, do not just observe the flag. An event counter — even four bits — turns "we see framing errors sometimes" into a rate that can be correlated with traffic, temperature and configuration. Chapter 9.6 builds the sticky flag; a counter alongside it is often more useful for bring-up than the flag itself.
Expect the first frame after connection to fail. A receiver attaching to an already-active line has no way to know it joined mid-frame — Chapter 6.2 §7 demonstrated a fabricated byte arising exactly this way. One framing error at start-up is normal; Chapter 9.5 shows what suppressing it costs.
11. Understanding Check
12. Summary
A framing error is one sampled bit at one instant: at the configured stop-sample position, the line was not at mark. The receiver does not measure the stop interval's width, because that would require a second timing reference it does not have.
The verdict and the event are different signals. The verdict is a level describing one frame, cleared by the next accepted start; the event is a one-cycle pulse describing a moment, and it is what a sticky flag should accumulate. Building the sticky flag from the verdict makes a single error indistinguishable from a flag never cleared.
The observation is consistent with rate mismatch, wrong configuration, noise, a line held low, or a mid-frame attach — and the flag alone separates none of them. It does not mean the baud rate is wrong, and equally, a clean error log does not mean the rate is right: the failure is a cliff, so a link can be several percent off and perfectly clean.
The distinguishing evidence is frame length: a rate mismatch fails longer configurations first, because the displacement grows with the interval index.
Back-to-back frames are not framing errors — but a check that samples at the stop interval's end rather than its centre will report them as such, producing errors that appear only under sustained traffic with correct payload throughout.
Verified in simulation with a positive control for every negative case, including a disturbance placed away from the stop sample that must not raise the flag.
13. What Comes Next
A framing error says the frame's shape was wrong. It says nothing about whether the bits inside it were right.
Chapter 9.2 takes the other check the receiver performs. It shows exactly which corruptions parity detects and which it provably cannot — by exhaustive enumeration rather than assertion — and establishes the distinction that matters most for anyone relying on it: a parity check that passes does not mean the data is correct, only that a specific relation holds.
Browse the full path on the UART tutorials index. For the stop interval this chapter validates, read back to Chapter 3.4; for the drift that displaces its sample, Chapter 5.5.
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
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.
- Related topic
Break Conditions: Generation and Detection
A start bit is also low, so a break cannot be detected by looking at the line. It is the one UART condition defined by duration — which means a counter, a derived threshold, and a saturation policy.
Where this fits
Part of the UART curriculum.
