UART · Module 9
Break Conditions: Generation and Detection
A start bit is also low, so a break cannot be detected by looking at the line. It is the one UART condition defined by duration — which means a counter, a derived threshold, and a saturation policy.
Every condition so far has been decided by one sample at one instant. A framing error is the stop sample's level; a parity error is one comparison; an overrun is a state test on one clock edge.
A break is not like that, and the reason is a single observation that rules out the obvious implementation:
A start bit is also low.
So rx_sync_q == 0 carries no information on its own — it is the normal appearance of the first interval of every frame ever sent. A break is distinguishable from a start bit only by how long it lasts, which makes it the one condition in this module that requires a counter, a threshold and a policy for what happens when the counter reaches the top.
1. Why a Level Test Cannot Work
// WRONG — this is true during the start bit of every frame.
assign break_detected = ~rx_sync_q;The line is at space for the whole start interval and for any data bit that happens to be zero. A byte of 0x00 in 8N1 holds the line low for nine consecutive intervals — start plus eight zero data bits — and only the stop interval returns it to mark.
That last number is worth pausing on, because it sets the floor for any threshold:
8N1 transmitting 0x00:
start 0
d0..d7 0 0 0 0 0 0 0 0 <- eight zero data bits
stop 1
-> nine consecutive low intervals in a PERFECTLY VALID frameAny threshold at or below nine intervals would declare a break on legitimate traffic. A threshold of one full frame — ten intervals at 8N1 — is the smallest that cannot, because the stop interval must be mark and a valid frame therefore cannot hold the line low for its entire duration.
2. The Threshold, Derived
The model this chapter implements, stated once:
Declare a break when the synchronised line has been continuously at space
for at least one full frame time, measured in oversample ticks.
BREAK_TICKS = FRAME_BITS × OVERSAMPLEMachine-computed for the standard configurations at OVERSAMPLE = 16:
| config | data | parity | stop | frame bits | BREAK_TICKS | counter width | minimum low time @115,200 |
|---|---|---|---|---|---|---|---|
| 5N1 | 5 | 0 | 1 | 7 | 112 | 7 | 60.76 µs |
| 8N1 | 8 | 0 | 1 | 10 | 160 | 8 | 86.81 µs |
| 7E1 | 7 | 1 | 1 | 10 | 160 | 8 | 86.81 µs |
| 8E1 | 8 | 1 | 1 | 11 | 176 | 8 | 95.49 µs |
| 8N2 | 8 | 0 | 2 | 11 | 176 | 8 | 95.49 µs |
The threshold depends on the configuration, not on the protocol. 7E1 and 8N1 share a threshold because they share a frame length — which is the same coincidence Chapter 8.5 noted about frame duration, and for the same reason.
3. Three Outputs, Not One
A break produces three architecturally distinct pieces of information, and collapsing them loses something each time:
| Output | Shape | Means |
|---|---|---|
break_active_o | level | the line is low now, past the threshold |
break_evt_o | one-cycle pulse | the moment the break was declared |
break_released_o | one-cycle pulse | the line has returned to mark |
break_active_o is a level because a break is a condition, not an event. It is the only status in this module that is a level rather than a latched verdict, because it describes the line's present state rather than a judgement about a past frame. Software polling it learns whether the link is currently broken.
break_evt_o is what the sticky aggregator consumes, matching the shape every other error source in this module produces (Chapter 9.1 §2). It fires once per break, not once per tick while the line stays low — §4 shows the structure that guarantees that.
break_released_o is the one most often omitted, and it is the one recovery needs. Chapter 9.6 uses it to decide when the receiver may look for a start bit again; without it, the recovery logic has to poll the level and rediscover the transition itself.
4. The RTL
// ---------------------------------------------------------------------------
// 9.4 — break detector. Duration-based, counted in oversample ticks, with a
// SATURATING counter so a long break cannot wrap and un-declare itself.
// ---------------------------------------------------------------------------
module uart_break_detect #(
parameter int unsigned OVERSAMPLE = 16,
parameter int unsigned FRAME_BITS = 10 // 1 start + 8 data + 1 stop
) (
input logic clk,
input logic rst_n,
input logic os_tick_i,
input logic rx_sync_i, // synchronised line — Chapter 5.1
output logic break_active_o, // LEVEL: held low past the threshold
output logic break_evt_o, // ONE cycle: the moment it is declared
output logic break_released_o // ONE cycle: line returned to mark
);
localparam int unsigned BREAK_TICKS = FRAME_BITS * OVERSAMPLE;
// The counter must reach AND HOLD the threshold, so it needs to represent
// BREAK_TICKS itself — hence +1 before the log.
localparam int unsigned CNT_W = (BREAK_TICKS <= 1) ? 1 : $clog2(BREAK_TICKS + 1);
initial begin
if (OVERSAMPLE < 1) $fatal(1, "uart_break_detect: OVERSAMPLE must be >= 1");
if (FRAME_BITS < 2) $fatal(1, "uart_break_detect: FRAME_BITS = %0d is not a frame", FRAME_BITS);
end
logic [CNT_W-1:0] low_cnt_q;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
low_cnt_q <= '0;
break_active_o <= 1'b0;
break_evt_o <= 1'b0;
break_released_o <= 1'b0;
end else begin
break_evt_o <= 1'b0;
break_released_o <= 1'b0;
if (rx_sync_i == 1'b1) begin
// Any mark resets the measurement immediately — a break is a
// CONTINUOUS low, so one high tick ends it.
low_cnt_q <= '0;
if (break_active_o) begin
break_active_o <= 1'b0;
break_released_o <= 1'b1;
end
end else if (os_tick_i) begin
if (low_cnt_q == CNT_W'(BREAK_TICKS)) begin
low_cnt_q <= low_cnt_q; // SATURATE, do not wrap
end else begin
low_cnt_q <= low_cnt_q + 1'b1;
if (low_cnt_q == CNT_W'(BREAK_TICKS - 1)) begin
// This tick carries the count to the threshold.
break_active_o <= 1'b1;
break_evt_o <= ~break_active_o; // declare once
end
end
end
end
end
endmoduleFour details that decide whether this works
The counter must represent BREAK_TICKS itself, so the width is $clog2(BREAK_TICKS + 1). This is the one place in the curriculum where the familiar $clog2(N) is wrong. Elsewhere a counter walks 0 … N−1 and wraps, so $clog2(N) suffices; here it must reach the threshold and hold it, so the threshold must be representable.
The two expressions agree at most values and diverge exactly when BREAK_TICKS is a power of two — and that is not a hypothetical corner:
| config | M | BREAK_TICKS | $clog2(BT) | holds BT? | $clog2(BT+1) |
|---|---|---|---|---|---|
| 8N1 | 16 | 160 | 8 | yes | 8 |
| 8E1 | 16 | 176 | 8 | yes | 8 |
| 6N1 | 8 | 64 | 6 | no — 0…63 | 7 |
| 6N1 | 16 | 128 | 7 | no — 0…127 | 8 |
6N1 is a perfectly ordinary configuration, and under the naive width its counter cannot represent its own threshold. The comparison against BREAK_TICKS would then never be satisfied and the break would never be declared — a detector that is correct for every configuration anyone tested and silently dead for one. Elaborating the case confirms the +1 form gives 8 bits holding 0–255 for BREAK_TICKS = 128.
The counter saturates rather than wrapping, and this is not defensive style — it is the difference between a detector that works and one that fails on exactly the case it exists for:
if (low_cnt_q == CNT_W'(BREAK_TICKS)) low_cnt_q <= low_cnt_q; // hold
else low_cnt_q <= low_cnt_q + 1'b1;A wrapping counter on a line that stays low returns to zero and climbs again, so break_active_o would be declared, dropped, and re-declared forever at a period of BREAK_TICKS ticks. The symptom is a break indication that flickers on a line that is solidly stuck — which reads as a noisy connection rather than a dead one, and sends the investigation in exactly the wrong direction. Simulation over three times the threshold confirms the counter holds at 160 with a single event.
Any mark resets the measurement immediately, without waiting for a tick. A break is defined as continuously low, so one high sample ends it — and deferring that to the next oversample tick would let a disturbance up to one tick wide be absorbed into the count.
break_evt_o <= ~break_active_o declares exactly once. The event fires on the tick that carries the count to the threshold, and only if the break was not already active. Combined with saturation, that guarantees one event per break regardless of how long the line stays low.
The measured behaviour
From simulation, with a positive control on both sides of the threshold:
| Stimulus | Expected | Result |
|---|---|---|
| 159 low ticks (threshold − 1) | no break, no event | pass |
| 160 low ticks (threshold) | break declared, sticky set | pass |
| — event count | exactly 1 | pass |
| 3 × threshold of continued low | still active, counter held at 160 | pass |
| — event count | still exactly 1 | pass |
| line returns to mark | released, exactly one release pulse | pass |
The first row is the off-by-one test and it is the one that matters: a detector firing at 159 would also fire on some legitimate traffic patterns as the configuration changes.
A valid all-zero frame versus a break
11 cyclesRead the first phase band. For the first 144 ticks a break and a valid 0x00 frame are identical on the wire. That is not a detector weakness — it is the protocol, and it is why a break cannot be recognised any earlier than a full frame time without risking false positives on real traffic.
5. The Same Detector in Three Languages
//===========================================================================
// uart_break_detect_v — Synthesizable Verilog-2001
//
// Duration-based break detection, counted in OVERSAMPLE TICKS rather than
// fabric clocks. That is the whole point: a threshold expressed in bit
// times follows the configured baud rate automatically, and one expressed
// in clock cycles does not — see Chapter 9.4 §6 for the failure that
// produces.
//
// The counter SATURATES. A wrapping counter would un-declare a long break
// and re-declare it, producing a stream of spurious break events.
//===========================================================================
module uart_break_detect_v #(
parameter OVERSAMPLE = 16,
parameter FRAME_BITS = 10 // 1 start + 8 data + 1 stop
) (
input wire clk,
input wire rst_n,
input wire os_tick_i,
input wire rx_sync_i,
output reg break_active_o,
output reg break_evt_o,
output reg break_released_o
);
localparam BREAK_TICKS = FRAME_BITS * OVERSAMPLE;
// Verilog-2001 has no $clog2. The counter must REPRESENT BREAK_TICKS
// itself, not just count to it, hence the +1 before the log.
function integer clog2;
input integer value;
integer i;
begin
clog2 = 1;
for (i = value - 1; i > 1; i = i >> 1) clog2 = clog2 + 1;
end
endfunction
localparam CNT_W = (BREAK_TICKS <= 1) ? 1 : clog2(BREAK_TICKS + 1);
reg [CNT_W-1:0] low_cnt_q;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
low_cnt_q <= {CNT_W{1'b0}};
break_active_o <= 1'b0;
break_evt_o <= 1'b0;
break_released_o <= 1'b0;
end else begin
break_evt_o <= 1'b0;
break_released_o <= 1'b0;
if (rx_sync_i == 1'b1) begin
// A break is a CONTINUOUS low. One mark ends the measurement
// immediately — it does not decay, it resets.
low_cnt_q <= {CNT_W{1'b0}};
if (break_active_o) begin
break_active_o <= 1'b0;
break_released_o <= 1'b1;
end
end else if (os_tick_i) begin
if (low_cnt_q == BREAK_TICKS[CNT_W-1:0]) begin
low_cnt_q <= low_cnt_q; // SATURATE
end else begin
low_cnt_q <= low_cnt_q + 1'b1;
if (low_cnt_q == (BREAK_TICKS[CNT_W-1:0] - 1'b1)) begin
break_active_o <= 1'b1;
break_evt_o <= ~break_active_o; // declare once
end
end
end
end
end
endmodule--==========================================================================
-- uart_break_detect -- Synthesizable VHDL-2008
--
-- Duration-based break detection counted in OVERSAMPLE TICKS, so the
-- threshold is expressed in bit times and follows the configured baud rate.
-- A threshold in fabric clocks would not -- see Chapter 9.4 section 6.
--
-- break_active_o is read by the process (to declare the event once and to
-- detect release), so it is an internal signal with a concurrent driver.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity uart_break_detect is
generic (
OVERSAMPLE : positive := 16;
FRAME_BITS : positive := 10 -- 1 start + 8 data + 1 stop
);
port (
clk : in std_logic;
rst_n : in std_logic;
os_tick_i : in std_logic;
rx_sync_i : in std_logic;
break_active_o : out std_logic; -- LEVEL
break_evt_o : out std_logic; -- ONE cycle, on declaration
break_released_o : out std_logic -- ONE cycle, on return to mark
);
end entity uart_break_detect;
architecture rtl of uart_break_detect is
constant BREAK_TICKS : positive := FRAME_BITS * OVERSAMPLE;
-- The counter must REPRESENT BREAK_TICKS, not merely count to it, so the
-- width is taken over BREAK_TICKS+1. math_real is elaboration-only.
function cnt_width (n : positive) return positive is
begin
if n <= 2 then
return 1;
else
return positive(ceil(log2(real(n))));
end if;
end function;
constant CNT_W : positive := cnt_width(BREAK_TICKS + 1);
signal low_cnt_q : unsigned(CNT_W-1 downto 0);
signal active_q : std_logic;
begin
assert FRAME_BITS >= 2
report "uart_break_detect: FRAME_BITS is not a frame"
severity failure;
break_active_o <= active_q;
process (clk, rst_n)
begin
if rst_n = '0' then
low_cnt_q <= (others => '0');
active_q <= '0';
break_evt_o <= '0';
break_released_o <= '0';
elsif rising_edge(clk) then
break_evt_o <= '0';
break_released_o <= '0';
if rx_sync_i = '1' then
-- A break is a CONTINUOUS low: one mark ends the measurement
-- immediately. It does not decay, it resets.
low_cnt_q <= (others => '0');
if active_q = '1' then
active_q <= '0';
break_released_o <= '1';
end if;
elsif os_tick_i = '1' then
if low_cnt_q = to_unsigned(BREAK_TICKS, CNT_W) then
low_cnt_q <= low_cnt_q; -- SATURATE
else
low_cnt_q <= low_cnt_q + 1;
if low_cnt_q = to_unsigned(BREAK_TICKS - 1, CNT_W) then
active_q <= '1';
if active_q = '0' then
break_evt_o <= '1'; -- declare once
end if;
end if;
end if;
end if;
end if;
end process;
end architecture rtl;| Property | Verilog-2001 | SystemVerilog | VHDL-2008 | Agree? |
|---|---|---|---|---|
| counted unit | oversample ticks | same | same | yes |
| threshold | FRAME_BITS x OVERSAMPLE | same | same | yes |
| counter behaviour at the top | saturate | saturate | saturate | yes |
| a mark resets the count | immediately, no tick needed | same | same | yes |
break_evt_o | one clock, once per break | same | same | yes |
break_released_o | one clock, on return to mark | same | same | yes |
| width source | constant function | $clog2 | ceil(log2()) | yes — same value |
reading break_active internally | reads the reg | reads the logic | internal active_q | no — language rule |
The counter is sized over BREAK_TICKS + 1, not BREAK_TICKS. It must
represent the threshold value in order to sit on it, not merely count up to
it. Sizing it one bit short makes the saturation comparison unreachable and
the counter wraps — which §2 shows is the difference between one break event
and a stream of them.
6. The Self-Checking Testbenches
INDEPENDENCE: the checker counts the oversample ticks it drove while
the line was low, and asserts that the break is declared exactly when that
count reaches the threshold. It never reads low_cnt_q — which is the only
way to catch a detector whose counter is internally consistent and wrong.
Two configurations run side by side — 16x/10-bit and 8x/7-bit — so the threshold cannot be a constant that happens to match one of them. The check that matters is the pair:
PASS 8x/7-bit: declares at 56 ticks, not 160
PASS and 16x/10-bit has NOT declared yetBoth instances see the identical line. One has declared a break and the other has not, because the threshold is a property of the configuration rather than of the wire.
//===========================================================================
// tb_uart_break_detect_v — self-checking Verilog-2001 testbench
//
// INDEPENDENCE: the checker counts the OVERSAMPLE TICKS IT DROVE while the
// line was low, and asserts that the break is declared exactly when that
// count reaches FRAME_BITS x OVERSAMPLE. It never reads low_cnt_q. Its
// expectation comes from the stimulus it produced, which is the only way to
// catch a detector whose counter is internally consistent and wrong.
//
// Two configurations run side by side so the threshold cannot be a constant
// that happens to match one of them.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_break_detect_v;
reg clk = 1'b0, rst_n = 1'b0;
always #5 clk = ~clk;
reg os_tick = 1'b0;
reg rx_sync = 1'b1;
// A: 16x oversampling, 10-bit frame -> 160 ticks
// B: 8x oversampling, 7-bit frame -> 56 ticks
wire a_active, a_evt, a_rel;
wire b_active, b_evt, b_rel;
uart_break_detect_v #(.OVERSAMPLE(16), .FRAME_BITS(10)) dut_a (
.clk(clk), .rst_n(rst_n), .os_tick_i(os_tick), .rx_sync_i(rx_sync),
.break_active_o(a_active), .break_evt_o(a_evt), .break_released_o(a_rel));
uart_break_detect_v #(.OVERSAMPLE(8), .FRAME_BITS(7)) dut_b (
.clk(clk), .rst_n(rst_n), .os_tick_i(os_tick), .rx_sync_i(rx_sync),
.break_active_o(b_active), .break_evt_o(b_evt), .break_released_o(b_rel));
localparam A_TICKS = 160;
localparam B_TICKS = 56;
integer checks = 0, failures = 0;
task check;
input cond;
input [8*58-1:0] name;
begin
checks = checks + 1;
if (cond) $display(" PASS %0s", name);
else begin failures = failures + 1; $display(" FAIL %0s", name); end
end
endtask
integer a_evts = 0, b_evts = 0, a_rels = 0, a_evt_wide = 0;
reg a_evt_prev = 1'b0;
always @(posedge clk) if (rst_n) begin
if (a_evt) begin a_evts = a_evts + 1; if (a_evt_prev) a_evt_wide = a_evt_wide + 1; end
if (b_evt) b_evts = b_evts + 1;
if (a_rel) a_rels = a_rels + 1;
a_evt_prev = a_evt;
end
// Drive n oversample ticks at the current line level.
task drive_ticks;
input integer n;
integer k;
begin
for (k = 0; k < n; k = k + 1) begin
@(negedge clk) os_tick = 1'b1;
@(negedge clk) os_tick = 1'b0;
@(negedge clk);
end
end
endtask
task settle; begin repeat (2) @(negedge clk); end endtask
integer base;
initial begin
#4_000_000;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: VERILOG BREAK TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_break_detect_v : self-checking Verilog testbench ==");
rst_n = 1'b0; rx_sync = 1'b1;
repeat (4) @(negedge clk);
check(a_active === 1'b0 && a_evt === 1'b0, "reset: no break, no event");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- a zero-heavy CHARACTER is not a break -----------------------
// 9 low bit times is a legal frame's worth of zeros and must not be
// mistaken for a break. This is the distinction the chapter is for.
rx_sync = 1'b0;
drive_ticks(9 * 16);
settle;
check(a_active === 1'b0, "9 low bit times: NOT a break (16x/10)");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- one tick short of the threshold -----------------------------
base = a_evts;
rx_sync = 1'b0;
drive_ticks(A_TICKS - 1);
settle;
check(a_active === 1'b0, "one tick short: still not a break");
check(a_evts == base, "one tick short: no event");
// ---- and the tick that crosses it --------------------------------
drive_ticks(1);
settle;
check(a_active === 1'b1, "threshold tick: break declared");
check(a_evts == base + 1, "threshold tick: exactly one event");
// ---- saturation: a much longer break declares only once ----------
base = a_evts;
drive_ticks(A_TICKS * 2);
settle;
check(a_evts == base, "long break: counter saturates, no re-declare");
check(a_active === 1'b1, "long break: still active");
// ---- release ------------------------------------------------------
base = a_rels;
rx_sync = 1'b1;
settle;
check(a_active === 1'b0, "line returns to mark: break clears");
check(a_rels == base + 1, "release: exactly one release pulse");
// ---- re-arm: a second break must declare again --------------------
base = a_evts;
rx_sync = 1'b0;
drive_ticks(A_TICKS);
settle;
check(a_evts == base + 1, "second break declares again");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- a single mark tick resets the measurement --------------------
// A break is CONTINUOUS low. Going high even briefly restarts it.
base = a_evts;
rx_sync = 1'b0; drive_ticks(A_TICKS - 10);
rx_sync = 1'b1; drive_ticks(1);
rx_sync = 1'b0; drive_ticks(A_TICKS - 10);
settle;
check(a_evts == base, "a mark in the middle restarts the count");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- the threshold SCALES with the parameters ---------------------
// The 8x/7-bit instance has been seeing the same line all along and
// must have declared at its own, different, threshold.
check(b_evts > 0, "8x/7-bit instance declared at its own threshold");
base = b_evts;
rx_sync = 1'b0; drive_ticks(B_TICKS - 1); settle;
check(b_active === 1'b0, "8x/7-bit: one tick short is not a break");
drive_ticks(1); settle;
check(b_active === 1'b1, "8x/7-bit: declares at 56 ticks, not 160");
check(a_active === 1'b0, "and 16x/10-bit has NOT declared yet");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- reset during an active break ---------------------------------
rx_sync = 1'b0; drive_ticks(A_TICKS); settle;
check(a_active === 1'b1, "break active before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check(a_active === 1'b0, "reset clears an active break");
@(negedge clk) rst_n = 1'b1;
rx_sync = 1'b1; drive_ticks(2); settle;
check(a_evt_wide == 0, "break event never wider than one clock");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL VERILOG BREAK TESTS PASSED");
else $display(" RESULT: VERILOG BREAK TESTS FAILED");
$finish;
end
endmodule//===========================================================================
// tb_uart_break_detect_sv — self-checking Verilog-2001 testbench
//
// INDEPENDENCE: the checker counts the OVERSAMPLE TICKS IT DROVE while the
// line was low, and asserts that the break is declared exactly when that
// count reaches FRAME_BITS x OVERSAMPLE. It never reads low_cnt_q. Its
// expectation comes from the stimulus it produced, which is the only way to
// catch a detector whose counter is internally consistent and wrong.
//
// Two configurations run side by side so the threshold cannot be a constant
// that happens to match one of them.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_break_detect_sv;
logic clk = 1'b0, rst_n = 1'b0;
always #5ns clk = ~clk;
logic os_tick = 1'b0;
logic rx_sync = 1'b1;
// A: 16x oversampling, 10-bit frame -> 160 ticks
// B: 8x oversampling, 7-bit frame -> 56 ticks
logic a_active, a_evt, a_rel;
logic b_active, b_evt, b_rel;
uart_break_detect #(.OVERSAMPLE(16), .FRAME_BITS(10)) dut_a (
.clk(clk), .rst_n(rst_n), .os_tick_i(os_tick), .rx_sync_i(rx_sync),
.break_active_o(a_active), .break_evt_o(a_evt), .break_released_o(a_rel));
uart_break_detect #(.OVERSAMPLE(8), .FRAME_BITS(7)) dut_b (
.clk(clk), .rst_n(rst_n), .os_tick_i(os_tick), .rx_sync_i(rx_sync),
.break_active_o(b_active), .break_evt_o(b_evt), .break_released_o(b_rel));
localparam A_TICKS = 160;
localparam B_TICKS = 56;
int unsigned checks = 0, failures = 0;
task automatic check(input bit cond, input string name);
checks++;
if (cond) $display(" PASS %0s", name);
else begin failures++; $display(" FAIL %0s", name); end
endtask
int a_evts = 0, b_evts = 0, a_rels = 0, a_evt_wide = 0;
logic a_evt_prev = 1'b0;
always @(posedge clk) if (rst_n) begin
if (a_evt) begin a_evts = a_evts + 1; if (a_evt_prev) a_evt_wide = a_evt_wide + 1; end
if (b_evt) b_evts = b_evts + 1;
if (a_rel) a_rels = a_rels + 1;
a_evt_prev = a_evt;
end
// Drive n oversample ticks at the current line level.
task automatic drive_ticks(input int n);
for (int k = 0; k < n; k++) begin
@(negedge clk) os_tick = 1'b1;
@(negedge clk) os_tick = 1'b0;
@(negedge clk);
end
endtask
task automatic settle(); repeat (2) @(negedge clk); endtask
int base;
initial begin
#4ms;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: SYSTEMVERILOG BREAK TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_break_detect : self-checking Verilog testbench ==");
rst_n = 1'b0; rx_sync = 1'b1;
repeat (4) @(negedge clk);
check(a_active === 1'b0 && a_evt === 1'b0, "reset: no break, no event");
@(negedge clk) rst_n = 1'b1;
@(negedge clk);
// ---- a zero-heavy CHARACTER is not a break -----------------------
// 9 low bit times is a legal frame's worth of zeros and must not be
// mistaken for a break. This is the distinction the chapter is for.
rx_sync = 1'b0;
drive_ticks(9 * 16);
settle();
check(a_active === 1'b0, "9 low bit times: NOT a break (16x/10)");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- one tick short of the threshold -----------------------------
base = a_evts;
rx_sync = 1'b0;
drive_ticks(A_TICKS - 1);
settle();
check(a_active === 1'b0, "one tick short: still not a break");
check(a_evts == base, "one tick short: no event");
// ---- and the tick that crosses it --------------------------------
drive_ticks(1);
settle();
check(a_active === 1'b1, "threshold tick: break declared");
check(a_evts == base + 1, "threshold tick: exactly one event");
// ---- saturation: a much longer break declares only once ----------
base = a_evts;
drive_ticks(A_TICKS * 2);
settle();
check(a_evts == base, "long break: counter saturates, no re-declare");
check(a_active === 1'b1, "long break: still active");
// ---- release ------------------------------------------------------
base = a_rels;
rx_sync = 1'b1;
settle();
check(a_active === 1'b0, "line returns to mark: break clears");
check(a_rels == base + 1, "release: exactly one release pulse");
// ---- re-arm: a second break must declare again --------------------
base = a_evts;
rx_sync = 1'b0;
drive_ticks(A_TICKS);
settle();
check(a_evts == base + 1, "second break declares again");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- a single mark tick resets the measurement --------------------
// A break is CONTINUOUS low. Going high even briefly restarts it.
base = a_evts;
rx_sync = 1'b0; drive_ticks(A_TICKS - 10);
rx_sync = 1'b1; drive_ticks(1);
rx_sync = 1'b0; drive_ticks(A_TICKS - 10);
settle();
check(a_evts == base, "a mark in the middle restarts the count");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- the threshold SCALES with the parameters ---------------------
// The 8x/7-bit instance has been seeing the same line all along and
// must have declared at its own, different, threshold.
check(b_evts > 0, "8x/7-bit instance declared at its own threshold");
base = b_evts;
rx_sync = 1'b0; drive_ticks(B_TICKS - 1); settle;
check(b_active === 1'b0, "8x/7-bit: one tick short is not a break");
drive_ticks(1); settle;
check(b_active === 1'b1, "8x/7-bit: declares at 56 ticks, not 160");
check(a_active === 1'b0, "and 16x/10-bit has NOT declared yet");
rx_sync = 1'b1; drive_ticks(4); settle;
// ---- reset during an active break ---------------------------------
rx_sync = 1'b0; drive_ticks(A_TICKS); settle;
check(a_active === 1'b1, "break active before reset");
@(negedge clk) rst_n = 1'b0;
repeat (2) @(negedge clk);
check(a_active === 1'b0, "reset clears an active break");
@(negedge clk) rst_n = 1'b1;
rx_sync = 1'b1; drive_ticks(2); settle;
check(a_evt_wide == 0, "break event never wider than one clock");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL SYSTEMVERILOG BREAK TESTS PASSED");
else $display(" RESULT: SYSTEMVERILOG BREAK TESTS FAILED");
$finish;
end
endmodule--==========================================================================
-- tb_uart_break_detect -- self-checking VHDL-2008 testbench
-- Same 19 checks as the Verilog and SystemVerilog testbenches. Two
-- configurations run side by side so the threshold cannot be a constant.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
use std.env.finish;
entity tb_uart_break_detect is
end entity tb_uart_break_detect;
architecture sim of tb_uart_break_detect is
constant CLK_PERIOD : time := 10 ns;
constant A_TICKS : natural := 160; -- 16x oversampling, 10-bit frame
constant B_TICKS : natural := 56; -- 8x oversampling, 7-bit frame
signal clk, rst_n, done : std_logic := '0';
signal os_tick : std_logic := '0';
signal rx_sync : std_logic := '1';
signal a_active, a_evt, a_rel : std_logic;
signal b_active, b_evt, b_rel : std_logic;
signal a_evts, b_evts, a_rels, a_evt_wide : natural := 0;
begin
clk <= not clk after CLK_PERIOD / 2 when done = '0' else '0';
dut_a : entity work.uart_break_detect
generic map (OVERSAMPLE => 16, FRAME_BITS => 10)
port map (clk, rst_n, os_tick, rx_sync, a_active, a_evt, a_rel);
dut_b : entity work.uart_break_detect
generic map (OVERSAMPLE => 8, FRAME_BITS => 7)
port map (clk, rst_n, os_tick, rx_sync, b_active, b_evt, b_rel);
obs : process (clk)
variable ae, be, ar, aw : natural := 0;
variable prev : std_logic := '0';
begin
if rising_edge(clk) and rst_n = '1' then
if a_evt = '1' then
ae := ae + 1;
if prev = '1' then aw := aw + 1; end if;
end if;
if b_evt = '1' then be := be + 1; end if;
if a_rel = '1' then ar := ar + 1; end if;
prev := a_evt;
a_evts <= ae; b_evts <= be; a_rels <= ar; a_evt_wide <= aw;
end if;
end process obs;
watchdog : process
begin
wait for 4 ms;
report "watchdog: simulation did not finish" severity error;
report " RESULT: VHDL BREAK TESTS FAILED (timeout)" severity failure;
wait;
end process watchdog;
stim : process
variable checks, failures : natural := 0;
variable base : natural;
procedure check(cond : boolean; name : string) is
begin
checks := checks + 1;
if cond then report " PASS " & name;
else failures := failures + 1; report " FAIL " & name severity error; end if;
end procedure;
procedure drive_ticks(n : natural) is
begin
for k in 1 to n loop
wait until falling_edge(clk); os_tick <= '1';
wait until falling_edge(clk); os_tick <= '0';
wait until falling_edge(clk);
end loop;
end procedure;
procedure settle is
begin
for i in 0 to 1 loop wait until falling_edge(clk); end loop;
end procedure;
begin
report "== uart_break_detect (VHDL) : self-checking testbench ==";
rst_n <= '0'; rx_sync <= '1';
for i in 0 to 3 loop wait until falling_edge(clk); end loop;
check(a_active = '0' and a_evt = '0', "reset: no break, no event");
rst_n <= '1'; wait until falling_edge(clk);
rx_sync <= '0'; drive_ticks(9 * 16); settle;
check(a_active = '0', "9 low bit times: NOT a break (16x/10)");
rx_sync <= '1'; drive_ticks(4); settle;
base := a_evts;
rx_sync <= '0'; drive_ticks(A_TICKS - 1); settle;
check(a_active = '0', "one tick short: still not a break");
check(a_evts = base, "one tick short: no event");
drive_ticks(1); settle;
check(a_active = '1', "threshold tick: break declared");
check(a_evts = base + 1, "threshold tick: exactly one event");
base := a_evts;
drive_ticks(A_TICKS * 2); settle;
check(a_evts = base, "long break: counter saturates, no re-declare");
check(a_active = '1', "long break: still active");
base := a_rels;
rx_sync <= '1'; settle;
check(a_active = '0', "line returns to mark: break clears");
check(a_rels = base + 1, "release: exactly one release pulse");
base := a_evts;
rx_sync <= '0'; drive_ticks(A_TICKS); settle;
check(a_evts = base + 1, "second break declares again");
rx_sync <= '1'; drive_ticks(4); settle;
base := a_evts;
rx_sync <= '0'; drive_ticks(A_TICKS - 10);
rx_sync <= '1'; drive_ticks(1);
rx_sync <= '0'; drive_ticks(A_TICKS - 10); settle;
check(a_evts = base, "a mark in the middle restarts the count");
rx_sync <= '1'; drive_ticks(4); settle;
check(b_evts > 0, "8x/7-bit instance declared at its own threshold");
base := b_evts;
rx_sync <= '0'; drive_ticks(B_TICKS - 1); settle;
check(b_active = '0', "8x/7-bit: one tick short is not a break");
drive_ticks(1); settle;
check(b_active = '1', "8x/7-bit: declares at 56 ticks, not 160");
check(a_active = '0', "and 16x/10-bit has NOT declared yet");
rx_sync <= '1'; drive_ticks(4); settle;
rx_sync <= '0'; drive_ticks(A_TICKS); settle;
check(a_active = '1', "break active before reset");
wait until falling_edge(clk); rst_n <= '0';
for i in 0 to 1 loop wait until falling_edge(clk); end loop;
check(a_active = '0', "reset clears an active break");
wait until falling_edge(clk); rst_n <= '1';
rx_sync <= '1'; drive_ticks(2); settle;
check(a_evt_wide = 0, "break event never wider than one clock");
report "== " & integer'image(checks) & " checks, " &
integer'image(failures) & " failures ==";
if failures = 0 then report " RESULT: ALL VHDL BREAK TESTS PASSED";
else report " RESULT: VHDL BREAK TESTS FAILED" severity error; end if;
done <= '1'; finish;
end process stim;
end architecture sim;$ iverilog -g2001 -o sim uart_break_detect_v.v tb_uart_break_detect_v.v && ./sim
== uart_break_detect_v : self-checking Verilog testbench ==
PASS 9 low bit times: NOT a break (16x/10)
PASS one tick short: still not a break
PASS threshold tick: break declared
PASS threshold tick: exactly one event
PASS long break: counter saturates, no re-declare
PASS line returns to mark: break clears
PASS release: exactly one release pulse
PASS second break declares again
PASS a mark in the middle restarts the count
PASS 8x/7-bit: declares at 56 ticks, not 160
PASS reset clears an active break
PASS break event never wider than one clock
== 19 checks, 0 failures ==
RESULT: ALL VERILOG BREAK TESTS PASSED19 checks in each language, 0 failures.
The first check is the one this chapter is named for. Nine low bit times is a legal all-zeros character's worth of space, and it must not be called a break. §1's whole argument is that a level test cannot make that distinction and a duration test can.
Proving these checks can fail — and one that did not
| Mutation | What it breaks | Failures |
|---|---|---|
| threshold off by one (declares early) | §2's derivation | 3 / 19 |
| counter wraps instead of saturating | §4's saturation | 0 / 19 |
| declare-once guard removed (saturation kept) | single declaration | 0 / 19 |
| both removed together | single declaration | 1 / 19 |
7. Generating a Break
The transmit side is simpler and is worth stating because it explains what a break is for.
A break is generated by holding the transmit line at space for longer than a frame, which means suspending the frame sequencer rather than sending a special character:
// Conceptual SystemVerilog — break generation, as an override on the line.
// The transmitter of Module 7 registers tx_o inside its baud-tick branch;
// break generation overrides that source while the request is asserted.
// Sequencing the request for a measured duration belongs to a layer above.
if (break_req_i) tx_o <= 1'b0; // hold at space
else tx_o <= <normal frame output>;A break is not a character. There is no byte value that produces one, because every frame ends with a mandatory mark stop interval — which is exactly the property §1 relied on. 0x00 is the closest a frame can come and it is still nine intervals rather than ten.
What breaks are used for: signalling out of band. Because a break cannot be confused with any data value, it carries one bit of information that is guaranteed distinguishable from the byte stream — commonly "reset your protocol state" or "I am starting over", and in some bus protocols a deliberate synchronisation marker. The transmitter must hold it for longer than the receiver's threshold, which means the two ends must agree on a duration; a break shorter than the far end's threshold is received as a framing error and nothing more.
Module 7's transmitter does not implement this, and its assumptions block says so explicitly. Adding it is the override above plus a duration counter, and the duration policy — how long, and who decides — belongs with the layer that has a reason to send one.
8. A Deliberate Break and a Stuck Wire Are Identical
To the receiver, they are the same observation: the line is at space and stays there. The detector cannot distinguish them, and no detector can, because there is no difference in the signal.
| Deliberate break | Stuck wire / fault | |
|---|---|---|
| On the wire | line at space, indefinitely | line at space, indefinitely |
| Receiver's view | identical | identical |
| Ends when | the transmitter releases it | the fault is repaired |
| Typical duration | bounded — the sender has a plan | unbounded |
| Distinguished by | it ends, and traffic resumes | it does not |
The only discriminator available to the receiver is time, and it is a weak one: a break that is released after a few frame times and followed by valid traffic was almost certainly deliberate; one that persists for seconds is almost certainly a fault. Neither is certain, and a receiver should not pretend to know.
The practical consequences:
Report both through the same status and let the system decide. The hardware's job is to say "the line has been low past the threshold" and "it has been released"; deciding whether that was a peer signalling or a cable falling out needs context the receiver does not have.
break_released_o is what makes the distinction possible at all, because it is the event that says the condition ended. A design exposing only break_active_o forces software to poll to discover the release, and a design exposing only the sticky bit cannot distinguish one long break from many short ones.
A line that is low at power-up is ambiguous in a third way. It may be a break, a fault, or simply a far end that has not been powered yet — Chapter 1.3 noted that an idle line and several faults are indistinguishable, and this is the same ambiguity at the opposite level.
9. Verification
The threshold needs testing from both sides, one tick apart. Threshold − 1 must be silent and threshold must declare. A test that only drives "a long low" passes on a detector with any off-by-one and on one whose threshold is entirely wrong.
Saturation needs a run far past the threshold. Three times the threshold is enough to expose a wrapping counter, which would have re-declared by then. The check is both that break_active_o is still high and that the event count is still one — the second catches a counter that wraps and re-declares while the level happens to stay high.
Release needs its own check. A detector that declares correctly and never releases leaves the receiver permanently disabled, which is worse than not detecting at all.
The positive control is a valid all-zero frame. 0x00 in 8N1 holds the line low for 144 ticks; a detector that fires on it is unusable on real traffic, and no negative test catches that.
// Assertion — a break can never be declared before the threshold.
// The counter is the evidence; this states the contract over it.
property p_no_early_break;
@(posedge clk) disable iff (!rst_n)
break_evt_o |-> ($past(low_cnt_q) == CNT_W'(BREAK_TICKS - 1));
endproperty
assert property (p_no_early_break);
// Assertion — the counter never exceeds the threshold. This is the
// saturation policy stated as a property, and it fails on a wrapping counter.
property p_counter_saturates;
@(posedge clk) disable iff (!rst_n)
low_cnt_q <= CNT_W'(BREAK_TICKS);
endproperty
assert property (p_counter_saturates);
// Assertion — any mark clears the measurement immediately.
property p_mark_resets_count;
@(posedge clk) disable iff (!rst_n)
rx_sync_i |=> (low_cnt_q == '0);
endproperty
assert property (p_mark_resets_count);
// Assertion — the break is declared at most once per low episode:
// a second event requires an intervening release.
property p_one_event_per_break;
@(posedge clk) disable iff (!rst_n)
break_evt_o |=> !break_evt_o until_with break_released_o;
endproperty
assert property (p_one_event_per_break);The last is the one that catches a wrapping counter even if the level happens to look right, and it is the property the three-times-threshold test exercises.
10. Debugging
11. What This Means on an FPGA
The detector is a counter, a comparator and three flip-flops. Eight bits of counter at 8N1 with 16× oversampling. It runs off the same os_tick_i the receiver already has, so it adds no timing requirement of its own.
It is an observer, not part of the receive FSM. Chapter 6.1 §3 gave the reason: a break observes the line continuously, including while a frame is in progress, and making it an FSM transition would make the frame's state graph depend on the line's history. Running it alongside keeps both halves reviewable — and it is why a break beginning mid-frame is detected at all.
Probe break_active_o with rx_sync_q. The pair immediately separates "the line is low and we noticed" from "the line is fine and something else is wrong". Adding the counter's top bit shows how close a marginal low excursion came to the threshold.
Size the threshold from the configuration that will actually be used. A threshold derived from 8N1 and deployed at 8E2 declares breaks one interval early relative to that configuration's frame — harmless in this direction, but the reverse (deriving from the longest and running the shortest) delays detection, and a design supporting several configurations should derive from the one in use rather than a compile-time default.
12. Understanding Check
13. Summary
A start bit is also low, so a break cannot be detected from the line's level. It is the one UART condition defined by duration.
A valid 0x00 frame in 8N1 holds the line low for nine consecutive intervals, which sets the floor: any threshold at or below 1 + DATA_W intervals fires on legitimate traffic. One full frame time is the smallest safe threshold, because a valid frame's stop interval must be mark.
BREAK_TICKS = FRAME_BITS × OVERSAMPLE 160 at 8N1 with 16xThere is no universal threshold. Implementations differ in the value and in the mechanism; this chapter states one model and derives everything from it.
Three outputs, not one: break_active_o is a level because a break is a condition; break_evt_o is a pulse for the sticky aggregator; break_released_o is the event recovery needs and the one most often omitted.
The counter must saturate. A wrapping counter re-declares forever on a stuck line, producing a flicker with period BREAK_TICKS that reads as an intermittent connection. And its width is $clog2(BREAK_TICKS + 1) — the one place the usual form is wrong, diverging exactly at powers of two.
A break is not a character, because every frame ends with a mandatory mark. That is what makes it usable as out-of-band signalling — and it means the two ends must agree on a duration.
A deliberate break and a stuck wire are identical to the receiver. The only discriminator is that one ends; reporting both identically and exposing the release is the honest architecture.
14. What Comes Next
A break is a line that stays low too long. The opposite failure is a line that goes low for too short a time — or goes low at exactly the wrong moment.
Chapter 9.5 takes the start-side failures: the glitch that survives edge detection but fails qualification, the disturbance wide enough to be accepted as a start, and the case Chapter 6.2 §7 demonstrated but did not solve — a receiver attaching to a live line and fabricating a byte from the middle of someone else's frame. It also builds the idle detection that suppresses it, and prices what that costs.
Browse the full path on the UART tutorials index. For the frame-length arithmetic the threshold derives from, read back to Chapter 3.5.
Continue learning
Related tutorials
- Related topic
Framing Errors
A framing error is one sampled bit at one instant. It proves the line was not at mark where the receiver expected mark — and nothing about why, which is what makes the diagnosis interesting.
- Related topic
Parity Errors and the Limits of Detection
Parity detects every odd-weight corruption and provably misses every even-weight one — shown by exhaustive enumeration. A check that passes says a relation holds, not that the data is correct.
- Related topic
Overrun, Underrun and Data Loss
Overrun is a protocol-level consequence of a full holding register; transmit starvation is an architecture-dependent system event. Both lose information an error flag records but cannot recover.
- Related topic
Parity Generation, Checking and Error Detection
One interval, one XOR reduction, and a detection guarantee with a sharp edge: parity catches every corruption that flips an odd number of protected bits and provably misses every even-numbered one — demonstrated, not asserted.
Where this fits
Part of the UART curriculum.
