异步FIFO为什么用打两拍的格雷码?

之前的理解:让外部不再对FIFO进行写操作,外部需要第一时间获得FIFO已满信息,哪怕信息是滞后的,full和empty同理

下面分三块回答:为什么用格雷码 + 打两拍、为什么满判断在写时钟域、满信号的目的。


一、为什么用写格雷码与打两拍的读格雷码比较?

1. 先看异步 FIFO 的指针在哪

异步 FIFO 有两个时钟域:

  • 写时钟域 :写指针 wr_ptr、写数据、写使能
  • 读时钟域 :读指针 rd_ptr、读数据、读使能

要判断满,写侧需要知道读指针;要判断空,读侧需要知道写指针。

所以指针必须跨时钟域传递:

  • rd_ptr 从读域 → 写域,用于满判断
  • wr_ptr 从写域 → 读域,用于空判断

2. 为什么指针要用格雷码

指针是连续变化的,比如从 7 到 8:

text 复制代码
二进制:0111 → 1000   (4 位同时变)
格雷码:0100 → 1100   (只有 1 位变)

跨时钟域采样时,如果多位同时变化:

  • 采样时刻刚好在变化窗口
  • 可能采到新旧混合的中间值
  • 比如 0111 → 1000 过程中可能采到 0000、1111、0110 等错误值
  • 这会导致空满判断完全错误

格雷码相邻值只变一位,跨域采样时:

  • 最多只错一位
  • 采到的要么是旧值,要么是新值
  • 不会出现多位跳变导致的严重错误

所以指针用格雷码,是为了跨域采样安全。

3. 为什么还要打两拍

即使格雷码只变一位,跨域采样仍可能遇到亚稳态:

  • 第一级触发器可能输出中间电平
  • 需要第二级触发器让它稳定
  • 这就是两级同步器

所以读格雷码进入写域时:

systemverilog 复制代码
// 读指针格雷码同步到写时钟域
always_ff @(posedge wr_clk or negedge rst_n) begin
  if (!rst_n) begin
    rd_ptr_gray_sync1 <= '0;
    rd_ptr_gray_sync2 <= '0;
  end else begin
    rd_ptr_gray_sync1 <= rd_ptr_gray;
    rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
  end
end

用 rd_ptr_gray_sync2 参与满判断。

4. 为什么同步后是"滞后"的

读指针从读域传到写域,需要:

  • 读域寄存器更新:1 拍
  • 两级同步器:2 拍
  • 组合比较:0 拍

所以写侧看到的读指针,比真实读指针滞后 2~3 拍。

这意味着:

  • 写侧以为 FIFO 更满
  • 可能提前报 full
  • 这是保守的,安全,不会溢出
  • 代价是损失一点性能

同理,读侧看到的写指针也是滞后的,可能提前报 empty,也是保守安全的。

5. 完整数据流

text 复制代码
读时钟域                          写时钟域
rd_ptr (二进制)
  ↓
rd_ptr_gray (格雷码)
  ↓ 跨域
rd_ptr_gray_sync1
  ↓
rd_ptr_gray_sync2
  ↓
转回二进制 rd_ptr_bin_sync
  ↓
与 wr_ptr 比较 → full

写侧满判断的核心:

systemverilog 复制代码
// 写指针和同步后的读指针比较
// 满:低 N 位相同,最高位不同
assign full = (wr_ptr_bin[N:0] == {~rd_ptr_bin_sync[N], rd_ptr_bin_sync[N-1:0]});

二、为什么在写时钟域做写满判断?

1. 满判断需要什么

判断满,需要比较:

  • 写指针:在写时钟域
  • 读指针:同步到写时钟域后的值

两者都在写时钟域,所以满判断自然在写时钟域做。

2. 满信号给谁用

full 是给写侧用的:

  • 写侧看到 full=1,就停止写
  • 防止覆盖还没被读走的数据
  • 防止写指针越过读指针

