Skip to content
VLSI Mentor

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.

EventState
Exampleframe_error_evt_oframe_error_sticky_o
Shapeone-cycle pulselevel, held
Answersdid something happen on this clock?has something happened since I last looked?
Lifetimeone cycleuntil cleared or reset
Consumerhardware — counters, aggregatorssoftware, or a slow observer
Loses information?noyes — 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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
//  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;
endmodule

3. The Same Status Block in Three Languages

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  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;
PropertyVerilog-2001SystemVerilogVHDL-2008Agree?
flagsfour sticky + any_errorsamesameyes
set sourceone-cycle event inputssamesameyes
clear sourceclear_i, one cyclesamesameyes
collision policySET-DOMINANTSET-DOMINANTSET-DOMINANTyes
how priority is expressedwrite order in one blocksamesignal assignment orderyes — same mechanism
any_error_ocombinational ORcombinational ORcombinational ORyes
resetclears all foursamesameyes
reading the flags internallyreads the regreads the logicinternal *_qno — 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
//===========================================================================
//  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
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
--==========================================================================
--  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;
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ 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 PASSED

20 checks in each language, 0 failures.

Proving these checks can fail

MutationWhat it breaksFailures
flag follows the event (not sticky)§1's lifetime contract7 / 20
made CLEAR-dominant§3's collision policy2 / 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-dominantClear-dominant
Resultflag stays setflag goes clear
Losesnothingthe new event, silently
Software seesthe error again on its next readnothing — as if it never happened
Riska duplicate report of one erroran error that is never reported at all
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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 collision

This design is set-dominant, and the argument is asymmetric consequences.

Clear and event on the same edge

8 cycles
A trace of eight fabric clock cycles showing a sticky error flag under two collision policies. An error event pulses at cycle one and both flags set at cycle two. At cycle four a software clear is asserted on the same cycle as a second error event. Under the set-dominant policy the flag remains set, because the set assignment is written after the clear and therefore wins, so software observes the error on its next read. Under the clear-dominant policy the flag goes low at cycle five and the second event is lost permanently, leaving no record anywhere in the design that it occurred.first errorfirst errorCLEAR + EVENT collideCLEAR + EVENT collideset-dom keeps it / clear-dom loses itset-dom keeps it /clear-dom loses iterror_evt_iclear_isticky (SET-dom)sticky (CLEAR-dom)t0t1t2t3t4t5t6t7
Figure 1 — the collision, both ways. Columns are FABRIC CLOCK CYCLES. At cycle 4 a clear and a new event land together. The set-dominant flag keeps the error and software sees it on its next read; the clear-dominant flag goes low and the event is gone from the design entirely, with nothing anywhere recording that it occurred.

That 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  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         0

Cycle 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-characterHistorical (sticky)
Questionwas this byte good?has anything gone wrong since I looked?
Scopeexactly one framemany frames
Travels withthe datanothing — it is global
Built byChapter 6.4's publish disciplinethis chapter's OR accumulation
Consumerlogic deciding what to do with a bytesoftware 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:

StimulusExpectedResult
event, no clearsticky setpass
clear, no eventsticky clearspass
any_error_o with any flag setassertedpass
clear and event on the same cyclesticky STAYS SETpass

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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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_STOP exits 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:

ConditionRecovery requirementSignal
Framing or parity errornone — report and continue—
Breakwait for release, then for proven idlebreak_released_o, line_idle_o
Suspected mis-framingwait for proven idleline_idle_o
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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);
fsm
A two-state recovery gate that runs alongside a UART receive state machine. In the normal state, start candidates are passed through to the receiver unchanged, so framing and parity errors have no effect on it because those conditions require no recovery. A break declaration, or a suspicion that the receiver's timing origin is wrong, moves the gate to an armed state in which start candidates are blocked. The gate returns to normal only when the line has been observed continuously at the mark level for a full frame time, which proves no frame is in progress. Because this machine is separate from the receive state machine, the receive machine's transitions still depend only on its own state, the configuration and the sampling event, so its graph remains enumerable and verifiable without line activity.NORMALARMEDbreak_evt / mis-frame suspectedbreak_evt / mis-frame suspectedbreak_evt /mis-frame…line_idle_o — a full frame of markline_idle_o — a full frame of markline_idle_o — afull frame of…framing / parity error: no effectframing / parityerror: no effectstart candidates BLOCKEDstart candidates BLOCKEDstartcandidates…
Figure 2 — the recovery gate, which is a separate two-state machine running alongside the receive FSM rather than inside it. The receive FSM of Chapter 6.2 is unchanged and still has its eight enumerated transitions; this machine only decides whether a start candidate may be offered to it. Ordinary framing and parity errors do not arm it, because they need no recovery.

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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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
PropertyDefect it catchesWhy it would otherwise be missed
p_event_is_one_cycleevent held as a levelconsumers double-count; the error rate is wrong, not the error
p_event_only_at_sampleverdict latched lateevery per-frame verdict is still correct
p_sticky_holdsstatus decays on its ownthe condition has already passed; nothing else records it
p_set_dominantcollision policy invertedonly wrong on a race, rarely and non-deterministically
p_break_declared_oncebreak re-declareda stream of events where there was one condition
p_idle_needs_markidle asserted too readilyno 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 elementUVM componentWhat it carries
a frame to transmit, correct or corruptsequence itemdata, parity_mode, corrupt_parity, corrupt_stop, inject_false_start, break_bits
producing that frame on the wiredriverbit-level serialisation at the configured baud
observing rx_imonitorreconstructs frames from the line alone
what the frame should have producedpredictorerror type and payload, from the item
comparisonscoreboarderror 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:

