Reset: assert, release, and what RTL cannot show

Tell an asynchronous reset from a synchronous one by what they do between clock edges, see why the release is the dangerous moment, and add the reset synchronizer that makes every flop leave reset in the same cycle.

Lesson
R2.2
Time
30 min
Runs on
Your browser
Check
5 questions

A block came out of reset one cycle later than its neighbour, and a handshake that expected both to be ready in the same cycle hung. The reset wire was released just before a clock edge: one flop saw the release before that edge, the other after it. Every RTL simulation had passed, because a simulator releases the reset at one exact instant for every flop.

One release, two flopsArrows show signals. The reset release reaches flop A over a short wire and flop B over a long one, both just before the same clock edge.
One release, two flopsArrows show signals. The reset release reaches flop A over a short wire and flop B over a long one, both just before the same clock edge. manager rst_n pin released near edge k manager clk edge k, then k+1 bridge Flop A near the pin bridge Flop B far end of the wire completer Handshake needs A and B readytogether short wirelong wirereadyready
  1. A sees the release in time

    The release reaches A well before edge k. A leaves reset at edge k.

    Follow the calls, values and waits
    1. data release: rst_n pin → Flop A
  2. B sees it inside its window

    The release reaches B a moment before edge k, inside its recovery window: the short time before an edge in which a flop cannot take a reset change cleanly. B may leave reset at edge k or k+1, or sit metastable, hovering between 0 and 1, for a while.

    Follow the calls, values and waits
    1. data release: rst_n pin → Flop B
  3. The handshake hangs

    A is ready at edge k and B at edge k+1. Logic that expected both in the same cycle waits for an event that already passed.

    Follow the calls, values and waits
    1. data ready: Flop A → Handshake
    2. data ready: Flop B → Handshake
Read the diagram as text
  • rst_n pin: released near edge k
  • clk: edge k, then k+1
  • Flop A: near the pin
  • Flop B: far end of the wire
  • Handshake: needs A and B ready together
  • rst_n pin → Flop A: short wire
  • rst_n pin → Flop B: long wire
  • clk → Flop A
  • clk → Flop B
  • Flop A → Handshake: ready
  • Flop B → Handshake: ready

Drawn from the timing rules, not from a simulation. A zero-delay RTL simulation releases every flop at the same instant.

Assertion: at once or at the next edge

A reset has two moments. Assertion puts every register into its reset state. Release (also called removal) lets the registers follow their inputs again. The two reset styles differ in when assertion acts.

Asynchronous reset

always_ff @(posedge clk or negedge rst_n)
  if (!rst_n) a_q <= 4'd0;
  else        a_q <= a_q + 4'd1;

rst_n is in the event list, so the block runs the moment rst_n falls, with or without a clock. Cell: $_DFF_PN0_.

Synchronous reset

always_ff @(posedge clk)
  if (!rst_n) s_q <= 4'd0;
  else        s_q <= s_q + 4'd1;

The block runs only at a clock edge, so rst_n is just another data input. Cell: $_SDFF_PN0_.

An asynchronous reset works before the clock runs, which is what a chip needs at power-up. A synchronous reset needs edges, and it needs reset to be low at one of them. The lab bench shows what happens when it is not.

A 5 ns reset pulse between two edgesThe lab design without a synchronizer. One line per edge. The bench pulls rst_n low 4 ns after edge 5 and lets go 1 ns before edge 6.
012345678910clkrst_na_q0123412345s_q0123456789pulse before e6
  1. Cycle 2

    The power-on reset was released 1 ns after edge 1. Both counters count from edge 2.

  2. Cycle 5

    Both counters are at 4. Four nanoseconds later rst_n falls.

  3. Cycle 6

    During the pulse the bench printed a_q=0 s_q=4: the asynchronous counter cleared at once. The pulse ended before edge 6, so s_q never saw it and counted on to 5.

  4. Cycle 10

    The two counters now disagree for good: 5 against 9. The per-edge samples of rst_n never show it low.

Measured on the lab bench. Verilator's lint also flags the mix: %Warning-SYNCASYNCNET: Signal flopped as both synchronous and async.

Release: the moment simulation cannot show

Assertion is safe in both styles. Release is where an asynchronous reset gets dangerous. A flop with an asynchronous reset has a recovery time: the release must arrive at least that long before the next rising edge. It also has a removal time: the release must not come sooner than that after the edge. These are the reset-side names for setup and hold.

Release inside that window and the flop may leave reset a cycle early or late, or go metastable: its output hovers between 0 and 1 for a while before settling. Two flops of one block can land on different sides of the window, as in the hook.

The cure is the reset synchronizer. The pin still asserts the domain's reset at once. Its release goes through two flops clocked by the domain's clock, so it reaches every register just after an edge, a whole period before the next one.

