异步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 一定没满可以写
满信号滞后 保守提前判满,损失性能但不会溢出
空判断在读域 读指针在读域,写指针同步过来,就地判断,及时停读
满判断放读域的问题 写指针同步滞后,低估已用空间,判满偏晚,再同步到写域更晚,导致溢出
相关推荐
Hello-FPGA5 小时前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安6 小时前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60167 小时前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld7 小时前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家19 小时前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x
szxinmai主板定制专家1 天前
AMR 项目落地:机器人控制器硬件定制化需求复盘
arm开发·单片机·fpga开发·系统架构·机器人·am62x
FPGA小迷弟2 天前
FPGA 与卫星:从星载硬件到在轨智能的完整技术解析
fpga开发·架构·fpga·1024程序员节·无线电
NamoAmitabha1282 天前
CPU 内部结构是什么?---deepseek
fpga开发
abc light2 天前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发
156082072193 天前
JFMZQ28DR、JFMZQ68DR、RFsoc XCZU47DR、XCZU48DR 信号处理板
fpga开发·信号处理