HomeCDC GuideDay 7: Reset Synchronization
DAY 7 · ADVANCED CDC PATTERNS

Reset Synchronization

By EcrioniX · Updated Jun 23, 2026

Reset is the single most overlooked CDC problem in chip design. Most engineers understand that data signals need synchronizers — but reset? Reset looks global, looks simple, and hides a dangerous trap: asynchronous resets must be asserted immediately but released synchronously to every clock domain. Get this wrong and your chip initializes inconsistently, produces corrupt state after power-on, and ships bugs that only appear during reset sequences.

Reset Synchronizer: Async Assert, Synchronous De-assert POR rst_n_in FF1 async rst FF2 async rst rst_n_sync to domain logic clk (domain clock) async reset path — assert is immediate (no clock needed)
Figure — The reset synchronizer: POR drives two flip-flops with async reset, clocked by the destination domain. Assert is immediate; de-assert waits two clock cycles.

1. Why Reset Is a CDC Problem

Most engineers treat reset as a simple global signal — connect it everywhere and it just works. That intuition breaks down in any design with multiple independent clocks.

Here is the problem. Your chip has a power-on reset (POR) signal, rst_n, generated by an always-on domain or a power management controller. This signal is asynchronous with respect to every clock domain in the chip. When reset de-asserts — when it goes from 0 back to 1 and flip-flops are released — that transition must be seen by each flip-flop in a controlled, synchronous way relative to its own clock.

If the reset release is not synchronized to the clock, each flip-flop in the domain may exit reset at a slightly different absolute time. Whether they all exit on the same clock edge depends entirely on the net routing delay from the reset source to each flip-flop — and that delay is non-deterministic across process, voltage, and temperature corners. A state machine whose flip-flops release in different clock cycles is in an undefined, inconsistent state from the very first cycle of operation.

This is not theoretical. Designs that share a raw POR across multiple clock domains without synchronizers regularly exhibit initialization failures in silicon — the chip boots correctly 95% of the time and randomly hangs the other 5%, depending on power ramp rate, clock startup timing, and PVT conditions. These bugs are extremely difficult to debug because they do not reproduce in simulation.

The danger is de-assertion, not assertion

Asserting reset (going low) is safe to do asynchronously — flip-flops' async reset pins are designed for it, and forcing every flip-flop to 0 immediately is exactly what you want during a power event. De-asserting reset (going from 0 to 1) is the CDC hazard. If de-assertion travels through combinational logic to reach flip-flops at different times relative to their clock edges, some flip-flops exit reset one cycle earlier than others. The reset synchronizer eliminates this race by re-timing the release through a 2-FF chain clocked by the destination domain.

2. Asynchronous Assert, Synchronous De-Assert — The Golden Rule

The industry-standard rule for reset handling in multi-clock designs is captured in one sentence: assert asynchronously, de-assert synchronously.

Why does de-assertion through combinational logic cause problems? Because every flip-flop has an asynchronous reset pin. When that pin changes state (de-asserts), the flip-flop transitions based on its own internal timing and the exact moment the pin voltage crosses the logic threshold. If the de-assertion propagates through a long net with non-uniform delay, two flip-flops in the same domain may see the reset pin go high in adjacent — or even the same — clock cycle, with no guarantee they all release on the same rising edge. The 2-FF synchronizer forces all downstream flip-flops to see a clean, stable de-assertion exactly two cycles after the input goes high.

3. The Reset Synchronizer Circuit

The circuit is a 2-flip-flop shift register where both flip-flops carry the incoming reset as their asynchronous reset and are clocked by the destination domain clock. The data input of FF1 is always tied to logic 1. This is the key insight: in steady state (no reset), the chain is always shifting in 1s. When reset asserts, the chain is immediately cleared to 0s. When reset de-asserts, the 1s propagate through the chain over two clock edges.

reset_sync_basic.v
// Basic 2-FF reset synchronizer
// rst_n_in  : asynchronous input reset (active-low)
// clk       : destination domain clock
// rst_n_sync: synchronized reset output (active-low)

