Skip to content
VLSI Mentor

UART · Module 18

UART Design Review: The Questions a Reviewer Asks

Twenty-five review questions across RTL, timing, integration, verification and debug, five of them implemented as a synthesisable probe in three HDLs and the same properties written in PSL, where they run as real concurrent assertions.

A design review produces a document. Three months later the design has changed, the document has not, and nobody can tell which of its concerns were addressed and which were quietly forgotten.

This chapter takes the opposite approach. It starts from the questions a reviewer should ask, and then writes down as much of that list as possible in a form that fails a regression when the answer stops being right. What cannot be made executable stays as a question — but the boundary between the two turns out to be further along than most review checklists assume.

1. The Questions That Can Be Made to Run

Five of the most useful review questions are structural invariants, and an invariant is a checker:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  R1  Can a frame start while one is in flight?
  R2  Is every frame exactly the configured number of bit-times?
  R3  Is the line at MARK whenever the transmitter is idle?
  R4  Does every frame that starts eventually finish?
  R5  Can a configuration write land mid-frame?

R5 is the Chapter 18.3 escape, promoted from a post-mortem to a standing question. That promotion is the point of the whole exercise: a bug that has been found once should become a check, or it will be found again in a different design.

2. The Probe

Verilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
// uart_review_probe -- the questions a reviewer asks, written down as logic.
//
// A design review that produces a list of concerns in a document produces
// nothing that can fail in regression. This block is the same list expressed
// as checkers, so each question becomes a signal that goes high and stays high
// the moment the answer is wrong.
//
// Every invariant here is one that a real UART has violated in the field:
//
//   R1  a frame is started while one is already in flight
//   R2  a frame is not exactly MAXBITS bit-times long
//   R3  the line is not MARK while the transmitter is idle
//   R4  busy is asserted and never drops
//   R5  a configuration write lands mid-frame     (Chapter 18.3's escape)
//
// The flags are STICKY. A violation that happens once, a million cycles into a
// regression, must still be visible at the end of the run -- a level that
// pulses and clears is a violation nobody sees.
// ---------------------------------------------------------------------------
module uart_review_probe #(
    parameter MAXBITS = 10,          // start + 8 data + stop
    parameter TIMEOUT = 4000         // clocks a frame may take before it hangs
)(
    input  wire       clk,
    input  wire       rst_n,

    input  wire       busy_i,        // the transmitter is mid-frame
    input  wire       line_i,        // the serial output
    input  wire       start_i,       // a frame was requested
    input  wire       bit_tick_i,    // one pulse per transmitted bit
    input  wire       cfg_wr_i,      // software wrote a configuration register

    output reg        v_overlap_o,   // R1
    output reg        v_framelen_o,  // R2
    output reg        v_idle_o,      // R3
    output reg        v_hang_o,      // R4
    output reg        v_cfg_o,       // R5

    output reg  [7:0] n_frames_o,
    output reg  [3:0] last_len_o,    // bits in the most recent frame
    output wire       any_violation_o
);

    reg        busy_q;
    reg [3:0]  bit_cnt;
    reg [15:0] age;

    wire frame_start = busy_i && !busy_q;
    wire frame_end   = !busy_i && busy_q;

    assign any_violation_o = v_overlap_o | v_framelen_o | v_idle_o |
                             v_hang_o    | v_cfg_o;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            busy_q       <= 1'b0;
            bit_cnt      <= 4'd0;
            age          <= 16'd0;
            v_overlap_o  <= 1'b0;
            v_framelen_o <= 1'b0;
            v_idle_o     <= 1'b0;
            v_hang_o     <= 1'b0;
            v_cfg_o      <= 1'b0;
            n_frames_o   <= 8'd0;
            last_len_o   <= 4'd0;
        end else begin
            busy_q <= busy_i;

            // ---- R1: no frame may begin while one is in flight ----------
            if (start_i && busy_i && !frame_start) v_overlap_o <= 1'b1;

            // ---- R3: an idle transmitter must hold the line at MARK -----
            // Checked only when settled, not on the clock busy drops, to avoid
            // flagging the one-cycle handover at the end of the stop bit.
            if (!busy_i && !busy_q && !line_i) v_idle_o <= 1'b1;

            // ---- R5: configuration must not change under a live frame ---
            if (cfg_wr_i && busy_i) v_cfg_o <= 1'b1;

            // ---- R2 / R4: frame length and liveness ---------------------
            if (frame_start) begin
                bit_cnt <= 4'd0;
                age     <= 16'd0;
            end else if (busy_i) begin
                if (bit_tick_i && bit_cnt != 4'd15) bit_cnt <= bit_cnt + 4'd1;
                if (age != 16'hFFFF)                age     <= age + 16'd1;
                if (age >= TIMEOUT[15:0])           v_hang_o <= 1'b1;
            end

            if (frame_end) begin
                last_len_o <= bit_cnt;
                n_frames_o <= n_frames_o + 8'd1;
                if (bit_cnt != MAXBITS[3:0]) v_framelen_o <= 1'b1;
            end
        end
    end

endmodule

SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ---------------------------------------------------------------------------
// uart_review_probe -- the questions a reviewer asks, written down as logic.
//
// A design review that produces a list of concerns in a document produces
// nothing that can fail in regression. This block is the same list expressed
// as checkers, so each question becomes a signal that goes high and stays high
// the moment the answer is wrong.
//
// Every invariant here is one that a real UART has violated in the field:
//
//   R1  a frame is started while one is already in flight
//   R2  a frame is not exactly MAXBITS bit-times long
//   R3  the line is not MARK while the transmitter is idle
//   R4  busy is asserted and never drops
//   R5  a configuration write lands mid-frame     (Chapter 18.3's escape)
//
// The flags are STICKY. A violation that happens once, a million cycles into a
// regression, must still be visible at the end of the run -- a level that
// pulses and clears is a violation nobody sees.
// ---------------------------------------------------------------------------
module uart_review_probe #(
    parameter int MAXBITS = 10,          // start + 8 data + stop
    parameter int TIMEOUT = 4000         // clocks a frame may take before it hangs
)(
    input  logic      clk,
    input  logic      rst_n,

    input  logic      busy_i,        // the transmitter is mid-frame
    input  logic      line_i,        // the serial output
    input  logic      start_i,       // a frame was requested
    input  logic      bit_tick_i,    // one pulse per transmitted bit
    input  logic      cfg_wr_i,      // software wrote a configuration register

    output logic      v_overlap_o,   // R1
    output logic      v_framelen_o,  // R2
    output logic      v_idle_o,      // R3
    output logic      v_hang_o,      // R4
    output logic      v_cfg_o,       // R5

    output logic [7:0] n_frames_o,
    output logic [3:0] last_len_o,    // bits in the most recent frame
    output logic      any_violation_o
);

    logic      busy_q;
    logic [3:0]  bit_cnt;
    logic [15:0] age;

    wire frame_start = busy_i && !busy_q;
    wire frame_end   = !busy_i && busy_q;

    assign any_violation_o = v_overlap_o | v_framelen_o | v_idle_o |
                             v_hang_o    | v_cfg_o;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            busy_q       <= 1'b0;
            bit_cnt      <= 4'd0;
            age          <= 16'd0;
            v_overlap_o  <= 1'b0;
            v_framelen_o <= 1'b0;
            v_idle_o     <= 1'b0;
            v_hang_o     <= 1'b0;
            v_cfg_o      <= 1'b0;
            n_frames_o   <= 8'd0;
            last_len_o   <= 4'd0;
        end else begin
            busy_q <= busy_i;

            // ---- R1: no frame may begin while one is in flight ----------
            if (start_i && busy_i && !frame_start) v_overlap_o <= 1'b1;

            // ---- R3: an idle transmitter must hold the line at MARK -----
            // Checked only when settled, not on the clock busy drops, to avoid
            // flagging the one-cycle handover at the end of the stop bit.
            if (!busy_i && !busy_q && !line_i) v_idle_o <= 1'b1;

            // ---- R5: configuration must not change under a live frame ---
            if (cfg_wr_i && busy_i) v_cfg_o <= 1'b1;

            // ---- R2 / R4: frame length and liveness ---------------------
            if (frame_start) begin
                bit_cnt <= 4'd0;
                age     <= 16'd0;
            end else if (busy_i) begin
                if (bit_tick_i && bit_cnt != 4'd15) bit_cnt <= bit_cnt + 4'd1;
                if (age != 16'hFFFF)                age     <= age + 16'd1;
                if (age >= TIMEOUT[15:0])           v_hang_o <= 1'b1;
            end

            if (frame_end) begin
                last_len_o <= bit_cnt;
                n_frames_o <= n_frames_o + 8'd1;
                if (bit_cnt != MAXBITS[3:0]) v_framelen_o <= 1'b1;
            end
        end
    end

endmodule

VHDL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- ---------------------------------------------------------------------------
-- uart_review_probe -- the questions a reviewer asks, written down as logic.
--
-- A design review that produces a list of concerns in a document produces
-- nothing that can fail in regression. This block is the same list expressed
-- as checkers, so each question becomes a signal that goes high and stays high
-- the moment the answer is wrong.
--
-- Every invariant here is one that a real UART has violated in the field:
--
--   R1  a frame is started while one is already in flight
--   R2  a frame is not exactly MAXBITS bit-times long
--   R3  the line is not MARK while the transmitter is idle
--   R4  busy is asserted and never drops
--   R5  a configuration write lands mid-frame     (Chapter 18.3's escape)
--
-- The flags are STICKY. A violation that happens once, a million cycles into a
-- regression, must still be visible at the end of the run -- a level that
-- pulses and clears is a violation nobody sees.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity uart_review_probe is
    generic (
        MAXBITS : natural := 10;               -- start + 8 data + stop
        TIMEOUT : natural := 4000              -- clocks before a frame hangs
    );
    port (
        clk             : in  std_logic;
        rst_n           : in  std_logic;

        busy_i          : in  std_logic;       -- the transmitter is mid-frame
        line_i          : in  std_logic;       -- the serial output
        start_i         : in  std_logic;       -- a frame was requested
        bit_tick_i      : in  std_logic;       -- one pulse per transmitted bit
        cfg_wr_i        : in  std_logic;       -- a configuration register write

        v_overlap_o     : out std_logic;       -- R1
        v_framelen_o    : out std_logic;       -- R2
        v_idle_o        : out std_logic;       -- R3
        v_hang_o        : out std_logic;       -- R4
        v_cfg_o         : out std_logic;       -- R5

        n_frames_o      : out unsigned(7 downto 0);
        last_len_o      : out unsigned(3 downto 0);
        any_violation_o : out std_logic
    );
end entity uart_review_probe;

architecture rtl of uart_review_probe is

    signal busy_q  : std_logic := '0';
    signal bit_cnt : unsigned(3 downto 0)  := (others => '0');
    signal age     : unsigned(15 downto 0) := (others => '0');

    signal frame_start, frame_end : std_logic;

    -- outputs mirrored internally: an entity may not read its own outputs
    signal v_overlap, v_framelen, v_idle, v_hang, v_cfg : std_logic := '0';
    signal n_frames : unsigned(7 downto 0) := (others => '0');
    signal last_len : unsigned(3 downto 0) := (others => '0');

begin

    frame_start <= '1' when (busy_i = '1' and busy_q = '0') else '0';
    frame_end   <= '1' when (busy_i = '0' and busy_q = '1') else '0';

    v_overlap_o     <= v_overlap;
    v_framelen_o    <= v_framelen;
    v_idle_o        <= v_idle;
    v_hang_o        <= v_hang;
    v_cfg_o         <= v_cfg;
    n_frames_o      <= n_frames;
    last_len_o      <= last_len;
    any_violation_o <= v_overlap or v_framelen or v_idle or v_hang or v_cfg;

    process (clk, rst_n)
    begin
        if rst_n = '0' then
            busy_q     <= '0';
            bit_cnt    <= (others => '0');
            age        <= (others => '0');
            v_overlap  <= '0';
            v_framelen <= '0';
            v_idle     <= '0';
            v_hang     <= '0';
            v_cfg      <= '0';
            n_frames   <= (others => '0');
            last_len   <= (others => '0');
        elsif rising_edge(clk) then
            busy_q <= busy_i;

            -- ---- R1: no frame may begin while one is in flight ----------
            if start_i = '1' and busy_i = '1' and frame_start = '0' then
                v_overlap <= '1';
            end if;

            -- ---- R3: an idle transmitter must hold the line at MARK -----
            -- Checked only when settled, not on the clock busy drops, to avoid
            -- flagging the one-cycle handover at the end of the stop bit.
            if busy_i = '0' and busy_q = '0' and line_i = '0' then
                v_idle <= '1';
            end if;

            -- ---- R5: configuration must not change under a live frame ---
            if cfg_wr_i = '1' and busy_i = '1' then
                v_cfg <= '1';
            end if;

            -- ---- R2 / R4: frame length and liveness ---------------------
            if frame_start = '1' then
                bit_cnt <= (others => '0');
                age     <= (others => '0');
            elsif busy_i = '1' then
                if bit_tick_i = '1' and bit_cnt /= to_unsigned(15, 4) then
                    bit_cnt <= bit_cnt + 1;
                end if;
                if age /= x"FFFF" then
                    age <= age + 1;
                end if;
                if age >= to_unsigned(TIMEOUT, 16) then
                    v_hang <= '1';
                end if;
            end if;

            if frame_end = '1' then
                last_len <= bit_cnt;
                n_frames <= n_frames + 1;
                if bit_cnt /= to_unsigned(MAXBITS, 4) then
                    v_framelen <= '1';
                end if;
            end if;
        end if;
    end process;

end architecture rtl;

Two things about this block are worth defending, because both were confirmed by mutation testing.

The flags are sticky. A violation that occurs once, a million cycles into a regression, must still be readable at the end of the run. A level that pulses and clears is a violation nobody sees — and the aggregate any_violation_o gives a regression script a single bit to fail on.

The idle check has a settling guard. !busy_i && !busy_q && !line_i tolerates exactly one clock between busy dropping and the line returning to MARK. A transmitter that hands over that way is legal, and a checker that flagged it would fire on correct designs — which is the fastest possible route to a checker being switched off. Mutation M11 removes the guard and the handover test catches it.

3. The Same Questions in PSL

The probe answers R4 — does every frame finish? — with a timeout counter, because that is what synthesisable logic can do. The property it is approximating is a liveness property, and liveness is what an assertion language exists to express.

On this toolchain, VHDL with PSL is the path where concurrent assertions genuinely execute:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity tb_uart_psl_review is
    generic (
        INJECT : natural := 0        -- 0 = behave; 1..3 select a violation
    );
end entity tb_uart_psl_review;

architecture psl of tb_uart_psl_review is

    constant TCLK    : time    := 10 ns;
    constant BITW    : natural := 4;
    constant MAXBITS : natural := 10;

    signal clk      : std_logic := '0';
    signal rst_n    : std_logic := '0';
    signal busy     : std_logic := '0';
    signal busy_q   : std_logic := '0';
    signal line     : std_logic := '1';
    signal start    : std_logic := '0';
    signal cfg_wr   : std_logic := '0';
    signal sim_done : boolean   := false;

    signal frame_start : std_logic;

begin

    clk <= '0' when sim_done else not clk after TCLK/2;

    frame_start <= '1' when (busy = '1' and busy_q = '0') else '0';

    delay : process (clk)
    begin
        if rising_edge(clk) then
            busy_q <= busy;
        end if;
    end process;

    -- =======================================================================
    -- The review invariants, as PSL properties.
    --
    -- These are the SAME questions the synthesisable probe answers, written in
    -- the form a formal tool or a simulator with assertion support can check
    -- directly -- no counters, no sticky flags, no testbench scaffolding.
    -- =======================================================================

    -- R1: a frame may not be requested while one is already in flight
    -- psl default clock is rising_edge(clk);
    -- psl r1_no_overlap : assert always
    --     (rst_n = '1' and start = '1' and busy = '1') -> (frame_start = '1')
    --     report "R1 violated: frame started while busy";

    -- R3: an idle transmitter holds the line at MARK
    -- psl r3_idle_mark : assert always
    --     (rst_n = '1' and busy = '0' and busy_q = '0') -> (line = '1')
    --     report "R3 violated: line is not MARK while idle";

    -- R5: configuration must not change under a live frame  (Chapter 18.3)
    -- psl r5_cfg_quiet : assert always
    --     (rst_n = '1' and cfg_wr = '1') -> (busy = '0')
    --     report "R5 violated: configuration written mid-frame";

    -- R4: every frame that starts must finish -- bounded liveness. This is the
    -- property a synthesisable checker can only approximate with a timeout
    -- counter, and the one that justifies having an assertion language at all.
    -- psl r4_frame_completes : assert always
    --     (rst_n = '1' and frame_start = '1') ->
    --     next_e[1 to 4*MAXBITS*BITW] (busy = '0')
    --     report "R4 violated: a frame never completed";

    stim : process
        procedure emit_frame (nbits : integer) is
        begin
            wait until falling_edge(clk);
            start <= '1'; busy <= '1';
            wait until rising_edge(clk);
            wait until falling_edge(clk); start <= '0';
            for b in 0 to nbits-1 loop
                if b = 0 then line <= '0';
                elsif b = nbits-1 then line <= '1';
                elsif (b mod 2) = 1 then line <= '1';
                else line <= '0';
                end if;
                for c in 0 to BITW-1 loop wait until rising_edge(clk); end loop;
            end loop;
            line <= '1';
            busy <= '0';
            wait until rising_edge(clk);
            for i in 0 to 3 loop wait until rising_edge(clk); end loop;
        end procedure;
    begin
        rst_n <= '0';
        for i in 0 to 3 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;

        -- well-formed traffic: every property must hold throughout
        for i in 0 to 3 loop emit_frame(MAXBITS); end loop;

        case INJECT is
            when 1 =>
                -- violate R1: request a frame while one is in flight
                report "injecting R1 violation (start while busy)";
                wait until falling_edge(clk); busy <= '1'; line <= '0';
                for i in 0 to 2 loop wait until rising_edge(clk); end loop;
                wait until falling_edge(clk); start <= '1';
                wait until rising_edge(clk);
                wait until falling_edge(clk); start <= '0'; busy <= '0'; line <= '1';
                for i in 0 to 3 loop wait until rising_edge(clk); end loop;

            when 2 =>
                -- violate R3: leave the line at SPACE while idle
                report "injecting R3 violation (idle at SPACE)";
                wait until falling_edge(clk); line <= '0';
                for i in 0 to 5 loop wait until rising_edge(clk); end loop;
                wait until falling_edge(clk); line <= '1';
                for i in 0 to 3 loop wait until rising_edge(clk); end loop;

            when 3 =>
                -- violate R5: write configuration mid-frame
                report "injecting R5 violation (config write mid-frame)";
                wait until falling_edge(clk); busy <= '1'; line <= '0';
                for i in 0 to 2 loop wait until rising_edge(clk); end loop;
                wait until falling_edge(clk); cfg_wr <= '1';
                wait until rising_edge(clk);
                wait until falling_edge(clk); cfg_wr <= '0';
                for i in 0 to 2 loop wait until rising_edge(clk); end loop;
                wait until falling_edge(clk); busy <= '0'; line <= '1';
                for i in 0 to 3 loop wait until rising_edge(clk); end loop;

            when others =>
                report "no violation injected -- all properties must hold";
        end case;

        for i in 0 to 20 loop wait until rising_edge(clk); end loop;
        report "PSL run complete (INJECT=" & integer'image(INJECT) & ")";
        sim_done <= true;
        wait;
    end process;

end architecture psl;

Run against well-formed traffic, all four properties hold and the simulator says nothing. Each injected violation is caught, with the property's own message, at the cycle it occurs:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  run                          result
  --------------------------   ---------------------------------------------
  INJECT=0  (behave)           no violation injected -- all properties hold
  INJECT=1  (start while busy) Error 1935ns: R1 violated: frame started while busy
  INJECT=2  (idle at SPACE)    Error 1905ns: R3 violated: line is not MARK while idle
  INJECT=3  (config mid-frame) Error 1935ns: R5 violated: configuration written mid-frame

r4_frame_completes is the property the synthesisable probe cannot express directly:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  next_e[1 to 4*MAXBITS*BITW] (busy = '0')

That reads: within this many cycles of a frame starting, busy must have gone low at least once. It is bounded liveness, stated once, with no counter, no state, and no reset logic to get wrong.

4. The Checklist

What follows is the reviewer's list. The five executable invariants appear in it, marked, alongside the questions that still need a human answer.

RTL

#QuestionWhy it exposes a weak designReference
1Is the RX input synchronised before any logic reads it?a raw asynchronous pin into an FSM is a metastability path that passes simulation12.2
2Is the start bit qualified, or is any falling edge a frame?an unqualified edge detector turns every glitch into a frame and a lost real one17.4
3R1 — can a frame start while one is in flight?overlapping frames corrupt bothexecutable
4R3 — is the line at MARK when idle?an idle-SPACE output is a permanent break to the far endexecutable
5Does the FIFO accept a push when full if a pop occurs on the same clock?getting this wrong drops bytes there was room for17.5
6Do counters saturate or wrap?a wrapping run counter reports a small number, which is worse than a large one17.1
7Is the reset strategy consistent across the domains?mixed sync and async reset release is a classic first-frame corrupter12.4

Timing and configuration

#QuestionWhy it mattersReference
8What is the measured baud error, and how much of the 5.26% budget does it use?the budget is shared with the far end, and both ends drift17.2
9R5 — can a configuration write land mid-frame?this is a real silicon escape, not a hypotheticalexecutable · 18.3
10R2 — is every frame exactly the configured length?a frame one bit short is a framing error at the far end and nothing at this oneexecutable
11Is the TX output registered at the pad?otherwise the bit period picks up routing delay18.1

Integration

#QuestionWhy it mattersReference
12Which interrupt sources are read-to-clear, and is that documented at the register?the console hang of 18.2 is a driver reading one register twice18.2
13How many bytes can the remote send after flow-off is asserted?the threshold is a deadline, not a preference17.5
14R4 — does every frame that starts finish?a transmitter that hangs holds the console foreverexecutable
15What does the driver do on overrun — and is that policy in hardware or software?policy in hardware is policy nobody can change13.3

Verification

#QuestionWhy it mattersReference
16Which requirements describe normal usage rather than legal usage?those migrate into testbench constraints and shrink the verified space silently18.3
17What state can the hardware reach that the testbench constraints forbid?the gap between those sets is where escapes live18.3
18Has the suite been shown to fail?a suite that has never failed has not been tested15.3
19Do the test vectors include a non-palindromic byte?0x00, 0xFF and 0xA5 cannot detect a reversed byte17.3
20Are there negative tests — cases that must FAIL?a suite that only asserts success cannot find a window that is too wide15.5

Debug and bring-up

#QuestionWhy it mattersReference
21Is there a counter for edges seen but not accepted?it separates a dead transmitter from a deaf receiver in one register read17.4
22Is there a FIFO high-water mark?an error counter is lagging; a high-water mark is leading17.5
23Are drops classified by cause, or just counted?in-flight and ignored losses have different fixes17.5
24Does the self-test report whether it could have failed?a palindrome-only pattern set passes a broken board18.1
25Can the capture buffer hold enough pre-trigger history for the fault you suspect?a short pre-window shows the consequence, never the cause17.6

5. The Testbench

A checker that never fires has proved nothing. Each test drives exactly one violation and asserts that its flag — and only its flag — goes high.

Verilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
// ---------------------------------------------------------------------------
// Testbench for uart_review_probe.
//
// A checker that never fires has proved nothing. Each test here drives ONE
// violation and asserts that its flag -- and only its flag -- goes high, then
// that the flag STAYS high for the rest of the run. A sticky violation that
// clears itself is worse than no checker at all, because it reports clean.
//
// The transmitter is synthesised in the testbench rather than instantiated, so
// each malformation can be produced exactly and in isolation. That is the
// point of a review probe: it must work against a design it did not come with.
// ---------------------------------------------------------------------------
module tb_uart_review_probe;

    localparam MAXBITS = 10;
    localparam TIMEOUT = 400;
    localparam BITW    = 8;          // clocks per bit in this model

    reg clk = 1'b0;
    reg rst_n = 1'b0;
    reg busy = 1'b0, line = 1'b1, start = 1'b0, bit_tick = 1'b0, cfg_wr = 1'b0;

    wire v_overlap, v_framelen, v_idle, v_hang, v_cfg, any_v;
    wire [7:0] n_frames;
    wire [3:0] last_len;

    integer checks = 0;
    integer fails  = 0;

    always #5 clk = ~clk;

    uart_review_probe #(.MAXBITS(MAXBITS), .TIMEOUT(TIMEOUT)) dut (
        .clk(clk), .rst_n(rst_n),
        .busy_i(busy), .line_i(line), .start_i(start),
        .bit_tick_i(bit_tick), .cfg_wr_i(cfg_wr),
        .v_overlap_o(v_overlap), .v_framelen_o(v_framelen), .v_idle_o(v_idle),
        .v_hang_o(v_hang), .v_cfg_o(v_cfg),
        .n_frames_o(n_frames), .last_len_o(last_len),
        .any_violation_o(any_v));

    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 do_reset;
        begin
            busy=0; line=1; start=0; bit_tick=0; cfg_wr=0; rst_n=0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            repeat (2) @(posedge clk);
        end
    endtask

    // Emit one frame of `nbits` bit-times. A well-formed frame is MAXBITS.
    task emit_frame;
        input integer nbits;
        integer b, c;
        begin
            @(negedge clk); start = 1'b1; busy = 1'b1;
            @(posedge clk);
            @(negedge clk); start = 1'b0;
            for (b = 0; b < nbits; b = b + 1) begin
                line = (b == 0) ? 1'b0 : ((b == nbits-1) ? 1'b1 : (b[0]));
                for (c = 0; c < BITW-1; c = c + 1) @(posedge clk);
                @(negedge clk); bit_tick = 1'b1;
                @(posedge clk);
                @(negedge clk); bit_tick = 1'b0;
            end
            line = 1'b1;
            busy = 1'b0;
            @(posedge clk);
            repeat (4) @(posedge clk);
        end
    endtask

    integer i;

    initial begin
        // ---------------- T1: well-formed traffic, nothing fires ----------
        do_reset;
        for (i = 0; i < 4; i = i + 1) emit_frame(MAXBITS);
        #1;
        $display("T1 four clean frames     : frames=%0d last_len=%0d any=%0b",
                 n_frames, last_len, any_v);
        chk("T1 four frames counted",  n_frames, 4);
        chk("T1 each was ten bits",    last_len, MAXBITS);
        chk("T1 no violation at all",  any_v,    0);

        // ---------------- T2: a frame started inside a frame --------------
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); start = 1'b1;              // request while busy
        @(posedge clk);
        @(negedge clk); start = 1'b0; busy = 1'b0; line = 1'b1;
        repeat (2) @(posedge clk); #1;
        $display("T2 overlapping start     : v_overlap=%0b", v_overlap);
        chk("T2 overlap detected",        v_overlap,  1);
        chk("T2 and nothing else fired",  v_cfg | v_idle | v_hang, 0);

        // ---------------- T3: a short frame -------------------------------
        do_reset;
        emit_frame(8);                             // two bits missing
        #1;
        $display("T3 eight-bit frame       : v_framelen=%0b last_len=%0d",
                 v_framelen, last_len);
        chk("T3 wrong frame length detected", v_framelen, 1);
        chk("T3 the length is reported",      last_len,   8);

        // ---------------- T4: a long frame too ----------------------------
        do_reset;
        emit_frame(12);
        #1;
        chk("T4 a long frame is also wrong", v_framelen, 1);
        chk("T4 the length is reported",     last_len,   12);

        // ---------------- T5: the line left at SPACE while idle -----------
        do_reset;
        @(negedge clk); line = 1'b0;               // idle at SPACE -- a break
        repeat (6) @(posedge clk); #1;
        $display("T5 idle at SPACE         : v_idle=%0b", v_idle);
        chk("T5 idle-level violation detected", v_idle, 1);

        // ---------------- T6: busy asserted and never released ------------
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (TIMEOUT + 20) @(posedge clk); #1;
        $display("T6 busy stuck            : v_hang=%0b", v_hang);
        chk("T6 hang detected", v_hang, 1);

        // ---------------- T7: a config write under a live frame -----------
        // This is the Chapter 18.3 escape, as a review question.
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); cfg_wr = 1'b1;
        @(posedge clk);
        @(negedge clk); cfg_wr = 1'b0; busy = 1'b0; line = 1'b1;
        repeat (2) @(posedge clk); #1;
        $display("T7 config write mid-frame: v_cfg=%0b", v_cfg);
        chk("T7 mid-frame config write detected", v_cfg, 1);

        // ---------------- T8: a config write while idle is legal ----------
        do_reset;
        @(negedge clk); cfg_wr = 1'b1;
        @(posedge clk);
        @(negedge clk); cfg_wr = 1'b0;
        repeat (2) @(posedge clk); #1;
        chk("T8 an idle config write is fine", v_cfg, 0);
        chk("T8 nothing else fired either",    any_v, 0);

        // ---------------- T9: violations are STICKY -----------------------
        // One fault, a long time ago, must still be visible at the end.
        do_reset;
        emit_frame(8);                             // violate once
        #1;
        chk("T9 the flag is up", v_framelen, 1);
        for (i = 0; i < 6; i = i + 1) emit_frame(MAXBITS);   // then behave
        #1;
        $display("T9 six clean frames later: v_framelen=%0b any=%0b frames=%0d",
                 v_framelen, any_v, n_frames);
        chk("T9 the flag is STILL up",        v_framelen, 1);
        chk("T9 the aggregate is still up",   any_v,      1);
        chk("T9 and the clean frames counted", n_frames,  7);

        // ---------------- T10: the one-cycle handover is legal ------------
        // A transmitter may drop busy one clock before it drives the line back
        // to MARK. That single cycle is a handover, not an idle violation, and
        // the `busy_q` term in the idle check is what tolerates it. Flagging it
        // would make the probe fire on correct designs, which is the fastest
        // way to get a checker switched off.
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); busy = 1'b0;             // busy drops, line still SPACE
        @(posedge clk);
        @(negedge clk); line = 1'b1;             // MARK one cycle later
        repeat (3) @(posedge clk); #1;
        $display("T10 one-cycle handover   : v_idle=%0b  (must be 0)", v_idle);
        chk("T10 the handover is tolerated", v_idle, 0);

        // But SPACE that persists past the handover is a real violation.
        @(negedge clk); line = 1'b0;
        repeat (3) @(posedge clk); #1;
        $display("T10 sustained idle SPACE : v_idle=%0b  (must be 1)", v_idle);
        chk("T10 a sustained idle SPACE still fires", v_idle, 1);

        $display("");
        $display("== %0d checks, %0d failures ==", checks, fails);
        if (fails == 0) $display("   RESULT: ALL VERILOG REVIEW-PROBE TESTS PASSED");
        else            $display("   RESULT: %0d FAILURE(S)", fails);
        $finish;
    end

