3. STA Fundamentals - Reset Recovery & Removal Time
Setup and hold cover the data pin of a flip-flop. They say nothing about the asynchronous pins, the ones that bypass the clock entirely: CLR, PRE, or in RTL terms the rst_n in always @(posedge clk or negedge rst_n). Those pins have their own pair of constraints, recovery and removal, and every synthesis and static timing tool reports violations against them. This post explains what the two numbers mean, why asserting a reset is free while releasing it is not, how the checks show up in a timing report, and the two-flop synchronizer that makes them pass by construction. It closes with what a verification engineer should check, because the failure mode here is silent in RTL simulation and only appears in gate-level or in silicon.
This is part 3 of the STA Fundamentals series. Part 1 covered setup and hold without clock skew and part 2 added clock skew; the same reasoning applies here with the reset pin standing in for the data pin.
Asserting is free, releasing is not
An asynchronous reset does what its name says on assertion. The moment rst_n goes low, the flop's output is forced to its reset value with no clock involved. Timing does not matter because there is nothing to race against: the output will settle a propagation delay later regardless of where the clock edge is.
Deassertion is different. When rst_n goes high, the flop switches from "forced" to "sampling D at the clock edge". If that switch happens right at a clock edge, the internal latches see the reset going away and the clock arriving at the same instant, and the output can go metastable, exactly as a data input that changes inside the setup and hold window would. The circuit has to be told a minimum distance between the reset release and the nearest active edge, on both sides of that edge. Those two distances are recovery and removal.
Recovery time
Recovery time is the minimum time the asynchronous control signal must be deasserted before the next active clock edge. It is the setup check of the reset pin: release the reset at leastt_recovery ahead of the edge, and the flop is guaranteed to sample D cleanly on that edge.
Removal time
Removal time is the minimum time the asynchronous control signal must stay asserted after an active clock edge. It is the hold check of the reset pin: if you are going to release the reset around a clock edge, do it at leastt_removal after the edge, so that edge is still cleanly a reset edge and not a half-sampled one.
{"signal": [
{"name": "clk", "wave": "p......", "period": 2},
{"name": "rst_n", "wave": "0....1........"},
{"name": "window", "wave": "3....4.5......", "data": ["reset asserted", "recovery", "removal"]}
], "config": {"hscale": 1}}
The two windows sit on either side of the active edge, just as setup sits before and hold sits after. A reset release that lands inside either window is a violation.
| Reset timing | Data timing | Meaning |
|---|---|---|
| Recovery time | Setup time | Signal must be stable this long before the edge |
| Removal time | Hold time | Signal must be stable this long after the edge |
What the tool checks
Every timing tool treats the reset release as an arrival time and compares it to the clock edge, using the library's recovery and removal arcs on the flop. The arithmetic mirrors the setup and hold equations from parts 1 and 2 of this series, with the reset path in place of the data path:
- Recovery (setup-like):
T_clk + t_clk_skew - t_recovery >= t_launch_edge + t_reset_path_delay. A slow reset path, or a fast clock, eats the margin. - Removal (hold-like):
t_reset_path_delay >= t_clk_arrival + t_removal. A reset path that is too fast relative to the clock tree fails this one.
In an SDC flow the reset is usually constrained through its source. If the release is generated by a flop on the same clock, the tool already knows the launch edge and checks recovery and removal automatically. If it comes from a pad, you give it an input delay relative to a clock, or declare it a false path when it is genuinely asynchronous and handled by a synchronizer:
# Reset released by on-chip logic on clk: checked automatically
# Reset from a pad, resynchronized on chip: cut the path at the synchronizer input
set_false_path -from [get_ports arst_n] -to [get_pins u_rst_sync/sync_reg[0]/CLR]
A typical report line looks like this. The tool names the check type, and the slack is computed exactly as for setup and hold:
Startpoint: u_rst_gen/rst_release_q (rising edge-triggered flip-flop clocked by clk)
Endpoint: u_core/state_reg[3] (recovery check against rising-edge clock clk)
Path Group: clk
Path Type: max
clock clk (rise edge) 0.000
...
u_core/state_reg[3]/CLR (DFFR_X1) 4.812 r
data arrival time 4.812
clock clk (rise edge) 5.000
clock network delay (propagated) 0.610
library recovery time -0.180
data required time 5.430
slack (MET) 0.618
If you see recovery check with negative slack, the reset is being released too close before an edge. If you see removal check failing, it is being released too soon after one. The fix is rarely to tune delays. It is to release the reset synchronously.
The fix: a reset synchronizer
The standard structure asserts asynchronously and deasserts synchronously. Two flops in series, both cleared by the raw asynchronous reset, with a constant 1 shifted in once the reset lifts. The output goes low the instant arst_n falls, and goes high two clock edges after arst_n rises, always aligned to clk. Because the release now comes out of a flop clocked by clk, the recovery and removal checks on every downstream flop are ordinary same-clock paths and pass with margin.
// Two-flop reset synchronizer: asynchronous assertion, synchronous deassertion
module reset_sync (
input wire clk,
input wire arst_n, // asynchronous active-low reset from pad or POR
output wire rst_n // reset for the clk domain
);
reg [1:0] sync;
always @(posedge clk or negedge arst_n) begin
if (!arst_n)
sync <= 2'b00; // assert immediately, no clock needed
else
sync <= {sync[0], 1'b1}; // release two clocks after arst_n rises
end
assign rst_n = sync[1];
endmodule
Simulated with Icarus, with the raw reset released at t=17, two nanoseconds before a rising edge at t=20, and asserted again mid-cycle later on:
t=15 clk=1 arst_n=0 rst_n=0
t=17 clk=1 arst_n=1 rst_n=0 <- raw release, close to an edge
t=25 clk=1 arst_n=1 rst_n=0 <- first edge: sync[0]=1
t=35 clk=1 arst_n=1 rst_n=1 <- second edge: rst_n released, aligned to clk
...
t=57 clk=1 arst_n=0 rst_n=0 <- assertion is immediate, no clock involved
t=60 clk=0 arst_n=1 rst_n=0
t=75 clk=1 arst_n=1 rst_n=1 <- two edges later, synchronous release again
The first flop, sync[0], is the one that can go metastable, since arst_n rises at an arbitrary time relative to clk. It has a full clock period to settle before sync[1] samples it, which is the same argument as any two-flop synchronizer. Only sync[1] fans out to the design, so the design never sees the metastable node.
Two rules follow from the structure:
- One synchronizer per clock domain. A reset released in domain A is asynchronous to domain B. Every domain gets its own
reset_sync, fed from the same raw reset, and the domains come out of reset at slightly different times, which the design must tolerate. - Constrain the raw path as a false path, and the synchronized path normally. The tool cannot time
arst_nintosync[0], and should not try. Everything downstream ofsync[1]is a regular same-clock path.
What verification should check
RTL simulation will not show a recovery or removal violation. There is no metastability model in a zero-delay always block, so a reset released one picosecond before a clock edge behaves perfectly in RTL and can fail in silicon. That makes this a structural and gate-level concern, and the verification engineer's job is to make sure it was handled, not to catch it in a regression by luck.
- Assert the synchronizer is there. A simple structural check: every clock domain's reset must come from a
reset_syncinstance. Lint tools and CDC tools (SpyGlass, Questa CDC, Conformal) flag asynchronous reset deassertion that is not synchronized as a reset domain crossing. - Assert release alignment in simulation. If the RTL contains a synchronizer, the synchronized reset can only be released coincident with a clock edge. A small checker bound to the domain catches anyone bypassing it:
// Bind into the clock domain: rst_n may only rise at a posedge of clk
time last_edge;
always @(posedge clk) last_edge = $time;
always @(posedge rst_n)
assert ($time == last_edge)
else $error("rst_n released between clock edges at %0t", $time);
- Run gate-level with timing on the reset sequence. Recovery and removal arcs in the standard-cell library become
$recoveryand$removalchecks in the SDF-annotated netlist, and the simulator will report violations with the offending instance name. A gate-level reset test is the one place these violations become visible. - Cover reset release across the clock phase. Randomize the delay between the raw reset release and the clock so that the synchronizer's first flop is exercised at every phase, and confirm the design comes out of reset cleanly each time.
Key takeaways
- Asynchronous resets assert without timing constraints and deassert with two: recovery before the edge, removal after it.
- Timing tools check both automatically once the reset source is constrained, and report them as
recovery checkandremoval checkwith the same slack arithmetic as setup and hold. - The two-flop reset synchronizer converts an asynchronous release into a synchronous one and is the standard fix. Use one per clock domain.
- RTL simulation cannot see these violations. Structural lint, CDC tools and gate-level simulation with SDF are where they appear, and a verification plan should say which one is responsible.
Verified with Icarus Verilog 13.0 and Verilator 5.052 on macOS.
Comments (0)
Leave a Comment