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
- Assertion: does reset need to work without a clock? (Power-up, watchdog, clock-loss recovery → yes.)
- Deassertion: are all flops guaranteed to leave reset in the same clock cycle? (This is the one that bites.)
- 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
- Async reset released asynchronously. The one-in-a-million wake-up bug described above. Always release through a synchronizer.
- Resetting everything. A 100k-flop reset net is a routing and timing burden; see strategy 3.
- Mixing polarities and styles per module. Pick a convention
(
rst_nasync-assert at the system level,rstsync inside) and hold the line — review-time consistency prevents integration-time surprises. - Forgetting PLL lock. Logic clocked by a PLL output
must stay in reset until
lockedasserts; OR it into the reset source. - Vendor defaults differ. AMD/Xilinx guidance leans synchronous, active-high; Intel historically async, active-low. Your library blocks will meet both — the per-domain synchronizer is the neutral ground.
The checklist
- One asynchronous system reset source (button, supervisor, watchdog),
combined with every PLL's
locked. - One reset synchronizer per clock domain — async assert, sync deassert.
- Inside the fabric: synchronous resets, and only on control-path flops.
- Datapath: bitstream initial values, no reset net.
- 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.