The Asynchronous FIFO
The standard way to move data between clocks
When you need to move a stream of data from one clock domain to another, the asynchronous FIFO is the standard answer. It is the single most important CDC structure to understand.
The idea
A dual-clock FIFO is a small memory written by the source clock and read by the destination clock. The writer pushes data using the write clock, the reader pops data using the read clock, and the FIFO absorbs the difference in timing between them. Neither side has to know the other side timing, as long as the FIFO never overflows or underflows.
The crossing is in the pointers
The data words themselves never need synchronizing, because a word is written, then sits still in memory until it is read. The thing that actually crosses clock domains is the pointers: to know if the FIFO is full, the write side needs the read pointer, and to know if it is empty, the read side needs the write pointer.
- The write pointer is maintained in the write clock domain.
- The read pointer is maintained in the read clock domain.
- Each pointer is converted to gray code and synchronized into the other domain with a two-flop synchronizer.
- Full is computed on the write side, empty on the read side, using the synchronized pointer.
Why gray-coded pointers are essential
The pointers increment by one, so gray code applies perfectly: only one bit changes per step, so the synchronized pointer is always a valid value, never a corrupt in-between. This is the reason the whole structure works.
Full and empty, safely
- Empty is detected when the read pointer equals the synchronized write pointer.
- Full is detected when the write pointer equals the synchronized read pointer with the wrap bit inverted.
- Because each side uses a pointer synchronized from the other, full and empty are always pessimistic and safe: you may briefly think the FIFO is more full or more empty than it really is, but you will never overflow or underflow.
The safe-but-pessimistic property is the elegant part. A slightly stale synchronized pointer can only make full assert a little early or empty assert a little late, which is always safe. It can never cause a real overflow. Being able to explain that in an interview marks you as someone who truly understands async FIFOs.
Size the FIFO for the worst-case rate difference and burst length between the two sides, including the latency the synchronizers add. An undersized async FIFO drops or stalls data under bursts even though the logic is correct.