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

sig_in 1clk 0DQd=11raw_out 1

2 DFF Through a synchronizer

sig_in 1clk 0DQd=1lines 17–191DQd=11sync_out 1meta

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

two_flop_synchronizer.sv
// 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

endmodule

The 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.

Cells after synth -nofsm, by type
CellWhat it isCount
$_DFF_P_D flip-flop on the rising edge2
Total2

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.

clksig_insync_out

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.

Tags rtl flip-flop metastability clock-domain-crossing

Found an error? Send a correction