FPGA reset strategies: async assert, sync deassert, and when to skip reset entirely

Reset looks like the simplest signal in a design and behaves like one of the subtlest. Whole app notes exist because of one fact: a reset that deasserts asynchronously is a clock-domain crossing into every flop it touches. Get the release edge wrong and different flip-flops leave reset in different cycles — a state machine wakes up in two states at once, once a month, on the hottest board. Here's the decision framework.

The three questions

  1. Assertion: does reset need to work without a clock? (Power-up, watchdog, clock-loss recovery → yes.)
  2. Deassertion: are all flops guaranteed to leave reset in the same clock cycle? (This is the one that bites.)
  3. Coverage: which flops actually need reset at all?

Strategy 1: synchronous reset (the FPGA default)

always @(posedge clk)
    if (rst)  q <= '0';
    else      q <= d;

Reset is just another data input: it wins timing analysis for free, can't glitch the flop, and deassertion is inherently clean. The cost: it needs a running clock, and it consumes a LUT input on some paths. For most FPGA logic — pipelines, counters, FSMs — this is the right answer, and it's what our course examples use.

Strategy 2: async assert, sync deassert (the system recipe)

When reset must take effect with no clock (power-on, PLL still locking), use an asynchronous reset — but release it synchronously, through a reset synchronizer per clock domain:

(* ASYNC_REG = "TRUE" *) reg [1:0] ff;
always @(posedge clk or negedge rst_async_n)
    if (!rst_async_n) ff <= 2'b00;
    else              ff <= {ff[0], 1'b1};
assign rst_sync_n = ff[1];

Assertion is instant and clock-free; deassertion arrives aligned to the clock, so every flop wakes in the same cycle. This is the standard system-level pattern — one synchronizer per domain, all fed from the same source (board reset AND PLL locked). The CDC generator emits this block with its matching constraints, which are half the job: recovery/removal timing on the release edge is exactly what the synchronizer exists to satisfy.

Strategy 3: no reset at all (more often right than you'd think)

FPGAs configure every flip-flop to a known value when the bitstream loads — reg [7:0] q = 8'h00; in Verilog sets the power-up value in the bitstream, no logic required. Datapath registers whose values are meaningless until the first valid strobe don't need reset: a shift register full of stale samples is harmless if valid is low.

Skipping reset where it isn't needed shortens the highest-fanout net in the design, saves routing, and often helps Fmax. Reset the control path (FSMs, valid flags, counters); let the data path initialize from the bitstream. This split is one of the easiest performance wins available.

The classic mistakes

The checklist

  1. One asynchronous system reset source (button, supervisor, watchdog), combined with every PLL's locked.
  2. One reset synchronizer per clock domain — async assert, sync deassert.
  3. Inside the fabric: synchronous resets, and only on control-path flops.
  4. Datapath: bitstream initial values, no reset net.
  5. Constrain the release path (the generator gives you the XDC) and check report_cdc (Vivado TCL cheatsheet) before signoff.

Do this once, template it, and reset becomes what it always should have been: completely dependable and never thought about again.