RTL Design — Reset

Reset Synchronizer
Async Assert, Sync Deassert

The most interviewed reset topic in ASIC design — and the one most engineers get subtly wrong. Reset assertion must be immediate; reset deassertion must be synchronised. Here's why, with an interactive lab and complete Verilog RTL.

Async Assert
Sync Deassert
Glitch Filter
Multi-Domain Trees

What Happens Without a Reset Synchronizer

A large RTL design has thousands of flip-flops, all sharing the same active-low reset net rst_n. When power comes on — or when a fault triggers reset — the POR cell asserts rst_n=0 asynchronously. So far, so good.

The danger is on the way out of reset. When rst_n goes back to 1, every flip-flop in the design samples this transition. If the transition happens near a clock rising edge — within the setup or hold window of any FF — some FFs may capture the new value one clock cycle before or after their neighbours. A design that was supposed to start with all registers at zero instead starts with a mix of 0s and Xs, and the bugs that follow are intermittent, temperature-sensitive, and nearly impossible to reproduce in simulation.

Root Cause

Reset Deassertion Near a Clock Edge

If rst_n rises within the setup window of a clock edge, the FF may go metastable — neither 0 nor 1. Its neighbour, whose setup window opens a few picoseconds later, samples a clean 1. The two FFs now disagree on the initial state.

Symptom

Inconsistent Initial State

State machines with multiple FFs may enter an illegal encoded state. Counters start at non-zero values. Handshake logic that requires both sides to begin in "idle" state finds one side already in "active" — protocol violation from cycle 1.

Simulation Blindness

Invisible in RTL Simulation

RTL simulation assigns reset synchronously and deterministically — no FF ever samples reset near a clock edge unless you explicitly force it. The bug only appears at speed, in real silicon, when process variation shifts the exact edge alignment into the danger zone.

The 2-FF Reset Synchronizer Circuit

The fix is elegant: pass reset through two flip-flops clocked by the destination domain. Both FFs have asynchronous reset from the raw input — so assertion is still immediate. But deassertion can only propagate through the chain on clock edges — two cycles after the raw reset releases.

VDD FF1 D Q CLK rst_n FF2 D Q CLK rst_n FF1_Q RST_N_SYNC CLK (shared, ungated) RST_N_IN D tied HIGH

Key insight: Both FFs have their async reset connected to RST_N_IN. When reset asserts (0), both FFs reset immediately — no waiting for a clock edge. When reset deasserts (1), FF1's D input is tied to VDD, so on the first clock edge after release, FF1_Q=1. On the second clock edge, FF2 captures FF1_Q=1, and RST_N_SYNC goes high. Clean, glitch-free, two cycles of latency.

Interactive Reset Sync Lab
Drag the slider to move reset deassertion relative to the clock edge. Watch FF1 go metastable in the danger zone — RST_SYNC stays clean.
Safe Zone
Reset Release Timing
Early (safe)← Danger →Late (safe)
Simulation Speed Live
SlowNormalFast
CLK
RST_N_IN (raw)
FF1_Q (intermediate)
RST_SYNC (output)
Setup/Hold danger zone

Why Async Assert Is Safe

Reset assertion (driving reset active) is safe to do asynchronously because it only drives flip-flops into their known reset state. There is no ambiguity: every FF responds identically to a low RST_N — output goes to 0, regardless of the clock.

More importantly, reset assertion must be asynchronous. Consider a chip experiencing a power fault or brownout. The clock may itself be unreliable or stopped. Waiting for a clock edge to propagate reset would mean the chip stays in an unknown state until a clock arrives — exactly when it is most dangerous. Async assert gives you guaranteed, immediate protection.

Rule: Assert reset asynchronously. Always. The clock cannot be trusted during fault events.

Why Sync Deassert Is Critical

Reset deassertion is the mirror image — and the dangerous one. When rst_n goes from 0 to 1, every flip-flop in the design is released from reset on the next sampling event. If different FFs sample this release at different clock edges — because the release edge landed in some FFs' setup window but not others' — the chip wakes up with inconsistent register contents.

❌ Without Synchronizer
rst_n releases near clock edge N
• FF_A (setup window open): samples at edge N, Q=0→1
• FF_B (setup window closed): samples at edge N+1, Q=0→1
• One cycle skew between two "synchronous" registers
• State machine may enter illegal encoded state
• Handshake may fire prematurely
✅ With 2-FF Synchronizer
rst_n releases at any time
• FF1 samples release on clock edge N or N+1
• FF2 samples FF1_Q=1 on edge N+1 or N+2
RST_SYNC goes high cleanly on one specific edge
• ALL downstream FFs using RST_SYNC deassert on the same edge
• Consistent, deterministic initial state guaranteed

The latency tradeoff: Sync deassert adds 2 clock cycles of latency to reset release. For most designs this is negligible. At 1 GHz that is 2 ns — the chip spent milliseconds in assertion already. Only designs with sub-nanosecond reset requirements (rare) would need a different approach.

Glitch Filtering — A Free Bonus