endmodule

SystemVerilog

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`timescale 1ns/1ps
// ---------------------------------------------------------------------------
// Testbench for uart_review_probe.
//
// A checker that never fires has proved nothing. Each test here drives ONE
// violation and asserts that its flag -- and only its flag -- goes high, then
// that the flag STAYS high for the rest of the run. A sticky violation that
// clears itself is worse than no checker at all, because it reports clean.
//
// The transmitter is synthesised in the testbench rather than instantiated, so
// each malformation can be produced exactly and in isolation. That is the
// point of a review probe: it must work against a design it did not come with.
// ---------------------------------------------------------------------------
module tb_uart_review_probe;

    localparam MAXBITS = 10;
    localparam TIMEOUT = 400;
    localparam BITW    = 8;          // clocks per bit in this model

    logic clk = 1'b0;
    logic rst_n = 1'b0;
    logic busy = 1'b0, line = 1'b1, start = 1'b0, bit_tick = 1'b0, cfg_wr = 1'b0;

    logic v_overlap, v_framelen, v_idle, v_hang, v_cfg, any_v;
    logic [7:0] n_frames;
    logic [3:0] last_len;

    integer checks = 0;
    integer fails  = 0;

    always #5 clk = ~clk;

    uart_review_probe #(.MAXBITS(MAXBITS), .TIMEOUT(TIMEOUT)) dut (
        .clk(clk), .rst_n(rst_n),
        .busy_i(busy), .line_i(line), .start_i(start),
        .bit_tick_i(bit_tick), .cfg_wr_i(cfg_wr),
        .v_overlap_o(v_overlap), .v_framelen_o(v_framelen), .v_idle_o(v_idle),
        .v_hang_o(v_hang), .v_cfg_o(v_cfg),
        .n_frames_o(n_frames), .last_len_o(last_len),
        .any_violation_o(any_v));

    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 do_reset();
        begin
            busy=0; line=1; start=0; bit_tick=0; cfg_wr=0; rst_n=0;
            repeat (3) @(posedge clk);
            @(negedge clk); rst_n = 1'b1;
            repeat (2) @(posedge clk);
        end
    endtask

    // Emit one frame of `nbits` bit-times. A well-formed frame is MAXBITS.
    task automatic emit_frame(input int nbits);
        int b, c;
        begin
            @(negedge clk); start = 1'b1; busy = 1'b1;
            @(posedge clk);
            @(negedge clk); start = 1'b0;
            for (b = 0; b < nbits; b = b + 1) begin
                line = (b == 0) ? 1'b0 : ((b == nbits-1) ? 1'b1 : (b[0]));
                for (c = 0; c < BITW-1; c = c + 1) @(posedge clk);
                @(negedge clk); bit_tick = 1'b1;
                @(posedge clk);
                @(negedge clk); bit_tick = 1'b0;
            end
            line = 1'b1;
            busy = 1'b0;
            @(posedge clk);
            repeat (4) @(posedge clk);
        end
    endtask

    integer i;

    initial begin
        // ---------------- T1: well-formed traffic, nothing fires ----------
        do_reset;
        for (i = 0; i < 4; i = i + 1) emit_frame(MAXBITS);
        #1;
        $display("T1 four clean frames     : frames=%0d last_len=%0d any=%0b",
                 n_frames, last_len, any_v);
        chk("T1 four frames counted",  n_frames, 4);
        chk("T1 each was ten bits",    last_len, MAXBITS);
        chk("T1 no violation at all",  any_v,    0);

        // ---------------- T2: a frame started inside a frame --------------
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); start = 1'b1;              // request while busy
        @(posedge clk);
        @(negedge clk); start = 1'b0; busy = 1'b0; line = 1'b1;
        repeat (2) @(posedge clk); #1;
        $display("T2 overlapping start     : v_overlap=%0b", v_overlap);
        chk("T2 overlap detected",        v_overlap,  1);
        chk("T2 and nothing else fired",  v_cfg | v_idle | v_hang, 0);

        // ---------------- T3: a short frame -------------------------------
        do_reset;
        emit_frame(8);                             // two bits missing
        #1;
        $display("T3 eight-bit frame       : v_framelen=%0b last_len=%0d",
                 v_framelen, last_len);
        chk("T3 wrong frame length detected", v_framelen, 1);
        chk("T3 the length is reported",      last_len,   8);

        // ---------------- T4: a long frame too ----------------------------
        do_reset;
        emit_frame(12);
        #1;
        chk("T4 a long frame is also wrong", v_framelen, 1);
        chk("T4 the length is reported",     last_len,   12);

        // ---------------- T5: the line left at SPACE while idle -----------
        do_reset;
        @(negedge clk); line = 1'b0;               // idle at SPACE -- a break
        repeat (6) @(posedge clk); #1;
        $display("T5 idle at SPACE         : v_idle=%0b", v_idle);
        chk("T5 idle-level violation detected", v_idle, 1);

        // ---------------- T6: busy asserted and never released ------------
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (TIMEOUT + 20) @(posedge clk); #1;
        $display("T6 busy stuck            : v_hang=%0b", v_hang);
        chk("T6 hang detected", v_hang, 1);

        // ---------------- T7: a config write under a live frame -----------
        // This is the Chapter 18.3 escape, as a review question.
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); cfg_wr = 1'b1;
        @(posedge clk);
        @(negedge clk); cfg_wr = 1'b0; busy = 1'b0; line = 1'b1;
        repeat (2) @(posedge clk); #1;
        $display("T7 config write mid-frame: v_cfg=%0b", v_cfg);
        chk("T7 mid-frame config write detected", v_cfg, 1);

        // ---------------- T8: a config write while idle is legal ----------
        do_reset;
        @(negedge clk); cfg_wr = 1'b1;
        @(posedge clk);
        @(negedge clk); cfg_wr = 1'b0;
        repeat (2) @(posedge clk); #1;
        chk("T8 an idle config write is fine", v_cfg, 0);
        chk("T8 nothing else fired either",    any_v, 0);

        // ---------------- T9: violations are STICKY -----------------------
        // One fault, a long time ago, must still be visible at the end.
        do_reset;
        emit_frame(8);                             // violate once
        #1;
        chk("T9 the flag is up", v_framelen, 1);
        for (i = 0; i < 6; i = i + 1) emit_frame(MAXBITS);   // then behave
        #1;
        $display("T9 six clean frames later: v_framelen=%0b any=%0b frames=%0d",
                 v_framelen, any_v, n_frames);
        chk("T9 the flag is STILL up",        v_framelen, 1);
        chk("T9 the aggregate is still up",   any_v,      1);
        chk("T9 and the clean frames counted", n_frames,  7);

        // ---------------- T10: the one-cycle handover is legal ------------
        // A transmitter may drop busy one clock before it drives the line back
        // to MARK. That single cycle is a handover, not an idle violation, and
        // the `busy_q` term in the idle check is what tolerates it. Flagging it
        // would make the probe fire on correct designs, which is the fastest
        // way to get a checker switched off.
        do_reset;
        @(negedge clk); busy = 1'b1; line = 1'b0;
        repeat (3) @(posedge clk);
        @(negedge clk); busy = 1'b0;             // busy drops, line still SPACE
        @(posedge clk);
        @(negedge clk); line = 1'b1;             // MARK one cycle later
        repeat (3) @(posedge clk); #1;
        $display("T10 one-cycle handover   : v_idle=%0b  (must be 0)", v_idle);
        chk("T10 the handover is tolerated", v_idle, 0);

        // But SPACE that persists past the handover is a real violation.
        @(negedge clk); line = 1'b0;
        repeat (3) @(posedge clk); #1;
        $display("T10 sustained idle SPACE : v_idle=%0b  (must be 1)", v_idle);
        chk("T10 a sustained idle SPACE still fires", v_idle, 1);

        $display("");
        $display("== %0d checks, %0d failures ==", checks, fails);
        if (fails == 0) $display("   RESULT: ALL SYSTEMVERILOG REVIEW-PROBE TESTS PASSED");
        else            $display("   RESULT: %0d FAILURE(S)", fails);
        $finish;
    end

endmodule

VHDL

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
-- ---------------------------------------------------------------------------
-- Testbench for uart_review_probe.
--
-- A checker that never fires has proved nothing. Each test here drives ONE
-- violation and asserts that its flag -- and only its flag -- goes high, then
-- that the flag STAYS high for the rest of the run. A sticky violation that
-- clears itself is worse than no checker at all, because it reports clean.
--
-- The transmitter is synthesised in the testbench rather than instantiated, so
-- each malformation can be produced exactly and in isolation. That is the
-- point of a review probe: it must work against a design it did not come with.
-- ---------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity tb_uart_review_probe is
end entity tb_uart_review_probe;

architecture sim of tb_uart_review_probe is

    constant MAXBITS : natural := 10;
    constant TIMEOUT : natural := 400;
    constant BITW    : natural := 8;           -- clocks per bit in this model
    constant TCLK    : time    := 10 ns;

    signal clk      : std_logic := '0';
    signal rst_n    : std_logic := '0';
    signal busy     : std_logic := '0';
    signal line     : std_logic := '1';
    signal start    : std_logic := '0';
    signal bit_tick : std_logic := '0';
    signal cfg_wr   : std_logic := '0';
    signal sim_done : boolean   := false;

    signal v_overlap, v_framelen, v_idle, v_hang, v_cfg, any_v : std_logic;
    signal n_frames : unsigned(7 downto 0);
    signal last_len : unsigned(3 downto 0);

begin

    clk <= '0' when sim_done else not clk after TCLK/2;

    dut : entity work.uart_review_probe
        generic map (MAXBITS => MAXBITS, TIMEOUT => TIMEOUT)
        port map (clk => clk, rst_n => rst_n,
                  busy_i => busy, line_i => line, start_i => start,
                  bit_tick_i => bit_tick, cfg_wr_i => cfg_wr,
                  v_overlap_o => v_overlap, v_framelen_o => v_framelen,
                  v_idle_o => v_idle, v_hang_o => v_hang, v_cfg_o => v_cfg,
                  n_frames_o => n_frames, last_len_o => last_len,
                  any_violation_o => any_v);

    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 do_reset is
        begin
            busy <= '0'; line <= '1'; start <= '0'; bit_tick <= '0';
            cfg_wr <= '0'; 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;
        end procedure;

        -- Emit one frame of `nbits` bit-times. A well-formed frame is MAXBITS.
        procedure emit_frame (nbits : integer) is
        begin
            wait until falling_edge(clk);
            start <= '1'; busy <= '1';
            wait until rising_edge(clk);
            wait until falling_edge(clk); start <= '0';
            for b in 0 to nbits-1 loop
                if b = 0 then
                    line <= '0';
                elsif b = nbits-1 then
                    line <= '1';
                elsif (b mod 2) = 1 then
                    line <= '1';
                else
                    line <= '0';
                end if;
                for c in 0 to BITW-2 loop wait until rising_edge(clk); end loop;
                wait until falling_edge(clk); bit_tick <= '1';
                wait until rising_edge(clk);
                wait until falling_edge(clk); bit_tick <= '0';
            end loop;
            line <= '1';
            busy <= '0';
            wait until rising_edge(clk);
            for i in 0 to 3 loop wait until rising_edge(clk); end loop;
        end procedure;

    begin
        -- ---------------- T1: well-formed traffic, nothing fires ----------
        do_reset;
        for i in 0 to 3 loop emit_frame(MAXBITS); end loop;
        wait for 1 ns;
        report "T1 four clean frames     : frames=" &
               integer'image(to_integer(n_frames)) & " last_len=" &
               integer'image(to_integer(last_len)) & " any=" &
               std_logic'image(any_v)(2);
        chk("T1 four frames counted", to_integer(n_frames), 4);
        chk("T1 each was ten bits",   to_integer(last_len), MAXBITS);
        chk("T1 no violation at all", to_integer(unsigned'("" & any_v)), 0);

        -- ---------------- T2: a frame started inside a frame --------------
        do_reset;
        wait until falling_edge(clk); busy <= '1'; line <= '0';
        for i in 0 to 2 loop wait until rising_edge(clk); end loop;
        wait until falling_edge(clk); start <= '1';       -- request while busy
        wait until rising_edge(clk);
        wait until falling_edge(clk); start <= '0'; busy <= '0'; line <= '1';
        for i in 0 to 1 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T2 overlapping start     : v_overlap=" & std_logic'image(v_overlap)(2);
        chk("T2 overlap detected", to_integer(unsigned'("" & v_overlap)), 1);
        chk("T2 and nothing else fired",
            to_integer(unsigned'("" & (v_cfg or v_idle or v_hang))), 0);

        -- ---------------- T3: a short frame -------------------------------
        do_reset;
        emit_frame(8);                                    -- two bits missing
        wait for 1 ns;
        report "T3 eight-bit frame       : v_framelen=" &
               std_logic'image(v_framelen)(2) & " last_len=" &
               integer'image(to_integer(last_len));
        chk("T3 wrong frame length detected",
            to_integer(unsigned'("" & v_framelen)), 1);
        chk("T3 the length is reported", to_integer(last_len), 8);

        -- ---------------- T4: a long frame too ----------------------------
        do_reset;
        emit_frame(12);
        wait for 1 ns;
        chk("T4 a long frame is also wrong",
            to_integer(unsigned'("" & v_framelen)), 1);
        chk("T4 the length is reported", to_integer(last_len), 12);

        -- ---------------- T5: the line left at SPACE while idle -----------
        do_reset;
        wait until falling_edge(clk); line <= '0';        -- idle at SPACE
        for i in 0 to 5 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T5 idle at SPACE         : v_idle=" & std_logic'image(v_idle)(2);
        chk("T5 idle-level violation detected",
            to_integer(unsigned'("" & v_idle)), 1);

        -- ---------------- T6: busy asserted and never released ------------
        do_reset;
        wait until falling_edge(clk); busy <= '1'; line <= '0';
        for i in 0 to TIMEOUT + 19 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T6 busy stuck            : v_hang=" & std_logic'image(v_hang)(2);
        chk("T6 hang detected", to_integer(unsigned'("" & v_hang)), 1);

        -- ---------------- T7: a config write under a live frame -----------
        -- This is the Chapter 18.3 escape, as a review question.
        do_reset;
        wait until falling_edge(clk); busy <= '1'; line <= '0';
        for i in 0 to 2 loop wait until rising_edge(clk); end loop;
        wait until falling_edge(clk); cfg_wr <= '1';
        wait until rising_edge(clk);
        wait until falling_edge(clk); cfg_wr <= '0'; busy <= '0'; line <= '1';
        for i in 0 to 1 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T7 config write mid-frame: v_cfg=" & std_logic'image(v_cfg)(2);
        chk("T7 mid-frame config write detected",
            to_integer(unsigned'("" & v_cfg)), 1);

        -- ---------------- T8: a config write while idle is legal ----------
        do_reset;
        wait until falling_edge(clk); cfg_wr <= '1';
        wait until rising_edge(clk);
        wait until falling_edge(clk); cfg_wr <= '0';
        for i in 0 to 1 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        chk("T8 an idle config write is fine", to_integer(unsigned'("" & v_cfg)), 0);
        chk("T8 nothing else fired either",    to_integer(unsigned'("" & any_v)), 0);

        -- ---------------- T9: violations are STICKY -----------------------
        -- One fault, a long time ago, must still be visible at the end.
        do_reset;
        emit_frame(8);                                    -- violate once
        wait for 1 ns;
        chk("T9 the flag is up", to_integer(unsigned'("" & v_framelen)), 1);
        for i in 0 to 5 loop emit_frame(MAXBITS); end loop;   -- then behave
        wait for 1 ns;
        report "T9 six clean frames later: v_framelen=" &
               std_logic'image(v_framelen)(2) & " any=" &
               std_logic'image(any_v)(2) & " frames=" &
               integer'image(to_integer(n_frames));
        chk("T9 the flag is STILL up",  to_integer(unsigned'("" & v_framelen)), 1);
        chk("T9 the aggregate is still up", to_integer(unsigned'("" & any_v)), 1);
        chk("T9 and the clean frames counted", to_integer(n_frames), 7);

        -- ---------------- T10: the one-cycle handover is legal ------------
        -- A transmitter may drop busy one clock before it drives the line back
        -- to MARK. That single cycle is a handover, not an idle violation, and
        -- the `busy_q` term in the idle check is what tolerates it. Flagging it
        -- would make the probe fire on correct designs, which is the fastest
        -- way to get a checker switched off.
        do_reset;
        wait until falling_edge(clk); busy <= '1'; line <= '0';
        for i in 0 to 2 loop wait until rising_edge(clk); end loop;
        wait until falling_edge(clk); busy <= '0';   -- busy drops, line SPACE
        wait until rising_edge(clk);
        wait until falling_edge(clk); line <= '1';   -- MARK one cycle later
        for i in 0 to 2 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T10 one-cycle handover   : v_idle=" & std_logic'image(v_idle)(2) &
               "  (must be 0)";
        chk("T10 the handover is tolerated", to_integer(unsigned'("" & v_idle)), 0);

        -- But SPACE that persists past the handover is a real violation.
        wait until falling_edge(clk); line <= '0';
        for i in 0 to 2 loop wait until rising_edge(clk); end loop;
        wait for 1 ns;
        report "T10 sustained idle SPACE : " & std_logic'image(v_idle)(2) &
               "  (must be 1)";
        chk("T10 a sustained idle SPACE still fires",
            to_integer(unsigned'("" & v_idle)), 1);

        report "";
        report "== " & integer'image(checks) & " checks, " &
               integer'image(fails) & " failures ==";
        if fails = 0 then
            report "   RESULT: ALL VHDL REVIEW-PROBE TESTS PASSED";
        else
            report "   RESULT: " & integer'image(fails) & " FAILURE(S)" severity error;
        end if;
        sim_done <= true;
        wait;
    end process;

end architecture sim;

Twenty checks per language. T9 and T10 are the two that came from mutation testing: T9 asserts that a violation raised once survives six subsequent clean frames, and T10 asserts that the legal one-cycle busy-to-MARK handover is not flagged, which is the guard that stops the probe firing on correct designs.

6. Proving the Tests Can Fail

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  mutation                                               checks failed   verdict
  ----------------------------------------------------   -------------   -------
  M10  make the frame-length flag non-sticky                         2    killed
  M11  remove the settling guard on the idle check                   1    killed
  M12  drop the exemption for a legitimate frame start               1    killed
  M13  push the hang timeout beyond any realistic run                1    killed
  M14  catch only short frames, not long ones                        1    killed

M12 and M14 are the two that would be easiest to write by accident. M12 makes every legitimate frame start report an overlap, so the probe fires constantly on a correct design. M14 uses < instead of !=, which catches short frames and silently passes long ones — and a frame one bit too long is exactly what a stop-bit configuration error produces.

7. Using This as Self-Assessment

Read the checklist as questions about a design you have written, and treat "I would have to go and look" as a different answer from "yes".

The items worth being uncomfortable about, in rough order:

  • 18 — has the suite been shown to fail? If no test has ever failed, the suite's ability to detect anything is unmeasured. Mutation testing is the cheap version of this, and every chapter in this module reports its results because that is the only thing that makes a passing suite meaningful.
  • 16 and 17 — what does the testbench forbid? The constraints are usually undocumented, which means the verified space is undocumented.
  • 19 — are the test vectors capable of failing? A suite of 0x00, 0xFF and 0xA5 is the single most common example of stimulus that cannot detect the fault it appears to test for.
  • 21 to 24 — is there anything to read when it goes wrong? Debug registers are added after the first hard bug, which is one bug too late.

8. Where This Curriculum Ends

Eighteen modules ago this began with a framing-and-timing engine that serialises bytes onto one wire. The protocol has not got any larger since — it is still a start bit, some data bits, an optional parity bit and a stop bit.

What has changed is the amount that can be said about it with evidence. The baud tolerance is not a rule of thumb but an arithmetic consequence of the stop bit being sampled 9.5 bit periods after the edge. The glitch filter's rejection width is not "narrow pulses" but DIV/2 + 1 clocks, measured by sweep. 0xA5 is not a good test byte, and the reason is checkable in one line.

That is the pattern worth carrying to the next protocol: the questions that matter are the ones that can be answered with a measurement, and the measurements worth making are the ones that could have come out the other way.

Continue learning

Where this fits

Part of the UART curriculum.