Two-flop synchronizer
One asynchronous input into a single flip-flop and into two, side by side. What metastability looks like, and what the second flop actually buys.
- Level
- rtl
- Code
- two-flop-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 two-flop synchronizer is two flip-flops in a row on the receiving clock, and nothing else. The bit it carries comes from somewhere that does not use that clock, so it can change at any moment — including in the window around a rising edge, where a flop is not guaranteed to decide anything. The second flop is there to give the first one a whole clock period to finish deciding before the rest of the design looks.
1 DFF Straight into the design
2 DFF Through a synchronizer
Run plays this scenario from the start; driving an input continues from here.
Two circuits driven by the same inputs, 1 DFF and 2 DFF, under one waveform. Inputs: sig_in. Outputs: raw_out. clk runs at 8 ticks a period. It is built from 1 D flip-flop. The waveform shows clk, sig_in, raw_out [1 DFF], meta [2 DFF], sync_out [2 DFF].
Figure 1. Press Run. Both drawings get the same sig_in and the same clk. The bracket over sig_in is the first flop's setup and hold window; the first two scenarios put sig_in inside it and resolve it each way.
How it works, scenario by scenario
Both drawings are fed by one pad and one clock, so every difference in the waveform is a difference between one flop and two.
In the first scenario, In the window, sig_in rises exactly on the second rising edge —
inside the window the bracket draws. Both first flops are caught by it, and from tick 13
raw_out and meta are X.
That X is the point of the page: it does not mean noise or a broken wire. The flop has some
real voltage on its output; it has simply not fallen to a rail yet, and the simulator refuses
to say which rail it will be.
Three ticks later it falls. In this scenario it falls back to the 0 it already held, and
sig_in's rise is picked up cleanly at the next edge instead, so meta goes high at tick 21
and sync_out follows at 29. Press The other way: identical stimulus, identical circuit,
and the flop falls to the new value instead — meta is 1 from tick 16 and sync_out rises at
21, a whole clock period earlier. Nothing in the model chose between those two scenarios except
the scenario itself, which is exactly how much a design gets to know about it.
Now watch the two drawings against each other during the X. raw_out carries it: for three
ticks, everything reading that signal is reading an undecided flop, and each gate that reads it
may read it differently. sync_out never moves, because ff2 does not look until the next
rising edge, by which time meta is 0 or 1 like any other signal.
Clear of the edge is the same circuit with sig_in rising in the middle of a period. No
window is violated, nothing goes X — and sync_out still rises at tick 29, one period behind
meta. That period is not the price of metastability; it is the price of the second flop, paid
on every crossing whether or not anything went wrong.
So the trade is not "X versus no X". It is an unknown value traded for an unknown cycle:
after the synchronizer, sync_out is always a clean 0 or 1, and the only thing left unknown is
whether it arrived at this edge or the next one.
The code
// A two-flop synchronizer: one bit from another clock domain, sampled twice
// before anything in clk's domain is allowed to read it.
//
// Reset style for this module: none. Both stages come up unknown and flush
// within two clock periods. Give them a reset only when something downstream
// has to hold a known value before that.
module two_flop_synchronizer (
input logic clk,
input logic sig_in,
output logic sync_out
);
// The stage allowed to be metastable: sig_in belongs to another clock
// domain, so nothing keeps its changes out of this flop's setup window.
logic meta;
always_ff @(posedge clk) begin
meta <= sig_in;
end
// A whole clock period later, meta has settled. Which way it settled is not
// knowable here, and the rest of the design is written so that it need not be.
always_ff @(posedge clk) begin
sync_out <= meta;
end
endmoduleThe two always_ff blocks are the two flops in the second drawing, and there is deliberately
nothing between them. The reset style is stated at the top of the file and it is "none": both
stages come up unknown — the waveform holds sync_out at X until two rising edges have gone
by — and the module needs no reset to reach a known value, only a running clock.
The first drawing has no code behind it at all, which is the honest way to draw it. It is not a different module — it is what the design looks like when this module was not written.
Synthesis result
Yosys's generic synthesis turns two_flop_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_P_ | D flip-flop on the rising edge | 2 |
| Total | 2 | |
Lint by Verilator 5.052, the testbench by Icarus Verilog 13.0, synthesis by Yosys 0.66.
None of this page's scenarios can be replayed in Icarus: two show metastability and one shows gate delays, which this engine models and an RTL simulation does not.
The netlist before synthesis
What Yosys's prep reads in two_flop_synchronizer.sv, before any gate is chosen, drawn by netlistsvg 1.0.2.
Pitfalls
Anything else reading meta. The moment ff1's output feeds a gate, a second flop
somewhere else, or an output port, that path is the first drawing again. meta belongs to
ff2 alone.
Logic between the stages. A gate inserted between ff1 and ff2 spends part of the period
ff1 needs for settling, and the second flop is worth only the time it leaves the first one.
Synchronize first, then compute.
One bit at a time. This circuit is for a level on a single wire. Per-bit synchronizers on a bus do not make the bus safe, because each bit can resolve its own way in the same cycle. Nor do they make a short pulse safe: a synchronizer does not lengthen anything, and a pulse narrower than the receiving clock period can be missed entirely. A pulse synchronizer turns the pulse into a level first.
What the figure does not model. The three ticks ff1 spends undecided are a fixed number
in the model, because the engine is deterministic and has to draw something. Silicon has no
such fixed number: the settling time has no guaranteed upper bound, which is why a second flop
reduces the chance of trouble rather than removing it, and why the useful question is how often
it fails and not whether it can.
Check yourself
- The second flop is the same kind of flop as the first. Why does adding it help?
- Because of when it looks, not what it is. ff2 samples meta a whole clock period after ff1 was disturbed, and a period is long enough that ff1 has settled to 0 or to 1 by then. The second flop buys the first one time; it does not make metastability impossible, and ff2 can be caught by it too — far more rarely.
- The two violated scenarios end with the same sync_out. What is different between them, and what does that difference cost?
- sync_out rises one clock period apart — at tick 21 in one and at tick 29 in the other, from the same input. The value is clean either way; what the design cannot know is which cycle it arrives in. Everything downstream has to be written so that one cycle either way does not matter.
- Can a 4-bit bus be synchronized by putting one of these on each bit?
- No. Each bit resolves on its own, so the destination can latch some bits old and some new — a value that was never sent. A multi-bit crossing needs a handshake, a FIFO, or a gray code, so that the data is held still while it is read or only one bit changes at a time.