A reset glitch is an unintended brief pulse on the reset line — from power supply noise, an ESD event, a board-level ringing, or a metastable reset controller output. Without synchronization, a glitch as short as one gate delay can momentarily reset flip-flops and corrupt state mid-operation.

The 2-FF synchronizer filters glitches automatically. Because FF1 only captures its input on clock edges, a glitch that arrives and disappears between two clock edges is never sampled. The synchronizer behaves like a minimum-pulse-width filter: only a reset deassertion that stays high for at least one full clock period — long enough to be captured at a clock edge — propagates through.

Minimum capturable pulse width ≈ one full clock period. At 500 MHz (2 ns period), glitches shorter than ~2 ns are filtered. At 100 MHz (10 ns period), glitches shorter than ~10 ns are filtered. Higher frequency = tighter glitch filter. Use the "Inject Glitch" button in the lab above to see this in action.

Verilog — From Wrong to Right

The wrong pattern fans out raw rst_n directly to every flip-flop. The correct pattern puts a synchronizer at the entry point of each clock domain.

verilog — ❌ WRONG: raw reset fan-out
// WRONG: rst_n_in fans out directly to thousands of flip-flops.
// If rst_n_in deasserts near a clock edge, some FFs exit reset
// one cycle before others → inconsistent initial state.