AxisBinsWhy
error typenone, framing, parity, overrun, break, false startthe module's own taxonomy
parity modenone, even, oddparity errors only exist in two of the three
payloadall-zero, all-one, alternating, single-oneall-zero is the one a break can be confused with
break durationbelow threshold, at threshold, far abovethe boundary is the interesting part
recovery resultnext frame clean / next frame also badan error that prevents recovery is a different defect
status at clearno 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.

ErrorStimulusDetection pointStatusData validityRecovery
Framingstop interval driven lowthe stop sample of that frameper-frame verdict + one-cycle event; sticky in 9.6payload already shifted; suspect, not discardednext accepted start re-establishes phase
Parityparity bit flippedthe parity sample, before the stopper-frame verdict + eventpayload intact and delivered; relation failednone needed — framing was fine
Parity, undetectedtwo data bits flippednevernonepayload wrong, reported good— the known limit (9.2 §5)
Overruna frame completes while the slot is fullthe arrival of the new characterone-cycle event; stickynew character lost, held one intactautomatic once the consumer reads
Breakline held low past FRAME_BITS x OVERSAMPLE ticksthe threshold ticklevel + declare-once event + release pulseno character; the frame is not dataon release to mark, then normal framing
False startshort low pulse in idlethe start validation samplenone — nothing happenedno character producedreceiver never left idle
Mid-frame attachreceiver enabled during someone else's characteridle qualification withholds consentnoneno character producedafter 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

RequirementDirected testCorner testAssertionCoverageWaveform
stop low at the stop sample -> framing error9.1 §5, 15 checks x3back-to-back errorsp_event_only_at_sampleerror type9.1 Fig 1
verdict cleared by a new framegood frame after badstart with no stop sample—error -> recovery9.1 Fig 1
event exactly one clockwhole-run observer—p_event_is_one_cycle——
parity relation, both modes32-frame sweep x3all-zero, all-one—parity mode x payload9.2 Fig 1
two-bit corruption is not detected0xA5 -> 0xA6————
collision keeps held byte, drops new9.3 §6, 22 checks x3same-cycle hand-off——9.3 Fig 1
consumer keeping up -> no overrun8 back-to-backready high throughout———
break at threshold, not beforeone tick short / at9 low bit timesp_break_declared_oncebreak duration9.4 Fig 1
break declared oncelong breakcounter wrap pointp_break_declared_once——
threshold scales with config16x/10 vs 8x/7both simultaneously———
idle earned, never assumedreset checkbusy line, 20 cyclesp_idle_needs_mark—9.5 Fig 1
sticky survives until clear20-cycle hold—p_sticky_holds—§5 Fig 1
set beats clear on a collision3 collision variantsreset during collisionp_set_dominantstatus at clear§5 Fig 1
reset clears all error stateevery blockreset 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.

BlockChapterVerilogSystemVerilogVHDL
uart_frame_check9.1151515
uart_parity_check9.2454545
uart_rx_holding9.3222222
uart_break_detect9.4191919
uart_idle_qualify9.5141414
uart_err_status§4 above202020
total135135135

405 checks across three languages, 0 failures.

LanguageToolCompileSimulate
Verilog-2001Icarus Verilog 13.0 (-g2001)cleanyes
SystemVerilogIcarus Verilog 13.0 (-g2012)cleanyes
VHDL-2008NVC 1.23.0 (LLVM 23.1.1)cleanyes

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:

MutationBlockFailures
parity polarity reversedparity42 / 45
parity ignores the configured modeparity17 / 45
framing polarity reversedframe10 / 15
sticky flag follows the eventstatus7 / 20
framing verdict sampled one cycle lateframe6 / 15
overrun overwrites the held byteholding3 / 22
break threshold off by onebreak3 / 19
idle assumed out of resetidle3 / 14
status made clear-dominantstatus2 / 20
verdict not cleared on a new startframe1 / 15
break counter wraps instead of saturatingbreak0 — 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

Where this fits

Part of the UART curriculum.