Reset synchronizer

Two flip-flops that assert a reset at once and release it only just after a clock edge. Why the release has to be synchronous, shown on a ring it can break.

Level
rtl
Code
reset-synchronizer
Checked
Verilator 5.052, Icarus Verilog 13.0, Yosys 0.66
Published
Authorship
Drafted with AI assistance
Status
In progress — published so it can be reviewed, not finished.

A reset synchronizer is two flip-flops that turn an asynchronous reset, arst_n, into one the rest of the design can use, rst_n. It passes an assertion straight through, at once and with no clock, and it lets the release through only just after a rising edge of clk, two edges after arst_n rose. Assertion may be asynchronous; release has to be synchronous, and the figure shows what goes wrong when it is not.

raw Released as it arrives

clk 0arst_n 1DQrstd=10DQrstd=11d=1d=1ab

synced Released through two flops

clk 0arst_n 11DQrstd=11DQrstd=11DQrstd=10DQrstd=11d=1d=1metarst_nab

Run plays this scenario from the start; driving an input continues from here.

Two circuits driven by the same inputs, raw and synced, under one waveform. Inputs: arst_n. clk runs at 8 ticks a period. It is built from 2 D flip-flops, 2 inverters. The waveform shows clk, arst_n, rst_b [raw], a [raw], b [raw], rst_n [synced], a [synced], b [synced].

Figure 1. Press Run, then choose Near an edge, which releases arst_n one tick before a rising edge: under [raw] a falls to 0 beside b's 0 and both stay there; under [synced] they start two edges later and run.

How it works, scenario by scenario

Both drawings carry the same small design: a ring of two flops that pass one token between them. a resets to 1 and b to 0, and at every edge each takes the other's value, so the rows a and b swap on every edge — as long as both leave reset on the same edge. b's flop gets its reset through a buffer, two inverters with a tick of delay each, standing in for the wires and buffers that carry a reset across a real chip; every flop takes one tick from its edge to its output. The figure runs in delay mode so that those ticks exist.

Mid-period holds arst_n low from tick 0, and every flop in both drawings is in reset before any edge has arrived: an asynchronous reset needs no clock to take hold. arst_n rises at tick 7. Under [raw], rst_b, the release as b's flop sees it, follows at 9, two ticks late — and both are before the edge at 12, so a and b leave reset together and swap from 13. Under [synced], rst_n rises at 21, just after the edge at 20 and two edges after the release, and its a and b swap from 29. Two edges of delay are what the synchronizer costs, and a release that catches ff1 at an edge can make it three, as the end of this section says.

Near an edge releases arst_n at tick 11, one tick before the edge at 12, and now the two ticks of skew straddle that edge: arst_n is high before it, rst_b only after it. Under [raw], a's flop leaves reset at 12 and takes b's value, which is the 0 b was reset to; b's flop is still in reset at that edge and keeps its 0. From tick 13 a and b are both 0. The token is gone, and nothing but another reset brings it back.

Under [synced] nothing is different from Mid-period. ff1 and ff2 take the same raw release, but rst_n still rises at 21, just after an edge, and its buffered copy, which no row draws, reaches b's flop at 23 — skewed by the same two ticks as rst_b under [raw], but five ticks clear of the edge at 28. Both flops leave reset at 28, and the ring runs.

Zero delay is the same release with no delay anywhere, as an RTL simulation sees it. Both flops of the raw ring are released at tick 11, both leave reset at the edge at 12, and the ring runs: without the delays the failure is not there to find. It lives in the time a release takes to arrive, which is exactly what a zero-delay simulation leaves out.

So the synchronizer does not remove the skew. It moves the release to just after an edge, where the skew has most of a clock period to settle before the next edge looks.

What the figure does not show is the other half of the reason. Leaving reset is sampling, like d is: the release has to arrive at least the flop's recovery time before an edge, or at least its removal time after it, and a release in between can leave the flop metastable. This engine does not check recovery or removal: a release next to an edge is simply sampled by the edge, and no flop in this figure goes X. The metastability it leaves out is why the synchronizer has two flops. ff1 is the one that can go metastable when arst_n is released near an edge, since its d is 1 and its reset value 0. Its output, meta, then has a whole clock period to settle before ff2 samples it at the next edge, and nothing else reads it. ff2 cannot be caught by its own release: at the edge that releases it, its d is meta, still at its reset value, so after that edge it is 0 whether it left reset there or not. What can still reach ff2 is a meta that has not settled a period later, and then rst_n can go metastable too: far rarer, but no more impossible than on the two-flop synchronizer. Short of that, the worst the raw release does is make rst_n rise an edge later than drawn, and every flop downstream reads the same rst_n, so they all move together.

The code

