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:
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
// ---------------------------------------------------------------------------
// 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
endmoduleSystemVerilog
// ---------------------------------------------------------------------------
// 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
endmoduleVHDL
-- ---------------------------------------------------------------------------
-- 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:
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:
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-framer4_frame_completes is the property the synthesisable probe cannot express directly:
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
| # | Question | Why it exposes a weak design | Reference |
|---|---|---|---|
| 1 | Is the RX input synchronised before any logic reads it? | a raw asynchronous pin into an FSM is a metastability path that passes simulation | 12.2 |
| 2 | Is 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 one | 17.4 |
| 3 | R1 — can a frame start while one is in flight? | overlapping frames corrupt both | executable |
| 4 | R3 — is the line at MARK when idle? | an idle-SPACE output is a permanent break to the far end | executable |
| 5 | Does the FIFO accept a push when full if a pop occurs on the same clock? | getting this wrong drops bytes there was room for | 17.5 |
| 6 | Do counters saturate or wrap? | a wrapping run counter reports a small number, which is worse than a large one | 17.1 |
| 7 | Is the reset strategy consistent across the domains? | mixed sync and async reset release is a classic first-frame corrupter | 12.4 |
Timing and configuration
| # | Question | Why it matters | Reference |
|---|---|---|---|
| 8 | What 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 drift | 17.2 |
| 9 | R5 — can a configuration write land mid-frame? | this is a real silicon escape, not a hypothetical | executable · 18.3 |
| 10 | R2 — is every frame exactly the configured length? | a frame one bit short is a framing error at the far end and nothing at this one | executable |
| 11 | Is the TX output registered at the pad? | otherwise the bit period picks up routing delay | 18.1 |
Integration
| # | Question | Why it matters | Reference |
|---|---|---|---|
| 12 | Which 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 twice | 18.2 |
| 13 | How many bytes can the remote send after flow-off is asserted? | the threshold is a deadline, not a preference | 17.5 |
| 14 | R4 — does every frame that starts finish? | a transmitter that hangs holds the console forever | executable |
| 15 | What does the driver do on overrun — and is that policy in hardware or software? | policy in hardware is policy nobody can change | 13.3 |
Verification
| # | Question | Why it matters | Reference |
|---|---|---|---|
| 16 | Which requirements describe normal usage rather than legal usage? | those migrate into testbench constraints and shrink the verified space silently | 18.3 |
| 17 | What state can the hardware reach that the testbench constraints forbid? | the gap between those sets is where escapes live | 18.3 |
| 18 | Has the suite been shown to fail? | a suite that has never failed has not been tested | 15.3 |
| 19 | Do the test vectors include a non-palindromic byte? | 0x00, 0xFF and 0xA5 cannot detect a reversed byte | 17.3 |
| 20 | Are there negative tests — cases that must FAIL? | a suite that only asserts success cannot find a window that is too wide | 15.5 |
Debug and bring-up
| # | Question | Why it matters | Reference |
|---|---|---|---|
| 21 | Is there a counter for edges seen but not accepted? | it separates a dead transmitter from a deaf receiver in one register read | 17.4 |
| 22 | Is there a FIFO high-water mark? | an error counter is lagging; a high-water mark is leading | 17.5 |
| 23 | Are drops classified by cause, or just counted? | in-flight and ignored losses have different fixes | 17.5 |
| 24 | Does the self-test report whether it could have failed? | a palindrome-only pattern set passes a broken board | 18.1 |
| 25 | Can the capture buffer hold enough pre-trigger history for the fault you suspect? | a short pre-window shows the consequence, never the cause | 17.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
`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
endmoduleSystemVerilog
`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
endmoduleVHDL
-- ---------------------------------------------------------------------------
-- 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
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 killedM12 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,0xFFand0xA5is 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
Related tutorials
- Related topic
Writing UART Assertions for TX, RX and FIFOs
The transmitter and FIFO property checkers written in three languages and bound to published designs, the sampling pitfalls that make an assertion argue with a correct design, and what each toolchain will actually run.
- Related topic
Case Study: Verifying and Debugging a Production UART IP
A UART taken to sign-off with full functional and code coverage, the silicon escape that followed, the malformed frame measured off the wire in three HDLs, and the missing coverage axis that explains both.
- Related topic
USB Assertions
“Eventually” has no failing case, so it cannot be checked in a finite run — every real liveness check is bounded, a window has two edges, and an obligation still outstanding at end of test is a failure, not an unknown.
- Related topic
Complete RX RTL Architecture
One synthesizable receiver assembled from the module's five preceding chapters — assumptions stated first, walked block by block with the invariant each maintains, then reviewed the way a reviewer would, including the defects found during its own development.
Where this fits
Part of the UART curriculum.
