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.

One Edge Inside the Windowrx falls 0.5 ns before the clock edge at 105 ns. One column per nanosecond.
0123456789101112131415161718192021222324252627282930313233343536373839clkrxs1 (first flop)reader A of s1reader B of s1s2 (second flop)edge at 105 nss1 settles
  1. Cycle 5

    rx changed 0.5 ns before the edge, inside the 1 ns window. The first flop goes to x.

  2. Cycle 15

    It settles 9.5 ns later, just before the next edge. Both readers of s1 sample it inside their window.

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

An Asynchronous Input Entering a Clocked ReceiverThe same input feeds two designs: logic reading the first flop, and logic reading a second flop.
An asynchronous input entering a clocked receiverThe same input feeds two designs: logic reading the first flop, and logic reading a second flop. component rx pin edges at any time component Flop 1 s1, may settle late component One-flop design two readers of s1 component Flop 2 s2, one clock later component Two-flop design two readers of s2 asyncread at onceone clock to settle
  1. The input arrives

    rx changes whenever the far end sends. Some edges land inside flop 1's window.

  2. Read at once

    If logic reads s1 directly, a late settle reaches it. Two readers can take different values.

  3. 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 flopValueWhat it means
Window W1 ns before the edgean input change this close to the edge, or an input still at x, makes q go to x
Settling timeseeded, P(t > x) = e^(−x/τ)long settles are rare, but never impossible
τ (TAU)2 nsfar larger than a real flop's, so failures show in thousands of edges
Clock to q TCO0.1 nsa 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 periodEdges inside the windowOne flop: late, splitTwo flops: late, split
10 ns1999 of 2000026, 160, 0
6 ns3358 of 20000281, 15326, 14
4 ns4979 of 200001146, 566271, 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 widthNo filter2-of-3 vote3-of-5 vote6 in a row
8 ns152000
15 ns2009300
25 ns200200990
45 ns2002002000
55 ns20020020093

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

LessonWhat you meet there
P6.3 The receiverthe UART receiver's 2-of-3 vote per bit and its eight-low start qualifier: both are glitch filters
P6.5 Errors, break and recoverythe vote twin: one low tick is ignored, two flip a bit
P7.3 SPI timingSPI sends its clock, so the window becomes a setup and hold budget at the pins
P8.7 I2C timingtSP, the spike width a Fast-mode input must reject, beside the other analog limits
P9.2 SDR frames and legacy I2Ca legacy target's 50 ns filter hides the fast I3C clock; the twin without it
P1 Protocol fundamentalson-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
  1. 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.

  2. Does a synchronizer remove metastability?

    No. It makes a late settle rare enough. A faster clock or a slower flop brings failures back.

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

RUNIcarus Verilog · in your browser
Edit the code, then run it
Expected: 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 times
Read 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

Predict the outputThe filter bench: the line idles high and is sampled every 10 ns. It sends 200 low spikes, each 8 ns wide, at seeded random times. What does the 2-of-3 vote count?
`timescale 1ns/1ps
module tb;
  reg clk = 0, rx = 1;                          // the line idles high
  always #5 clk = ~clk;                         // the receiver samples every 10 ns
  wire none, vote3, vote5, run6;
  vote_filter #(.M(1), .N(1)) f_none  (clk, rx, none);
  vote_filter #(.M(3), .N(2)) f_vote3 (clk, rx, vote3);
  vote_filter #(.M(5), .N(3)) f_vote5 (clk, rx, vote5);
  vote_filter #(.M(6), .N(6)) f_run6  (clk, rx, run6);
  // A spike passes when it pulls a filter's output low.
  int p_none = 0, p_vote3 = 0, p_vote5 = 0, p_run6 = 0;
  always @(negedge none) p_none++;
  always @(negedge vote3) p_vote3++;
  always @(negedge vote5) p_vote5++;
  always @(negedge run6) p_run6++;
  reg [31:0] seed = 32'd20261108;
  // n low spikes of the given width, at seeded random times 300 to 500 ns apart.
  task automatic spikes(input real width, input int n);
    repeat (n) begin
      seed = seed * 32'd1664525 + 32'd1013904223;
      #(300.0 + (seed >> 8) / 16777216.0 * 200.0);
      rx = 0; #(width); rx = 1;
    end
    #200;
  endtask
  task automatic clear(); p_none = 0; p_vote3 = 0; p_vote5 = 0; p_run6 = 0; endtask
  initial begin
    spikes(8, 200);                             // 200 spikes, each 8 ns wide
    $display("2-of-3 passed %0d of 200", p_vote3);
    $finish;
  end
endmodule
Read included file: vote_filter.sv
`timescale 1ns/1ps
// vote_filter: samples d on every clock edge and keeps the last M samples. The output goes to 1 when at
// least N of them are 1 and to 0 when at least N are 0; otherwise it holds.
//   M = 1, N = 1: no filter.  M = 3, N = 2: a 2-of-3 vote.  M = 5, N = 3: 3-of-5.  M = N: N in a row.
module vote_filter #(parameter int M = 3, N = 2) (input wire clk, d, output reg q = 1);
  reg [7:0] last = 8'hff; int ones;
  always @(posedge clk) begin
    last = {last[6:0], d};
    ones = 0;
    for (int i = 0; i < M; i++) ones += last[i];
    if (ones >= N) q <= 1; else if (M - ones >= N) q <= 0;
  end
endmodule
Spot the bugEach option builds the path from the asynchronous rx to rx_s out of the lesson's meta_flop (10 ns clock, 1 ns window, τ 2 ns). Two readers take rx_s. The bench sends 20,000 edges and fails if the readers ever disagree. Which path fails?
Read 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
ConceptA team moves its receiver from a 10 ns clock to a 4 ns clock and keeps its two-flop synchronizer. What happens to the failure rate, and why?
ConceptA 6-in-a-row filter on a line sampled every 10 ns. Which statement about it is right?

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