Why not just synchronize the data bits directly?
A 2-flop synchronizer only works for a single bit that changes slowly and predictably. A multi-bit data bus has no such guarantee — different bits can settle at different times, so a destination domain sampling several synchronized bits together could catch some old and some new, producing a value that was never actually on the bus (a coherency failure, not just a metastability risk). The Asynchronous FIFO problem solves this for streaming data using Gray-coded pointers; this problem solves it for one-off or infrequent transfers using a handshake: synchronize only the control signals (req/ack), and let those synchronized controls guarantee the data bus has been stable long enough to sample directly. This “bundled data” technique is one of the most common CDC patterns for control-plane or configuration data (e.g. crossing a CPU-programmed register value into a datapath clock domain).
Interface
| Signal | Domain | Description |
|---|---|---|
src_clk |
source | Source-domain clock. |
src_rst |
source | Synchronous reset for the source domain. |
src_valid |
source | Request to send src_data; must stay high until src_ready samples it. |
src_data |
source | Data word to transfer. |
src_ready |
source | High when the module can accept a new src_data (i.e. is idle). |
dst_clk |
dest | Destination-domain clock — may run at any frequency vs. src_clk. |
dst_rst |
dest | Synchronous reset for the destination domain. |
dst_valid |
dest | Pulses high for one dst_clk cycle when dst_data is newly valid. |
dst_data |
dest | The transferred data word. |
The 4-phase protocol
| Phase | req (src) |
ack (dst) |
What’s happening |
|---|---|---|---|
| 1 | 0 → 1 | 0 | Source latches src_data, asserts req. |
| 2 | 1 | 0 → 1 | Destination detects synchronized req, samples data, asserts ack. |
| 3 | 1 → 0 | 1 | Source detects synchronized ack, deasserts req (data may now change). |
| 4 | 0 | 1 → 0 | Destination detects synchronized req low, deasserts ack. Ready to repeat. |
Both req and ack are 2-flop synchronized independently, in opposite directions, exactly like the 2-Flop Synchronizer problem — this module is really two of those wired into a control loop around a shared data register.
Correctness constraints
src_data(viadata_hold) must remain stable from the momentreqasserts untilackhas been seen asserted and then deasserted (the full round trip) — the source FSM enforces this by not returning toS_IDLE(and hence not accepting a newsrc_valid) until that point.- Because the source waits a full 4-phase round trip before the data can change, and the destination only samples data after
req_synchas already risen (which itself takes at least 2dst_clkcycles to happen afterreqasserts), the data bus has been stable far longer than any setup-time requirement — no per-bit synchronization ofdata_holdis needed. reqandackmust each go through independent 2-flop synchronizers in their respective destination domains.- The design must support
src_clkanddst_clkat arbitrary, unrelated frequencies — verify with a testbench using two independent clock generators. - Throughput is intentionally low (one transfer takes several clock cycles in both domains) — this pattern is for infrequent/control-plane transfers, not streaming data. For streaming, use the Asynchronous FIFO (in the FIFOs category) instead.