GLS · Chapter 9 · CDC & Asynchronous Behaviour in GLS
Asynchronous FIFO Behaviour in GLS
A two-flop synchronizer crosses only one bit safely, so how does a multi-bit data stream cross between two clock domains? The answer is an asynchronous FIFO, and gate-level simulation shows its moving parts under real delays. It uses a dual-port memory with a write pointer and a read pointer, each gray-coded, synchronized into the opposite domain, and compared to generate the full and empty flags. Gray coding is the key: consecutive values differ by exactly one bit, so a synchronizer sampling the pointer mid-transition gets either the old value or the new one, never a mix, which a binary pointer would give. This lesson shows the async FIFO in GLS, why gray coding is essential, and what a binary pointer crossing corrupts, while keeping metastability survival with MTBF.
Foundation12 min readGLSAsync FIFOGray CodeCDCPointers
Chapter 9 · Section 9.4 · CDC & Asynchronous Behaviour in GLS
Project thread — the mini-SoC moves data between clock domains (e.g. a peripheral to the bus) through an async FIFO. This lesson shows it under real delays; 9.5 root-causes a real-delay CDC glitch.
1. Why Should I Learn This?
Multi-bit data crossing is the hard CDC problem, and the async FIFO is its standard solution.
- A synchronizer crosses one bit; a FIFO crosses a stream (9.3).
- Gray-coded pointers make the multi-bit crossing safe (one bit changes).
- GLS shows the pointer timing, gray-code, flags, data integrity under real delays.
This extends the synchronizer (9.3) to real data and sets up the CDC glitch capstone (9.5).
2. Real Silicon Story — the FIFO that reported full when it was nearly empty
An async FIFO occasionally asserted full when it was nearly empty — corrupting flow control and dropping data.
The write pointer was crossed to the read domain as a binary value, not gray-coded. When the synchronizer sampled it mid-increment (e.g. 0111 → 1000, four bits changing), it captured a mix — a pointer value that never existed (like 1111) — so the flag comparison computed a wrong full/empty. Gray-coding the pointer (one bit per step) fixed it: a mid-transition sample now yields only the old or new pointer.
Lesson: cross FIFO pointers gray-coded. A binary multi-bit pointer sampled mid-transition yields a nonexistent value → wrong flags and data corruption.
3. Concept — the async FIFO and gray coding
Structure:
- Dual-port memory — written by the write domain, read by the read domain.
- Write pointer (write domain) and read pointer (read domain).
- Each pointer is gray-coded, synchronized into the opposite domain (two-flop, 9.3), and compared there to make full (write side) and empty (read side).
Why gray code is essential:
- Gray code: consecutive values differ by exactly one bit (
000→001→011→010→…). - A synchronizer samples the crossing pointer asynchronously — possibly mid-transition.
- With one bit changing, a mid-transition sample gets the old or the new value — never a mix.
- With binary (multiple bits changing), a mid-transition sample can get a value that never existed → wrong flags / corruption.
What GLS reveals (real delays):
- Pointer synchronization timing (two-flop settle, 9.3).
- Gray-code correctness (only one bit changes per step).
- Flag generation (full/empty from synchronized pointers).
- Data integrity across the crossing.
Scope (accuracy):
- GLS shows structure, data integrity, real-delay timing — metastability survival is MTBF (9.2).
- Disable timing checks on the pointer synchronizer first flops (async crossings, 9.3/8.4).
4. Mental Model — pass the odometer one digit at a time
Crossing a pointer is like reading a neighbour's odometer through a window as it rolls.
- A binary odometer rolling
0999 → 1000flips four wheels at once — glance mid-roll and you might read 1999 or 0000, numbers that never happened. - A gray-coded odometer rolls one wheel at a time — glance mid-roll and you read either the old or the new number, never a fiction.
- The FIFO compares two such odometers (write vs read pointer) to know if it's full or empty — so a fictitious reading (binary) gives a wrong answer.
Roll one wheel at a time (gray code) so a mid-glance is always a real number.
5. Working Example — gray coding and a binary-pointer bug
Gray-code conversion and the pointer crossing (representative):
// Gray-coded pointer for CDC crossing — REPRESENTATIVE
logic [3:0] bin_ptr, gray_ptr;
assign gray_ptr = bin_ptr ^ (bin_ptr >> 1); // binary -> gray (one bit changes per increment)
// gray_ptr is synchronized into the other domain (two-flop, 9.3), then compared for full/empty.# Why gray, not binary (tool-neutral):
# binary 0111 -> 1000 : FOUR bits change -> mid-transition sample could be 1111 (NEVER existed)
# gray 0100 -> 1100 : ONE bit changes -> mid-transition sample is 0100 or 1100 (both REAL)
# the FIFO compares synced pointers for full/empty -> a fictitious value = WRONG flag = corruptionPractical context (representative, tool-neutral):
# Async FIFO GLS checklist (tool-neutral):
# [ ] pointers GRAY-CODED (one bit changes per step) -> safe crossing
# [ ] pointer synchronizers: disable checks on first flops (async, 9.3/8.4)
# [ ] full/empty flags correct from synchronized pointers
# [ ] data integrity across the crossing (real delays)
# [ ] metastability survival -> MTBF (9.2), not this simA gray pointer crossing safely vs a binary pointer corrupting a flag, as a real waveform:
Gray pointer crosses safely (old or new); a binary pointer sampled mid-transition yields a nonexistent value → wrong flag
8 cycles6. Debugging Session — wrong full/empty from a binary pointer crossing
An async FIFO reports wrong full/empty flags because a pointer was crossed as binary rather than gray-coded, so a synchronizer sampling it mid-increment captured a value with multiple bits changing that never existed; gray-coding the pointer fixes the crossing
CROSS FIFO POINTERS GRAY-CODED (ONE BIT PER STEP)An async FIFO asserts full when nearly empty (or vice versa), corrupting flow control and dropping/duplicating data.
A binary pointer crossed a clock domain. The pointer was synchronized as a binary value, so an increment changes multiple bits at once (e.g. 0111 → 1000, four bits). Because the synchronizer samples the crossing pointer asynchronously, it can catch it mid-transition, capturing a mix of old and new bits — a pointer value that never existed (like 1111). The flag comparison (write vs read pointer) then uses this fictitious value and computes a wrong full/empty. The fix is the gray code: with only one bit changing per step, a mid-transition sample is always the old or new (real) pointer. It's not a synchronizer bug — it's crossing a multi-bit binary value where gray is required (extends 9.3's single-bit rule).
Gray-code the pointers before crossing (gray = bin ^ (bin >> 1)), synchronize the gray value (two-flop, 9.3), and compare gray pointers (or convert back after synchronizing) for the flags — so a mid-transition sample is always a real pointer. Also disable timing checks on the pointer synchronizer first flops (async crossings, 9.3/8.4) to avoid X-flooding. The lesson: an async FIFO crosses multi-bit pointers safely only when they are gray-coded (one bit changes per step), so a synchronizer sampling mid-transition gets the old or new value, never a nonexistent mix; a binary pointer crossing corrupts the full/empty flags and data. (GLS reveals this under real delays; metastability survival is MTBF, 9.2; STA signs off timing, 0.3.)
7. Common Mistakes
- Crossing a binary (non-gray) pointer. Multiple bits change → mid-transition mix → wrong flags.
- Comparing pointers before synchronizing. Compare synchronized (gray) values in the receiving domain.
- Leaving checks on the pointer synchronizer first flops. Expected async
Xfloods (9.3/8.5). - Sending raw multi-bit data through a synchronizer instead of a FIFO. Use the FIFO for streams (9.3).
- Expecting GLS to prove metastability. That's MTBF (9.2).
8. Industry Best Practices
- Gray-code FIFO pointers before crossing (one bit per step).
- Synchronize the gray pointer (two-flop, 9.3); convert back after if needed.
- Disable checks on pointer synchronizer first flops (async, 8.4) — only there.
- Verify flags and data integrity under real delays in GLS.
- Leave metastability survival to MTBF (9.2).
Senior Engineer Thinking
- Beginner: "The FIFO flags are wrong — the flag logic is buggy."
- Senior: "Is the pointer gray-coded across the crossing? A binary pointer sampled mid-increment gives a value that never existed, so the flag is wrong. Gray-code it — one bit per step."
The senior checks gray-coding of crossed pointers first, knowing a binary crossing corrupts flags.
Silicon Impact
The async FIFO is the standard, safe way to move data between clock domains, and its correctness hinges on the gray-code pointer crossing. A binary pointer crossing is a real, serious silicon bug: mid-transition sampling yields nonexistent pointer values, so the FIFO computes wrong full/empty flags — dropping or duplicating data, or overrunning/underrunning the memory, an intermittent, throughput-dependent failure that's brutal to reproduce (0.3). GLS reveals this under real delays (the pointer sync timing, the gray-code property, the flags), catching a crossing built wrong before tape-out. As always, GLS shows structure and data integrity; MTBF handles the metastability probability. Correct async-FIFO design — gray pointers, proper synchronizers — is foundational to every multi-domain SoC.
Engineering Checklist
- Gray-coded the FIFO pointers before crossing (one bit per step).
- Synchronized the gray pointer (two-flop, 9.3); converted back after if needed.
- Disabled checks on pointer synchronizer first flops (async, 8.4) — only there.
- Verified full/empty flags and data integrity under real delays.
- Left metastability survival to MTBF (9.2).
Try Yourself
- Build an async FIFO; cross the write pointer as binary and watch the read domain briefly see a nonexistent pointer mid-increment → wrong empty flag.
- Observe: multiple bits changed at once; the synchronizer sampled a mix.
- Change: gray-code the pointer (
gray = bin ^ (bin >> 1)) and cross that. - Expect: a mid-transition sample is now always the old or new pointer — flags correct, data intact. Prove gray coding is what makes it safe.
Any free Verilog simulator with two async clocks reproduces the binary-vs-gray pointer crossing. No paid tool required.
Interview Perspective
- Weak: "An async FIFO is just a FIFO with two clocks."
- Good: "It crosses gray-coded write/read pointers through synchronizers and compares them for full/empty."
- Senior: "The gray code is the key: one bit changes per step, so a synchronizer sampling the pointer mid-transition gets the old or new value, never a mix — which a binary pointer (multiple bits) would corrupt into a nonexistent value and a wrong flag. GLS reveals the pointer timing, gray-code, flags, and data integrity under real delays; MTBF verifies metastability survival."
9. Interview / Review Questions
10. Key Takeaways
- A multi-bit data stream crosses clock domains through an asynchronous FIFO — a dual-port memory with gray-coded write/read pointers synchronized into the opposite domain and compared for full/empty flags.
- Gray coding is essential: consecutive values differ by one bit, so a synchronizer sampling the pointer mid-transition gets the old or new value — never a nonexistent mix (which a binary pointer would give).
- GLS reveals, under real delays: pointer sync timing (9.3), gray-code correctness, flag generation, and data integrity across the crossing.
- A binary pointer crossing is a real bug — mid-transition sampling yields a value that never existed → wrong flags and data corruption; fix by gray-coding the pointer.
- GLS shows structure/data-integrity/timing; metastability survival is MTBF's (9.2); disable checks on pointer synchronizer first flops (async, 8.4). Next: 9.5 — a CDC glitch only GLS caught.
Quick Revision
Async FIFO crosses multi-bit data: dual-port memory + gray-coded write/read pointers, synchronized across (9.3) and compared for full/empty. Gray code = one bit per step → a mid-transition sample is old or new (real), never a mix — a binary pointer gives a nonexistent value → wrong flags / corruption. GLS reveals pointer timing, gray-code, flags, data integrity under real delays; MTBF verifies metastability (9.2). Next: 9.5 — a CDC glitch only GLS caught.