A one-cycle pulse in a fast domain can be shorter than a destination period. A plain synchroniser may never see it.
The fix is a source flop that toggles on every pulse. The level persists, a 2FF synchronises it, and the destination XORs the output with its previous value.
Part | Job |
|---|---|
| Flips on every source pulse |
Two | Synchronise the level |
| Remembers the previous synchronised value |
| High for exactly one destination cycle after every change |
Read the circuit left to right. The only signal that crosses between clocks is toggle_s, a level, so an ordinary two-flop synchroniser can carry it.
The XOR in front of toggle_s flips it on every pulse_s, so the event becomes a level change that lasts.
q1 and q2 are the synchroniser. q2_prev holds q2 from one destination cycle earlier.
The XOR at the output compares q2 with q2_prev, so it is high only in the cycle right after a flip.
Read the waveform with one slot per source cycle. Because clk_d is sampled in the same slots, it alternates every slot, so one clk_d period is two slots.
In slot 0 pulse_s is high for a single source cycle, shorter than one clk_d period. toggle_s goes high in slot 1 and stays high.
q2 rises in slot 5, the second clk_d edge after toggle_s changed (q1 rises one edge earlier, in slot 3). q2_prev follows two slots later, so pulse_d is high in slots 5 and 6 only: one clk_d period.
The second pulse_s in slot 4 is exactly two destination periods after the first. It flips toggle_s back to 0, and pulse_d fires again in slots 9 and 10.
pulse_d appears on both the rising and the falling change of q2, which is why the circuit counts flips and not levels.
Each flip must be seen separately in the destination domain. Two flips inside one destination period can merge and lose a pulse.
The first test starts pulses at least 5 source cycles (50 ns, about 7 destination periods) apart and receives all 5,000. A second run with a faster source (4.9 ns) and a slower destination (10.13 ns) varies the gap:
Pulse starts | Gap | Spacing / destination period | Pulses sent | Pulses received |
|---|---|---|---|---|
Pulse starts every 2 cycles | 9.8 ns | 0.97 | 5,000 | 4,648 (352 lost) |
Every 3 cycles | 14.7 ns | 1.45 | 5,000 | 5,000 |
Every 4 cycles | 19.6 ns | 1.93 | 5,000 | 5,000 |
Every 5 or more cycles | 24.5 ns or more | 2.4 or more | 5,000 | 5,000 |
At least two destination periods is an open-loop engineering heuristic, not a delivery guarantee: arbitrary late resolution can defeat a fixed spacing bound. If each event must be conserved, use an acknowledged transfer, a sized event counter with wrap/observation bounds, or a FIFO with backpressure.
pulse_d exactly one destination cycle wide?Because q2 changes at one destination edge and q2_prev copies it one edge later. The XOR is high only between those two edges.
The toggle synchroniser with a testbench that sends 5,000 spaced pulses.
`timescale 1ns/1ps
module meta_ff #(parameter real TCQ = 0.25, TSU = 0.3, THD = 0.15) (input logic clk, d, output logic q);
realtime t_d = -100.0, t_clk = -100.0;
initial q = 1'b0;
always @(d) begin
t_d = $realtime;
if ($realtime - t_clk < THD) q <= #TCQ $urandom_range(0, 1);
end
always @(posedge clk) begin
t_clk = $realtime;
if ($realtime - t_d < TSU) q <= #TCQ $urandom_range(0, 1);
else q <= #TCQ d;
end
endmodule
// Pulse crossing by toggling: every source pulse flips a level, which crosses like any single-bit level;
// the destination turns each flip back into a one-cycle pulse.
module pulse_sync (
input logic clk_s, rst_s_n, pulse_s,
input logic clk_d, rst_d_n,
output logic pulse_d
);
logic toggle_s, q1, q2, q2_prev;
always_ff @(posedge clk_s or negedge rst_s_n)
if (!rst_s_n) toggle_s <= 1'b0;
else if (pulse_s) toggle_s <= ~toggle_s; // one flip per pulse
meta_ff f1 (.clk(clk_d), .d(toggle_s), .q(q1));
meta_ff f2 (.clk(clk_d), .d(q1), .q(q2));
always_ff @(posedge clk_d or negedge rst_d_n)
if (!rst_d_n) q2_prev <= 1'b0; else q2_prev <= q2;
assign pulse_d = rst_d_n && (q2 ^ q2_prev); // ignore history while the link is reset/flushed
endmodule
The spacing counts (source 4.9 ns, destination 10.13 ns) come from one digital-model run. Two destination periods is an open-loop engineering heuristic under bounded clock, jitter and sampling assumptions, not a deterministic physical delivery guarantee.