Crossing into the Receiver's Clock
Sample an asynchronous serial input with a clocked receiver, watch one flop go metastable, count what one flop and two flops let through, and filter spikes before they become false edges.
- Lesson
- P5.9
- Time
- 25 min
- Runs on
- Your browser
- Check
- 4 questions
A debug console worked for months, then hung about once a week after a burst of typing. Its start detector and its bit counter each read the RX pin directly, with no synchronizer in front. Once in a long while the pin changed right at a clock edge, the two disagreed, and the state machine locked up.
- Cycle 5
rx changed 0.5 ns before the edge, inside the 1 ns window. The first flop goes to x.
- Cycle 15
It settles 9.5 ns later, just before the next edge. Both readers of s1 sample it inside their window.
- Cycle 15
The two readers of s1 disagree for a whole clock. The second flop also went to x, but it had a whole clock to settle before anything read it.
Measured on the lesson's model. Reader A holds 1 and reader B holds 0 for one clock: a state register and a bit counter that no longer agree.
A Sample Inside the Window
A flop needs its input steady for a short window around the clock edge. A serial input owes nothing to the receiver's clock, so sooner or later it changes inside that window. The flop then sits between 0 and 1 and settles late, to either value.
- The input arrives
rx changes whenever the far end sends. Some edges land inside flop 1's window.
- Read at once
If logic reads s1 directly, a late settle reaches it. Two readers can take different values.
- Give it a clock
Flop 2 samples s1 one clock later. Only a settle longer than a whole clock gets through.
Read the diagram as text
- rx pin: edges at any time
- Flop 1: s1, may settle late
- One-flop design: two readers of s1
- Flop 2: s2, one clock later
- Two-flop design: two readers of s2
- rx pin → Flop 1: async
- Flop 1 → One-flop design: read at once
- Flop 1 → Flop 2: one clock to settle
- Flop 2 → Two-flop design
The lesson's bench builds both designs from one model flop, meta_flop, on one 10 ns clock.
| Model flop | Value | What it means |
|---|---|---|
Window W | 1 ns before the edge | an input change this close to the edge, or an input still at x, makes q go to x |
| Settling time | seeded, P(t > x) = e^(−x/τ) | long settles are rare, but never impossible |
τ (TAU) | 2 ns | far larger than a real flop's, so failures show in thousands of edges |
Clock to q TCO | 0.1 ns | a clean sample appears on q this long after the edge |
meta_flop.sv in the example. A reader is the same flop with a tiny τ: it only decides 0 or 1.
One Flop or Two
The example sends 20000 edges at seeded random times into a 10 ns clock. Each design has two readers. A read is late when the input was still settling; a split is a clock in which the two readers disagree.
One flop: logic reads s1
meta_flop sync1 (clk, rx, s1);
meta_flop read_a1 (clk, s1, a1);
meta_flop read_b1 (clk, s1, b1);
Measured: one flop: readers sampled a settling input 26 times, split 16 times.
Two flops: logic reads s2
meta_flop sync1 (clk, rx, s1);
meta_flop sync2 (clk, s1, s2);
meta_flop read_a2 (clk, s2, a2);
Measured: two flops: readers sampled a settling input 0 times, split 0 times.
| Clock period | Edges inside the window | One flop: late, split | Two flops: late, split |
|---|---|---|---|
| 10 ns | 1999 of 20000 | 26, 16 | 0, 0 |
| 6 ns | 3358 of 20000 | 281, 153 | 26, 14 |
| 4 ns | 4979 of 20000 | 1146, 566 | 271, 146 |
Measured: the same seeded edges at three clock periods (p59.example, p59.clocks6, p59.clocks4). A faster clock leaves less time to settle, and two flops start to fail too.
A second flop does not prevent metastability. It buys the first flop one whole clock to settle, and long settles get exponentially rarer. The standard form of the failure rate writes this as MTBF = e^(t_r / τ) / (T_0 · f_clk · f_data), where t_r is the settling time the design allows and τ and T_0 belong to the flop.
Each extra flop adds about one clock period to t_r. Use the formula to compare designs; the numbers for a real flop come from its library, never from this model.
Spikes and the Glitch Filter
A synchronizer makes a clean 0 or 1 out of every sample, including a sample of a noise spike. A filter decides how many samples must agree before the output changes. The bench samples every 10 ns and sends 200 seeded low spikes per width.
| Spike width | No filter | 2-of-3 vote | 3-of-5 vote | 6 in a row |
|---|---|---|---|---|
| 8 ns | 152 | 0 | 0 | 0 |
| 15 ns | 200 | 93 | 0 | 0 |
| 25 ns | 200 | 200 | 99 | 0 |
| 45 ns | 200 | 200 | 200 | 0 |
| 55 ns | 200 | 200 | 200 | 93 |
Measured: spikes that pulled each filter's output low, of 200 (p59.filter). A clean edge reaches the output 0, 1, 2 and 5 clocks after its first low sample.
A spike shorter than one sample period covers at most one sample, so a 2-of-3 vote never passes one: 0 of 200 at 8 ns. In general, N in a row rejects every spike shorter than N − 1 periods and delays every real edge by N − 1 clocks.
An I2C Fast-mode input must suppress spikes shorter than 50 ns (UM10204 §5.1; §6.1, Table 10, tSP and note 8). At a 10 ns sample clock that is six in a row: 0 of 200 spikes pass at 45 ns, 93 at 55 ns.
Where It Shows Up
| Lesson | What you meet there |
|---|---|
| P6.3 The receiver | the UART receiver's 2-of-3 vote per bit and its eight-low start qualifier: both are glitch filters |
| P6.5 Errors, break and recovery | the vote twin: one low tick is ignored, two flip a bit |
| P7.3 SPI timing | SPI sends its clock, so the window becomes a setup and hold budget at the pins |
| P8.7 I2C timing | tSP, the spike width a Fast-mode input must reject, beside the other analog limits |
| P9.2 SDR frames and legacy I2C | a legacy target's 50 ns filter hides the fast I3C clock; the twin without it |
| P1 Protocol fundamentals | on-chip handshakes, where both sides share a clock and no synchronizer is needed |
The same two ideas return in every serial module: settle the sample, then filter it.
Asked in interviews: synchronizers
Why two flops and not one?
The second flop gives the first a whole clock to settle before any logic reads it. Failures fall exponentially with that time.
Does a synchronizer remove metastability?
No. It makes a late settle rare enough. A faster clock or a slower flop brings failures back.
Can you synchronize a multi-bit bus with two flops per bit?
No. Bits settle on different clocks and the word tears. Synchronize one bit, such as a valid or a Gray-coded pointer.
Try It
The first line counts the edges and how many landed inside the first flop's 1 ns window. The next two lines are the twin: the readers of s1 sampled a settling input 26 times and split 16 times; the readers of s2 never did.
Change the clock to always #3 clk = ~clk; and pass .T(6.0) to every flop: the two-flop line stops reading 0. Then try .TAU(1.0) on sync1 and sync2, a faster flop.
20000 edges at a 10 ns clock, 1999 sampled inside the window one flop: readers sampled a settling input 26 times, split 16 times two flops: readers sampled a settling input 0 times, split 0 timesRead included file: meta_flop.sv
`timescale 1ns/1ps
// meta_flop: a D flip-flop with a metastability window. A teaching model, not a cell library.
// - d changed less than W ns before the clock edge, or d is still unresolved (x): q goes to x and
// resolves to a seeded random 0 or 1 after a seeded random time t, with P(t > x) = exp(-x / TAU).
// - otherwise q copies d, TCO ns after the edge.
// T is the clock period: the next edge samples again, so a resolution is cut off there.
// TRES > 0 fixes the resolution time (used for the one-event figure).
module meta_flop #(parameter real T = 10.0, W = 1.0, TCO = 0.1, TAU = 2.0, TRES = 0.0,
parameter int SEED = 1)
(input wire clk, d, output reg q = 0);
realtime changed = -1.0e9; // when d last changed
reg [31:0] seed = SEED;
int samples = 0, meta = 0; // edges seen, and edges that sampled inside the window
real t; reg v;
function automatic real uniform(); // (0, 1]
seed = seed * 32'd1664525 + 32'd1013904223;
return ((seed >> 8) + 1.0) / 16777216.0;
endfunction
always @(d) changed = $realtime;
always @(posedge clk) begin
samples++;
if (d === 1'bx || $realtime - changed < W) begin
meta++;
v = uniform() < 0.5;
t = TRES > 0 ? TRES : -TAU * $ln(uniform());
if (t > T - 0.002) t = T - 0.002;
q <= #(TCO) 1'bx;
q <= #(TCO + t) v;
end else q <= #(TCO) d;
end
endmodule
Check Yourself
Remember
- An input that owes nothing to the receiver's clock will change inside a flop's window sooner or later, and that flop settles late to either value.
- Two flops give the first one a whole clock to settle; failures fall exponentially with that time but never reach zero.
- Logic that reads a single flop directly can see two values of one sample: the readers split.
- N samples in a row reject every spike shorter than N − 1 sample periods and delay every real edge by N − 1 clocks.
- An I2C Fast-mode input must reject spikes shorter than 50 ns (UM10204 Table 10, tSP).