module bad_design (
  input  clk, rst_n_in,
  output [7:0] data_out
);
  reg [7:0] counter;
  reg       fsm_idle;

  // ← rst_n_in used raw — no synchronizer!
  always @(posedge clk or negedge rst_n_in)
    if (!rst_n_in) counter <= 8'h00;
    else           counter <= counter + 1;

  // ← same raw rst_n_in — may deassert one cycle earlier than counter
  always @(posedge clk or negedge rst_n_in)
    if (!rst_n_in) fsm_idle <= 1'b1;
    else           fsm_idle <= (counter == 8'hFF);

endmodule
verilog — ✅ CORRECT: 2-FF reset synchronizer module
// 2-FF reset synchronizer: async assert, sync deassert.
// Instantiate once per clock domain at the domain entry point.
// DONT_TOUCH prevents synthesis from optimising away the chain.

module rst_sync #(parameter STAGES = 2) (
  input  clk,         // destination domain clock — must be UNGATED
  input  rst_n_in,    // raw async reset (active-low)
  output rst_n_sync   // synchronized reset for all logic in this domain
);
  (* DONT_TOUCH = "true", ASYNC_REG = "true" *)
  reg [STAGES-1:0] chain;

  always @(posedge clk or negedge rst_n_in)
    if (!rst_n_in) chain <= {STAGES{1'b0}};         // async assert: all 0
    else           chain <= {chain[STAGES-2:0], 1'b1}; // sync deassert: shift in 1s

  assign rst_n_sync = chain[STAGES-1];

endmodule

// Usage — top-level wires one synchronizer per domain:
wire rst_n_core;
rst_sync #(.STAGES(2)) u_rst_core (
  .clk       (clk_core),
  .rst_n_in  (por_rst_n),   // from POR cell or pin
  .rst_n_sync(rst_n_core)
);
// All flip-flops in clk_core domain use rst_n_core — never por_rst_n directly.
verilog — Multi-domain reset distribution
// Each clock domain gets its own rst_sync instance.
// NEVER share a synchronized reset across two different clock domains.

module reset_dist (
  input  por_rst_n,     // raw power-on reset from POR cell
  input  clk_core,      // 1 GHz core domain
  input  clk_io,        // 200 MHz IO domain
  input  clk_usb,       // 480 MHz USB domain
  output rst_n_core,
  output rst_n_io,
  output rst_n_usb
);
  // Each domain: independent sync chain, independent clock
  rst_sync #(2) u_core (.clk(clk_core), .rst_n_in(por_rst_n), .rst_n_sync(rst_n_core));
  rst_sync #(2) u_io   (.clk(clk_io),   .rst_n_in(por_rst_n), .rst_n_sync(rst_n_io));
  rst_sync #(3) u_usb  (.clk(clk_usb),  .rst_n_in(por_rst_n), .rst_n_sync(rst_n_usb));
  // USB uses 3 stages: 480 MHz leaves only 2 ns per stage — extra margin helps

endmodule
verilog — Brown-out aware reset (POR integration)
// Brown-out: VDD dips below FF min operating voltage.
// POR cell asserts rst_n whenever VDD < threshold.
// Synchronizer FFs MUST use the free-running (ungated) clock.
// If clock is gated off during brown-out, sync chain never propagates release.

module por_reset_sync (
  input  clk_free,     // ungated oscillator — always running
  input  vdd_good,     // from analog POR: 1 when VDD > threshold
  output rst_n_sync
);
  (* DONT_TOUCH = "true", ASYNC_REG = "true" *)
  reg [1:0] ff;

  always @(posedge clk_free or negedge vdd_good)
    if (!vdd_good) ff <= 2'b00;        // VDD dip → instant assert
    else           ff <= {ff[0], 1'b1}; // VDD OK → sync release

  assign rst_n_sync = ff[1];

endmodule

// ⚠  Constraint required — tell STA the sync chain is intentional:
//   set_false_path -from [get_ports vdd_good] -to [get_pins ff_reg[0]/D]
//   set_max_delay  -datapath_only 2 -from [get_pins ff_reg[0]/Q] \
//                                   -to   [get_pins ff_reg[1]/D]

The Five Reset Synchronizer Rules

Rule 1

One Synchronizer Per Domain

Never share a synchronized reset across two clock domains. Each domain must synchronize reset independently using its own clock. A reset synchronized to CLK_A is not safe in CLK_B.

Rule 2

Use the Ungated Clock

The synchronizer FFs must connect to the free-running (ungated) clock. If the clock is gated off — which can happen during power-down or debug — the synchronizer can never propagate reset deassertion.

Rule 3

No Logic Between Stages

Insert zero combinational logic between the flip-flop stages. Any gate introduces delay that can cause a hold violation within the sync chain itself — defeating the purpose of the synchronizer.

Rule 4

DONT_TOUCH Attribute

Mark the synchronizer with (* DONT_TOUCH = "true" *) or set_dont_touch. Without this, synthesis may merge, retime, or remove stages it considers redundant.

Rule 5

False Path on Input

Constrain the crossing from rst_n_in to FF1's D-pin as a false path in SDC. The raw reset is asynchronous — STA cannot meaningfully analyse a timing path from it to a synchronous FF.

Questions Engineers Actually Get Asked

Assert (going active): drives every FF into its known reset state (Q=0). There is no ambiguity — the outcome is always the same regardless of when the clock edge arrives relative to the assertion. More importantly, faults and power events require immediate reset; waiting for a clock edge would leave the chip in an unknown state during the most dangerous window.

Deassert (going inactive): releases FFs to capture new data on the next clock edge. If deassertion happens within the setup or hold window of a clock edge, some FFs may capture the released value one cycle before their neighbours — creating inconsistent initial conditions that corrupt state machine encoding, counter values, and handshake logic from the first operational cycle.
The destination flip-flop violates its setup time. It may go metastable — its output is neither a clean 0 nor a clean 1. Adjacent FFs whose setup windows open slightly later may capture a clean 1 on the same edge, or may miss this edge entirely and capture at the next one.

The result: two FFs that were both in reset (Q=0) one cycle ago now have Q=0 and Q=1 — one cycle out of sync with each other. A 4-bit encoded state machine that needs to start in state 4'b0000 may start in 4'b0001 or 4'b0010, which may be an illegal encoding. The corruption is deterministic given a specific silicon die and operating conditions, but varies die-to-die — making it appear random in production.
Two stages is the standard minimum. At 1 GHz with a typical cell metastability time constant τ ≈ 40 ps, two FF stages give MTBF exceeding 10⁶ years — effectively infinite for any practical chip lifetime.

Three stages are sometimes used for high-reliability applications (automotive, space), at advanced nodes below 7 nm where τ is smaller (less resolution time per stage), or at very high clock rates (>2 GHz) where the resolution window per stage narrows. The reset synchronizer's MTBF calculation follows the same formula as any 2-FF CDC synchronizer: MTBF = exp(T_res/τ) / (f_clk × f_change × T_w).
No — this is one of the most common reset synchronizer mistakes. If the synchronizer FF is clocked by a gated clock and the gate is off (clock stopped), no clock edges arrive and the synchronizer can never propagate the reset deassertion. The downstream domain stays in reset forever — or until the gate turns on, at which point the release may happen without the POR being properly re-evaluated.

Always connect the synchronizer to the free-running root clock of the domain — the clock that runs continuously, before any ICG (Integrated Clock Gate) cells. In clock-gating-heavy designs this usually means connecting to the clock buffer output before the first gate in the clock tree.
Three constraints are needed:

1. set_false_path -from [get_ports rst_n_in] — the raw async reset is not a synchronous signal; STA should not analyze a timing path from it to FF1's D-pin.

2. set_max_delay -datapath_only [period] -from [get_pins chain_reg[0]/Q] -to [get_pins chain_reg[1]/D] — within the synchronizer chain, ensure the inter-stage path meets hold without being over-constrained by clock uncertainty.

3. Mark the synchronizer with set_dont_touch or the (* DONT_TOUCH = "true" *) attribute to prevent synthesis from optimizing or merging the chain stages.
SpyGlass CDC identifies clock domain boundaries and checks every reset net that crosses domains. Key violations it flags:

ResetSync: a reset signal that reaches a flip-flop in a different clock domain without a proper synchronizer
GatedClockReset: a synchronizer FF detected on a gated (non-free-running) clock path
ResetDivergence: a reset that is synchronized into domain A and then directly used in domain B without re-synchronization — the same structural bug as CDC divergence, applied to resets

These checks require the user to define clock domain intent via define_domain and define_reset pragmas or via a domain intent file (DIF). Without domain intent, SpyGlass cannot distinguish intentional from unintentional crossings.

Related Topics