多 bit CDC:异步 FIFO 与 Gray 码指针设计
更新时间:2026-09-06。本文是「集成电路大类 · 芯片设计」进阶层(intermediate)第九篇新正文(#11)。上一篇 CDC 基础 说了多 bit 跨域三条出路,高速流式数据的标准答案就是异步 FIFO:UART 收到字节喂给 CPU 域、AXI 数据过 CDC、以太网包过网口时钟——到处都是它。同步 FIFO 的绕圈指针和深度计算我们在 FIFO 深度设计(#05)练过,这一篇攻克异步版本独有的难题:指针本身怎么跨时钟域。
本文要回答的问题
- 异步 FIFO 的空满为什么难判断?
- Gray 码为什么能安全跨域?二进制为什么不行?
- 空和满的判断条件具体是什么?
- 整块 FIFO 的 RTL 结构怎么搭?
一、结构:双口 RAM + 两个时钟域
写侧:wclk 每写一个数据,写指针 +1;写侧要判断「满」必须知道读指针(数据被读走多少)→ rptr 跨到 wclk 域。读侧对称:要判断「空」必须知道写指针 → wptr 跨到 rclk 域。数据本身存在双口 RAM 里,两端口各用各的时钟,数据从不需要跨域做时序收敛——RAM 的写和读天然隔离,这是异步 FIFO 安全的根基。
二、指针为什么必须用 Gray 码
指针是多 bit 计数值,直接二进制打两拍会踩 CDC 基础 讲的坑:0111→1000 跳变瞬间采样可能得到任意中间值。Gray 码相邻两个数只差 1 bit:
| 十进制 | 二进制 | Gray 码 |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
| 5 | 101 | 111 |
| 6 | 110 | 101 |
| 7 | 111 | 100 |
跳变时只有 1 bit 在变,跨域采样无论采到跳变前还是跳变后,得到的都是合法的旧指针或新指针——最多空满判断保守一拍(以为还满/还空),绝不会算出错误地址。转换逻辑极简:gray = bin ^ (bin >> 1)。
三、空满判断:多 1 bit 的绕圈指针
指针比 RAM 地址宽 1 bit(深度为 2^N 时指针 N+1 bit),用最高位记录「绕了几圈」:
- 空(读侧判断):同步过来的 wptr_gray 与 rptr_gray 完全相等 → 读写位置重合,没有数据;
- 满(写侧判断):wptr_gray 比同步过来的 rptr_gray 正好多绕一整圈。Gray 码下满的条件是:最高两位相反、其余位全相同,即
wgray[N:N-1] == ~rgray[N:N-1]且wgray[N-2:0] == rgray[N-2:0]。
| 条件 | 含义 |
|---|---|
| wptr_gray == rptr_gray(同步后) | 空(读写在同一点,且圈数相同) |
| 高 2 bit 相反 + 低位全等 | 满(写比读多绕整整一圈) |
跨域同步带来 1–2 拍延迟,所以空满信号都是保守的:满可能晚几拍才解除(实际已有空位但还报满,损失一点带宽)、空可能晚几拍解除——方向都是安全的(不会真满了还写、真空了还读)。almost full/almost empty 水位线做法与 同步 FIFO 相同。
四、RTL 骨架要点
// 1. 二进制指针递增 + 转 Gray;指针寄存器在各自时钟域
always_ff @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin wbin <= 0; wgray <= 0; end
else if (wen && !wfull) begin
wbin <= wbin + 1;
wgray <= (wbin + 1) ^ ((wbin + 1) >> 1); // bin->gray
end
// 2. 对方指针打两拍同步(经典 2-FF)
always_ff @(posedge wclk) {rgray_s2, rgray_s1} <= {rgray_s1, rgray_async};
// 3. 满:高 2 bit 相反、低位相等
assign wfull = (wgray[N:N-1] == ~rgray_s2[N:N-1])
&& (wgray[N-2:0] == rgray_s2[N-2:0]);
// 4. RAM:写用 wclk、读用 rclk,双口行为
always_ff @(posedge wclk) if (wen && !wfull) mem[waddr] <= wdata;
assign rdata = mem[raddr]; // 读组合输出或寄存输出易踩的坑:① 复位要两个域各自复位(见 复位设计 #12),且释放不能让指针处于亚稳采样窗口,实践中两域复位都用「异步断言、同步释放」;② 同步过来的指针是旧值,别拿它做精确水位,只能做空满;③ RAM 写满禁写、读空禁读;④ 深度选择仍按 #05 的突发长度/时钟比/延迟公式,异步场景还要留 CDC 同步延迟的余量。
五、什么时候用异步 FIFO,什么时候握手
- 高速、连续流(数据面:AXI stream 过域、网口、音频)→ 异步 FIFO,带宽高;
- 低速、偶发、带配置语义(寄存器访问、状态上报)→ req/ack 握手即可(#10),省 RAM;
- 现成工具:Memory Compiler 能直接生成双口 RAM(进阶 #25),FIFO 控制器自己写或用 vendor IP,但指针/Gray 码这层逻辑必须看懂——CDC 评审和 debug 全靠它。
相关与延伸
上一篇:跨时钟域 CDC 基础。下一篇:复位设计:异步断言同步释放与复位树(#12)。同步 FIFO 的深度公式与水位线见 FIFO 深度设计;RAM 生成见进阶 #25 Memory Compiler,CDC 静态签核见 #45。
总结
异步 FIFO = 双口 RAM(数据不跨域)+ Gray 码指针(每次只变 1 bit,跨域采到旧值/新值都合法)+ 指针打两拍到对域;空判 Gray 全等、满判高两位相反低位全等,跨域延迟只让空满偏保守、不出错——这是高速多 bit CDC 的工业标准答案。