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.