如果满判断放在读时钟域:

  • 读侧判断出满,再传给写侧
  • 又要跨域同步
  • 延迟更大
  • 写侧不能第一时间停写

所以满判断必须在写时钟域,就地判断,就地使用。

3. 空判断同理

空判断需要:

  • 读指针:在读时钟域
  • 写指针:同步到读时钟域后的值

所以空判断在读时钟域做,empty 给读侧用。

4. 总结

判断 需要的信息 在哪个域做 信号给谁
满 写指针 + 同步后的读指针 写时钟域 写侧
空 读指针 + 同步后的写指针 读时钟域 读侧

一句话:

谁要用,就在谁的域里判断;跨域的信息先同步过来。


三、写满信号产生的目的

你写的第 1 点方向对,我帮你展开和修正:

让外部不再对 FIFO 进行写操作,外部需要第一时间获得 FIFO 已满信息,哪怕信息是滞后的。

更完整的表述:

1. 防止溢出

FIFO 深度有限。如果写侧不停写,读侧来不及读:

  • 写指针会追上读指针
  • 新数据覆盖旧数据
  • 数据丢失

full=1 告诉写侧:别再写了,再写就覆盖了。

2. 流控/反压

full 是一种反压信号:

  • 上游看到 full,暂停发送
  • 等读侧读走数据,full 撤销
  • 上游再继续发

这是握手/流控的基本机制。

3. 第一时间性

full 必须在写时钟域就地产生,因为:

  • 写侧每个时钟沿都可能写
  • 如果 full 晚一拍,就可能多写一次
  • 多写一次就可能溢出

所以满判断要在写域组合/时序逻辑里尽快产生。

4. 滞后但保守

由于读指针同步有 2~3 拍延迟:

  • 写侧看到的读指针偏旧
  • FIFO 可能实际没满,但 full 已经拉高
  • 这叫保守判断
  • 不会溢出,但可能浪费一点带宽
  • 这是安全设计,宁可提前停,不可溢出

5. 满信号不是"精确满"

异步 FIFO 的 full 是保守满:

  • full=1 时,FIFO 一定满或快满
  • full=0 时,FIFO 不一定不满,可能刚好满但还没同步到
  • 所以写侧看到 full=0 也不能无限写,要靠协议保证

同理,empty 是保守空:

  • empty=1 时,FIFO 一定空或快空
  • empty=0 时,不一定真的有数据

6. 修正你的表述

你说:

让外部不再对FIFO进行写操作,外部需要第一时间获得FIFO已满信息,哪怕信息是滞后的

更准确:

满信号的目的,是让写侧在 FIFO 真正溢出之前停止写。由于跨域同步有延迟,满信号可能提前拉高,是保守的。写侧拿到的是"可能满"的信息,宁可早停,不可溢出。


四、一张图总结

text 复制代码
写时钟域                                读时钟域
                                        
wr_ptr ──┬── 满判断 ──▶ full ──▶ 写侧停写
         │                              
         │ 格雷码                       
         ▼                              
      wr_ptr_gray ──跨域──▶ 同步2拍 ──▶ rd_ptr_gray_sync
                                           │
                                           ▼
                                       空判断 ──▶ empty ──▶ 读侧停读
                                           ▲
                                           │
rd_ptr ──┬── 空判断 ────────────────────────┘
         │
         │ 格雷码
         ▼
      rd_ptr_gray ──跨域──▶ 同步2拍 ──▶ wr_ptr_gray_sync
                                           │
                                           ▼
                                       满判断 ──▶ full

核心:

  • 满判断在写域:写指针 + 同步来的读指针
  • 空判断在读域:读指针 + 同步来的写指针
  • 指针用格雷码:跨域只变一位,安全
  • 打两拍:消除亚稳态
  • 满/空都是保守的:滞后但安全,宁可早停,不可溢出/下溢

