December 2025 · Verilog
Asynchronous FIFO for Clock-Domain Crossing
A Verilog FIFO that moves data safely between two unrelated clocks, following Clifford Cummings' SNUG 2002 design: Gray-code pointers, two-flop synchronizers, and full and empty flags generated in the clock domain that needs them.
Modules
| Module | Role |
|---|---|
fifomem | Dual-port RAM, 2ADDRSIZE words of DATAWIDTH bits (8-bit by default) |
wptr_full | Write pointer and full flag, in the write clock domain |
rptr_empty | Read pointer and empty flag, in the read clock domain |
sync_w2r, sync_r2w | Two-flop synchronizers carrying each Gray-code pointer into the other domain |
bin2gray, gray2bin | Pointer conversions between binary and Gray code |
async_fifo_wrapper | Top level, also packaged as a Vivado block design |
tb_fifo | Testbench |
Design notes
- Gray-code pointers
- A binary counter can change several bits at once (0111 to 1000 changes four). Sampled mid-transition in the other clock domain, that can read as a completely wrong value. A Gray-code count changes exactly one bit per step, so a synchronized pointer is either the old value or the new one.
- Two-flop synchronizers
- The first flop may go metastable; the second gives it a full clock period to settle. That does not make failure impossible, but it pushes the mean time between failures far beyond the life of the product at normal clock rates.
- Empty and full
- Pointers carry one extra bit. Empty is when the read pointer equals the synchronized write pointer. Full is when the write pointer equals the synchronized read pointer with its two most significant bits inverted: same address, but the writer has wrapped once more.
- Safe by design
- Each flag is computed in the domain that acts on it, from a pointer that is a little stale. Stale pointers can only make the FIFO look fuller or emptier than it is, never cause an overflow or underflow.
- Depth
- A power of two, set by ADDRSIZE, so the Gray-code wrap-around works.
Simulation
