Overview
In digital IC design, asynchronous FIFOs are commonly used to handle multi-bit data transfer and synchronization across clock domains. They function like a buffer or reservoir to decouple upstream and downstream data rates and to safely pass data between different clock domains.
What is an asynchronous FIFO?
In large-scale ASIC and FPGA designs, multi-clock systems are often unavoidable, which creates challenges for transferring data between different clock domains. A common solution is to use an asynchronous FIFO to buffer data written in one clock domain and read in another. The FIFO allows data to be transferred safely from one domain to another even when the domains are asynchronous.
If precautions are not taken, read and write operations can overlap in ways that cause data loss or instability. To prevent this, read and write pointers and status flags are synchronized so that data is neither overwritten nor read twice. For cross-clock-domain status (full/empty) detection, pessimistic full/empty conditions must be considered because pointer values cross clock boundaries.
Basic operations
Write operation
Writing involves storing data into the FIFO until a stop condition (such as full) occurs. To perform a write, present the data at the input, assert write enable, and capture the data on the next rising edge of the write clock.
Read operation
Reading retrieves data from the FIFO until no more data is available. To perform a read, assert read enable and sample the output data on the next rising edge of the read clock.
Pointers that control operations
- Write pointer: controls the write operation and points to the memory location for the next write.
- Read pointer: controls the read operation and points to the memory location for the next read.
Status flags
An asynchronous FIFO typically provides two status flags used to control operations:
- Empty flag: prevents issuing read requests when the FIFO is empty.
- Full flag: prevents issuing write requests when the FIFO is full.
Pointer synchronization and Gray code
Synchronizing a binary counter value across clock domains can lead to metastability because multiple bits may change simultaneously (for example, 7 -> 8 is 0111 -> 1000, where several bits change). Gray code addresses this by ensuring only one bit changes between successive values, which reduces ambiguity when multi-bit values are sampled in another clock domain.
Therefore, it is common to convert binary addresses to Gray code, synchronize the Gray-coded pointer into the other clock domain, and then compare synchronized Gray codes for full/empty detection. A binary-to-Gray conversion circuit is used to produce the Gray-coded address before synchronization.
Using Gray code solves the simultaneous-bit-change problem but raises the question of how to detect full and empty conditions using Gray-coded pointers. Typical rules are:
- Empty detection: in the read clock domain, the local read-pointer Gray code must equal the write-pointer Gray code after the write-pointer has been synchronized into the read clock domain.
- Full detection: in the write clock domain, the local write-pointer Gray code must match the read-pointer Gray code after the read-pointer has been synchronized into the write clock domain, except that the two most significant bits are inverted when checking for full condition.
Because the read pointer belongs to the read clock domain and the write pointer belongs to the write clock domain, comparing them directly is invalid. Each pointer must be synchronized into the other domain before comparison. A common method uses two-stage register synchronization (two flip-flop stages) to reduce metastability risk.
Summary: key implementation steps
- Convert binary address to Gray code.
- Synchronize the Gray-coded write address into the read clock domain for empty detection.
- Synchronize the Gray-coded read address into the write clock domain for full detection.