五、自测题

  1. 异步 FIFO 中,为什么指针要用格雷码而不是二进制?
  2. 读指针同步到写域,为什么要打两拍?
  3. 满判断为什么在写时钟域做,而不是读时钟域?
  4. full=1 时,FIFO 一定满吗?full=0 时,FIFO 一定不满吗?
  5. 满信号滞后会带来什么后果?为什么这种滞后是安全的?
  6. 空判断需要哪些信息?在哪个域做?
  7. 如果满判断放在读域,再同步到写域,会有什么问题?

下面按题号给出答案。


1. 异步 FIFO 中,为什么指针要用格雷码而不是二进制?

因为指针要跨时钟域传递,而跨域采样可能发生在指针变化的瞬间。

  • 二进制指针 :相邻值可能多位同时变化。

    例如 0111 → 1000,4 位同时变。跨域采样时可能采到新旧混合的错误值,比如 0000、1111、0110 等,导致空满判断完全错误。

  • 格雷码指针 :相邻值只变一位。

    跨域采样时最多只错一位,采到的要么是旧值,要么是新值,不会出现多位跳变导致的严重错误。

所以格雷码是为了保证指针跨时钟域采样安全,让空满判断不会因为多位同时变化而出错。


2. 读指针同步到写域,为什么要打两拍?

打两拍是为了消除亚稳态。

  • 第一级触发器:异步输入可能在 setup/hold 窗口内变化,输出可能进入亚稳态。
  • 第二级触发器:让第一级的输出再稳定一个时钟周期,把亚稳态传播概率降到极低。

格雷码只保证"只变一位",但不能消除亚稳态 。

只要跨时钟域采样,就有亚稳态风险,所以必须用两级同步器。

两级同步器的作用:

  • 第一级:可能亚稳态。
  • 第二级:输出稳定,供本地逻辑使用。
  • 大幅提高 MTBF。

3. 满判断为什么在写时钟域做,而不是读时钟域?

因为满判断需要:

  • 写指针:在写时钟域。
  • 读指针:需要从读时钟域同步到写时钟域。

两者都在写时钟域后,比较最直接、延迟最小。

更重要的是:full 信号是给写侧 用的,写侧看到 full=1 要立即停止写。

如果满判断放在读域:

  • 读域先判断满。
  • 再把 full 同步到写域。
  • 又多一级跨域延迟。
  • 写侧可能已经多写了一次,导致溢出。

所以满判断必须在写时钟域就地判断、就地使用。

同理,空判断在读时钟域做,因为 empty 是给读侧用的。


4. full=1 时,FIFO 一定满吗?full=0 时,FIFO 一定不满吗?

异步 FIFO 的 full 是保守满。

  • full=1 时 :

    写域看到的读指针是同步过来的,滞后于实际读指针。

    写域会高估 FIFO 中已用的空间,所以更容易判满。

    因此 full=1 时,实际 FIFO 不一定真的满 ,可能读侧已经读走了一些数据,但写域还没看到。

    但写侧必须停写,因为不能冒险。

  • full=0 时 :

    写域认为没满。由于写域已经高估了已用空间,如果它认为没满,那么实际已用空间只会更少。

    所以 full=0 时,FIFO 一定实际没满,可以安全写。

一句话:

full=1 不一定真满,但不能写;

full=0 一定没满,可以写。

同理,empty 也是保守空:

  • empty=1 不一定真空,但不能读。
  • empty=0 一定不空,可以读。

5. 满信号滞后会带来什么后果?为什么这种滞后是安全的?

满信号滞后,是指写域看到的读指针比实际读指针旧,导致 full 提前拉高,而不是延迟拉高。

后果:

  • 写侧可能过早停止写。
  • FIFO 实际还有空间,但写侧认为满了。
  • 损失一点性能,浪费一点带宽。

