HDLbits
Start Practicing

RTL Design Patterns/Clock Domain Crossing

Handshake Synchronizer (4-Phase Req/Ack)

hard
cdchandshakereq-ackmulti-bit

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 (via data_hold) must remain stable from the moment req asserts until ack has been seen asserted and then deasserted (the full round trip) — the source FSM enforces this by not returning to S_IDLE (and hence not accepting a new src_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_sync has already risen (which itself takes at least 2 dst_clk cycles to happen after req asserts), the data bus has been stable far longer than any setup-time requirement — no per-bit synchronization of data_hold is needed.
  • req and ack must each go through independent 2-flop synchronizers in their respective destination domains.
  • The design must support src_clk and dst_clk at 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.