UART · Module 17
Bit Order, Parity and Framing Failure Signatures
Why 0xA5 cannot detect a reversed byte, why parity is structurally blind to bit order, why one frame can never separate a parity misconfiguration from noise, and a classifier in three HDLs that resolves all four faults.
From an application's point of view there is one symptom: the byte that arrived is not the byte that was sent. Underneath it are at least four distinct faults, with four distinct fixes, and a status register that reports "parity error" or "framing error" resolves only some of them — sometimes into the wrong one.
This chapter builds a classifier that separates them, and in doing so runs into two results that are worth the chapter on their own. One of the most widely used UART test bytes in the world cannot detect a reversed byte at all. And a parity misconfiguration is, from a single frame, mathematically indistinguishable from a single noise event.
1. Four Faults, One Symptom
| Fault | What is physically wrong | The fix |
|---|---|---|
| Bit order reversed | the byte is assembled MSB-first at one end | one line of RTL, or a configuration bit |
| Parity configuration | one end is even, the other odd | a configuration bit |
| Parity noise | the wire occasionally flips a bit | cabling, termination, shielding, slew rate |
| Framing violation | the stop bit is not MARK | baud, or a genuinely malformed frame |
The four are not distinguishable from a single corrupted byte, and two of them are not distinguishable from a single frame at all — no matter how carefully you look at it. That is not an engineering limitation to be designed around; it is a property of how much information one frame contains. The way out is to stop looking at frames and start looking at rates.
2. Bit Order, and the Bytes That Cannot Detect It
A UART transmits least-significant bit first. Assemble the incoming bits the other way round and every byte arrives bit-reversed — a completely systematic, completely repeatable remapping.
It is also, under the right test vector, completely invisible.
A byte is unchanged by reversal when bit 0 equals bit 7, bit 1 equals bit 6, bit 2 equals bit 5 and bit 3 equals bit 4. That is four free bits, so there are exactly sixteen such bytes:
0x00 0x18 0x24 0x3C 0x42 0x5A 0x66 0x7E 0x81 0x99 0xA5 0xBD 0xC3 0xDB 0xE7 0xFFSeveral of those are among the most-reached-for test values in the field:
byte reversed can it detect a reversal?
---- -------- -------------------------
0x00 0x00 NO - its own mirror
0xFF 0xFF NO - its own mirror
0x55 0xAA yes
0xAA 0x55 yes
0xA5 0xA5 NO - its own mirror
0x5A 0x5A NO - its own mirror
0x3C 0x3C NO - its own mirror
0x0F 0xF0 yes
0xF0 0x0F yes
0x81 0x81 NO - its own mirror
0x7E 0x7E NO - its own mirror
0xDE 0x7B yesThere is a second blindness here, and it is structural rather than accidental:
3. Parity: Configuration or Noise?
Suppose the receiver is configured for even parity and the transmitter is sending odd. Every frame arrives with a parity bit that is the complement of what the receiver computes, so every frame raises a parity error.
Now suppose instead the link is correctly configured and a noise event flips the parity bit on one frame. That frame arrives with a parity bit that is the complement of what the receiver computes, so it raises a parity error.
The two frames are bit-for-bit identical on the wire. There is no measurement that separates them, because there is nothing to separate — a single frame simply does not carry the information.
What does separate them is the rate:
every frame wrong -> configuration. Change a config bit.
some frames wrong -> noise. Change the physical layer.
no frames wrong -> nothing to fix (or nothing detectable -- see §2)This is why the classifier below accumulates across frames and refuses to answer until it has seen MINFRAMES of them. A debug instrument that reports a verdict from one frame is reporting a guess.
4. The Classifier
The block records six counters and derives a verdict from them. The counters matter as much as the verdict: they are the evidence, and they remain available when the verdict is unhelpful.
Verilog
// ---------------------------------------------------------------------------
// uart_fail_classifier -- decide WHICH failure a capture shows.
//
// From software, a reversed byte, a parity-configuration mismatch and a
// genuine framing violation all look the same: "the data is wrong". They are
// not the same fault and they do not have the same fix. This block separates
// them.
//
// The central design decision is that the verdict is accumulated over MANY
// frames, not formed from one. That is forced by the physics, not by taste:
//
// * A parity CONFIGURATION mismatch (even vs odd) inverts the parity bit on
// every single frame. Noise inverts it occasionally. From ONE frame the
// two are literally indistinguishable -- the wire carries the same bits.
// Only the RATE separates them.
//
// * Bit-order reversal cannot be detected at all using a payload that is a
// bit-palindrome (0x00, 0xFF, 0x81, 0x66 ...), because the reversed byte
// equals the original. The block counts those frames separately and
// reports them, so a clean result on useless stimulus is never mistaken
// for evidence of correctness.
// ---------------------------------------------------------------------------
module uart_fail_classifier #(
parameter MINFRAMES = 4 // evidence required before judging
)(
input wire clk,
input wire rst_n,
input wire clr_i, // begin a new investigation
input wire go_i, // strobe: one observed frame is presented
input wire [7:0] exp_i, // what the transmitter sent
input wire [7:0] rx_i, // what the receiver assembled
input wire par_rx_i, // the parity bit as it arrived
input wire par_odd_i, // receiver config: 0 = even, 1 = odd
input wire stop_i, // the stop bit as sampled
output reg [7:0] n_frames_o,
output reg [7:0] n_stop_bad_o, // stop sampled as SPACE
output reg [7:0] n_rev_o, // rx == bit-reverse(exp), and differs
output reg [7:0] n_data_bad_o, // wrong, and not a clean reversal
output reg [7:0] n_par_bad_o, // parity bit disagrees with the data
output reg [7:0] n_ambig_o, // payload could not prove bit order
output wire [2:0] verdict_o
);
localparam [2:0] V_NEED = 3'd0, // not enough frames yet
V_OK = 3'd1,
V_FRAMING = 3'd2,
V_REVERSED = 3'd3,
V_DATA = 3'd4,
V_PAR_CFG = 3'd5, // every frame -- a configuration fault
V_PAR_NOISE = 3'd6; // some frames -- a signal-integrity fault
function [7:0] bitrev;
input [7:0] b;
integer i;
begin
for (i = 0; i < 8; i = i + 1) bitrev[i] = b[7-i];
end
endfunction
wire [7:0] exp_rev = bitrev(exp_i);
wire is_rev = (rx_i == exp_rev) && (rx_i != exp_i);
wire is_ambig = (exp_rev == exp_i); // palindrome: proves nothing
wire par_exp = par_odd_i ? ~(^rx_i) : (^rx_i);
wire par_bad = (par_rx_i != par_exp);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_frames_o <= 8'd0; n_stop_bad_o <= 8'd0; n_rev_o <= 8'd0;
n_data_bad_o <= 8'd0; n_par_bad_o <= 8'd0; n_ambig_o <= 8'd0;
end else if (clr_i) begin
n_frames_o <= 8'd0; n_stop_bad_o <= 8'd0; n_rev_o <= 8'd0;
n_data_bad_o <= 8'd0; n_par_bad_o <= 8'd0; n_ambig_o <= 8'd0;
end else if (go_i) begin
n_frames_o <= n_frames_o + 8'd1;
if (!stop_i) n_stop_bad_o <= n_stop_bad_o + 8'd1;
if (is_rev) n_rev_o <= n_rev_o + 8'd1;
if (is_ambig) n_ambig_o <= n_ambig_o + 8'd1;
if ((rx_i != exp_i) && !is_rev) n_data_bad_o <= n_data_bad_o + 8'd1;
if (par_bad) n_par_bad_o <= n_par_bad_o + 8'd1;
end
end
// ---- the verdict, and the order in which evidence is weighed ----------
// Framing first: if the frame boundary is wrong, every field inside it was
// sampled at an unknown offset and none of the other evidence is sound.
// Reversal next, but only when EVERY frame is reversed -- an intermittent
// "reversal" is not a wiring fault, it is corruption that happened to land
// on a mirror image. Parity last, split by rate: always-wrong is a
// configuration fault, sometimes-wrong is a signal-integrity fault.
assign verdict_o =
(n_frames_o < MINFRAMES) ? V_NEED :
(n_stop_bad_o != 8'd0) ? V_FRAMING :
(n_rev_o == n_frames_o) ? V_REVERSED :
(n_data_bad_o != 8'd0) ? V_DATA :
(n_par_bad_o == n_frames_o) ? V_PAR_CFG :
(n_par_bad_o != 8'd0) ? V_PAR_NOISE :
V_OK;
endmoduleSystemVerilog
// ---------------------------------------------------------------------------
// uart_fail_classifier -- decide WHICH failure a capture shows.
//
// From software, a reversed byte, a parity-configuration mismatch and a
// genuine framing violation all look the same: "the data is wrong". They are
// not the same fault and they do not have the same fix. This block separates
// them.
//
// The central design decision is that the verdict is accumulated over MANY
// frames, not formed from one. That is forced by the physics, not by taste:
//
// * A parity CONFIGURATION mismatch (even vs odd) inverts the parity bit on
// every single frame. Noise inverts it occasionally. From ONE frame the
// two are literally indistinguishable -- the wire carries the same bits.
// Only the RATE separates them.
//
// * Bit-order reversal cannot be detected at all using a payload that is a
// bit-palindrome (0x00, 0xFF, 0x81, 0x66 ...), because the reversed byte
// equals the original. The block counts those frames separately and
// reports them, so a clean result on useless stimulus is never mistaken
// for evidence of correctness.
// ---------------------------------------------------------------------------
module uart_fail_classifier #(
parameter int MINFRAMES = 4 // evidence required before judging
)(
input logic clk,
input logic rst_n,
input logic clr_i, // begin a new investigation
input logic go_i, // strobe: one observed frame is presented
input logic [7:0] exp_i, // what the transmitter sent
input logic [7:0] rx_i, // what the receiver assembled
input logic par_rx_i, // the parity bit as it arrived
input logic par_odd_i, // receiver config: 0 = even, 1 = odd
input logic stop_i, // the stop bit as sampled
output logic [7:0] n_frames_o,
output logic [7:0] n_stop_bad_o, // stop sampled as SPACE
output logic [7:0] n_rev_o, // rx == bit-reverse(exp), and differs
output logic [7:0] n_data_bad_o, // wrong, and not a clean reversal
output logic [7:0] n_par_bad_o, // parity bit disagrees with the data
output logic [7:0] n_ambig_o, // payload could not prove bit order
output logic [2:0] verdict_o
);
localparam [2:0] V_NEED = 3'd0, // not enough frames yet
V_OK = 3'd1,
V_FRAMING = 3'd2,
V_REVERSED = 3'd3,
V_DATA = 3'd4,
V_PAR_CFG = 3'd5, // every frame -- a configuration fault
V_PAR_NOISE = 3'd6; // some frames -- a signal-integrity fault
function automatic logic [7:0] bitrev(input logic [7:0] b);
int i;
begin
for (i = 0; i < 8; i = i + 1) bitrev[i] = b[7-i];
end
endfunction
wire [7:0] exp_rev = bitrev(exp_i);
wire is_rev = (rx_i == exp_rev) && (rx_i != exp_i);
wire is_ambig = (exp_rev == exp_i); // palindrome: proves nothing
wire par_exp = par_odd_i ? ~(^rx_i) : (^rx_i);
wire par_bad = (par_rx_i != par_exp);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
n_frames_o <= 8'd0; n_stop_bad_o <= 8'd0; n_rev_o <= 8'd0;
n_data_bad_o <= 8'd0; n_par_bad_o <= 8'd0; n_ambig_o <= 8'd0;
end else if (clr_i) begin
n_frames_o <= 8'd0; n_stop_bad_o <= 8'd0; n_rev_o <= 8'd0;
n_data_bad_o <= 8'd0; n_par_bad_o <= 8'd0; n_ambig_o <= 8'd0;
end else if (go_i) begin
n_frames_o <= n_frames_o + 8'd1;
if (!stop_i) n_stop_bad_o <= n_stop_bad_o + 8'd1;
if (is_rev) n_rev_o <= n_rev_o + 8'd1;
if (is_ambig) n_ambig_o <= n_ambig_o + 8'd1;
if ((rx_i != exp_i) && !is_rev) n_data_bad_o <= n_data_bad_o + 8'd1;
if (par_bad) n_par_bad_o <= n_par_bad_o + 8'd1;
end
end
// ---- the verdict, and the order in which evidence is weighed ----------
// Framing first: if the frame boundary is wrong, every field inside it was
// sampled at an unknown offset and none of the other evidence is sound.
// Reversal next, but only when EVERY frame is reversed -- an intermittent
// "reversal" is not a wiring fault, it is corruption that happened to land
// on a mirror image. Parity last, split by rate: always-wrong is a
// configuration fault, sometimes-wrong is a signal-integrity fault.
assign verdict_o =
(n_frames_o < MINFRAMES) ? V_NEED :
(n_stop_bad_o != 8'd0) ? V_FRAMING :
(n_rev_o == n_frames_o) ? V_REVERSED :
(n_data_bad_o != 8'd0) ? V_DATA :
(n_par_bad_o == n_frames_o) ? V_PAR_CFG :
(n_par_bad_o != 8'd0) ? V_PAR_NOISE :
V_OK;
endmoduleVHDL
-- ---------------------------------------------------------------------------
-- uart_fail_classifier -- decide WHICH failure a capture shows.
--
-- From software, a reversed byte, a parity-configuration mismatch and a
-- genuine framing violation all look the same: "the data is wrong". They are
-- not the same fault and they do not have the same fix. This block separates
-- them.
--
-- The central design decision is that the verdict is accumulated over MANY
-- frames, not formed from one. That is forced by the physics, not by taste:
--
-- * A parity CONFIGURATION mismatch (even vs odd) inverts the parity bit on
-- every single frame. Noise inverts it occasionally. From ONE frame the
-- two are literally indistinguishable -- the wire carries the same bits.
-- Only the RATE separates them.
--
-- * Bit-order reversal cannot be detected at all using a payload that is a
-- bit-palindrome (0x00, 0xFF, 0x81, 0xA5 ...), because the reversed byte
-- equals the original. The block counts those frames separately and
-- reports them, so a clean result on useless stimulus is never mistaken
-- for evidence of correctness.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity uart_fail_classifier is
generic (
MINFRAMES : natural := 4 -- evidence required before judging
);
port (
clk : in std_logic;
rst_n : in std_logic;
clr_i : in std_logic; -- begin a new investigation
go_i : in std_logic; -- one observed frame presented
exp_i : in std_logic_vector(7 downto 0); -- what the transmitter sent
rx_i : in std_logic_vector(7 downto 0); -- what the receiver assembled
par_rx_i : in std_logic; -- the parity bit as it arrived
par_odd_i : in std_logic; -- config: '0' = even, '1' = odd
stop_i : in std_logic; -- the stop bit as sampled
n_frames_o : out unsigned(7 downto 0);
n_stop_bad_o : out unsigned(7 downto 0); -- stop sampled as SPACE
n_rev_o : out unsigned(7 downto 0); -- rx = mirror(exp), and differs
n_data_bad_o : out unsigned(7 downto 0); -- wrong, not a clean reversal
n_par_bad_o : out unsigned(7 downto 0); -- parity disagrees with data
n_ambig_o : out unsigned(7 downto 0); -- payload could not prove order
verdict_o : out std_logic_vector(2 downto 0)
);
end entity uart_fail_classifier;
architecture rtl of uart_fail_classifier is
constant V_NEED : std_logic_vector(2 downto 0) := "000"; -- not enough frames
constant V_OK : std_logic_vector(2 downto 0) := "001";
constant V_FRAMING : std_logic_vector(2 downto 0) := "010";
constant V_REVERSED : std_logic_vector(2 downto 0) := "011";
constant V_DATA : std_logic_vector(2 downto 0) := "100";
constant V_PAR_CFG : std_logic_vector(2 downto 0) := "101"; -- every frame
constant V_PAR_NOISE : std_logic_vector(2 downto 0) := "110"; -- some frames
function bitrev (b : std_logic_vector(7 downto 0))
return std_logic_vector is
variable r : std_logic_vector(7 downto 0);
begin
for i in 0 to 7 loop
r(i) := b(7-i);
end loop;
return r;
end function;
function xor_all (b : std_logic_vector(7 downto 0)) return std_logic is
variable r : std_logic := '0';
begin
for i in 0 to 7 loop
r := r xor b(i);
end loop;
return r;
end function;
signal exp_rev : std_logic_vector(7 downto 0);
signal is_rev : std_logic;
signal is_ambig : std_logic;
signal par_exp : std_logic;
signal par_bad : std_logic;
-- outputs mirrored internally: an entity may not read its own outputs
signal n_frames, n_stop_bad, n_rev, n_data_bad, n_par_bad, n_ambig
: unsigned(7 downto 0) := (others => '0');
begin
exp_rev <= bitrev(exp_i);
is_rev <= '1' when (rx_i = exp_rev and rx_i /= exp_i) else '0';
is_ambig <= '1' when exp_rev = exp_i else '0'; -- palindrome: proves nothing
par_exp <= not xor_all(rx_i) when par_odd_i = '1' else xor_all(rx_i);
par_bad <= '1' when par_rx_i /= par_exp else '0';
n_frames_o <= n_frames;
n_stop_bad_o <= n_stop_bad;
n_rev_o <= n_rev;
n_data_bad_o <= n_data_bad;
n_par_bad_o <= n_par_bad;
n_ambig_o <= n_ambig;
process (clk, rst_n)
begin
if rst_n = '0' then
n_frames <= (others => '0'); n_stop_bad <= (others => '0');
n_rev <= (others => '0'); n_data_bad <= (others => '0');
n_par_bad <= (others => '0'); n_ambig <= (others => '0');
elsif rising_edge(clk) then
if clr_i = '1' then
n_frames <= (others => '0'); n_stop_bad <= (others => '0');
n_rev <= (others => '0'); n_data_bad <= (others => '0');
n_par_bad <= (others => '0'); n_ambig <= (others => '0');
elsif go_i = '1' then
n_frames <= n_frames + 1;
if stop_i = '0' then n_stop_bad <= n_stop_bad + 1; end if;
if is_rev = '1' then n_rev <= n_rev + 1; end if;
if is_ambig = '1' then n_ambig <= n_ambig + 1; end if;
if rx_i /= exp_i and is_rev = '0' then
n_data_bad <= n_data_bad + 1;
end if;
if par_bad = '1' then n_par_bad <= n_par_bad + 1; end if;
end if;
end if;
end process;
-- ---- the verdict, and the order in which evidence is weighed ----------
-- Framing first: if the frame boundary is wrong, every field inside it was
-- sampled at an unknown offset and none of the other evidence is sound.
-- Reversal next, but only when EVERY frame is reversed -- an intermittent
-- "reversal" is not a wiring fault, it is corruption that happened to land
-- on a mirror image. Parity last, split by rate: always-wrong is a
-- configuration fault, sometimes-wrong is a signal-integrity fault.
verdict_o <= V_NEED when n_frames < to_unsigned(MINFRAMES, 8) else
V_FRAMING when n_stop_bad /= 0 else
V_REVERSED when n_rev = n_frames else
V_DATA when n_data_bad /= 0 else
V_PAR_CFG when n_par_bad = n_frames else
V_PAR_NOISE when n_par_bad /= 0 else
V_OK;
end architecture rtl;The n_ambig_o counter is the §2 result made operational. It counts frames whose expected payload is its own mirror image — frames that, whatever else they prove, cannot prove anything about bit order. A clean run with n_ambig_o equal to n_frames_o is not evidence that the bit order is right; it is evidence that the stimulus was incapable of testing it.
The precedence order
The verdict weighs evidence in a fixed order, and the order encodes real reasoning rather than convenience:
- Framing first. If the stop bit is not where it should be, the frame boundary is wrong, every field inside it was sampled at an unknown offset, and no other observation from that frame is sound. A capture that is both framing-broken and apparently reversed must report framing, because the "reversal" may be an artefact of sampling the wrong bits.
- Reversal next, but only if every frame shows it. A wiring or configuration fault is deterministic. An intermittent "reversal" is corruption that happened to land on a mirror image, which is a different problem.
- Data corruption, when bytes are wrong in a way that is not a clean mirror.
- Parity last, split by rate — the §3 distinction.
5. What the Classifier Measured
Ten scenarios, eight frames each:
scenario verdict supporting counters
------------------------------- ------------ ------------------------
T1 healthy traffic OK par_bad 0/8
T2 parity wrong on every frame PARITY_CFG par_bad 8/8
T3 parity wrong on one frame PARITY_NOISE par_bad 1/8
T4 reversed, probed with 0x55 REVERSED rev 8/8, par_bad 0
T5 reversed, probed with 0xA5 OK rev 0, ambiguous 8/8
T6 one bad stop bit FRAMING stop_bad 1
T7 thin evidence NEED_MORE then PARITY_CFG at frame 4
T8 corruption, not a mirror DATA data_bad 8, rev 0
T10 reversed AND bad stop bits FRAMING rev 8/8, stop_bad 8/8T4 and T5 are the same physical fault. The wire is assembling bytes backwards in both. With 0x55 the classifier detects it on all eight frames; with 0xA5 it detects nothing and the capture is spotless — while flagging all eight frames as ambiguous, which is the only honest thing it can say.
The same reversed wire, probed with two different payloads
11 cyclesT10 is the precedence rule under test. Every frame is reversed and every stop bit is SPACE. The reversal counter still records 8 of 8 — the evidence is not discarded — but the verdict is FRAMING, because until the frame boundary is trusted the contents cannot be.
That test exists because of a mutation. It was not in the original suite, and §7 explains how its absence was found.
6. The Testbench
Each test presents a stream describing one physical fault, rather than a single frame, because §3 established that a single frame cannot carry the answer.
Verilog
`timescale 1ns/1ps
// ---------------------------------------------------------------------------
// Testbench for uart_fail_classifier.
//
// Each test presents a STREAM of frames describing one physical fault, and
// asserts the verdict the evidence supports. The interesting cases are the two
// where the honest answer is "this stimulus cannot tell you":
//
// T5 bit-order reversal probed with 0xA5 -- a bit-palindrome. The reversed
// byte equals the original, so the capture is clean and proves nothing.
// T3 parity wrong on SOME frames, which is noise, versus T2 where it is
// wrong on EVERY frame, which is a configuration fault. One frame cannot
// separate these; a rate can.
// ---------------------------------------------------------------------------
module tb_uart_fail_classifier;
localparam MINF = 4;
localparam [2:0] V_NEED = 3'd0, V_OK = 3'd1, V_FRAMING = 3'd2,
V_REVERSED = 3'd3, V_DATA = 3'd4, V_PAR_CFG = 3'd5,
V_PAR_NOISE = 3'd6;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg clr = 1'b0;
reg go = 1'b0;
reg [7:0] exp_b = 8'd0, rx_b = 8'd0;
reg par_rx = 1'b0, par_odd = 1'b0, stop_b = 1'b1;
wire [7:0] n_frames, n_stop_bad, n_rev, n_data_bad, n_par_bad, n_ambig;
wire [2:0] verdict;
integer checks = 0;
integer fails = 0;
always #5 clk = ~clk;
uart_fail_classifier #(.MINFRAMES(MINF)) dut (
.clk(clk), .rst_n(rst_n), .clr_i(clr), .go_i(go),
.exp_i(exp_b), .rx_i(rx_b), .par_rx_i(par_rx),
.par_odd_i(par_odd), .stop_i(stop_b),
.n_frames_o(n_frames), .n_stop_bad_o(n_stop_bad), .n_rev_o(n_rev),
.n_data_bad_o(n_data_bad), .n_par_bad_o(n_par_bad), .n_ambig_o(n_ambig),
.verdict_o(verdict));
function [7:0] bitrev;
input [7:0] b;
integer i;
begin
for (i = 0; i < 8; i = i + 1) bitrev[i] = b[7-i];
end
endfunction
function [12*8:1] vname;
input [2:0] v;
begin
case (v)
V_NEED: vname = "NEED_MORE";
V_OK: vname = "OK";
V_FRAMING: vname = "FRAMING";
V_REVERSED: vname = "REVERSED";
V_DATA: vname = "DATA";
V_PAR_CFG: vname = "PARITY_CFG";
V_PAR_NOISE: vname = "PARITY_NOISE";
default: vname = "?";
endcase
end
endfunction
task chk;
input [255:0] name;
input integer got;
input integer exp;
begin
checks = checks + 1;
if (got !== exp) begin
fails = fails + 1;
$display(" FAIL %0s: got %0d expected %0d", name, got, exp);
end
end
endtask
task present;
input [7:0] e;
input [7:0] r;
input p;
input po;
input s;
begin
@(negedge clk);
exp_b = e; rx_b = r; par_rx = p; par_odd = po; stop_b = s; go = 1'b1;
@(posedge clk);
@(negedge clk);
go = 1'b0;
end
endtask
task restart;
begin
@(negedge clk); clr = 1'b1;
@(posedge clk);
@(negedge clk); clr = 1'b0;
@(posedge clk);
end
endtask
integer i;
initial begin
rst_n = 1'b0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
repeat (2) @(posedge clk);
// ---------------- T1: healthy traffic -----------------------------
restart;
for (i = 0; i < 8; i = i + 1) present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b1);
#1;
$display("T1 healthy : verdict=%0s frames=%0d", vname(verdict), n_frames);
chk("T1 verdict OK", verdict, V_OK);
chk("T1 no parity complaints", n_par_bad, 0);
// ---------------- T2: parity wrong on EVERY frame -----------------
// The transmitter is using odd parity; the receiver is configured even.
restart;
for (i = 0; i < 8; i = i + 1) present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
$display("T2 parity every fr. : verdict=%0s par_bad=%0d/%0d",
vname(verdict), n_par_bad, n_frames);
chk("T2 verdict PARITY_CFG", verdict, V_PAR_CFG);
chk("T2 every frame flagged", n_par_bad, n_frames);
// ---------------- T3: parity wrong on ONE frame in eight ----------
restart;
for (i = 0; i < 8; i = i + 1) begin
if (i == 3) present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
else present(8'h55, 8'h55, (^(8'h55)), 1'b0, 1'b1);
end
#1;
$display("T3 parity one frame : verdict=%0s par_bad=%0d/%0d",
vname(verdict), n_par_bad, n_frames);
chk("T3 verdict PARITY_NOISE", verdict, V_PAR_NOISE);
chk("T3 exactly one frame flagged", n_par_bad, 1);
// ---------------- T4: every byte arrives bit-reversed -------------
// Note the parity bit stays CONSISTENT: XOR is order-independent, so
// parity cannot see a reversal at all.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, bitrev(8'h55), ^(8'h55), 1'b0, 1'b1);
#1;
$display("T4 reversed (0x55) : verdict=%0s rev=%0d/%0d par_bad=%0d",
vname(verdict), n_rev, n_frames, n_par_bad);
chk("T4 verdict REVERSED", verdict, V_REVERSED);
chk("T4 every frame reversed", n_rev, n_frames);
chk("T4 parity did NOT notice", n_par_bad, 0);
// ---------------- T5: the same fault, probed with 0xA5 ------------
// 0xA5 is a bit-palindrome. Reversed, it is still 0xA5. The wire is
// just as broken as in T4 and the capture is spotless.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'hA5, bitrev(8'hA5), ^(8'hA5), 1'b0, 1'b1);
#1;
$display("T5 reversed (0xA5) : verdict=%0s rev=%0d ambiguous=%0d/%0d",
vname(verdict), n_rev, n_ambig, n_frames);
chk("T5 0xA5 is its own mirror", bitrev(8'hA5), 8'hA5);
chk("T5 the broken wire looks clean", verdict, V_OK);
chk("T5 no reversal was detectable", n_rev, 0);
chk("T5 every frame flagged ambiguous", n_ambig, n_frames);
// ---------------- T6: one framing error outranks everything -------
restart;
for (i = 0; i < 8; i = i + 1) begin
if (i == 5) present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b0); // stop = SPACE
else present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b1);
end
#1;
$display("T6 one framing err : verdict=%0s stop_bad=%0d",
vname(verdict), n_stop_bad);
chk("T6 verdict FRAMING", verdict, V_FRAMING);
chk("T6 one bad stop", n_stop_bad, 1);
// ---------------- T7: refuse to judge on thin evidence ------------
restart;
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
chk("T7 one frame is not evidence", verdict, V_NEED);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
chk("T7 three frames still not enough", verdict, V_NEED);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
$display("T7 fourth frame : verdict=%0s", vname(verdict));
chk("T7 the fourth frame decides it", verdict, V_PAR_CFG);
// ---------------- T8: corruption that is not a clean mirror -------
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, 8'h5D, ^(8'h55), 1'b0, 1'b1);
#1;
$display("T8 plain corruption : verdict=%0s data_bad=%0d rev=%0d",
vname(verdict), n_data_bad, n_rev);
chk("T8 verdict DATA", verdict, V_DATA);
chk("T8 not mistaken for a reversal", n_rev, 0);
// ---------------- T10: both faults at once ------------------------
// Every frame is reversed AND every stop bit is SPACE. The evidence for
// the reversal is still recorded, but the VERDICT must be FRAMING: if
// the frame boundary is wrong, the bits inside it were sampled at an
// unknown offset, so "it looks reversed" is not yet a sound conclusion.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, bitrev(8'h55), ^(8'h55), 1'b0, 1'b0);
#1;
$display("T10 reversed + framing: verdict=%0s rev=%0d/%0d stop_bad=%0d",
vname(verdict), n_rev, n_frames, n_stop_bad);
chk("T10 framing outranks reversal", verdict, V_FRAMING);
chk("T10 the reversal is still recorded", n_rev, n_frames);
// ---------------- T9: clear really clears -------------------------
restart;
#1;
chk("T9 frames cleared", n_frames, 0);
chk("T9 verdict reset", verdict, V_NEED);
chk("T9 counters cleared", n_data_bad + n_par_bad + n_rev + n_stop_bad + n_ambig, 0);
$display("");
$display("== %0d checks, %0d failures ==", checks, fails);
if (fails == 0) $display(" RESULT: ALL VERILOG FAIL-CLASSIFIER TESTS PASSED");
else $display(" RESULT: %0d FAILURE(S)", fails);
$finish;
end
endmoduleSystemVerilog
`timescale 1ns/1ps
// ---------------------------------------------------------------------------
// Testbench for uart_fail_classifier.
//
// Each test presents a STREAM of frames describing one physical fault, and
// asserts the verdict the evidence supports. The interesting cases are the two
// where the honest answer is "this stimulus cannot tell you":
//
// T5 bit-order reversal probed with 0xA5 -- a bit-palindrome. The reversed
// byte equals the original, so the capture is clean and proves nothing.
// T3 parity wrong on SOME frames, which is noise, versus T2 where it is
// wrong on EVERY frame, which is a configuration fault. One frame cannot
// separate these; a rate can.
// ---------------------------------------------------------------------------
module tb_uart_fail_classifier;
localparam MINF = 4;
localparam [2:0] V_NEED = 3'd0, V_OK = 3'd1, V_FRAMING = 3'd2,
V_REVERSED = 3'd3, V_DATA = 3'd4, V_PAR_CFG = 3'd5,
V_PAR_NOISE = 3'd6;
logic clk = 1'b0;
logic rst_n = 1'b0;
logic clr = 1'b0;
logic go = 1'b0;
logic [7:0] exp_b = 8'd0, rx_b = 8'd0;
logic par_rx = 1'b0, par_odd = 1'b0, stop_b = 1'b1;
logic [7:0] n_frames, n_stop_bad, n_rev, n_data_bad, n_par_bad, n_ambig;
logic [2:0] verdict;
integer checks = 0;
integer fails = 0;
always #5 clk = ~clk;
uart_fail_classifier #(.MINFRAMES(MINF)) dut (
.clk(clk), .rst_n(rst_n), .clr_i(clr), .go_i(go),
.exp_i(exp_b), .rx_i(rx_b), .par_rx_i(par_rx),
.par_odd_i(par_odd), .stop_i(stop_b),
.n_frames_o(n_frames), .n_stop_bad_o(n_stop_bad), .n_rev_o(n_rev),
.n_data_bad_o(n_data_bad), .n_par_bad_o(n_par_bad), .n_ambig_o(n_ambig),
.verdict_o(verdict));
function automatic logic [7:0] bitrev(input logic [7:0] b);
int i;
begin
for (i = 0; i < 8; i = i + 1) bitrev[i] = b[7-i];
end
endfunction
function automatic string vname(input logic [2:0] v);
begin
case (v)
V_NEED: vname = "NEED_MORE";
V_OK: vname = "OK";
V_FRAMING: vname = "FRAMING";
V_REVERSED: vname = "REVERSED";
V_DATA: vname = "DATA";
V_PAR_CFG: vname = "PARITY_CFG";
V_PAR_NOISE: vname = "PARITY_NOISE";
default: vname = "?";
endcase
end
endfunction
task automatic chk(input string name, input int got, input int exp);
begin
checks = checks + 1;
if (got !== exp) begin
fails = fails + 1;
$display(" FAIL %0s: got %0d expected %0d", name, got, exp);
end
end
endtask
task automatic present(input logic [7:0] e, input logic [7:0] r,
input logic p, input logic po, input logic s);
begin
@(negedge clk);
exp_b = e; rx_b = r; par_rx = p; par_odd = po; stop_b = s; go = 1'b1;
@(posedge clk);
@(negedge clk);
go = 1'b0;
end
endtask
task automatic restart();
begin
@(negedge clk); clr = 1'b1;
@(posedge clk);
@(negedge clk); clr = 1'b0;
@(posedge clk);
end
endtask
integer i;
initial begin
rst_n = 1'b0;
repeat (3) @(posedge clk);
@(negedge clk); rst_n = 1'b1;
repeat (2) @(posedge clk);
// ---------------- T1: healthy traffic -----------------------------
restart;
for (i = 0; i < 8; i = i + 1) present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b1);
#1;
$display("T1 healthy : verdict=%0s frames=%0d", vname(verdict), n_frames);
chk("T1 verdict OK", verdict, V_OK);
chk("T1 no parity complaints", n_par_bad, 0);
// ---------------- T2: parity wrong on EVERY frame -----------------
// The transmitter is using odd parity; the receiver is configured even.
restart;
for (i = 0; i < 8; i = i + 1) present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
$display("T2 parity every fr. : verdict=%0s par_bad=%0d/%0d",
vname(verdict), n_par_bad, n_frames);
chk("T2 verdict PARITY_CFG", verdict, V_PAR_CFG);
chk("T2 every frame flagged", n_par_bad, n_frames);
// ---------------- T3: parity wrong on ONE frame in eight ----------
restart;
for (i = 0; i < 8; i = i + 1) begin
if (i == 3) present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
else present(8'h55, 8'h55, (^(8'h55)), 1'b0, 1'b1);
end
#1;
$display("T3 parity one frame : verdict=%0s par_bad=%0d/%0d",
vname(verdict), n_par_bad, n_frames);
chk("T3 verdict PARITY_NOISE", verdict, V_PAR_NOISE);
chk("T3 exactly one frame flagged", n_par_bad, 1);
// ---------------- T4: every byte arrives bit-reversed -------------
// Note the parity bit stays CONSISTENT: XOR is order-independent, so
// parity cannot see a reversal at all.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, bitrev(8'h55), ^(8'h55), 1'b0, 1'b1);
#1;
$display("T4 reversed (0x55) : verdict=%0s rev=%0d/%0d par_bad=%0d",
vname(verdict), n_rev, n_frames, n_par_bad);
chk("T4 verdict REVERSED", verdict, V_REVERSED);
chk("T4 every frame reversed", n_rev, n_frames);
chk("T4 parity did NOT notice", n_par_bad, 0);
// ---------------- T5: the same fault, probed with 0xA5 ------------
// 0xA5 is a bit-palindrome. Reversed, it is still 0xA5. The logic is
// just as broken as in T4 and the capture is spotless.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'hA5, bitrev(8'hA5), ^(8'hA5), 1'b0, 1'b1);
#1;
$display("T5 reversed (0xA5) : verdict=%0s rev=%0d ambiguous=%0d/%0d",
vname(verdict), n_rev, n_ambig, n_frames);
chk("T5 0xA5 is its own mirror", bitrev(8'hA5), 8'hA5);
chk("T5 the broken logic looks clean", verdict, V_OK);
chk("T5 no reversal was detectable", n_rev, 0);
chk("T5 every frame flagged ambiguous", n_ambig, n_frames);
// ---------------- T6: one framing error outranks everything -------
restart;
for (i = 0; i < 8; i = i + 1) begin
if (i == 5) present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b0); // stop = SPACE
else present(8'h55, 8'h55, ^(8'h55), 1'b0, 1'b1);
end
#1;
$display("T6 one framing err : verdict=%0s stop_bad=%0d",
vname(verdict), n_stop_bad);
chk("T6 verdict FRAMING", verdict, V_FRAMING);
chk("T6 one bad stop", n_stop_bad, 1);
// ---------------- T7: refuse to judge on thin evidence ------------
restart;
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
chk("T7 one frame is not evidence", verdict, V_NEED);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
chk("T7 three frames still not enough", verdict, V_NEED);
present(8'h55, 8'h55, ~(^(8'h55)), 1'b0, 1'b1);
#1;
$display("T7 fourth frame : verdict=%0s", vname(verdict));
chk("T7 the fourth frame decides it", verdict, V_PAR_CFG);
// ---------------- T8: corruption that is not a clean mirror -------
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, 8'h5D, ^(8'h55), 1'b0, 1'b1);
#1;
$display("T8 plain corruption : verdict=%0s data_bad=%0d rev=%0d",
vname(verdict), n_data_bad, n_rev);
chk("T8 verdict DATA", verdict, V_DATA);
chk("T8 not mistaken for a reversal", n_rev, 0);
// ---------------- T10: both faults at once ------------------------
// Every frame is reversed AND every stop bit is SPACE. The evidence for
// the reversal is still recorded, but the VERDICT must be FRAMING: if
// the frame boundary is wrong, the bits inside it were sampled at an
// unknown offset, so "it looks reversed" is not yet a sound conclusion.
restart;
for (i = 0; i < 8; i = i + 1)
present(8'h55, bitrev(8'h55), ^(8'h55), 1'b0, 1'b0);
#1;
$display("T10 reversed + framing: verdict=%0s rev=%0d/%0d stop_bad=%0d",
vname(verdict), n_rev, n_frames, n_stop_bad);
chk("T10 framing outranks reversal", verdict, V_FRAMING);
chk("T10 the reversal is still recorded", n_rev, n_frames);
// ---------------- T9: clear really clears -------------------------
restart;
#1;
chk("T9 frames cleared", n_frames, 0);
chk("T9 verdict reset", verdict, V_NEED);
chk("T9 counters cleared", n_data_bad + n_par_bad + n_rev + n_stop_bad + n_ambig, 0);
$display("");
$display("== %0d checks, %0d failures ==", checks, fails);
if (fails == 0) $display(" RESULT: ALL SYSTEMVERILOG FAIL-CLASSIFIER TESTS PASSED");
else $display(" RESULT: %0d FAILURE(S)", fails);
$finish;
end
endmoduleVHDL
-- ---------------------------------------------------------------------------
-- Testbench for uart_fail_classifier.
--
-- Each test presents a STREAM of frames describing one physical fault, and
-- asserts the verdict the evidence supports. The interesting cases are the two
-- where the honest answer is "this stimulus cannot tell you":
--
-- T5 bit-order reversal probed with 0xA5 -- a bit-palindrome. The reversed
-- byte equals the original, so the capture is clean and proves nothing.
-- T3 parity wrong on SOME frames, which is noise, versus T2 where it is
-- wrong on EVERY frame, which is a configuration fault. One frame cannot
-- separate these; a rate can.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity tb_uart_fail_classifier is
end entity tb_uart_fail_classifier;
architecture sim of tb_uart_fail_classifier is
constant MINF : natural := 4;
constant TCLK : time := 10 ns;
constant V_NEED : std_logic_vector(2 downto 0) := "000";
constant V_OK : std_logic_vector(2 downto 0) := "001";
constant V_FRAMING : std_logic_vector(2 downto 0) := "010";
constant V_REVERSED : std_logic_vector(2 downto 0) := "011";
constant V_DATA : std_logic_vector(2 downto 0) := "100";
constant V_PAR_CFG : std_logic_vector(2 downto 0) := "101";
constant V_PAR_NOISE : std_logic_vector(2 downto 0) := "110";
signal clk : std_logic := '0';
signal rst_n : std_logic := '0';
signal clr : std_logic := '0';
signal go : std_logic := '0';
signal exp_b : std_logic_vector(7 downto 0) := (others => '0');
signal rx_b : std_logic_vector(7 downto 0) := (others => '0');
signal par_rx : std_logic := '0';
signal par_odd : std_logic := '0';
signal stop_b : std_logic := '1';
signal done : boolean := false;
signal n_frames, n_stop_bad, n_rev, n_data_bad, n_par_bad, n_ambig
: unsigned(7 downto 0);
signal verdict : std_logic_vector(2 downto 0);
function bitrev (b : std_logic_vector(7 downto 0))
return std_logic_vector is
variable r : std_logic_vector(7 downto 0);
begin
for i in 0 to 7 loop
r(i) := b(7-i);
end loop;
return r;
end function;
function xor_all (b : std_logic_vector(7 downto 0)) return std_logic is
variable r : std_logic := '0';
begin
for i in 0 to 7 loop
r := r xor b(i);
end loop;
return r;
end function;
function vname (v : std_logic_vector(2 downto 0)) return string is
begin
case v is
when V_NEED => return "NEED_MORE";
when V_OK => return "OK";
when V_FRAMING => return "FRAMING";
when V_REVERSED => return "REVERSED";
when V_DATA => return "DATA";
when V_PAR_CFG => return "PARITY_CFG";
when V_PAR_NOISE => return "PARITY_NOISE";
when others => return "?";
end case;
end function;
begin
clk <= '0' when done else not clk after TCLK/2;
dut : entity work.uart_fail_classifier
generic map (MINFRAMES => MINF)
port map (clk => clk, rst_n => rst_n, clr_i => clr, go_i => go,
exp_i => exp_b, rx_i => rx_b, par_rx_i => par_rx,
par_odd_i => par_odd, stop_i => stop_b,
n_frames_o => n_frames, n_stop_bad_o => n_stop_bad,
n_rev_o => n_rev, n_data_bad_o => n_data_bad,
n_par_bad_o => n_par_bad, n_ambig_o => n_ambig,
verdict_o => verdict);
stim : process
variable checks, fails : integer := 0;
procedure chk (name : string; got : integer; exp : integer) is
begin
checks := checks + 1;
if got /= exp then
fails := fails + 1;
report " FAIL " & name & ": got " & integer'image(got) &
" expected " & integer'image(exp) severity error;
end if;
end procedure;
procedure present (e : std_logic_vector(7 downto 0);
r : std_logic_vector(7 downto 0);
p : std_logic; po : std_logic; s : std_logic) is
begin
wait until falling_edge(clk);
exp_b <= e; rx_b <= r; par_rx <= p; par_odd <= po; stop_b <= s;
go <= '1';
wait until rising_edge(clk);
wait until falling_edge(clk);
go <= '0';
end procedure;
procedure restart is
begin
wait until falling_edge(clk); clr <= '1';
wait until rising_edge(clk);
wait until falling_edge(clk); clr <= '0';
wait until rising_edge(clk);
end procedure;
begin
rst_n <= '0';
for i in 0 to 2 loop wait until rising_edge(clk); end loop;
wait until falling_edge(clk); rst_n <= '1';
for i in 0 to 1 loop wait until rising_edge(clk); end loop;
-- ---------------- T1: healthy traffic -----------------------------
restart;
for i in 0 to 7 loop
present(x"55", x"55", xor_all(x"55"), '0', '1');
end loop;
wait for 1 ns;
report "T1 healthy : verdict=" & vname(verdict) &
" frames=" & integer'image(to_integer(n_frames));
chk("T1 verdict OK", to_integer(unsigned(verdict)), to_integer(unsigned(V_OK)));
chk("T1 no parity complaints", to_integer(n_par_bad), 0);
-- ---------------- T2: parity wrong on EVERY frame -----------------
-- The transmitter is using odd parity; the receiver is configured even.
restart;
for i in 0 to 7 loop
present(x"55", x"55", not xor_all(x"55"), '0', '1');
end loop;
wait for 1 ns;
report "T2 parity every fr. : verdict=" & vname(verdict) &
" par_bad=" & integer'image(to_integer(n_par_bad)) &
"/" & integer'image(to_integer(n_frames));
chk("T2 verdict PARITY_CFG", to_integer(unsigned(verdict)), to_integer(unsigned(V_PAR_CFG)));
chk("T2 every frame flagged", to_integer(n_par_bad), to_integer(n_frames));
-- ---------------- T3: parity wrong on ONE frame in eight ----------
restart;
for i in 0 to 7 loop
if i = 3 then present(x"55", x"55", not xor_all(x"55"), '0', '1');
else present(x"55", x"55", xor_all(x"55"), '0', '1');
end if;
end loop;
wait for 1 ns;
report "T3 parity one frame : verdict=" & vname(verdict) &
" par_bad=" & integer'image(to_integer(n_par_bad)) &
"/" & integer'image(to_integer(n_frames));
chk("T3 verdict PARITY_NOISE", to_integer(unsigned(verdict)), to_integer(unsigned(V_PAR_NOISE)));
chk("T3 exactly one frame flagged", to_integer(n_par_bad), 1);
-- ---------------- T4: every byte arrives bit-reversed -------------
-- Note the parity bit stays CONSISTENT: XOR is order-independent, so
-- parity cannot see a reversal at all.
restart;
for i in 0 to 7 loop
present(x"55", bitrev(x"55"), xor_all(x"55"), '0', '1');
end loop;
wait for 1 ns;
report "T4 reversed (0x55) : verdict=" & vname(verdict) &
" rev=" & integer'image(to_integer(n_rev)) &
"/" & integer'image(to_integer(n_frames)) &
" par_bad=" & integer'image(to_integer(n_par_bad));
chk("T4 verdict REVERSED", to_integer(unsigned(verdict)), to_integer(unsigned(V_REVERSED)));
chk("T4 every frame reversed", to_integer(n_rev), to_integer(n_frames));
chk("T4 parity did NOT notice", to_integer(n_par_bad), 0);
-- ---------------- T5: the same fault, probed with 0xA5 ------------
-- 0xA5 is a bit-palindrome. Reversed, it is still 0xA5. The wire is
-- just as broken as in T4 and the capture is spotless.
restart;
for i in 0 to 7 loop
present(x"A5", bitrev(x"A5"), xor_all(x"A5"), '0', '1');
end loop;
wait for 1 ns;
report "T5 reversed (0xA5) : verdict=" & vname(verdict) &
" rev=" & integer'image(to_integer(n_rev)) &
" ambiguous=" & integer'image(to_integer(n_ambig)) &
"/" & integer'image(to_integer(n_frames));
if bitrev(x"A5") = x"A5" then chk("T5 0xA5 is its own mirror", 1, 1);
else chk("T5 0xA5 is its own mirror", 0, 1); end if;
chk("T5 the broken wire looks clean", to_integer(unsigned(verdict)), to_integer(unsigned(V_OK)));
chk("T5 no reversal was detectable", to_integer(n_rev), 0);
chk("T5 every frame flagged ambiguous", to_integer(n_ambig), to_integer(n_frames));
-- ---------------- T6: one framing error outranks everything -------
restart;
for i in 0 to 7 loop
if i = 5 then present(x"55", x"55", xor_all(x"55"), '0', '0');
else present(x"55", x"55", xor_all(x"55"), '0', '1');
end if;
end loop;
wait for 1 ns;
report "T6 one framing err : verdict=" & vname(verdict) &
" stop_bad=" & integer'image(to_integer(n_stop_bad));
chk("T6 verdict FRAMING", to_integer(unsigned(verdict)), to_integer(unsigned(V_FRAMING)));
chk("T6 one bad stop", to_integer(n_stop_bad), 1);
-- ---------------- T7: refuse to judge on thin evidence ------------
restart;
present(x"55", x"55", not xor_all(x"55"), '0', '1');
wait for 1 ns;
chk("T7 one frame is not evidence", to_integer(unsigned(verdict)), to_integer(unsigned(V_NEED)));
present(x"55", x"55", not xor_all(x"55"), '0', '1');
present(x"55", x"55", not xor_all(x"55"), '0', '1');
wait for 1 ns;
chk("T7 three frames still not enough", to_integer(unsigned(verdict)), to_integer(unsigned(V_NEED)));
present(x"55", x"55", not xor_all(x"55"), '0', '1');
wait for 1 ns;
report "T7 fourth frame : verdict=" & vname(verdict);
chk("T7 the fourth frame decides it", to_integer(unsigned(verdict)), to_integer(unsigned(V_PAR_CFG)));
-- ---------------- T8: corruption that is not a clean mirror -------
restart;
for i in 0 to 7 loop
present(x"55", x"5D", xor_all(x"55"), '0', '1');
end loop;
wait for 1 ns;
report "T8 plain corruption : verdict=" & vname(verdict) &
" data_bad=" & integer'image(to_integer(n_data_bad)) &
" rev=" & integer'image(to_integer(n_rev));
chk("T8 verdict DATA", to_integer(unsigned(verdict)), to_integer(unsigned(V_DATA)));
chk("T8 not mistaken for a reversal", to_integer(n_rev), 0);
-- ---------------- T10: both faults at once ------------------------
-- Every frame is reversed AND every stop bit is SPACE. The evidence for
-- the reversal is still recorded, but the VERDICT must be FRAMING: if
-- the frame boundary is wrong, the bits inside it were sampled at an
-- unknown offset, so "it looks reversed" is not yet a sound conclusion.
restart;
for i in 0 to 7 loop
present(x"55", bitrev(x"55"), xor_all(x"55"), '0', '0');
end loop;
wait for 1 ns;
report "T10 reversed + framing: verdict=" & vname(verdict) &
" rev=" & integer'image(to_integer(n_rev)) &
"/" & integer'image(to_integer(n_frames)) &
" stop_bad=" & integer'image(to_integer(n_stop_bad));
chk("T10 framing outranks reversal",
to_integer(unsigned(verdict)), to_integer(unsigned(V_FRAMING)));
chk("T10 the reversal is still recorded",
to_integer(n_rev), to_integer(n_frames));
-- ---------------- T9: clear really clears -------------------------
restart;
wait for 1 ns;
chk("T9 frames cleared", to_integer(n_frames), 0);
chk("T9 verdict reset", to_integer(unsigned(verdict)), to_integer(unsigned(V_NEED)));
chk("T9 counters cleared",
to_integer(n_data_bad) + to_integer(n_par_bad) + to_integer(n_rev) +
to_integer(n_stop_bad) + to_integer(n_ambig), 0);
report "";
report "== " & integer'image(checks) & " checks, " &
integer'image(fails) & " failures ==";
if fails = 0 then
report " RESULT: ALL VHDL FAIL-CLASSIFIER TESTS PASSED";
else
report " RESULT: " & integer'image(fails) & " FAILURE(S)" severity error;
end if;
done <= true;
wait;
end process;
end architecture sim;7. Proving the Tests Can Fail — and One That Could Not
mutation checks failed verdict
-------------------------------------------------- ------------- --------
M6 drop the "and differs" qualifier from is_rev 2 killed
M7 test parity noise before parity configuration 2 killed
M8 weigh reversal before framing 0 SURVIVEDM8 survived the original suite, and that survival was informative rather than embarrassing.
Swapping the first two branches of the verdict only changes the answer when a capture is both fully reversed and framing-broken at the same time. Every existing test had one fault or the other, never both, so the precedence rule — which the block's comments explicitly claimed — was asserted nowhere. The suite could not tell whether the order mattered.
The fix was not to delete the mutation or weaken the claim. It was to add T10, which drives exactly that combination, and re-run:
M8 weigh reversal before framing (after adding T10) 1 killed8. Choosing Test Vectors That Can Actually Fail
Everything in §2 comes down to a single practical rule: a test vector is only as good as the faults it is capable of revealing.
| To detect | Use | Because |
|---|---|---|
| Bit-order reversal | 0x55 / 0xAA | mirror images of each other; maximally sensitive |
| Sampling drift (17.2) | 0x55 / 0xAA | every adjacent bit differs, so any drift lands on a different value |
| Stuck-at-one on any data line | 0x00 | any 1 that arrives is a fault |
| Stuck-at-zero on any data line | 0xFF | any 0 that arrives is a fault |
| Parity configuration | any payload, many frames | it is the rate that carries the answer |
| Framing | any payload | the stop bit does not depend on the data |
0xA5 is a fine byte for confirming that a link carries data at all. It is a poor byte for proving anything about bit order, and 0x00 and 0xFF — the other two reflexive choices — are equally blind to it.
A loopback suite that sends 0x00, 0xFF and 0xA5 and passes has demonstrated that the wire conducts. It has not demonstrated that the bit order is right, because none of the three could have shown otherwise.
Continue learning
Related tutorials
- Related topic
Reading a UART Waveform as Evidence
Separating what a capture observes from what it implies, the run-length structure that makes a bit period measurable, a measuring block in three HDLs, and the ordinary payload that makes it report nine times the right answer.
- Related topic
Baud Mismatch and Sampling-Error Signatures
Why sampling error accumulates across a frame and corrupts the high bits first, the arithmetic that fixes the tolerance at 5.26 percent, and the measured drift table for five receiver dividers sharing one wire.
- Related topic
Missing Start Bits, False Starts and Noise
A naive start detector and a majority-vote qualifier racing on one wire, a rejection boundary measured rather than assumed, and the counter that separates a transmitter that never sent from a receiver that never listened.
- Related topic
FIFO Overrun and Flow-Control Failures
Three scenarios with the identical symptom and three different fixes, an instrumented FIFO in three HDLs that classifies each lost byte, and the headroom arithmetic that explains why RTS often fails on a USB-serial bridge.
Where this fits
Part of the UART curriculum.
