UART · Module 12
Reset Strategy and Reset During Active Traffic
Reset style and release, and what a reset asserted mid-frame actually leaves behind — measured, and worse than a framing error: well-formed frames carrying bytes nobody sent.
Reset looks like the simplest thing in a design and it is where a surprising number of bring-up failures live. Two questions matter, and they are unrelated to each other:
How does reset arrive and leave? That is a structural question with a standard answer, and §1–§3 give it.
What does reset leave behind on a link that was mid-transfer? That is a protocol question, it has no standard answer, and §4 measures it. The result is worse than the usual expectation: not a framing error but a perfectly well-formed frame carrying a byte that was never sent, with every status flag clear.
1. Assertion Is Easy; Release Is the Problem
| Assertion | Release | |
|---|---|---|
| Needs a clock? | no, if asynchronous | yes |
| Timing requirement | none — it is a forcing function | recovery and removal, exactly like setup and hold |
| Consequence of getting it wrong | nothing | flops leave reset on different edges |
| Where it shows up | — | one cycle after release, then never again |
The hazard is that release is a timing event. A reset that deasserts asynchronously, close to a clock edge, is captured by some flops on that edge and by others on the next. A state machine can then leave reset with its state register updated and its counter not, landing in a combination the designer never enumerated — and it happens once, at power-on, which is exactly when nobody is looking.
Asynchronous assertion is still wanted, because it works with no clock: at power-on, before a PLL locks, or when a clock has been gated away. The standard answer takes both — asynchronous assert, synchronous release.
2. The Reset Synchroniser
// Chapter 12.4 — asynchronous assertion, synchronous release.
// Reset asserts the instant the source asserts, with no clock required, and
// releases only on a clock edge — so every flop in this domain leaves reset
// on the SAME edge, which is the property recovery timing actually needs.
module uart_reset_sync #(
parameter int unsigned STAGES = 2
) (
input logic clk,
input logic arst_n_i, // asynchronous, from the board or a PLL lock
output logic rst_n_o // async assert, sync release, for this domain
);
if (STAGES < 2) begin : g_bad_stages
$error("uart_reset_sync: STAGES must be at least 2");
end
logic [STAGES-1:0] chain_q;
always_ff @(posedge clk or negedge arst_n_i) begin
if (!arst_n_i) chain_q <= '0; // ASYNC assert
else chain_q <= {chain_q[STAGES-2:0], 1'b1}; // SYNC release
end
assign rst_n_o = chain_q[STAGES-1];
endmoduleThe whole design is in the sensitivity list. negedge arst_n_i makes assertion asynchronous; the shift register means the release propagates one stage per clock, so rst_n_o rises only on a clock edge — and every flop reset by it therefore leaves reset on the same edge.
Both properties measured:
pass asserts with NO clock running at all
clock stopped, arst_n driven low -> rst_n_o = 0
pass does NOT release without a clock
pass still in reset after 1 clock (STAGES=2)
pass releases on the 2nd clock edge
release took exactly STAGES = 2 clock edges
== 5 checks, 0 failures ==The clock was stopped for the assertion test, which is the point: a design that needs a running clock to enter reset cannot be reset before its clock exists.
3. Reset in a Multi-Clock Design
One reset synchroniser per clock domain, all fed from the same asynchronous source:
uart_reset_sync u_rst_bus (.clk(bus_clk), .arst_n_i(arst_n), .rst_n_o(bus_rst_n));
uart_reset_sync u_rst_uart (.clk(uart_clk), .arst_n_i(arst_n), .rst_n_o(uart_rst_n));They assert together and release independently, each on its own clock — which is the correct behaviour and has a consequence worth stating: the two domains come out of reset at different times, by however many of the slower clock's periods the alignment demands.
For the asynchronous FIFO of Chapter 12.3 this matters. Its write side may leave reset while its read side is still held, and during that window the write side is pushing into a FIFO whose read pointer is pinned at zero. The structure survives it — the write side's full is computed from a read pointer it believes to be zero, which is the pessimistic direction — but the general lesson is that a design with two domains must be correct during the interval when only one of them is running.
4. Reset During a Frame
A serial link has no transaction boundary to abort at. The transmitter is holding a line at a level for a bit period; the receiver is counting sample ticks. Reset one of them and the other continues, because it is in a different chip and cannot know.
Setting up a real link — a transmitter and a receiver on separate reset synchronisers, exchanging real frames at 115,200 baud — and resetting one end 4.5 bit times into a frame:
The transmitter, reset mid-frame
-- 2. reset the TRANSMITTER 4.5 bit times into a frame
tx line just before reset = 1
tx line during reset = 1 (reset value is MARK)
[B] received fd frame_err=0
[B] got 1 byte(s), 0 with framing errorsThe far end received 0xFD with no framing error. The byte sent was 0xA5. Nothing anywhere reports a problem.
The arithmetic is exact and worth following, because it explains why this is the expected outcome rather than bad luck:
0xA5 = 1010_0101, transmitted LSB-first: 1 0 1 0 0 1 0 1
reset at 4.5 bit times = start bit + 3.5 data bits
-> data bits 0,1,2 were sent: 1 0 1
-> reset forces tx_o to MARK (idle): 1 1 1 1 1
receiver assembles: 1 0 1 1 1 1 1 1 = 0xFD
then sees MARK where the stop bit belongs -> stop bit VALID -> no errorThe receiver, reset mid-frame
Worse, and less obvious. Sweeping the moment reset is released, with 0xC3 in flight:
-- 4. reset the RECEIVER mid-frame; sweep the RELEASE instant
release +0.5 bit times -> B delivered 1 byte(s), last fe, fe=0
release +1.0 bit times -> B delivered 1 byte(s), last ff, fe=0
release +1.5 bit times -> B delivered 1 byte(s), last ff, fe=0
release +2.0 bit times -> B delivered 1 byte(s), last ff, fe=0
release +2.5 bit times -> B delivered 1 byte(s), last ff, fe=0
release +3.0 bit times -> B delivered 0 byte(s)The byte that was sent is never delivered. A different byte usually is — 0xFE or 0xFF depending on exactly when reset released — and always with no error flag.
The mechanism: the receiver comes out of reset in S_IDLE with no memory of being mid-frame, looks at the line, and finds it at SPACE — because a data bit happens to be zero. A data bit is indistinguishable from a start bit to a receiver that has just been reset, so it frames on it and assembles whatever follows into a byte. The value depends on the release instant, which is why the sweep produces different answers, and why no particular value is worth memorising: the point is that it is arbitrary and it is not flagged.
At +3.0 bit times the frame had already ended, so the receiver came back to a quiet line and correctly delivered nothing. That is the only benign case, and it is the one where reset missed the frame entirely.
0xA5 truncated into a valid 0xFD
12 cyclesBoth ends recover
-- 3. does the link recover?
[B] received 3c frame_err=0
pass next frame after TX reset is clean
-- 5. does the receiver recover?
[B] received 7e frame_err=0
pass next frame after RX reset is cleanThe damage is confined to the frame in flight. That is worth stating because it is the design working: Chapter 6.2's decision that a bad stop returns to S_IDLE rather than retrying is what makes the next frame clean, and a receiver that tried to resynchronise cleverly would still be confused two frames later.
7 checks, 0 failures across the whole reset suite.
5. What To Do About It
Nothing, inside the UART. Every option is worse than the problem:
| Idea | Why it fails |
|---|---|
| hold reset until the frame completes | reset must work when the design is wedged, which is when a frame will never complete |
| drive the line low on reset so the far end sees a break | a device powering on would emit a break into a healthy link |
| flag "reset occurred" to the far end | requires a channel that survives reset, which is the thing being reset |
| have the receiver detect impossible bit patterns | there are none; every byte value is legal |
The answer is above the UART. A message protocol with a length and a checksum detects a truncated or fabricated byte; a raw byte stream cannot, because every byte is valid. §4's result is one of the clearest arguments for never treating a UART as a reliable byte pipe.
Inside the UART, the useful measures are smaller and real:
Reset the queues, not the engines, for a software-visible reset. Chapter 11.2 §4 already made cfg_tx_flush_i reset only the FIFO, so software can discard queued data without truncating the frame currently on the wire. A software reset that hits the engine turns a queue flush into §4's corruption.
Make the hardware reset genuinely global and rare. It is for power-on and for unwedging, not for flow control.
Ensure the far end can resynchronise, which a UART does automatically: an idle line plus a start edge is all a receiver needs, and §4 confirmed the next frame is clean in both directions.
6. The Reset Synchroniser in Verilog-2001 and VHDL-2008
The SystemVerilog above is the reference. Both translations are short, and both have one thing worth pointing at.
Verilog-2001 has no elaboration-time $error, so the depth guard becomes a
runtime check in an initial block. That is genuinely weaker — it fires when
the simulation starts rather than refusing to build — and it is what the
language offers.
//===========================================================================
// uart_reset_sync_v — Synthesizable Verilog-2001
//
// Asynchronous assertion, synchronous release. Reset asserts the instant
// the source asserts with no clock required, and releases only on a clock
// edge -- so every flop in this domain leaves reset on the SAME edge, which
// is the property recovery timing actually needs.
//
// The chain fills with ones from the bottom, so rst_n_o is the LAST bit to
// rise. That is the whole mechanism: STAGES clocks of settling between the
// asynchronous release and this domain seeing it.
//===========================================================================
module uart_reset_sync_v #(
parameter STAGES = 2 // MUST be >= 2 -- not enforceable in 2001
) (
input wire clk,
input wire arst_n_i, // asynchronous, from the board or a PLL lock
output wire rst_n_o // async assert, sync release, for this domain
);
reg [STAGES-1:0] chain_q;
always @(posedge clk or negedge arst_n_i) begin
if (!arst_n_i) chain_q <= {STAGES{1'b0}}; // ASYNC assert
else chain_q <= {chain_q[STAGES-2:0], 1'b1}; // SYNC release
end
assign rst_n_o = chain_q[STAGES-1];
endmoduleVHDL puts the asynchronous assertion in the most legible form of the three.
if arst_n_i = '0' then ... elsif rising_edge(clk) then ... inside a process
sensitive to (clk, arst_n_i) says "assertion does not wait for a clock"
without needing a reader to parse a sensitivity list for a negedge. And the
depth guard is a concurrent assert checked at elaboration — the strongest of
the three.
--==========================================================================
-- uart_reset_sync -- Synthesizable VHDL-2008
--
-- Asynchronous assertion, synchronous release. Reset asserts the instant
-- the source asserts with no clock required, and releases only on a clock
-- edge -- so every flop in this domain leaves reset on the SAME edge.
--==========================================================================
library ieee;
use ieee.std_logic_1164.all;
entity uart_reset_sync is
generic (
STAGES : positive := 2
);
port (
clk : in std_logic;
arst_n_i : in std_logic; -- asynchronous, from the board or a PLL
rst_n_o : out std_logic -- async assert, sync release
);
end entity uart_reset_sync;
architecture rtl of uart_reset_sync is
signal chain_q : std_logic_vector(STAGES-1 downto 0);
begin
assert STAGES >= 2
report "uart_reset_sync: STAGES must be at least 2" severity failure;
rst_n_o <= chain_q(STAGES-1);
process (clk, arst_n_i)
begin
if arst_n_i = '0' then
chain_q <= (others => '0'); -- ASYNC assert
elsif rising_edge(clk) then
chain_q <= chain_q(STAGES-2 downto 0) & '1'; -- SYNC release
end if;
end process;
end architecture rtl;7. Testing a Reset, Which Means Stopping the Clock
Most testbenches for a reset synchroniser test the half that is easy to test and quietly skip the half that matters.
The release is easy. Drive arst_n_i high and count clock edges until
rst_n_o follows. It must be exactly STAGES. Three instances at STAGES of
2, 3 and 4 make that a measurement rather than an assumption.
The assertion is the one that needs care, because a testbench with a
free-running clock cannot tell an asynchronous reset from a synchronous one.
Drive the source low while the clock is running and both designs deassert
rst_n_o within a cycle; the waveforms are nearly identical and the check
passes either way. So the suite stops the clock:
PASS clock stopped, source still high: reset released
PASS ASYNC assert: rst_n_o fell with the clock STOPPED
PASS every depth asserts without a clock
PASS release with the clock stopped does NOT take effect
PASS release completes two edges after the clock returnsThat pair of checks is the whole contract: assertion needs no clock; release needs a clock. A design that fails either one is broken in a way that will appear at power-on, before any clock is stable, and not in any test that ran with a clock.
One further check earns its place — a 1 ns assertion pulse, far shorter than a clock period, must still reach the output. It does, because the assertion path does not go through a flop's clock input at all. A design that required a minimum pulse width here would drop a short power-supply glitch on the reset line, which is exactly the event reset exists to survive.
//===========================================================================
// tb_uart_reset_sync — self-checking SystemVerilog testbench
//
// The two halves of the contract are tested with deliberately different
// techniques, because they are different kinds of claim:
//
// ASSERT is asynchronous. The test STOPS THE CLOCK and asserts the
// source; rst_n_o must fall anyway. A test that keeps the clock
// running cannot distinguish an async reset from a sync one, and
// will pass on a design that has no async path at all.
// RELEASE is synchronous. The test measures the number of active edges
// between the source releasing and this domain seeing it, and
// requires exactly STAGES.
//
// Same 16 counted checks as the Verilog twin.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_reset_sync;
logic clk = 1'b0;
logic clk_en = 1'b1; // lets the test stop the clock
always #5 if (clk_en) clk = ~clk;
logic arst_n = 1'b0;
logic r2, r3, r4;
uart_reset_sync #(.STAGES(2)) d2 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r2));
uart_reset_sync #(.STAGES(3)) d3 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r3));
uart_reset_sync #(.STAGES(4)) d4 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r4));
int checks = 0, failures = 0;
task automatic check(input logic cond, input string name);
checks++;
if (cond) $display(" PASS %0s", name);
else begin failures++; $display(" FAIL %0s", name); end
endtask
// rst_n_o must never be high while the source is low. This is sampled on a
// fine time grid that is deliberately OFFSET from the grid the stimulus
// uses. An always @(*) observer — or a sampler aligned to whole
// nanoseconds — wakes in the same simulation instant in which arst_n falls,
// possibly BEFORE the non-blocking update of r2/r3/r4 for that instant has
// been applied, and reports a violation that does not exist in the design.
// Reading 0.3 ns after each whole nanosecond reads settled values only.
int viol = 0;
initial begin
#0.3;
forever begin
if (arst_n === 1'b0 && (r2 === 1'b1 || r3 === 1'b1 || r4 === 1'b1)) begin
viol++;
a_no_glitch: assert (0)
else $error("rst_n_o high while the source is asserted");
end
#1;
end
end
int lat;
initial begin
#200_000;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: SYSTEMVERILOG RESET-SYNC TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_reset_sync : self-checking SystemVerilog testbench ==");
arst_n = 1'b0;
repeat (6) @(negedge clk);
check(r2 === 1'b0 && r3 === 1'b0 && r4 === 1'b0,
"source asserted: every domain reset is asserted");
// ---- SYNCHRONOUS RELEASE, measured ---------------------------------
@(negedge clk) arst_n = 1'b1;
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat++; end
check(lat == 2, "STAGES=2: release takes exactly TWO active edges");
check(r3 === 1'b0, "STAGES=3 has not released yet");
while (r3 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat++; end
check(lat == 3, "STAGES=3: release takes exactly THREE");
while (r4 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat++; end
check(lat == 4, "STAGES=4: release takes exactly FOUR");
check(r2 === 1'b1 && r3 === 1'b1 && r4 === 1'b1, "all three released");
// ---- ASYNCHRONOUS ASSERT, with the clock STOPPED -------------------
repeat (4) @(negedge clk);
@(negedge clk) clk_en = 1'b0; // clock stops, parked low
#50;
check(r2 === 1'b1, "clock stopped, source still high: reset released");
arst_n = 1'b0; // assert with NO clock
#50;
check(r2 === 1'b0, "ASYNC assert: rst_n_o fell with the clock STOPPED");
check(r3 === 1'b0 && r4 === 1'b0, "every depth asserts without a clock");
// ---- and release still needs a clock -------------------------------
arst_n = 1'b1;
#200;
check(r2 === 1'b0, "release with the clock stopped does NOT take effect");
clk_en = 1'b1;
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat++; end
check(lat == 2, "release completes two edges after the clock returns");
// ---- a narrow asynchronous assert is still captured -----------------
repeat (4) @(negedge clk);
arst_n = 1'b0;
#1;
check(r2 === 1'b0, "a 1 ns assert pulse still reaches the output");
arst_n = 1'b1;
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat++; end
check(lat == 2, "and the release is still synchronous afterwards");
// ---- re-assert while already released --------------------------------
repeat (4) @(negedge clk);
@(negedge clk) arst_n = 1'b0;
repeat (2) @(negedge clk);
check(r2 === 1'b0, "re-assert works from the released state");
@(negedge clk) arst_n = 1'b1;
repeat (6) @(negedge clk);
check(r2 === 1'b1, "and releases again");
check(viol == 0, "rst_n_o never high while the source is low");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL SYSTEMVERILOG RESET-SYNC TESTS PASSED");
else $display(" RESULT: SYSTEMVERILOG RESET-SYNC TESTS FAILED");
$finish;
end
endmodule//===========================================================================
// tb_uart_reset_sync_v — self-checking Verilog-2001 testbench
//
// The two halves of the contract are tested with deliberately different
// techniques, because they are different kinds of claim:
//
// ASSERT is asynchronous. The test STOPS THE CLOCK and asserts the
// source; rst_n_o must fall anyway. A test that keeps the clock
// running cannot distinguish an async reset from a sync one.
// RELEASE is synchronous. The test measures the number of active edges
// between the source releasing and this domain seeing it, and
// requires exactly STAGES.
//
// INDEPENDENCE: every expectation comes from the stimulus, never from
// reading chain_q.
//===========================================================================
`timescale 1ns/1ps
module tb_uart_reset_sync_v;
reg clk = 1'b0;
reg clk_en = 1'b1; // lets the test stop the clock
always #5 if (clk_en) clk = ~clk;
reg arst_n = 1'b0;
wire r2, r3, r4;
uart_reset_sync_v #(.STAGES(2)) d2 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r2));
uart_reset_sync_v #(.STAGES(3)) d3 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r3));
uart_reset_sync_v #(.STAGES(4)) d4 (.clk(clk), .arst_n_i(arst_n), .rst_n_o(r4));
integer checks = 0, failures = 0;
task check;
input cond;
input [8*80-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
// rst_n_o must never be high while the source is low. This is sampled on a
// fine time grid that is deliberately OFFSET from the grid the stimulus uses.
// An always @(*) observer -- or a sampler aligned to whole nanoseconds --
// wakes in the same simulation instant in which arst_n falls, possibly BEFORE
// the non-blocking update of r2/r3/r4 for that instant has been applied, and
// reports a violation that does not exist in the design. Reading 0.3 ns after
// each whole nanosecond reads settled values only. This is a testbench
// artefact to be engineered away, not a property of the DUT.
integer viol = 0;
initial begin
#0.3; // step OFF the integer-ns stimulus grid
forever begin
if (arst_n === 1'b0 && (r2 === 1'b1 || r3 === 1'b1 || r4 === 1'b1))
viol = viol + 1;
#1;
end
end
integer lat;
initial begin
#200_000;
$display(" FAIL watchdog: simulation did not finish");
$display("== %0d checks, %0d failures ==", checks+1, failures+1);
$display(" RESULT: VERILOG RESET-SYNC TESTS FAILED (timeout)");
$finish;
end
initial begin
$display("== uart_reset_sync_v : self-checking Verilog testbench ==");
arst_n = 1'b0;
repeat (6) @(negedge clk);
check(r2 === 1'b0 && r3 === 1'b0 && r4 === 1'b0,
"source asserted: every domain reset is asserted");
// ---- SYNCHRONOUS RELEASE, measured ---------------------------------
@(negedge clk) arst_n = 1'b1;
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat = lat + 1; end
check(lat == 2, "STAGES=2: release takes exactly TWO active edges");
check(r3 === 1'b0, "STAGES=3 has not released yet");
while (r3 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat = lat + 1; end
check(lat == 3, "STAGES=3: release takes exactly THREE");
while (r4 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat = lat + 1; end
check(lat == 4, "STAGES=4: release takes exactly FOUR");
check(r2 === 1'b1 && r3 === 1'b1 && r4 === 1'b1, "all three released");
// ---- ASYNCHRONOUS ASSERT, with the clock STOPPED -------------------
// This is the half a running-clock test cannot distinguish. The clock
// is halted, the source is asserted, and the outputs must fall with
// no active edge available to carry them.
repeat (4) @(negedge clk);
@(negedge clk) clk_en = 1'b0; // clock stops, parked low
#50;
check(r2 === 1'b1, "clock stopped, source still high: reset released");
arst_n = 1'b0; // assert with NO clock
#50;
check(r2 === 1'b0, "ASYNC assert: rst_n_o fell with the clock STOPPED");
check(r3 === 1'b0 && r4 === 1'b0, "every depth asserts without a clock");
// ---- and release still needs a clock -------------------------------
arst_n = 1'b1;
#200;
check(r2 === 1'b0, "release with the clock stopped does NOT take effect");
clk_en = 1'b1; // clock restarts
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat = lat + 1; end
check(lat == 2, "release completes two edges after the clock returns");
// ---- a narrow asynchronous assert is still captured -----------------
// A reset pulse shorter than a clock period must still assert, because
// the assertion path does not go through a flop's clock input.
repeat (4) @(negedge clk);
arst_n = 1'b0;
#1;
check(r2 === 1'b0, "a 1 ns assert pulse still reaches the output");
arst_n = 1'b1;
lat = 0;
while (r2 !== 1'b1 && lat < 12) begin @(posedge clk); #1; lat = lat + 1; end
check(lat == 2, "and the release is still synchronous afterwards");
// ---- re-assert while already released --------------------------------
repeat (4) @(negedge clk);
@(negedge clk) arst_n = 1'b0;
repeat (2) @(negedge clk);
check(r2 === 1'b0, "re-assert works from the released state");
@(negedge clk) arst_n = 1'b1;
repeat (6) @(negedge clk);
check(r2 === 1'b1, "and releases again");
check(viol == 0, "rst_n_o never high while the source is low");
$display("== %0d checks, %0d failures ==", checks, failures);
if (failures == 0) $display(" RESULT: ALL VERILOG RESET-SYNC TESTS PASSED");
else $display(" RESULT: VERILOG RESET-SYNC TESTS FAILED");
$finish;
end
endmodule--===========================================================================
-- tb_uart_reset_sync — self-checking VHDL-2008 testbench
--
-- The two halves of the contract are tested with deliberately different
-- techniques, because they are different kinds of claim:
--
-- ASSERT is asynchronous. The test STOPS THE CLOCK and asserts the
-- source; rst_n_o must fall anyway. A test that keeps the clock
-- running cannot distinguish an async reset from a sync one, and
-- will pass on a design that has no async path at all.
-- RELEASE is synchronous. The test measures the number of active edges
-- between the source releasing and this domain seeing it, and
-- requires exactly STAGES.
--
-- Same 16 counted checks as the Verilog and SystemVerilog twins.
--===========================================================================
library ieee;
use ieee.std_logic_1164.all;
entity tb_uart_reset_sync is
end entity tb_uart_reset_sync;
architecture sim of tb_uart_reset_sync is
constant TCLK : time := 10 ns;
signal clk : std_logic := '0';
signal clk_en : boolean := true; -- lets the test stop the clock
signal done : boolean := false;
signal arst_n : std_logic := '0';
signal r2, r3, r4 : std_logic;
signal viol : natural := 0;
begin
-- Parked LOW when disabled, which is what the assert-without-a-clock test
-- needs: no edge of any kind is available to carry the assertion.
clk <= '0' when (done or not clk_en) else not clk after TCLK/2;
d2 : entity work.uart_reset_sync generic map (STAGES => 2)
port map (clk => clk, arst_n_i => arst_n, rst_n_o => r2);
d3 : entity work.uart_reset_sync generic map (STAGES => 3)
port map (clk => clk, arst_n_i => arst_n, rst_n_o => r3);
d4 : entity work.uart_reset_sync generic map (STAGES => 4)
port map (clk => clk, arst_n_i => arst_n, rst_n_o => r4);
-- rst_n_o must never be high while the source is low. Sampled on a fine
-- grid OFFSET from the whole-nanosecond grid the stimulus uses, so that
-- every read is of settled values. VHDL's delta-cycle model makes this
-- less critical than it is in Verilog, where the same observer written
-- combinationally reports violations that do not exist; keeping the two
-- files structurally identical is worth more than the saved line.
obs : process
begin
wait for 0.3 ns;
loop
if arst_n = '0' and (r2 = '1' or r3 = '1' or r4 = '1') then
viol <= viol + 1;
end if;
exit when done;
wait for 1 ns;
end loop;
wait;
end process obs;
watchdog : process
begin
wait for 200 us;
report "watchdog: simulation did not finish" severity failure;
end process watchdog;
stim : process
variable checks, failures : natural := 0;
variable lat : natural := 0;
procedure check(cond : boolean; name : string) is
begin
checks := checks + 1;
if cond then
report " PASS " & name severity note;
else
failures := failures + 1;
report " FAIL " & name severity error;
end if;
end procedure check;
begin
report "== uart_reset_sync : self-checking VHDL testbench ==" severity note;
arst_n <= '0';
for i in 1 to 6 loop wait until falling_edge(clk); end loop;
check(r2 = '0' and r3 = '0' and r4 = '0',
"source asserted: every domain reset is asserted");
---- SYNCHRONOUS RELEASE, measured ------------------------------------
wait until falling_edge(clk); arst_n <= '1';
lat := 0;
while r2 /= '1' and lat < 12 loop
wait until rising_edge(clk); wait for 1 ns; lat := lat + 1;
end loop;
check(lat = 2, "STAGES=2: release takes exactly TWO active edges");
check(r3 = '0', "STAGES=3 has not released yet");
while r3 /= '1' and lat < 12 loop
wait until rising_edge(clk); wait for 1 ns; lat := lat + 1;
end loop;
check(lat = 3, "STAGES=3: release takes exactly THREE");
while r4 /= '1' and lat < 12 loop
wait until rising_edge(clk); wait for 1 ns; lat := lat + 1;
end loop;
check(lat = 4, "STAGES=4: release takes exactly FOUR");
check(r2 = '1' and r3 = '1' and r4 = '1', "all three released");
---- ASYNCHRONOUS ASSERT, with the clock STOPPED ----------------------
-- This is the half a running-clock test cannot distinguish.
for i in 1 to 4 loop wait until falling_edge(clk); end loop;
wait until falling_edge(clk); clk_en <= false;
wait for 50 ns;
check(r2 = '1', "clock stopped, source still high: reset released");
arst_n <= '0'; -- assert with NO clock
wait for 50 ns;
check(r2 = '0', "ASYNC assert: rst_n_o fell with the clock STOPPED");
check(r3 = '0' and r4 = '0', "every depth asserts without a clock");
---- and release still needs a clock -----------------------------------
arst_n <= '1';
wait for 200 ns;
check(r2 = '0', "release with the clock stopped does NOT take effect");
clk_en <= true;
lat := 0;
while r2 /= '1' and lat < 12 loop
wait until rising_edge(clk); wait for 1 ns; lat := lat + 1;
end loop;
check(lat = 2, "release completes two edges after the clock returns");
---- a narrow asynchronous assert is still captured ---------------------
-- A reset pulse shorter than a clock period must still assert, because
-- the assertion path does not go through a flop's clock input.
for i in 1 to 4 loop wait until falling_edge(clk); end loop;
arst_n <= '0';
wait for 1 ns;
check(r2 = '0', "a 1 ns assert pulse still reaches the output");
arst_n <= '1';
lat := 0;
while r2 /= '1' and lat < 12 loop
wait until rising_edge(clk); wait for 1 ns; lat := lat + 1;
end loop;
check(lat = 2, "and the release is still synchronous afterwards");
---- re-assert while already released -----------------------------------
for i in 1 to 4 loop wait until falling_edge(clk); end loop;
wait until falling_edge(clk); arst_n <= '0';
for i in 1 to 2 loop wait until falling_edge(clk); end loop;
check(r2 = '0', "re-assert works from the released state");
wait until falling_edge(clk); arst_n <= '1';
for i in 1 to 6 loop wait until falling_edge(clk); end loop;
check(r2 = '1', "and releases again");
check(viol = 0, "rst_n_o never high while the source is low");
report "== " & integer'image(checks) & " checks, "
& integer'image(failures) & " failures ==" severity note;
if failures = 0 then
report " RESULT: ALL VHDL RESET-SYNC TESTS PASSED" severity note;
else
report " RESULT: VHDL RESET-SYNC TESTS FAILED" severity error;
end if;
done <= true;
wait;
end process stim;
end architecture sim;Sixteen checks, and all three languages agree:
Verilog-2001 : 16 checks, 0 failures
SystemVerilog : 16 checks, 0 failures
VHDL-2008 : 16 checks, 0 failures8. Verification
// Assertion — reset assertion needs no clock. Checked by stopping the clock
// entirely, which is the only way to test it meaningfully.
// Measured: with the clock halted, driving arst_n low took rst_n_o low.
// Assertion — release is synchronous: rst_n_o may only RISE at a clock edge.
property p_release_is_synchronous;
@(negedge clk) disable iff (!arst_n_i)
$rose(rst_n_o) |-> 1'b0; // must never rise away from posedge clk
endproperty
// Assertion — every flop in the domain leaves reset together. This is the
// property the whole structure exists to provide, and it is why a design
// must never mix a synchronised reset with the raw asynchronous one.
property p_one_release_edge;
@(posedge clk) $rose(rst_n_o) |-> (u_rx.state_q == S_IDLE && u_tx.state_q == S_IDLE);
endpropertyTest reset with traffic in flight, not only at time zero. A reset test that asserts before any activity and releases into a quiet design verifies almost nothing — it is the state every simulation starts in anyway. The interesting cases are all mid-transfer, and §4 is what they look like.
Sweep the release instant. The receiver result changed with the release offset, and a single-point test would have reported one arbitrary value as though it were the behaviour. Where an outcome depends on alignment, one measurement is an anecdote.
Check recovery explicitly. "Is the next frame clean?" is a different question from "was this frame damaged?", and it is the one that determines whether a reset is survivable or fatal.
Mutation testing
Two defects were installed in uart_reset_sync, each verified to have actually
changed the source before being scored.
| # | Defect installed | Result |
|---|---|---|
| M4 | assertion made synchronous — the negedge arst_n_i removed from the sensitivity list | killed, 7 checks |
| M5 | rst_n_o taken from the first flop instead of stage STAGES-1 | killed, 6 checks |
M4 is the defect this chapter exists to prevent, and it is worth noticing which checks kill it. Not the release-latency checks — those still pass, because a synchronous-assert design releases on exactly the same edge. The seven failures are all in the clock-stopped block: the reset that must fall with no clock available does not fall, and everything downstream of that assumption collapses.
Had the suite been written with a free-running clock throughout — which is the natural way to write it — M4 would have survived every check. The design would have shipped with a reset that cannot assert before the PLL locks, and the failure would first appear on a board, intermittently, at power-on.
9. Debugging
10. What This Means on an FPGA
FPGAs initialise flops at configuration, so a reset is often not needed for correctness at all — the bitstream sets the initial state. A reset is still wanted for re-initialisation without reconfiguring, and adding one to every flop consumes routing for no benefit. Reset what genuinely needs a defined state; leave datapath registers alone.
ASIC flows are the opposite: nothing has a defined state at power-on, so every flop that is read before it is written must be reset.
Prefer one reset style per domain. Mixing synchronous and asynchronous resets in one clock domain gives the tool two different timing problems on the same signal and makes the reset tree harder to close — Chapter 12.5.
The release path needs a constraint too. Recovery and removal are real checks on a real path, and an unconstrained reset tree closes them by luck.
11. Understanding Check
12. Summary
Asynchronous assert, synchronous release. Assertion must work with no clock — verified with the clock stopped — and release must happen on a clock edge so every flop in the domain leaves reset together. Release took exactly STAGES edges, as designed.
Release is the timing event, with recovery and removal requirements analogous to setup and hold, and its failure appears once, at power-on.
The reset synchroniser's own asynchronous input is not a violation: it drives reset pins, not data inputs, and those carry different rules.
One synchroniser per clock domain, all from one source: they assert together and release independently, so the design must be correct while only one domain is running.
A transmitter reset mid-frame produces a well-formed frame with the wrong byte. Sending 0xA5 and resetting 4.5 bit times in delivered 0xFD with no framing error, because the line's reset value is mark and mark is a legal stop bit.
A receiver reset mid-frame can invent a byte, framing on a data bit that happens to sit at space. Sweeping the release instant gave 0xFE and 0xFF; the transmitted 0xC3 was never delivered and nothing was flagged.
Both ends recover on the next frame — the damage is confined, which is Chapter 6.2's return-to-idle decision paying off.
And the conclusion that generalises past UART: when every value is legal, the protocol above has to carry the check.
13. What Comes Next
The design is now correct in RTL terms — synchronised, reset properly, with the crossings identified. Chapter 12.5 hands it to synthesis, which will believe exactly what it is told.
That means constraining an asynchronous input that has no real setup requirement, the recovery and removal checks this chapter created, and the two things a tool will silently build if the RTL is ambiguous: an inferred latch and an accidental gated clock.
Browse the full path on the UART tutorials index. For the transmit line's reset value that makes §4's corruption silent, read back to Chapter 7.1.
Continue learning
Related tutorials
- Related topic
Why Receiving Is Harder Than Transmitting
A transmitter executes a schedule it wrote itself. A receiver must decide whether something is happening, whether it was real, where the positions are, and what value was there — four judgements from one edge on an input it does not control.
- Related topic
Parameterisation, Reset and Error Reporting
Compute divider values at elaboration, surface the resulting error, and define reset and reconfiguration behaviour — keeping apart the three kinds of error that are routinely conflated.
- Related topic
UCIe Reset
Reset as a distributed protocol — asynchronous assert and synchronous deassert, per-domain synchronisers, what must and must not be reset, cross-layer state lifetime, reset-domain crossings, partial-reset hazards, observability, and why reset release is never link readiness.
- Related topic
FPGA Cards — The Half of PCIe You Actually Write
A PCIe core hands you an application interface and everything past it is yours. Doubling the link gave 0% because local DDR was the limit, and removing one epoch check misdelivered 70,976 bytes across a reset boundary.
Where this fits
Part of the UART curriculum.
