“Pass a one-cycle pulse across clocks.” Then: “What if one side resets mid-transfer?” One lesson holds both questions: events first, reset-domain crossings second.
The short answer for the pulse: a toggle or a request and acknowledge handshake, never a blind stretch. The reset half follows the course reset-domain lessons.
A short pulse can fall between two destination edges, so it is first turned into a level: each pulse toggles a source flop. A two-flop synchroniser carries the level, and an XOR of the output with its previous value remakes one destination pulse per toggle (pulse lesson).
Each slot is half a destination clock cycle, so clk_d reads high then low across each pair of slots. The source pulse is one fast cycle wide: narrower than one destination cycle.
Slot 2: pulse_s is high for this one slot only, and toggle_s flips to 1 in the same slot. The pulse is shorter than one destination cycle, so it can fall between two destination edges; without the toggle the destination could miss it entirely.
Slots 3 to 5: the new level crosses the two-flop chain (the first stage is internal and not shown), while q2 still reads 0.
Slots 6 to 7: q2 turns 1 and pulse_d is high. Those two slots are one full destination cycle: exactly one output pulse for one input pulse.
Slots 8 to 11: the level holds steady at 1, so no second pulse appears.
Spacing rule: each toggle level must survive long enough for destination sampling and edge detection across clock modes, phase and margin. A conservative open-loop contract spans at least three clean destination sampling edges per level; two destination periods is not a universal lossless bound. Stoppable or unbounded clocks need an acknowledged event protocol, a counter or a FIFO.
The synchronised level changes at one destination edge and the previous-value flop copies it at the next. The XOR is high only between those two edges.
With a busy or ready flag: the source may not raise another pulse until the handshake or hold counter clears. A producer that ignores it overwrites events.
Reset-domain crossing starts where CDC stops: a reset hits one side mid-protocol. Three hazards follow.
Hazard | What goes wrong | Fix |
|---|---|---|
Cleared source data | Source data cleared while a request is visible is captured as a real word. | Retain data through completion or authorised cancellation, and qualify destination acceptance during recovery. Leaving data unreset alone is not enough. |
Cleared destination history | History cleared while a request or toggle is retained duplicates a transfer or creates a phantom event. | Block acceptance and establish a fresh baseline or epoch. Acknowledging a stale request is cancellation, not delivery. |
Release order | The producer starts while the consumer is still in reset. | Ordered release, or a ready flag the sender waits for (quiescence, isolation). |
All three have one cure: a planned recovery sequence run by logic that stays alive, the always-on recovery controller.
Read the chart top to bottom; traffic is closed from the first box until the last.
Box 2 is the cure for hazard 1: no word is in flight when the reset lands.
Box 5 is the cure for hazard 2: the XOR has a fresh baseline before any pulse can count.
Boxes 4 and 6 are the cure for hazard 3: release is synchronised and the source waits for ready.
Recovery and removal close the timing side: every asynchronous release passes a reset synchroniser in its own domain (recovery). A timeout in box 5 keeps the link closed; it does not return ownership of an accepted event. Account accepted = delivered + cancelled + pending per epoch.
The consumer loses protocol state and may duplicate the word in flight, while the producer waits for an acknowledge. The crossing needs the isolation and tolerance rules above, plus a randomised reset test.
No. A destination rising-edge detector on the synchronised level emits one pulse. It must also observe a sufficiently long low interval before the next event; stretching without width and spacing guarantees is not a complete CDC protocol.
“A plain 2FF catches a one-cycle pulse if the destination is faster.” Only if the pulse is guaranteed to span enough destination edges (about three) in every clock mode; with unknown or changing clock ratios, or a fast source, use a toggle or handshake.
“One reset wire to both domains is enough.” The wire reaches each domain through its own synchroniser, so the domains still release in different cycles. Protocol tolerance or sequencing is still needed.
The toggle synchroniser with an init_d gate, and a bench that resets the destination at the start and between bursts of events. The comments at the top of the bench say what each phase checks.
// Pulse crossing by toggling: each source pulse flips a level, a 2FF carries it,
// and 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, init_d,
output logic pulse_d, ready_d, raw_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
always_ff @(posedge clk_d or negedge rst_d_n)
if (!rst_d_n) begin
q1 <= 1'b0; q2 <= 1'b0; q2_prev <= 1'b0; ready_d <= 1'b0;
end else begin
q1 <= toggle_s; q2 <= q1; q2_prev <= q2;
ready_d <= !init_d;
end
// Controller holds source traffic closed throughout init and baseline capture.
// Deassert init only after stable source history has traversed q1/q2/q2_prev.
assign raw_pulse_d = q2 ^ q2_prev; // diagnostic: unsafe without initialization
assign pulse_d = ready_d && !init_d && raw_pulse_d;
endmodule
Bench Run with Icarus Verilog 13.0 (2026-10-16): prints PASS. It delivers 61 events and resets destination history at both idle polarities. After 40 events the toggle is 0; after one more it is 1. It models the external controller and does not implement the full always-on barrier or unexpected-fault containment. Reset releases are ideal bench stimulus.