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
synced Released through two flops
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
// 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
endmoduleThe 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.
| Cell | What it is | Count |
|---|---|---|
$_DFF_PN0_ | D flip-flop on the rising edge, active-low asynchronous reset to 0 | 2 |
| Total | 2 | |
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.
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.