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 least t_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 least t_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 timingData timingMeaning
Recovery timeSetup timeSignal must be stable this long before the edge
Removal timeHold timeSignal must be stable this long after the edge
Note Recovery and removal are checked only on deassertion. Assertion of an asynchronous reset is not timed by STA, which is precisely why the reset can be asserted by a power-on circuit or a watchdog with no clock running.

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_n into sync[0], and should not try. Everything downstream of sync[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_sync instance. 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 $recovery and $removal checks 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 takeaway Recovery is setup for the reset pin. Removal is hold for the reset pin. Both only apply when the reset is released, and both are satisfied by construction when the release comes from a synchronizer clocked by the destination clock.

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 check and removal check with 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.

Author
Mayur Kubavat
DV engineer working on SoC verification. Writes here about UVM, PCIe, SystemVerilog, and the everyday craft of getting designs to tape-out.

Comments (0)

Leave a Comment