Full is computed in the write domain from a synchronised (hence stale) read pointer, and empty in the read domain from a stale write pointer. A newcomer to signoff often reads this as a bug: decisions made from out-of-date information. It is in fact the central safety argument of the FIFO, because the staleness always points in the safe direction.
The synchronised copy of a pointer always lags the true pointer, and pointers only advance. So the write side overestimates how full the FIFO is, and the read side underestimates it:
Flag | Computed from | Staleness effect | Direction of error |
|---|---|---|---|
Full (write side) | Own write pointer minus stale read pointer | Reads that happened recently are not yet visible | Occupancy overestimated: full asserts early, never late |
Empty (read side) | Stale write pointer minus own read pointer | Writes that happened recently are not yet visible | Occupancy underestimated: empty asserts early, never late |
Early full costs a little throughput (the writer waits while space already exists); early empty costs a little latency (the reader waits while data already exists). Neither corrupts data. A flag that could assert late would be a genuine bug: writes into a full FIFO overflow, reads from an empty one underflow.
A structural review confirms that full is registered in the write domain from the next write pointer against the synchronised read pointer, and empty symmetrically; that the comparisons use the Gray-domain identities (equal for empty, top bits inverted for full); and that no flag is consumed combinationally in the far domain. Verification checks the safety direction directly: whenever true occupancy is within the margin of full or empty, the flag must already be set, as the coverage lesson exercises.
Too early: up to three cycles of reads are invisible, so the FIFO looks fuller than it is. That is the safe direction; the symmetric argument protects empty.