Skip to content

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 existedwrong 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 timingmetastability survival is MTBF (9.2).
  • Disable timing checks on the pointer synchronizer first flops (async crossings, 9.3/8.4).
Async FIFO: dual-port memory, gray-coded write/read pointers synchronized across domains, compared for full/empty flagswritereadWrite domainwrite pointer (gray-coded)Dual-port memorywritten by wr, read by rdRead domainread pointer (gray-coded)wr ptr → read domainsynchronized (9.3) → EMPTYflagGray code (1bit/step)mid-transition sample = oldOR new (not a mix)rd ptr → write domainsynchronized (9.3) → FULLflag12
Figure 1 — an asynchronous FIFO in GLS (representative). A dual-port memory is written in the write domain (write pointer) and read in the read domain (read pointer). Each pointer is GRAY-CODED (one bit changes per step), synchronized into the opposite domain (two-flop, 9.3), and compared to make the FULL and EMPTY flags. Gray coding is what makes the multi-bit pointer safe to cross: a synchronizer sampling mid-transition gets the OLD or NEW value, never a mix (a binary pointer would give a nonexistent value). GLS reveals pointer timing, gray-code correctness, flags, and data integrity under real delays.

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 → 1000 flips 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):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// 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.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 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 = corruption

Practical context (representative, tool-neutral):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# 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 sim

A 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 cycles
A gray-coded pointer synchronizes to a valid value; a binary pointer sampled mid-transition gives a value that never existed, corrupting the flagbinary mid-transition → nonexistent value → wrong flagbinary mid-transition …clk_rdgray_sync (safe)01000100110011001101110111111111bin_sync (mid-transition)01111111100010001001100110101010flag_badXt0t1t2t3t4t5t6t7
Representative. gray_sync (a gray-coded pointer synchronized across) always resolves to a real value (old or new), so the flag is correct. bin_sync (a binary pointer sampled mid-increment, multiple bits changing) briefly shows a value that never existed, so flag_bad computes a wrong full/empty. Gray coding is what makes the multi-bit pointer crossing safe.

6. Debugging Session — wrong full/empty from a binary pointer crossing

1

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)
Symptom

An async FIFO asserts full when nearly empty (or vice versa), corrupting flow control and dropping/duplicating data.

Root Cause

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).

Fix

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 X floods (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 flagsdropping 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

  1. 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.
  2. Observe: multiple bits changed at once; the synchronizer sampled a mix.
  3. Change: gray-code the pointer (gray = bin ^ (bin >> 1)) and cross that.
  4. 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 existedwrong 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 valuewrong 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.