reset_sync: assert at once, release two edges laterArrows show signals. rst_n_in clears both flops asynchronously. The first flop's D input is tied to 1.
reset_sync: assert at once, release two edges laterArrows show signals. rst_n_in clears both flops asynchronously. The first flop's D input is tied to 1. manager rst_n_in from the pin manager 1'b1 constant bridge q1 first flop bridge rst_n_out second flop completer a_q, s_q every flop of thedomain async clearasync clearDDrst_n_sync
  1. Assertion

    rst_n_in falls. Both flops clear at once, with no clock, and the domain is in reset immediately. In the lab this is the e5+5ns line, a_q=0.

    Follow the calls, values and waits
    1. data reset (0): rst_n_in → rst_n_out
    2. data reset (0): rst_n_out → a_q, s_q
  2. First edge after release

    q1 captures the 1. If the release fell inside q1's window, q1 may be late or metastable; it has a whole period to settle before anything reads it.

    Follow the calls, values and waits
    1. data release (1): 1'b1 → q1
  3. Second edge

    rst_n_out takes the 1 from q1. It changes just after an edge, far from every window in the domain.

    Follow the calls, values and waits
    1. data release (1): q1 → rst_n_out
  4. Third edge

    Every flop in the domain sees the release at the same edge and counts. In the lab both counters count from edge 4 after power-on, and from edge 8 after the pulse.

    Follow the calls, values and waits
    1. data release (1): rst_n_out → a_q, s_q
Read the diagram as text
  • rst_n_in: from the pin
  • 1'b1: constant
  • q1: first flop
  • rst_n_out: second flop
  • a_q, s_q: every flop of the domain
  • rst_n_in → q1: async clear
  • rst_n_in → rst_n_out: async clear
  • 1'b1 → q1: D
  • q1 → rst_n_out: D
  • rst_n_out → a_q, s_q: rst_n_sync

The synchronizer is two $_DFF_PN0_ flops on this flow. Its first flop can still go metastable; the second gives it a period to settle.

Measured on the lab benchNo synchronizerWith reset_sync
First counting edge after the power-on releaseedge 2edge 4
Counters at edge 6, after the 5 ns pulsea_q=1, s_q=5a_q=0, s_q=0
First counting edge after the pulseedge 6, a_q from 0 and s_q from 4edge 8, both from 0
Synthesis on this flow20 cells, 8 flops, 3 levels of logic22 cells, 10 flops, 3 levels of logic

The synchronizer costs two flops and two edges of release delay. It also stretches the short pulse, so the synchronous counter sees it too.

Three rules for reset code

  1. Reset first, alone

    The reset test is the first if of the block, and everything else sits in its else.

  2. Reset every register of the block

    A register left out of the reset branch is unknown until something loads it. Leave one out only when the brief says so, and give it its own block.

  3. Never treat the reset as data

    Do not read rst_n in the non-reset branch, and do not gate or derive it with logic inside the domain.

Design brief
PortDirectionWidthMeaning
clkinput1clock
rst_ninput1from the reset pin, active-low, may change at any time
a_qoutput[3:0]free-running counter with an asynchronous reset, plus one at every rising edge
s_qoutput[3:0]free-running counter with a synchronous reset, plus one at every rising edge
Reset
a_q asynchronous, s_q synchronous, both through one reset_sync
Latency
after rst_n rises, the counters count from the third rising edge
Parameters
Memory: 8 counter flops plus 2 synchronizer flops
Corner cases
a reset pulse shorter than a clock period; a release just before an edge

The lab ships without the synchronizer, and reset_sync.sv sits beside it, read-only. Run it and find the pulse that cleared one counter only. Then put the synchronizer in and run again.

Asked in interviews
  1. Synchronous or asynchronous reset: what are the trade-offs?

    Asynchronous resets act without a clock and keep the reset off the data path, but their release must be synchronized. Synchronous resets are plain data inputs that filter short glitches, but they need a running clock and a pulse that spans an edge.

  2. What are recovery and removal time?

    They are the reset-side setup and hold of a flop. The release must come at least the recovery time before the next edge, and no sooner than the removal time after the previous one. Violate them and the flop may leave reset in the wrong cycle or go metastable.

  3. Why synchronize the release of an asynchronous reset, and can RTL simulation check it?

    So every flop of the domain leaves reset at the same edge, with the only metastable risk confined to the synchronizer's first flop. RTL simulation cannot check it: it has no recovery window and no metastability, so release timing needs the synchronizer by rule and static timing.

Try it

Without a synchronizer, both counters count from edge 2. The pulse after edge 5 clears a_q at once and misses s_q, so from edge 6 the counters disagree. Synthesize: 20 cells, four $_DFF_PN0_ flops for a_q and four $_SDFF_PN0_ for s_q.

Replace the assign line with reset_sync u_rs (.clk(clk), .rst_n_in(rst_n), .rst_n_out(rst_n_sync)); and run again. Both counters now count from edge 4. The pulse still clears a_q at once, sync stays low through edge 6, and both counters count together from edge 8. Synthesis grows to 22 cells and 10 flops.

Then delete the reset from the s_q block, leaving s_q <= s_q + 4'd1;. Every line prints s_q=x: an unknown value plus one is still unknown.

