VHDL · Chapter 17.6 · FPGA-Oriented VHDL Design
CDC FIFOs and Handshake Synchronizers
A multi-bit bus cannot be synchronized one bit at a time, so coherent clock crossings use one of two proven structures. A handshake synchronizer keeps the data stable: the source holds the bus unchanged and raises a request, the destination synchronizes that single-bit request and then safely captures the already-settled data before raising an acknowledge that crosses back. It is simple and robust but slow, ideal for occasional control or configuration words. For streaming throughput you use a dual-clock, or asynchronous, FIFO: a dual-port RAM written in one clock and read in another, with the write and read pointers crossed as Gray code so full and empty are computed correctly in each domain. This lesson covers both patterns and why teams usually lean on proven IP for the async FIFO.
Foundation15 min readVHDLFPGACDCAsync FIFOHandshakeGray Code
1. Engineering intuition — hold it still, or buffer it across
A bus can't cross domains bit-by-bit because its bits resolve independently. Two strategies dodge that. The first: don't let the data move while it crosses — the source holds the bus rock-steady and just sends a single request bit (which can be safely synchronized); once the destination sees the request, the data has been stable for ages, so reading it is safe; an acknowledge bit closes the loop. That is a handshake — slow (several sync delays per word) but dead simple and perfect for the occasional config write. The second: buffer the stream through memory — a FIFO whose write side runs in one clock and read side in another, with the tricky part being how each side learns the other's pointer coherently. The shared trick is the same as before: whatever crosses domains must be one-bit-safe — a single request/ack bit, or a Gray-coded pointer where only one bit changes at a time.
2. Formal explanation — handshake synchronizer
-- HANDSHAKE: pass a multi-bit word by HOLDING it stable while single-bit req/ack cross.
-- SOURCE (src_clk):
-- 1. drive data_bus (HOLD it stable), assert req
-- 2. wait for ack (synchronized back from dst) → then deassert req, may change data
-- DESTINATION (dst_clk):
-- 1. synchronize req with a 2-FF synchronizer (17.5) → req_sync
-- 2. on req_sync, CAPTURE data_bus (it has been stable → no metastable bits)
-- 3. assert ack; source synchronizes ack back with its own 2-FF synchronizer
--
-- Only single BITS (req, ack) cross domains; the DATA never needs synchronizing because it is HELD.
-- Cost: several synchronizer delays per word → LOW throughput. Use for occasional control/config words.A handshake synchronizer moves a bus by keeping it stable and crossing only single-bit req/ack through two-FF synchronizers; the destination samples the data only after the synchronized request, when it is guaranteed stable. It is robust and simple but slow (round-trip sync per word) — suited to infrequent control transfers.
3. Production usage — dual-clock FIFO for streaming
-- DUAL-CLOCK (ASYNC) FIFO: high-throughput streaming across domains. Built on a DUAL-PORT RAM (BRAM, 17.2).
-- WRITE side (wclk): write data at wr_ptr; increment wr_ptr.
-- READ side (rclk): read data at rd_ptr; increment rd_ptr.
-- FULL/EMPTY need each side to know the OTHER side's pointer → CROSS THE POINTERS.
--
-- The KEY: cross pointers as GRAY CODE (only ONE bit changes per increment).
-- • binary pointer would have many bits changing at once → can't synchronize coherently (17.5).
-- • Gray-coded pointer: at most one bit uncertain when synchronized → safe to 2-FF sync.
-- wr_ptr_gray --(2-FF sync into rclk)--> compare with rd_ptr → EMPTY in read domain
-- rd_ptr_gray --(2-FF sync into wclk)--> compare with wr_ptr → FULL in write domain
--
-- This is subtle: prefer PROVEN async-FIFO IP (vendor / OSVVM / verified core) over hand-rolling.What hardware does this become? A dual-port block RAM (data storage) plus pointer logic in each clock domain. Data never needs synchronizing — it sits in the BRAM, written by one clock and read by another. What crosses are the pointers, encoded in Gray code so only one bit changes per increment and can be passed through a two-FF synchronizer without the incoherent-bus problem; each domain compares the synchronized remote pointer with its local one to derive full (write side) and empty (read side). This gives high throughput (one word per clock once flowing) across unrelated clocks — but the pointer/flag logic is famously subtle, so production designs almost always use a proven async-FIFO core rather than rolling their own.
4. Structural interpretation — async FIFO with Gray-coded pointer sync
5. Why this is structural, not timing
Coherent CDC mechanisms are structures — a handshake protocol holding data stable, or an async FIFO with Gray-coded pointer synchronization — so the structural diagram above is the right picture, not a waveform. The underlying single-bit synchronization and its metastable-settling waveform were shown in Module 17.5; here the substance is how the structure guarantees coherence: hold-and-handshake, or buffer-through-RAM-with-Gray-pointers. That is a design-time architectural property of the crossing, not a signal trace — which is why these are presented as structures built atop the single-bit synchronizer.
6. Debugging example — full/empty wrong, or a moving handshake bus
Expected: a reliable multi-bit crossing. Observed (FIFO): the async FIFO occasionally reports full or empty wrongly (overflow/underflow or lost data) under certain clock-ratio conditions. Observed (handshake): captured control words are sometimes corrupt. Root cause (FIFO): the pointers were crossed as binary (many bits change at once) instead of Gray code, so the synchronized pointer was momentarily an incoherent value and the full/empty comparison was wrong — the classic async-FIFO bug. Root cause (handshake): the source changed the data before the acknowledge returned, so the destination captured a moving bus. Fix (FIFO): cross pointers as Gray code (one bit per step) through two-FF synchronizers, or use proven IP. Fix (handshake): hold the data stable until the synchronized ack confirms capture. Engineering takeaway: coherent CDC depends on one-bit-at-a-time safety — Gray-code FIFO pointers and hold handshake data until ack; binary pointers or a moving bus corrupt the crossing.
-- BUG (FIFO): binary pointer crossed → multiple bits change → incoherent → wrong full/empty.
-- wr_ptr_bin --2FF--> (garbage during multi-bit transition)
-- FIX: Gray-code the pointer (one bit changes per increment) before synchronizing.
-- wr_ptr_gray <= bin_to_gray(wr_ptr_bin); wr_ptr_gray --2FF--> compare in read domain
-- BUG (handshake): source changes data before ack → moving bus captured.
-- FIX: hold data stable until the synchronized ack returns, THEN release.7. Common mistakes & what to watch for
- Binary FIFO pointers across domains. Cross pointers as Gray code so only one bit changes per step; binary pointers desync full/empty.
- Releasing handshake data too early. Hold the bus stable until the synchronized ack confirms the destination captured it.
- Hand-rolling an async FIFO. The pointer/flag logic is notoriously subtle; prefer proven vendor/verified IP.
- Using a handshake for high throughput. It costs round-trip sync delays per word; use an async FIFO for streams.
- Forgetting the single-bit basis. Both schemes still rely on two-FF synchronizers (17.5) for the control bits
/ pointers — apply
async_regand CDC constraints.
8. Engineering insight & continuity
Coherent multi-bit CDC uses two structures: a handshake synchronizer (hold the data stable, cross single-bit req/ack) for occasional control words, and a dual-clock FIFO (dual-port RAM with Gray-coded pointers synchronized across domains) for high-throughput streams — both built on the single-bit two-FF synchronizer, both best taken from proven IP for the FIFO. With clocking and CDC mastered, the FPGA design module turns to the boundary between the chip and the board: the next lesson, I/O, Constraints, and Timing Closure — I/O standards, registering at the pins, and meeting timing across the whole design.