为什么安全:

  • 这种滞后是保守的:宁可提前判满,也不延迟判满。
  • 如果满信号延迟拉高,写侧会多写,导致写指针追上读指针,覆盖未读数据,造成溢出。
  • 提前判满只会少写,不会覆盖数据。

所以:

满信号滞后 = 保守 = 安全,代价是性能略降。


6. 空判断需要哪些信息?在哪个域做?

空判断需要:

  • 读指针:在读时钟域。
  • 写指针:从写时钟域同步到读时钟域后的值。

所以空判断在读时钟域做。

empty 信号给读侧用:

  • empty=1 时,读侧停止读。
  • empty=0 时,读侧可以读。

和满判断对称:

判断 需要的信息 在哪个域做 信号给谁
满 写指针 + 同步后的读指针 写时钟域 写侧
空 读指针 + 同步后的写指针 读时钟域 读侧

7. 如果满判断放在读域,再同步到写域,会有什么问题?

如果满判断放在读域:

  1. 读域有读指针。
  2. 写指针需要从写域同步到读域。
  3. 同步有延迟,读域看到的写指针是滞后的旧值。
  4. 读域用旧写指针和当前读指针判断满,会低估已用空间,更不容易判满。
  5. 判断出的 full 会偏晚。
  6. 再把 full 同步到写域,又增加延迟。
  7. 写侧可能已经多写了,导致溢出。

所以满判断不能放在读域,必须放在写域,用同步过来的读指针。

这样写域看到的是滞后的读指针 ,会高估已用空间,提前判满,保证安全。

一句话:

满判断在写域,用的是"旧的读指针",所以保守;

如果放在读域,用的是"旧的写指针",会误判为不满,导致溢出。


总结表

问题 核心答案
为什么用格雷码 相邻只变一位,跨域采样安全
为什么打两拍 消除亚稳态,提高 MTBF
满判断在写域 写指针在写域,读指针同步过来,就地判断,及时停写
full=1 / full=0 full=1 不一定真满但不能写;full=0 一定没满可以写
满信号滞后 保守提前判满,损失性能但不会溢出
空判断在读域 读指针在读域,写指针同步过来,就地判断,及时停读
满判断放读域的问题 写指针同步滞后,低估已用空间,判满偏晚,再同步到写域更晚,导致溢出
相关推荐
努力搬砖的小H2 小时前
FPGA应用技巧之AXI4-FULL-MASTER IP FDMA(Fast Direct Memory)详细介绍
经验分享·fpga开发·fdma·axi总线·米联客
szxinmai主板定制专家19 小时前
电力边缘 AI 落地难点剖析:RK 系列 + FPGA 异构平台在储能监控终端的 EMC 与算力优化
人工智能·fpga开发·arm+fpga·rk3588+fpga
szxinmai主板定制专家20 小时前
基于RK3588+FPGA+AI的储能电站边缘网关,实现电力数据采集与智能预警
arm开发·人工智能·fpga开发·rk3576·半导体设备
XMAIPC_Robot21 小时前
储能控制器产品化:ARM+FPGA整机高低温、EMC可靠性测试实战总结
arm开发·人工智能·fpga开发·arm+fpga·rk3588+fpga
szxinmai主板定制专家21 小时前
面向虚拟电厂协调控制:RK3588+FPGA+AI边缘控制器设计思路与算力评估
人工智能·嵌入式硬件·fpga开发·机器人·jetson·zynq·振动采集
XMAIPC_Robot1 天前
国产FPGA替代Xilinx在储能电力采集场景可行性评估与移植经验
人工智能·算法·机器学习·fpga开发·rk3588+fpga
星夜离尘561 天前
Testbench不只看波形:用scoreboard抓住丢包、重复和乱序
fpga开发·verilog·testbench·scoreboard
狂奔蜗牛(bradley)1 天前
EtherCAT 主站拓扑判定的三级防护:从去抖到振荡检测
人工智能·嵌入式硬件·fpga开发·架构