The two-flop synchronizer and what it cannot carry
Know what one two-flop synchronizer carries safely, see a multi-bit count torn by four of them, and carry the count with a Gray code instead.
- Lesson
- R6.2
- Time
- 30 min
- Runs on
- Your browser
- Check
- 5 questions
A 4-bit count crossed into another clock domain through four two-flop synchronizers, one per bit. When the count went from 0111 to 1000, the receiver read 1100 for one cycle, a value the counter never held. Every synchronizer worked; the bus did not.
x is the bus as the sender drives it; rx is the count after the synchronizers.- Cycle 2
Edge 11: the sender has just gone from 0111 to 1000. All four bits changed at once.
- Cycle 3
Edge 12: the receiver reads 12, binary 1100. Bits 0, 1 and 3 arrived in time for edge 11; bit 2 did not.
- Cycle 4
Edge 13: a cycle later every bit agrees again, and the receiver reads 8.
Measured on the lab bench. The receiver lags the sender by its two synchronizer flops, so a sample taken at edge 11 shows at edge 12.
Why the bits land in different cycles
In silicon, the four bit wires never arrive at exactly the same instant: wires differ in length and load. The lab bench models that by giving each bit wire its own delay, 0.4, 1.2, 2.8 and 2.0 ns. No metastability model is needed. Simulator note: zero-delay RTL never shows metastability, and the four wire delays are a model of skew, not measured silicon.
- Three bits make edge 11
Edge 11 of clk is at 115 ns. Bits 0, 1 and 3 have their new values by then.
- Bit 2 misses it
Bit 2 arrives at 115.55 ns, after the edge, so its synchronizer still holds the old 1.
- A torn value
The receiver sees new 1, 0, 0 for bits 3, 1, 0 and old 1 for bit 2: 1100. The count never held 12 here.
Read the diagram as text
- count_tx: 0111 to 1000 at 112.75 ns
- bit 0 sync: arrives 113.15 ns
- bit 1 sync: arrives 113.95 ns
- bit 3 sync: arrives 114.75 ns
- bit 2 sync: arrives 115.55 ns
- count_q: reads 1100 at edge 12
- count_tx → bit 0 sync: 0.4 ns
- count_tx → bit 1 sync: 1.2 ns
- count_tx → bit 3 sync: 2.0 ns
- count_tx → bit 2 sync: 2.8 ns
- bit 0 sync → count_q
- bit 1 sync → count_q
- bit 3 sync → count_q
- bit 2 sync → count_q
Times from the lab bench. With metastability added, the same thing can happen even with perfectly matched wires: each first flop settles on its own.
A two-flop synchronizer gives a bit a clean value and a known timing in the new clock. It cannot say in which receiving cycle the change lands. For one bit that does not matter. For several bits it means each may land in a different cycle, and the receiver reads a mixture of old and new: a torn value.
What may cross a two-flop synchronizer
| Signal | Through one two-flop synchronizer per bit? | Why |
|---|---|---|
| One bit that holds each value for at least two receiving cycles | yes | the job it was built for |
| A multi-bit binary count or bus | no | bits that change together can land in different cycles |
| A Gray-coded count that steps by one | yes | only one bit changes per step, so a read is the old or the new value |
| A bus held still, with a synchronized valid flag | only the flag | the data bits never change while they are read (the multi-cycle path scheme) |
| A pulse shorter than the receiving period | no | an edge may never see it (R6.3) |
A synchronizer settles one bit. Everything else is about making sure only one bit is uncertain at a time.
One bit per synchronizer
Never put two bits that change together through separate synchronizers and use them as one value.
No logic between the two flops
Logic there eats the settle time of the first flop, which is the whole point of the second.
Send from a flop
Drive the crossing wire straight from a flop in its own domain. Logic there can glitch, and every glitch is another chance to be caught mid-change.
Name the synchronizer
Put the flops in one module, such as
sync2, so reviewers and CDC tools can find every crossing.
Gray code: one bit per step
A Gray code orders the numbers so that each step changes exactly one bit. Binary to Gray is g = b ^ (b >> 1). Gray back to binary takes, for each bit i, the XOR of Gray bits i and above.
Binary on the wires
assign count_x = cnt;
The step from 0111 to 1000 changes four bits; the bench reads 12 once.
Gray on the wires
logic [3:0] cnt, cnt_d, gray;
assign cnt_d = cnt + 4'd1;
always_ff @(posedge clk_a or negedge rst_n)
if (!rst_n) begin cnt <= 4'd0; gray <= 4'd0; end
else begin cnt <= cnt_d; gray <= cnt_d ^ (cnt_d >> 1); end
assign count_x = gray; // one bit changes per step
The same step, 0100 to 1100, changes one bit; the bench reads no torn value.
The Gray value is computed from cnt_d, the next count, and registered, so the wires change once per step from a flop. The receiver converts back after the synchronizers.
assign count_q = s2;.always_comb // Gray to binary: bit i is the XOR of bits 3..i
for (int i = 0; i < 4; i++) count_q[i] = ^(s2 >> i);| Measured | Binary | Gray |
|---|---|---|
| Torn values in 24 edges on the lab bench | 1 (12 at edge 12) | 0 |
| Synthesis of count_cdc on this flow | 18 cells, 12 flops, 3 levels of logic | 29 cells, 15 flops, 3 levels of logic |
The Gray sender has 7 flops, not 8: the top Gray bit equals the top count bit, and the synthesizer merged the two flops.
Gray works for a count that steps by one through a power-of-two range. A count that jumps, or wraps at another value, changes several bits at once again.
| Port | Direction | Width | Meaning |
|---|---|---|---|
| clk_a | input | 1 | the sender's clock |
| clk | input | 1 | the receiver's clock |
| rst_n | input | 1 | reset, asynchronous, active-low, both domains |
| count_q | output | [3:0] | count_q shows the sender's count, two or three clk cycles late, and never a value the count did not hold |
- Clock
clk_a13 ns for the sender,clk10 ns for the receiver, unrelated. Crossing: one two-flop synchronizer per bit; the wires are driven from flops- Corner cases
- steps that change several binary bits: 0111 to 1000, and 1111 to 0000
The lab ships with binary on the wires. Run it and find the TORN line. Then replace the body of count_tx with the Gray version, and assign count_q = s2; with the decode. Run again: torn=0.
Asked in interviews
Can you synchronize a bus bit by bit?
Only if at most one bit changes at a time, as in a Gray count. Otherwise each synchronizer may take its change in a different cycle and the receiver reads a torn mixture of old and new bits.
What may cross a two-flop synchronizer?
One bit that changes slowly compared with the receiving clock: a level held for at least two receiving cycles. Multi-bit values need Gray coding, a synchronized valid flag over held data, a handshake or a FIFO.
Why no logic between the synchronizer flops?
The first flop may be metastable, and the second flop exists to give it a whole period to settle. Logic between them takes part of that period and shrinks the MTBF exponentially.
Try it
Each line is printed 1 ns after an edge of clk. rx steps by one or repeats, because clk is faster than clk_a, except at edge 12. The 15 to 0 step at edge 23 also changed four bits, but on this bench no edge fell between their arrivals.
With the Gray sender and the decode, the wires show 0100, 1100, 1101 around the same edges, and the last line reads torn=0. Synthesize both: 18 cells and 12 flops, then 29 cells and 15 flops.
Synthesize the design to measure its cells and inspect the mapped gates.
e0 x=0000 rx=0 e1 x=0000 rx=0 e2 x=0001 rx=0 e3 x=0010 rx=1 e4 x=0010 rx=1 e5 x=0011 rx=2 e6 x=0100 rx=3 e7 x=0101 rx=4 e8 x=0101 rx=5 e9 x=0110 rx=5 e10 x=0111 rx=6 e11 x=1000 rx=7 e12 x=1001 rx=12 TORN e13 x=1001 rx=8 e14 x=1010 rx=9 e15 x=1011 rx=10 e16 x=1100 rx=11 e17 x=1100 rx=11 e18 x=1101 rx=12 e19 x=1110 rx=13 e20 x=1111 rx=14 e21 x=1111 rx=15 e22 x=0000 rx=15 e23 x=0001 rx=0 e24 x=0010 rx=1 e25 x=0011 rx=2 torn=1Original design · 18 cells · 12 flip-flops · 0 latches · 3 logic levels. Synthesis uses Yosys; counts depend on the toolchain.
Read included file: tb.sv
`timescale 1ns/1ps
module tb;
logic clk = 0;
always #5 clk = ~clk; // the sampling clock: rising edges at 5, 15, 25, ... ns
logic clk_a = 0; // the second clock: rising edges at 8.75, 21.75, ... ns
initial begin
#8.75;
forever begin clk_a = 1; #6.5; clk_a = 0; #6.5; end
end
logic rst_n = 1; // falls at 1 ns: a real edge, so asynchronous resets act before any clock
int edge_n = -1; // the number of the last rising edge of clk
always @(posedge clk) edge_n = edge_n + 1;
logic [3:0] count_x; // the bus as count_tx drives it
wire [3:0] count_w; // the same bus at count_rx: each bit wire has its own delay
logic [3:0] count_q;
assign #0.4 count_w[0] = count_x[0];
assign #1.2 count_w[1] = count_x[1];
assign #2.8 count_w[2] = count_x[2];
assign #2.0 count_w[3] = count_x[3];
logic [3:0] last = 0; // the last value that was not torn
int torn = 0;
count_tx u_tx (.clk_a(clk_a), .rst_n(rst_n), .count_x(count_x));
count_rx u_rx (.clk(clk), .rst_n(rst_n), .count_x(count_w), .count_q(count_q));
// Advance to the next rising edge of clk, wait for the design to settle, then print.
task automatic tick();
@(posedge clk); #1;
if (count_q != last && count_q != last + 4'd1) begin
torn++; // a value the count never held at this point
$display("e%0d x=%b rx=%0d TORN", edge_n, count_x, count_q);
end
else begin
$display("e%0d x=%b rx=%0d", edge_n, count_x, count_q);
last = count_q;
end
endtask
initial begin
#1 rst_n = 0; tick(); tick(); rst_n = 1; // asserted at 1 ns, clk edges 0 and 1 in reset, released 1 ns after edge 1
repeat (24) tick();
$display("torn=%0d", torn);
$finish(0); // quiet: no "$finish called" line
end
endmodule
Check yourself
Remember
- A two-flop synchronizer carries one bit that holds each value for at least two receiving cycles; the change lands in a cycle it cannot predict.
- Bits of one value through separate synchronizers can land in different cycles and read as a torn mixture of old and new.
- A Gray count changes one bit per step, so each read is the old or the new value; it needs steps of one through a power-of-two range.
- One bit per synchronizer, no logic between its flops, the crossing wire driven from a flop, and the synchronizer in a named module.