RTLIcarus Verilog · in your browser · Lint: Verilator service · Synthesize: Yosys service
Edit the design, then Run, Lint or Synthesize
Expected: e0 rst_n=0 sync=0 a_q=0 s_q=0 e1 rst_n=0 sync=0 a_q=0 s_q=0 e2 rst_n=1 sync=1 a_q=1 s_q=1 e3 rst_n=1 sync=1 a_q=2 s_q=2 e4 rst_n=1 sync=1 a_q=3 s_q=3 e5 rst_n=1 sync=1 a_q=4 s_q=4 e5+5ns rst_n=0 sync=0 a_q=0 s_q=4 e6 rst_n=1 sync=1 a_q=1 s_q=5 e7 rst_n=1 sync=1 a_q=2 s_q=6 e8 rst_n=1 sync=1 a_q=3 s_q=7 e9 rst_n=1 sync=1 a_q=4 s_q=8 e10 rst_n=1 sync=1 a_q=5 s_q=9

Original design · 20 cells · 8 flip-flops · 0 latches · 3 logic levels. Synthesis uses Yosys; counts depend on the toolchain.

Read included file: reset_sync.sv
// reset_sync.sv: asynchronous assertion, synchronous release (read-only).
// rst_n_in may fall at any time and the output falls with it; when rst_n_in rises, the output rises
// two rising edges later, so every flop in the domain leaves reset in the same cycle.
module reset_sync (input logic clk, input logic rst_n_in, output logic rst_n_out);
  logic q1;
  always_ff @(posedge clk or negedge rst_n_in)
    if (!rst_n_in) begin q1 <= 1'b0; rst_n_out <= 1'b0; end
    else begin q1 <= 1'b1; rst_n_out <= q1; end
endmodule
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
  logic clk = 0;
  always #5 clk = ~clk;                   // rising edges at 5, 15, 25, ...
  int edge_n = -1;
  always @(posedge clk) edge_n = edge_n + 1;
  logic       rst_n = 1;
  logic [3:0] a_q, s_q;
  rst_demo dut (.clk(clk), .rst_n(rst_n), .a_q(a_q), .s_q(s_q));
  // Advance to the next rising edge, wait for the design to settle, then print one line.
  task automatic tick();
    @(posedge clk); #1;
    $display("e%0d rst_n=%b sync=%b a_q=%0d s_q=%0d", edge_n, rst_n, dut.rst_n_sync, a_q, s_q);
  endtask
  initial begin
    #1 rst_n = 0;                           // power-on reset, asserted before edge 0
    tick(); tick(); rst_n = 1;              // released 1 ns after edge 1
    repeat (4) tick();                      // e2 .. e5
    #3 rst_n = 0;                           // a 5 ns reset pulse: falls 4 ns after edge 5 ...
    #1 $display("e5+5ns rst_n=%b sync=%b a_q=%0d s_q=%0d", rst_n, dut.rst_n_sync, a_q, s_q);
    #4 rst_n = 1;                           // ... and rises 1 ns before edge 6
    repeat (5) tick();                      // e6 .. e10
    $finish(0);                             // quiet: no "$finish called" line
  end
endmodule

Check yourself

Predict the outputTwo counters, one with an asynchronous and one with a synchronous reset, count three edges from 0. Then reset falls between two edges, and the program prints 1 ns later. What is the output line?
module tb;
  logic clk = 0, rst_n = 0;
  logic [3:0] a, s;
  always_ff @(posedge clk or negedge rst_n)     // asynchronous reset
    if (!rst_n) a <= 4'd0; else a <= a + 4'd1;
  always_ff @(posedge clk)                      // synchronous reset
    if (!rst_n) s <= 4'd0; else s <= s + 4'd1;
  initial begin
    #5 clk = 1; #5 clk = 0;                     // one edge in reset: a = 0, s = 0
    rst_n = 1;
    repeat (3) begin #5 clk = 1; #5 clk = 0; end
    #2 rst_n = 0;                               // reset falls between edges
    #1 $display("a=%0d s=%0d", a, s);
    $finish(0);
  end
endmodule
Spot the bugEach option is the body of a block with a counter c and a sticky flag hit. The bench applies one edge in reset, then two edges with en = 1, and expects c = 2 and hit = 0. Which body fails?
ConceptAn asynchronous reset already clears every flop at once. What does adding a reset synchronizer protect against?
ConceptAt power-up the clock is not running yet. Which reset style puts the registers in their reset state during that time?
Read the waveformThis is the lab bench with the synchronizer in place. The bench pulses rst_n low for 5 ns between edges 5 and 6. Which reading of the trace is right?
The lab design with reset_sync
012345678910clkrst_nsynca_q0120123s_q0120123

One value per edge, printed 1 ns after it. sync is rst_n_sync, the synchronizer's output.

Remember

  • An asynchronous reset acts the moment it is asserted, without a clock; a synchronous reset acts only at a rising edge, so a pulse between edges can miss it.
  • The release is the dangerous moment: inside a flop's recovery or removal window it may leave reset a cycle early or late, or go metastable.
  • A zero-delay RTL simulation cannot show recovery, removal or metastability; the reset synchronizer is added by rule.
  • reset_sync asserts at once and releases two edges later, so every flop of the domain leaves reset in the same cycle.
  • Put the reset test first, reset every register of the block, and never use the reset as data.