异步 FIFO 为什么要用格雷码(Gray code)传指针?
更新时间:2026-09-21。问题驱动短答页。完整结构见 异步 FIFO 与 CDC,亚稳态见 亚稳态是什么,深度计算见 FIFO 深度怎么算。
一句话回答
因为读写指针要跨时钟域比较「空/满」,而二进制指针加 1 时经常多位同时翻转(如 0111→1000 变 4 位),跨域打拍时各 bit 亚稳态恢复不同步,会采到一个既非旧值也非新值的错误地址;格雷码相邻两个数只差 1 bit,就算跨域那拍没采准,结果也只能是旧值或新值之一,不会跳到第三个值,指针比较因此不会产生致命的虚假空满。
为什么二进制不行
把一个多位总线从快时钟域送到慢时钟域,不能逐位各打两拍:每个触发器的亚稳态恢复时刻不同,本来想传 0111,接收侧可能采到 0100、1011 之类的中间混搭值。对地址指针来说,这会让空满判断瞬间错乱——FIFO 没满却报满(丢性能)或没满却认为空/半空(最坏溢出)。
格雷码怎么解决
格雷码相邻值仅 1 bit 不同:
bash
二进制 格雷码
000 000
001 001
010 011
011 010
100 110
... ...指针从 N 变到 N+1 时只有 1 根线在变。这根变化的线即使被采到亚稳态,接收方读到的要么是变化前的完整格雷值、要么是变化后的完整格雷值——二者都是合法的旧/新指针,绝不会是第三个地址。配合两级同步器把这 1 bit 的亚稳态概率压到足够低即可(单 bit 打两拍的做法与代码见 亚稳态是什么)。
空满判断的细节
- 格雷码没有大小含义、不能直接算差,做法是把一侧指针转成格雷码、打两拍同步到另一侧再比较。
- 判空在读时钟域:用写指针同步过来的值和读指针比,相等即空。
- 判满在写时钟域:把读指针同步过来与写指针比;满条件利用格雷码「最高两位相反、其余相同」的对称特性判断(深度取 2^n 正是为此)。
- 因为同步有延迟,空满是保守的:可能「实际没满却报满一拍」,宁可保守也不能误判成可写而溢出。
常见坑
- 二进制指针直接打两拍:多 bit 混搭,空满乱跳。
- 格雷码和二进制转换写错:
gray = bin ^ (bin >> 1),反向恢复要逐位异或。 - 深度不取 2 的幂:指针回绕的格雷对称性被破坏,满判断失效,见 FIFO 深度。
- 对数据总线也用格雷码:只有「单调递增的指针」适合格雷码,普通多位数据要靠双口 RAM 整体存放,不是给每 bit 打拍。
总结
异步 FIFO 的读写指针必须跨时钟域,二进制指针一次多位翻转经同步会采到新旧 bit 混搭的虚假地址,格雷码相邻值只差 1 bit,跨域采样只能落在旧值或新值、绝不可能跳到第三个值,因此把指针转成格雷码再打两拍同步就能安全地比较空满;判空在读域、判满在写域且依赖 2 的幂深度的格雷对称,结论天然偏保守以杜绝溢出,而普通多位数据不能照搬这套、必须经双口 RAM 传递。