An async FIFO is two pointer engines plus a dual-port RAM. Data never crosses. Gray pointers cross through 2FF synchronizers. Full lives with the writer. Empty lives with the reader.
Dual-port RAM sits on the boundary: write-side sequential, read-side sequential, no combinational path between clocks.
| Net | Direction | Encoding | Synchronizer |
|---|---|---|---|
| wgray | wr โ rd | registered Gray | 2FF on rd_clk โ rq2_wgray |
| rgray | rd โ wr | registered Gray | 2FF on wr_clk โ wq2_rgray |
| wdata / rdata | none | RAM | not synchronized |
Because a 2FF chain delays the pointer by two destination clocks, flags are slightly pessimistic: you may see full a little early and empty a little early. That can cost a few entries of apparent depth. It must never allow overflow/underflow.
Gray pointer wrap only stays unit-distance for a binary count that uses all 2^n states. Classic async FIFO depth must be a power of two. For a 10-deep FIFO you either waste 6 locations (use 16) or use a different (harder) style: dual n-bit Gray plus converted occupancy, or a vendor hard FIFO.
MTBF of a synchronizer grows exponentially with extra flops and with a faster receiving clock (more time to resolve before the next sample โ actually faster clocks hurt resolution time). Datasheets for FPGA synchronizer hardness assume you used the dedicated ASYNC_REG pair. Custom ASIC 2FF cells are characterized. Do not roll your own three latches. For this course, 2FF is the answer unless a safety spec mandates 3FF.
Use a hard/generated FIFO when you need ECC, FWFT, non-power-of-two, very deep BRAM cascade, or built-in almost-full calibrated in the GUI. Still draw the same boxes in a review: two clocks, RAM, Gray (or vendor-secret) pointers. Never mix two vendor FIFOsโ almost-full encodings without reading the data sheet โ some count in words, some in cycles of lag.
Only the registered Gray write pointer into the read domain and the registered Gray read pointer into the write domain, each through a 2FF. Data stays in dual-port RAM.
The extra-MSB Gray scheme assumes a binary pointer that uses all 2^n states so wrap remains unit-distance. A 10-deep FIFO uses 16 locations or a different (harder) coding.
Each 2FF hides the last two destination clocks of opposite-side traffic. You may stall with a free slot or wait with data already in RAM. You must not overwrite or read garbage.
Write port sequential on wr_clk, read port sequential on rd_clk. No combinational path between the two clocks through the RAM.