reset_synchronizer.sv
// A reset synchronizer: rst_n goes low the moment arst_n does, and comes back
// high only just after a rising edge of clk, two edges after arst_n rose.
//
// Reset style for this module: asynchronous, active low. arst_n asserts both
// stages at once, clock or no clock; its release reaches rst_n only through
// them, on edges of clk. Everything clocked by clk is reset by rst_n.
module reset_synchronizer (
    input  logic clk,
    input  logic arst_n,
    output logic rst_n
);

  // The stage that may go metastable: arst_n comes from outside clk's domain,
  // so nothing keeps its release away from this flop's edge.
  logic meta;

  always_ff @(posedge clk or negedge arst_n) begin
    if (!arst_n) begin
      meta <= 1'b0;
    end else begin
      meta <= 1'b1;
    end
  end

  // A clock period later meta has almost always settled, and rst_n changes
  // only just after an edge, like any flop's output. At the edge that releases
  // this stage its d is meta, still at its reset value: nothing to decide.
  always_ff @(posedge clk or negedge arst_n) begin
    if (!arst_n) begin
      rst_n <= 1'b0;
    end else begin
      rst_n <= meta;
    end
  end

endmodule

The reset style is stated at the top of the file and it is the page's subject: asynchronous, active low, asserted at once and released through the two stages. The two always_ff blocks are ff1 and ff2 in the drawing under [synced], and the 1 tied to ff1's input is meta <= 1'b1. Both blocks have arst_n in their sensitivity lists, which is what lets the assertion through without a clock; neither lets the release through except at an edge.

The ring has no code here, and nor does the drawing under [raw]: they are the design a reset is for, drawn to show what the release does to it. In a real design rst_n would reset every flop clocked by clk, and the ring stands for all of them.

Synthesis result

Yosys's generic synthesis turns reset_synchronizer.sv into 2 cells, 2 of them flip-flops. The cells are Yosys's own gate library, not an FPGA's or a standard-cell library's: the count measures the logic, not the area of any chip.

Cells after synth -nofsm, by type
CellWhat it isCount
$_DFF_PN0_D flip-flop on the rising edge, active-low asynchronous reset to 02
Total2

Lint by Verilator 5.052, the testbench by Icarus Verilog 13.0, synthesis by Yosys 0.66.

Icarus replayed one of this page's three scenarios, Zero delay, on this code, and all 102 values it compared match. It compared every row except rst_b, a and b, which no signal in the code carries. Mid-period and Near an edge are left out: they show gate delays, which this engine models and an RTL simulation does not.

The netlist before synthesis

What Yosys's prep reads in reset_synchronizer.sv, before any gate is chosen, drawn by netlistsvg 1.0.2.

$adffARSTCLKDQ$adffARSTCLKDQclkarst_nrst_n1

Pitfalls

Releasing a reset as it arrives. Near an edge is the whole failure: one release, reaching two flops on either side of an edge, starts them on different cycles and leaves the design in a state it can never otherwise be in. Mid-period is why it survives testing: the same circuit, released four ticks earlier, works, and so does a simulation without delays. A release that comes straight from a pin works most of the time, which is the worst way for a circuit to fail.

Anything but ff2 reading meta. meta is the stage allowed to be undecided. A gate, a flop or a port that reads it directly is reading a raw release again, as the two-flop synchronizer says of its own meta.

A release that waits for a clock. Assertion needs no clock, and release needs two edges of it. With clk stopped, rst_n stays low however long ago arst_n rose, and the design stays in reset until the clock runs. That is usually what is wanted; it has to be known.

One synchronizer for two clocks. "Synchronous" means synchronous to one clock. A design with two clock domains needs the release synchronized into each of them, by a synchronizer clocked by that domain's clock; rst_n from this one is exactly as asynchronous as arst_n to any other clock.

Check yourself

ff1 and ff2 take the raw arst_n themselves, the release the page calls dangerous. Why may they?
Because ff2 has nothing to decide at its own release, and ff1 has a whole period to settle before anything reads it. At the edge that releases ff2, its d is meta, which still holds the reset value 0, so after that edge ff2 is 0 whether it left reset there or not. ff1 is the one that can go metastable, or leave reset one edge late, because its d is 1; that is why its output, meta, gets a clock period to settle before ff2 samples it, and nothing else reads it. Short of a meta still undecided a period later, which is far rarer and not impossible, the worst the raw release can do is make rst_n rise one edge later than drawn, and every flop downstream reads the same rst_n, so they all move together.
Why may the reset be asserted asynchronously, when its release may not?
Because a flop going into reset does not sample anything: it goes to its reset value whenever the reset arrives, and a flop that gets there two ticks later still gets there. Leaving reset is different: the next edge decides what the flop does, so a release arriving on one side of an edge for one flop and on the other side for another starts them on different cycles.
In Near an edge the raw ring ends with a and b both 0. What gets it out of that state?
Only a reset. Each flop takes the other's value at every edge, so two zeros stay two zeros for ever. Nothing in the design notices that the token is gone: to the ring, two zeros are just another state.

Tags rtl flip-flop asynchronous-reset reset-synchronizer

Found an error? Send a correction