module reset_sync_basic (
    input  wire clk,
    input  wire rst_n_in,
    output wire rst_n_sync
);

    reg q1, q2;

    always @(posedge clk or negedge rst_n_in) begin
        if (!rst_n_in)
            {q2, q1} <= 2'b00;          // assert: both FFs reset immediately (async)
        else
            {q2, q1} <= {q1, 1'b1};     // de-assert: shift 1 through chain over 2 cycles
    end

    assign rst_n_sync = q2;              // output de-asserts 2 cycles after rst_n_in goes high

endmodule

Trace through the behavior step by step:

Why exactly two flip-flops?

The first flip-flop (FF1) absorbs any metastability caused by the rst_n_in de-assertion edge arriving near a clock edge. By the time FF1's output propagates to FF2's D-input, it has a full clock period to resolve to a valid 0 or 1. FF2's output is guaranteed stable. Two stages is the industry minimum; critical designs at aggressive nodes (sub-5nm, ultra-high frequency) sometimes use three stages for extra MTBF margin.

4. Multi-Domain Reset

A real chip with three clock domains needs three independent reset synchronizers — one per domain. The same rst_n_in feeds all three, but each synchronizer uses its own clock. This means each domain exits reset independently, on its own clock boundary, with no coupling between domains.

multi_domain_reset.v
// Three independent reset synchronizers for three clock domains
// Each uses its own clock. All share the same POR input.
// Never share one synchronized reset output across two different domains.

module multi_domain_reset (
    input  wire clk_sys,     // system clock  (e.g. 200 MHz)
    input  wire clk_ddr,     // DDR ctrl clock (e.g. 333 MHz)
    input  wire clk_pcie,    // PCIe clock     (e.g. 250 MHz)
    input  wire por_rst_n,   // power-on reset (active-low, async source)

    output wire rst_n_sys,   // synchronized reset for sys domain
    output wire rst_n_ddr,   // synchronized reset for ddr domain
    output wire rst_n_pcie   // synchronized reset for pcie domain
);

    // Each synchronizer is fully independent — uses its own clock
    reset_sync_basic u_sys_rst  (.clk(clk_sys),  .rst_n_in(por_rst_n), .rst_n_sync(rst_n_sys));
    reset_sync_basic u_ddr_rst  (.clk(clk_ddr),  .rst_n_in(por_rst_n), .rst_n_sync(rst_n_ddr));
    reset_sync_basic u_pcie_rst (.clk(clk_pcie), .rst_n_in(por_rst_n), .rst_n_sync(rst_n_pcie));

    // Flip-flops in clk_sys domain  --> use rst_n_sys
    // Flip-flops in clk_ddr domain  --> use rst_n_ddr
    // Flip-flops in clk_pcie domain --> use rst_n_pcie
    // NEVER cross-connect: e.g., clk_sys FF with rst_n_ddr is a CDC violation

endmodule

The three rst_n_xxx outputs are not guaranteed to de-assert at exactly the same absolute time, because the three clocks are independent. rst_n_sys de-asserts two clk_sys cycles after por_rst_n goes high; rst_n_ddr de-asserts two clk_ddr cycles after. In wall-clock time these differ by nanoseconds. That is fine — as long as logic in each domain only uses its own synchronized reset, there is no cross-domain inconsistency.

5. Reset De-Assertion Ordering

In designs where domains depend on each other, the order in which domains exit reset matters. A downstream block that relies on an upstream block's initialization must not exit reset first.

Classic example: a CPU domain that fetches instructions from an SRAM domain. If the CPU's reset de-asserts before the SRAM domain is fully initialized, the CPU issues fetch requests to an SRAM that has not yet completed its reset sequence. The SRAM may return garbage values or enter an illegal state.

The correct ordering for a typical SoC:

  1. Always-on domain — power management, clock generators, always-on registers exit reset first
  2. Memory and infrastructure domains — SRAM controllers, DDR PHY, fabric interconnect exit reset second
  3. Processing and compute domains — CPUs, DSPs, accelerators exit reset third
  4. I/O and peripheral domains — UART, SPI, USB exit last or in parallel with compute

This sequencing is enforced either by the power management unit (PMU) — which gates the reset synchronizer input of domain N until domain N-1 signals initialization complete — or through UPF power intent. In UPF-based flows, reset sequencing is specified explicitly using set_domain_supply_net and add_power_state commands, and the formal verification tool checks that no domain violates the sequence.

UPF and reset sequencing: In multi-Vdd designs, UPF describes which power domains exist and which resets belong to each domain. Always specify reset sequences in UPF rather than relying on informal documentation — the EDA tools can formally verify the sequence and flag violations before tapeout.

6. Common Reset Bugs

Bug Root Cause Symptom
Shared reset net across multiple domains rst_n connected directly to flip-flops in clk_a and clk_b domains without per-domain synchronizers Intermittent initialization failure; state machine starts in invalid encoding
Missing synchronizer for new clock domain Engineer adds a new IP block during integration but omits a reset synchronizer for its clock Domain occasionally misses reset; partial initialization; rare field failures
Reset glitch propagation External reset pin bounces; glitch shorter than one clock period partially resets some flip-flops Random register corruption without full re-initialization; hard to reproduce
Active-high vs active-low mismatch Synchronizer coded active-low, connected to active-high reset source without inversion Domain stays in reset forever, or never resets at all
Synchronizer clocked by gated clock Reset synchronizer clocked by a gated version of the domain clock that is off during reset Domain never exits reset because the clock is not running to shift in de-assertion

Never clock the reset synchronizer from a gated clock

The reset synchronizer must use the free-running version of the domain clock, not a clock gated by logic that depends on reset state. If the clock is disabled when reset de-asserts, the synchronizer can never shift in the de-assertion — the domain is permanently stuck in reset. Connect the synchronizer to the ungated, always-running version of the clock, before any clock gate in the path.

7. Reset Glitch Filtering

External reset pins (test points, board-level reset buttons, hot-plug connectors) are notorious for producing glitches — brief spurious pulses caused by contact bounce or inductive coupling on PCB traces. A glitch shorter than one clock period can partially reset some flip-flops while leaving others untouched, causing subtle corruption that looks like a software bug but originates in hardware.

The solution is a reset glitch filter: a counter-based debounce circuit that only accepts a reset assertion after it has been stable for N consecutive clock cycles. Short glitches are rejected; only a sustained reset signal passes through.

reset_glitch_filter.v
// Reset glitch filter — debounce external reset_n input
// Requires rst_n_ext to be asserted (low) for FILTER_CYCLES consecutive
// always-on clock cycles before passing the reset through.
// De-assertion (going high) passes immediately without delay.

module reset_glitch_filter #(
    parameter FILTER_CYCLES = 8
)(
    input  wire clk_ao,          // always-on reference clock (32 kHz or divided sysclk)
    input  wire rst_n_ext,       // raw external reset (may have glitches)
    output reg  rst_n_filtered   // debounced reset output
);

    localparam CNT_W = $clog2(FILTER_CYCLES + 1);
    reg [CNT_W-1:0] cnt;

    always @(posedge clk_ao) begin
        if (!rst_n_ext) begin
            // External reset asserted: increment debounce counter
            if (cnt < FILTER_CYCLES)
                cnt <= cnt + 1'b1;
            // Latch reset output only after counter reaches threshold
            if (cnt == (FILTER_CYCLES - 1))
                rst_n_filtered <= 1'b0;
        end else begin
            // External reset released: clear counter and output immediately
            cnt            <= {CNT_W{1'b0}};
            rst_n_filtered <= 1'b1;
        end
    end

endmodule

The filtered output is then fed into the reset synchronizer chain for each clock domain. The debounce counter itself is clocked by an always-on reference clock — the RTC oscillator or the top-level clock divided down — that is guaranteed to be running regardless of the reset state of any other domain.

8. Scan / DFT Interaction

Asynchronous resets create a specific problem for scan-based DFT: because the reset signal is asynchronous, it can override scan shift operations at any clock edge. During scan shift, flip-flops must hold their state to propagate patterns correctly — an asynchronous reset asserting during scan would corrupt the entire scan chain and invalidate all test patterns.

The standard solution is to gate the asynchronous reset with scan_enable using an OR gate: when scan is active, the OR forces the reset pin high (inactive), blocking it from the synchronizer. In functional mode (scan_en=0), the gate is transparent and the circuit behaves normally.

reset_sync_dft.v
// DFT-aware reset synchronizer
// During scan shift (scan_en=1), reset is blocked to prevent
// corruption of scan chain patterns during shift or capture.

module reset_sync_dft (
    input  wire clk,
    input  wire rst_n_in,    // async reset input (active-low)
    input  wire scan_en,     // scan enable from ATPG controller
    output wire rst_n_sync
);

    // Gate: when scan_en=1 force reset inactive (high) to block async reset
    // When scan_en=0 (functional mode), gate is transparent
    wire rst_n_gated = rst_n_in | scan_en;

    reg q1, q2;

    always @(posedge clk or negedge rst_n_gated) begin
        if (!rst_n_gated)
            {q2, q1} <= 2'b00;
        else
            {q2, q1} <= {q1, 1'b1};
    end

    assign rst_n_sync = q2;

endmodule
// Synthesis note: the OR gate must be placed before the FF async reset pin.
// CDC tools will flag the OR as a reset-domain crossing — add a waiver with
// justification that scan_en is a DFT-controlled, glitch-free signal.

Implementation requirements for the DFT reset gate:

9. Full Chip Reset Architecture

Putting it all together, a production chip's reset architecture follows a clear pipeline from board-level input to domain-local synchronized reset output.

Full Chip Reset Architecture External Reset Pin Glitch Filter (counter debounce) POR / PMU (sequencer) Sync (clk_sys) rst_n_sys Sync (clk_ddr) rst_n_ddr Sync (clk_pcie) rst_n_pcie SYS Logic clk_sys domain DDR Ctrl clk_ddr domain PCIe PHY clk_pcie domain PMU enforces ordering: always-on first, then memory, then compute, then I/O
Figure — Full chip reset pipeline: external pin to per-domain synchronized reset. Each stage adds a layer of filtering, control, and synchronization.

The five stages in order:

  1. External reset pin — board-level signal, may be noisy, may bounce, may have inductive coupling
  2. Glitch filter — counter-based debounce clocked by always-on reference; rejects pulses shorter than N cycles
  3. POR / Power Management Unit — combines filtered external reset with internal power-good signals; controls reset de-assertion sequencing between domains; may add programmable delays
  4. Per-domain reset synchronizers — one 2-FF chain per clock domain, each clocked by that domain's free-running clock
  5. Domain logic — every flip-flop in a domain uses only that domain's synchronized reset output

10. Parameterized Reset Synchronizer (Production Module)

The following is a production-ready parameterized reset synchronizer with configurable number of synchronization stages and configurable reset polarity. For most designs N=2 is correct. Safety-critical or sub-5nm high-frequency designs may use N=3 for greater MTBF margin.

reset_sync.v
// Parameterized reset synchronizer
// Async assert, synchronous de-assert — production ready
//
// Parameters:
//   N          : number of synchronization stages (default 2, use 3 for safety-critical)
//   RST_ACTIVE : 0 = active-low reset, 1 = active-high reset

module reset_sync #(
    parameter N          = 2,
    parameter RST_ACTIVE = 0
)(
    input  wire clk,
    input  wire rst_in,    // raw async reset input
    input  wire scan_en,   // DFT scan enable (tie to 0 if unused)
    output wire rst_sync   // synchronized reset output (same polarity as rst_in)
);

    // Normalize to active-low internally
    wire rst_n_raw = (RST_ACTIVE == 0) ? rst_in : ~rst_in;

    // Block reset during scan shift to protect scan chain integrity
    wire rst_n_gated = rst_n_raw | scan_en;

    // Synchronizer chain — keep FFs physically adjacent for best MTBF
    // Xilinx/Vivado : (* ASYNC_REG = "TRUE" *)
    // Synopsys DC   : set_false_path -to [get_cells *sync_reg*]; set_dont_touch
    (* ASYNC_REG = "TRUE" *) reg [N-1:0] sync_reg;

    always @(posedge clk or negedge rst_n_gated) begin
        if (!rst_n_gated)
            sync_reg <= {N{1'b0}};                        // async assert: clear all stages
        else
            sync_reg <= {sync_reg[N-2:0], 1'b1};          // shift 1 through chain
    end

    // Output de-asserts only after N clock cycles following rst_n_in de-assertion
    wire rst_n_sync_out = sync_reg[N-1];

    // Convert output back to requested polarity
    assign rst_sync = (RST_ACTIVE == 0) ? rst_n_sync_out : ~rst_n_sync_out;

endmodule

Synthesis and implementation notes:

Day 7 Key Takeaways

FAQ

Why must reset de-assertion be synchronous?

Flip-flop reset pins are asynchronous by design — de-assertion (going high) travels through combinational logic and may reach different flip-flops in different clock cycles. If two flip-flops in a state machine exit reset in different clock cycles, the machine starts in an inconsistent, undefined state. A reset synchronizer ensures every flip-flop in the domain exits reset on the same clock edge, eliminating the race condition.

What is the reset synchronizer circuit?

A 2-flip-flop chain where both flip-flops have their asynchronous reset inputs tied to the incoming reset signal and are clocked by the destination domain's clock. Assert (going low) is immediate — both FFs are forced to 0 asynchronously, with no clock required. De-assert (going high) shifts a 1 through the chain over two clock cycles before the output goes high. This prevents metastability at downstream flip-flop reset pins during the de-assertion transition.

Why does each clock domain need its own reset synchronizer?

Each reset synchronizer uses its own domain's clock to re-time the reset release. If one synchronized reset output was shared across two different clock domains, it would be synchronous to one domain's clock but asynchronous to the other's — defeating the entire purpose of synchronization. Each domain gets its own synchronizer so the reset de-assertion is always aligned to that specific domain's clock boundary.

Previous
← Day 6: Data Handshake Crossing

← Full course roadmap