FIFO_IP核使用说明文档

Vivado FIFO Generator IP 核使用说明文档

适用平台 :Vivado 17.4(FIFO Generator v13.2)

适用器件 :Xilinx 7 系列 / UltraScale / UltraScale+(本文以 xcvu9p 为例)

参考手册:PG057(FIFO Generator Product Guide)


目录

  1. [FIFO 概述](#FIFO 概述)
  2. [FIFO 的分类](#FIFO 的分类)
  3. [Vivado 17.4 中 FIFO IP 核创建步骤](#Vivado 17.4 中 FIFO IP 核创建步骤)
  4. 各选项卡参数详解
  5. 水位概念详解(重点)
  6. 接口信号说明
  7. [读模式:Standard vs First Word Fall Through](#读模式:Standard vs First Word Fall Through)
  8. 使用示例代码
  9. 复位与初始化注意事项
  10. 常见问题与最佳实践
  11. 反压(Backpressure)与高级使用技巧

1. FIFO 概述

1.1 什么是 FIFO

FIFO(First In First Out,先进先出)是一种没有外部地址线的存储缓冲器,数据按写入顺序依次读出。本质上由 RAM + 读写指针控制逻辑 构成。

与 RAM 的区别:

特性 RAM FIFO
地址线 有,可随机访问 无,内部指针自动递增
读写顺序 任意地址 严格先进先出
数据读出后 仍然保留 被消耗,不再存在
适用场景 需要随机寻址 数据缓冲、跨时钟域、速率匹配

1.2 FIFO 的典型用途

  1. 跨时钟域数据传输:异步 FIFO 是不同时钟域之间安全传递多 bit 数据的标准方法
  2. 读写速率匹配:写端和读端带宽不同时做缓冲(如 ADC 高速采样 → 低速处理)
  3. 数据位宽转换:写位宽和读位宽可以不同,实现宽窄口转换
  4. 突发数据缓冲:吸收短时数据突发,平滑数据流

2. FIFO 的分类

2.1 按时钟域分类

同步 FIFO(Common Clock)
  • 读时钟和写时钟是同一个时钟
  • 读写操作在同一时钟沿触发
  • 结构简单,资源少,不需要跨时钟域同步
  • 适合同一时钟域内的速率匹配、数据缓存
异步 FIFO(Independent Clocks)
  • 读时钟(rd_clk)和写时钟(wr_clk相互独立,可以不同频不同相
  • 内部使用格雷码(Gray Code)跨时钟域传递读写指针,避免亚稳态
  • 是跨时钟域传递多 bit 数据的标准方案
  • 资源略多,有固定的读写延迟

选择原则:读写两端在同一个时钟域选同步 FIFO;跨时钟域必须选异步 FIFO。

2.2 按实现资源分类

实现类型 资源 适用深度 特点
Block RAM(BRAM) 块 RAM 硬件资源 中大容量(≥512) 最常用,不占逻辑资源,性能好;支持异步时钟;有最小深度限制
Distributed RAM LUT 查找表 小容量(<512) 用 LUT 搭建,容量灵活不浪费;支持异步读;占用逻辑资源
Shift Register 移位寄存器(SRL) 极浅(≤32) 仅支持同步时钟;延迟固定;适合极浅的流水线缓冲
Built-in FIFO 专用 FIFO 硬宏 固定容量 UltraScale 系列部分器件有专用 FIFO 硬核,性能最高

选择建议:

  • 大多数场景 → Block RAM
  • 深度很小(几十)且 BRAM 紧张 → Distributed RAM
  • UltraScale 器件且追求最高性能 → 考虑 Built-in FIFO

2.3 按接口类型分类

接口类型 特点
Native 原生接口,wr_en/rd_en + din/dout + 状态标志,最灵活最常用
AXI4-Stream tvalid/tready 握手机制,适合对接 AXI 总线系统
AXI Memory Mapped 内存映射型,较少用

本文以最常用的 Native 接口 为主进行讲解。


3. Vivado 17.4 中 FIFO IP 核创建步骤

  1. Flow NavigatorIP Catalog 中搜索 FIFO,双击 FIFO Generator
  2. 填写组件名称(如 fifo_32x512),进入配置界面
  3. 依次配置 5 个选项卡:
    • Basic:接口类型、时钟域、实现资源
    • Native Ports:数据位宽、深度、读模式、ECC、复位
    • Status Flags:空满标志、Almost 标志、握手信号
    • Data Counts:数据计数(水位计)
    • Summary:配置汇总核对
  4. 点击 OK 生成 IP

4. 各选项卡参数详解

4.1 Basic 选项卡

参数 含义 选项说明
Interface Type 接口类型 Native / AXI4-Stream / AXI Memory Mapped
FIFO Implementation 时钟域 + 实现资源组合 下拉选择,如 Independent Clocks Block RAM(异步 BRAM)、Common Clock Block RAM(同步 BRAM)等
Component Name IP 实例名 自定义

FIFO Implementation 常见组合:

选项 时钟 资源
Common Clock Block RAM 同步 BRAM
Common Clock Distributed RAM 同步 LUT
Common Clock Shift Register 同步 SRL
Common Clock Built-in FIFO 同步 硬核
Independent Clocks Block RAM 异步 BRAM
Independent Clocks Distributed RAM 异步 LUT

4.2 Native Ports 选项卡

4.2.1 数据端口参数
参数 含义 范围
Write Width 写数据位宽 1 ~ 1024 bit
Write Depth 写深度(可存储的写数据个数) 根据资源类型有可选值
Read Width 读数据位宽 1 ~ 1024 bit,可与写位宽不同
Read Depth 读深度,由写位宽/写深度/读位宽自动计算 自动推算,不可手动改

位宽转换关系写深度 × 写位宽 = 读深度 × 读位宽(总存储容量不变)。例如写 32bit × 深度 512 = 读 64bit × 深度 256。

4.2.2 读模式(Read Mode)
模式 含义 读延迟
Standard FIFO 标准模式,rd_en 拉高后下一个时钟周期出数据 1 拍(无输出寄存器)或 2 拍(有输出寄存器)
First Word Fall Through(FWFT) 预读模式,FIFO 非空时第一个数据自动出现在 dout 上,rd_en 拉高后立即弹出并准备下一个 0 拍(数据预取)

详见 [第 7 节](#第 7 节)。

4.2.3 其他参数
参数 含义
Enable ECC 启用纠错码(仅 BRAM 模式),可纠 1bit 错、检 2bit 错,会增加延迟和资源
Output Register 输出寄存器,在 dout 前加一级寄存器,改善时序但增加 1 拍读延迟
Reset Type 复位类型:异步复位 / 同步复位(仅同步 FIFO 可选)
Full Flags Reset Value 复位期间满标志的电平(高/低)
Dout Reset Value 复位期间输出数据的值
Read Latency 只读显示,当前配置下的读延迟拍数

4.3 Status Flags 选项卡

4.3.1 标准标志
标志 含义 高有效
Full Flag full,FIFO 已满,禁止写
Empty Flag empty,FIFO 已空,禁止读
4.3.2 Almost 标志(几乎满/几乎空)
标志 含义
Almost Full Flag almost_full,FIFO 即将满(剩余空间 ≤ 1 时拉高)
Almost Empty Flag almost_empty,FIFO 即将空(剩余数据 ≤ 1 时拉高)

Almost 标志的阈值是固定的:Almost Full 在数据量 ≥ 深度-1 时拉高;Almost Empty 在数据量 ≤ 1 时拉高。相当于提前 1 个数据预警。

4.3.3 握手信号(Handshaking)
信号 方向 含义
Write Acknowledge 输出 wr_ack,写成功应答,每成功写入一个数据脉冲一次
Overflow Flag 输出 overflow,写溢出标志,FIFO 满时仍写则脉冲一次
Valid Flag 输出 valid,读数据有效标志,FWFT 模式下常用
Underflow Flag 输出 underflow,读下溢标志,FIFO 空时仍读则脉冲一次
4.3.4 可编程标志(Programmable Flags)------ 水位核心

这是 FIFO 水位控制的核心,详见 [第 5 节](#第 5 节)。

4.4 Data Counts 选项卡

参数 含义
Write Data Count wr_data_count,写时钟域下的数据计数,指示当前 FIFO 中有多少个写宽度的数据
Read Data Count rd_data_count,读时钟域下的数据计数,指示当前 FIFO 中有多少个读宽度的数据
Data Count Width 计数总线位宽,最大为 log2(深度);设小了会截断低位
More Accurate Data Counts 更精确的数据计数(仅 FWFT 模式 + BRAM/Distributed 可选),消除计数延迟

异步 FIFO 中,wr_data_count 同步于写时钟,rd_data_count 同步于读时钟,两者数值可能因跨域同步延迟而短暂不一致,这是正常的。


5. 水位概念详解(重点)

5.1 什么是水位

FIFO 的水位(Water Level / Watermark) 指的是 FIFO 当前存储的数据量,可以理解为一个水池里的水深。水位从 0(空)到 深度(满)之间变化。

复制代码
深度 = 512
┌─────────────────────────────┐  ← 满(full),水位 = 512
│                             │
│  ┌──────────────────────┐   │  ← 可编程满阈值(prog_full),如水位 ≥ 400
│  │                      │   │
│  │   当前水位 = 256     │   │  ← 半满
│  │                      │   │
│  └──────────────────────┘   │  ← 可编程空阈值(prog_empty),如水位 ≤ 50
│                             │
└─────────────────────────────┘  ← 空(empty),水位 = 0

5.2 水位相关信号总览

信号 类型 触发条件 时钟域
full 固定阈值 水位 = 深度 写时钟域
almost_full 固定阈值 水位 ≥ 深度-1 写时钟域
prog_full 可编程阈值 水位 ≥ 用户设定的满阈值 写时钟域
empty 固定阈值 水位 = 0 读时钟域
almost_empty 固定阈值 水位 ≤ 1 读时钟域
prog_empty 可编程阈值 水位 ≤ 用户设定的空阈值 读时钟域
wr_data_count 数值 当前水位(写宽度单位) 写时钟域
rd_data_count 数值 当前水位(读宽度单位) 读时钟域

5.3 可编程满标志(prog_full)

5.3.1 五种可编程类型
类型 含义
Single Programmable Full Threshold Constant 单个常量阈值,prog_full 在水位 ≥ 阈值时拉高,< 阈值时拉低
Single Programmable Full Threshold Pin 阈值由输入引脚 prog_full_thresh 动态输入,运行时可改
Dual Programmable Full Threshold Constant 双阈值(Assert + Negate),带迟滞,避免在阈值附近反复跳变
Dual Programmable Full Threshold Pin 双阈值,均由引脚动态输入
No Programmable Full Flag 不使用
5.3.2 单阈值工作原理

以写深度 512,设 Full Threshold Assert Value = 400 为例:

复制代码
水位变化:  ... 398 → 399 → 400 → 401 → ... → 401 → 400 → 399 → 398 ...
prog_full: ...  0    0    1    1   ...   1    1    0    0  ...
  • 当水位从 399 增加到 400 时,prog_full 在写时钟上升沿拉高
  • 当水位从 400 减少到 399 时,prog_full 在写时钟上升沿拉低
  • 即:prog_full = (水位 ≥ 400)

阈值取值范围:3 ~ 写深度-3

5.3.3 双阈值(迟滞)工作原理

Assert Value = 400,Negate Value = 300

复制代码
水位上升: ... 398 → 399 → 400 → 401 → ...  prog_full: 0→0→1→1
水位下降: ... 301 → 300 → 299 → 298 → ...  prog_full: 1→1→0→0
  • 水位上升到 ≥ 400 时拉高
  • 水位下降到 ≤ 299 时拉低(即 < 300 时拉低)
  • 水位在 300~399 之间时,prog_full 保持原有状态

为什么需要双阈值? 当水位在单阈值附近波动时(比如一会写一个一会读一个),单阈值会导致 prog_full 频繁跳变(抖动)。双阈值引入迟滞带,保证信号稳定。

取值范围

  • Assert Value:4 ~ 写深度-3
  • Negate Value:3 ~ Assert Value-1

5.4 可编程空标志(prog_empty)

prog_full 对称,有同样的五种类型:

类型 含义
Single Programmable Empty Threshold Constant 单个常量阈值,prog_empty 在水位 ≤ 阈值时拉高
Single Programmable Empty Threshold Pin 阈值由引脚动态输入
Dual Programmable Empty Threshold Constant 双阈值迟滞
Dual Programmable Empty Threshold Pin 双阈值引脚输入
No Programmable Empty Flag 不使用

以读深度 512,设 Empty Threshold Assert Value = 50 为例:

复制代码
水位下降: ... 52 → 51 → 50 → 49 → ...  prog_empty: 0→0→1→1
水位上升: ... 49 → 50 → 51 → 52 → ...  prog_empty: 1→1→0→0
  • 水位 ≤ 50 时 prog_empty 拉高
  • 水位 > 50 时拉低

阈值取值范围:2 ~ 读深度-3(单阈值);2 ~ 读深度-4(双阈值 Assert)

5.5 数据计数(Data Count)------ 精确水位值

prog_full / prog_empty 只能告诉你"是否超过某个阈值",而 wr_data_count / rd_data_count 直接给出当前水位的精确数值

5.5.1 同步 FIFO

只有一个 data_count(或同时有 wr_data_countrd_data_count,但数值相同),位宽 = log2(深度)

5.5.2 异步 FIFO
  • wr_data_count:同步于 wr_clk,以写宽度为单位计数
  • rd_data_count:同步于 rd_clk,以读宽度为单位计数
  • 两者因跨时钟域同步存在延迟,可能短暂不一致,这是正常现象
5.5.3 位宽设置注意

如果设置的计数位宽小于 log2(深度),低位会被截断。例如深度 512(需要 9bit),若设位宽为 7,则:

复制代码
实际水位:  0  1  2  3  4  5  6  7  8 ...
data_count: 0  0  0  0  1  1  1  1  2 ...  (每 4 个才加 1,低 2 位被截断)

建议位宽设为最大值 log2(深度),避免精度损失。

5.6 水位的典型应用

应用 1:流量控制(Flow Control)
复制代码
写端逻辑:
  if (!prog_full) begin       // 水位未到高水位,继续写
      wr_en = 1;
  end else begin
      wr_en = 0;              // 到高水位,暂停写入,等对端消费
  end

读端逻辑:
  if (!prog_empty) begin      // 水位未到低水位,继续读
      rd_en = 1;
  end else begin
      rd_en = 0;              // 到低水位,暂停读取,等写端填充
  end
应用 2:帧级缓冲(攒够一批再处理)
复制代码
// 攒够 256 个数据才开始批量读取
if (wr_data_count >= 256 && !processing) begin
    start_processing = 1;
end
应用 3:半满判断

没有专门的"半满"信号,可以用 prog_full 设阈值为深度/2,或者直接判断 data_count >= 深度/2


6. 接口信号说明

6.1 异步 FIFO Native 接口信号表

信号 方向 时钟域 含义
rst 输入 异步 全局复位,高有效
wr_clk 输入 --- 写时钟
rd_clk 输入 --- 读时钟
din 输入 wr_clk 写数据,位宽 = Write Width
wr_en 输入 wr_clk 写使能,高有效
full 输出 wr_clk 满标志
almost_full 输出 wr_clk 几乎满
prog_full 输出 wr_clk 可编程满
wr_data_count 输出 wr_clk 写数据计数
overflow 输出 wr_clk 写溢出
wr_ack 输出 wr_clk 写应答
dout 输出 rd_clk 读数据,位宽 = Read Width
rd_en 输入 rd_clk 读使能,高有效
empty 输出 rd_clk 空标志
almost_empty 输出 rd_clk 几乎空
prog_empty 输出 rd_clk 可编程空
rd_data_count 输出 rd_clk 读数据计数
valid 输出 rd_clk 读数据有效(FWFT 模式常用)
underflow 输出 rd_clk 读下溢
wr_rst_busy 输出 wr_clk 写域复位忙,高表示复位中不可写
rd_rst_busy 输出 rd_clk 读域复位忙,高表示复位中不可读

6.2 同步 FIFO 差异

同步 FIFO 只有一个时钟 clk,没有 wr_clk / rd_clk 之分,wr_data_countrd_data_count 合并为 data_count,也没有 wr_rst_busy / rd_rst_busy(部分配置有 rst_busy)。


7. 读模式:Standard vs First Word Fall Through

7.1 Standard FIFO(标准模式)

读使能 rd_en 和数据 dout 的关系:

复制代码
时钟沿:    1    2    3    4    5
rd_en:    0    1    1    0    0
dout:    XX   D0   D1   D1   XX
empty:   0    0    0    1    1
valid:   0    1    1    0    0
  • 第 2 个时钟沿 rd_en=1,第 3 个时钟沿 dout 出现 D0(延迟 1 拍)
  • 必须先判断 !empty 再拉 rd_en
  • 读延迟 = 1 拍(无输出寄存器)或 2 拍(有输出寄存器)

7.2 First Word Fall Through(FWFT,预读模式)

复制代码
时钟沿:    1    2    3    4    5
wr_en:    1    0    0    0    0
din:      D0   XX   XX   XX   XX
empty:    1    0    0    0    0
dout:    XX   D0   D0   D0   D0   ← 数据自动出现在输出口
rd_en:    0    0    1    1    0
valid:    0    1    1    1    1
  • 写入 D0 后,empty 拉低,D0 自动出现在 dout ,无需 rd_en
  • rd_en=1 时弹出当前数据,下一个数据自动顶上
  • 读延迟 = 0 拍(数据预取)
  • 常配合 valid 信号使用:valid=1 表示 dout 上有有效数据

FWFT 的优势 :读端逻辑更简单,看到 valid=1(或 empty=0)就可以直接取数据,不需要先发读使能再等一拍。

FWFT 的代价 :资源略多,data_count 的含义有细微差别(已预取的数据是否计入),建议开启 More Accurate Data Counts 选项。

7.3 选择建议

场景 推荐模式
自定义逻辑,需要精确控制读时序 Standard
对接 AXI-Stream / 数据流处理 FWFT
读端希望数据立即可用,减少延迟 FWFT
资源极度紧张 Standard

8. 使用示例代码

8.1 异步 FIFO 示例(跨时钟域)

配置:异步 BRAM FIFO,写 32bit × 512,读 32bit × 512,Standard 模式,带 prog_full/prog_empty 和 data_count。

verilog 复制代码
module async_fifo_example (
    input  wire        wr_clk,      // 写时钟 250MHz
    input  wire        rd_clk,      // 读时钟 125MHz
    input  wire        rst,         // 全局复位

    // 写端口
    input  wire [31:0] wr_data,
    input  wire        wr_valid,    // 写端数据有效
    output wire        wr_ready,    // 写端可以接收(FIFO 未满到阈值)

    // 读端口
    output reg  [31:0] rd_data,
    output reg         rd_valid,    // 读端数据有效
    input  wire        rd_ready     // 读端可以接收
);

    wire        full;
    wire        empty;
    wire        prog_full;     // 水位 ≥ 400 时拉高
    wire        prog_empty;    // 水位 ≤ 50 时拉高
    wire [8:0]  wr_data_count;
    wire [8:0]  rd_data_count;
    wire        rd_en;
    wire [31:0] fifo_dout;

    // 写端:FIFO 未到高水位且写端有数据就写入
    assign wr_ready = ~prog_full;
    wire wr_en = wr_valid & wr_ready;

    // 读端:FIFO 非空且读端就绪就读取
    assign rd_en = ~empty & rd_ready;

    // FIFO IP 例化
    fifo_32x512 u_fifo (
        .rst           (rst),
        .wr_clk        (wr_clk),
        .rd_clk        (rd_clk),
        .din           (wr_data),
        .wr_en         (wr_en),
        .rd_en         (rd_en),
        .dout          (fifo_dout),
        .full          (full),
        .empty         (empty),
        .prog_full     (prog_full),
        .prog_empty    (prog_empty),
        .wr_data_count (wr_data_count),
        .rd_data_count (rd_data_count)
    );

    // 读端数据打拍(Standard 模式下 rd_en 后 1 拍出数据)
    always @(posedge rd_clk) begin
        rd_data  <= fifo_dout;
        rd_valid <= rd_en;
    end

endmodule

8.2 FWFT 模式读端示例

verilog 复制代码
// FWFT 模式下,数据自动出现在 dout,用 valid 指示
wire [31:0] dout;
wire        valid;
wire        empty;
reg         rd_en;

always @(*) begin
    // valid=1 表示 dout 上有有效数据
    // 如果下游 ready 且 FIFO 有数据,就弹出(rd_en=1)
    rd_en = valid & downstream_ready;
end

fifo_fwft u_fifo (
    .rd_en  (rd_en),
    .dout   (dout),
    .valid  (valid),
    .empty  (empty),
    // ...
);

8.3 水位监控示例

verilog 复制代码
// 在写时钟域监控水位
always @(posedge wr_clk) begin
    if (prog_full) begin
        // 水位过高,向上游发送暂停信号(如 XOFF)
        tx_pause <= 1'b1;
    end else if (prog_empty) begin
        // 水位过低,恢复发送(XON)
        tx_pause <= 1'b0;
    end
end

9. 复位与初始化注意事项

9.1 复位忙信号(异步 FIFO)

异步 FIFO 复位后,内部需要多个时钟周期完成指针同步和初始化,期间输出 wr_rst_busyrd_rst_busy

  • wr_rst_busy = 1:写域正在复位,禁止写操作
  • rd_rst_busy = 1:读域正在复位,禁止读操作

正确的复位流程:

verilog 复制代码
// 写端逻辑必须等 wr_rst_busy 拉低后才能写
assign wr_en = wr_valid & ~full & ~wr_rst_busy;

// 读端逻辑必须等 rd_rst_busy 拉低后才能读
assign rd_en = ~empty & ~rd_rst_busy & rd_ready;

复位后 empty 可能不会立即拉高,必须以 rd_rst_busy 为准。复位忙通常持续几十个时钟周期。

9.2 复位期间的写操作

复位期间(rst=1rst_busy=1)写入的数据不会被正确存储,必须等复位完全结束后再开始操作。

9.3 热复位

运行中重新复位 FIFO 时:

  1. 拉低 wr_enrd_en
  2. 拉高 rst 至少几个时钟周期
  3. 释放 rst
  4. 等待 wr_rst_busyrd_rst_busy 都拉低
  5. 恢复正常读写

10. 常见问题与最佳实践

10.1 常见问题

问题 1:仿真时 dout 是 X(未知态)

原因 :Standard 模式下,rd_en 拉高后需要 1 拍才出数据;或者复位后未等 rst_busy 拉低就读取。

解决 :检查读时序,确保 rd_en 只在 !empty && !rst_busy 时拉高;FWFT 模式下看 valid 信号。

问题 2:异步 FIFO 数据丢失或错乱

原因

  • 写端在 full=1 时仍写(溢出)
  • 读端在 empty=1 时仍读(下溢)
  • 复位期间进行了读写
  • 跨时钟域约束未加(时序问题)

解决 :严格用 full / empty 做握手机制;复位后等 rst_busy 拉低;在 XDC 中设置异步时钟约束 set_clock_groups -asynchronous

问题 3:wr_data_countrd_data_count 不一致

原因 :异步 FIFO 中两个计数分别同步于不同时钟域,存在同步延迟,数值短暂不一致是正常的

解决 :不要在同一个逻辑中同时使用两个计数做精确比较;写端逻辑只用 wr_data_count,读端逻辑只用 rd_data_count

问题 4:prog_full 频繁抖动

原因:水位在单阈值附近波动,一会写一个一会读一个。

解决 :改用双阈值模式(Dual Threshold),引入迟滞带。

问题 5:BRAM FIFO 最小深度限制

原因:BRAM 有最小容量,配置深度太小时 Wizard 可能报错或自动调整。

解决:小深度 FIFO 改用 Distributed RAM 或 Shift Register 实现。

10.2 最佳实践

  1. 写端必须检查 fullprog_full,禁止溢出
  2. 读端必须检查 empty,禁止下溢
  3. 异步 FIFO 复位后等 rst_busy 拉低再操作
  4. 水位控制用 prog_full / prog_empty 做阈值判断,比轮询 data_count 更省逻辑
  5. 需要精确水位值时才开 data_count,不需要就关掉,节省资源
  6. 跨时钟域必须加异步时钟约束,否则实现可能报时序违例
  7. 位宽转换时注意数据字节序:写端高位先入,读端对应关系见 PG057
  8. FWFT 模式建议开启 valid 信号,读端逻辑更简洁
  9. FIFO 深度选择:至少能容纳最大突发长度 + 跨域延迟裕量,一般留 1.5~2 倍余量
  10. ECC 仅在高可靠性场景开启,会增加延迟和资源

11. 反压(Backpressure)与高级使用技巧

11.1 什么是反压

反压(Backpressure) 是指下游模块处理不过来时,通过某种信号告诉上游"暂停发送"的流量控制机制。在 FIFO 系统中,反压是保证数据不丢失的核心手段。

复制代码
上游数据源 ───wr_en/din──▶  FIFO  ───rd_en/dout──▶ 下游处理模块
              ▲                                  │
              │         full / prog_full         │
              └────────── 反压信号 ◀──────────────┘
                         (下游慢 → FIFO 满 → 通知上游停)

核心思想 :FIFO 的 full / prog_full 信号就是天然的反压信号------当 FIFO 水位达到阈值,上游停止写入,等下游消费后水位下降再恢复。

11.2 Native 接口下的反压实现

11.2.1 基本反压(用 full 信号)

最简单的反压:上游在 full=0 时才写。

verilog 复制代码
// 写端反压:FIFO 满了就不写
assign wr_en = upstream_valid & ~full & ~wr_rst_busy;
assign upstream_ready = ~full & ~wr_rst_busy;  // 告诉上游可以发

full 是"已经满了"才拉高,此时上游组合逻辑立刻停写还来得及(因为 wr_en 是组合输出),不过如果上游有流水线延迟,数据可能已经在途中了,会导致溢出。

11.2.2 提前反压(用 prog_full 信号)------ 推荐

prog_full 提前预警,给上游流水线留出排空时间。

verilog 复制代码
// 设 prog_full 阈值 = 深度 - 上游流水线级数 - 裕量
// 例如深度 512,上游流水线 3 级,裕量 5 → 阈值设 504
assign wr_en = upstream_valid & ~prog_full & ~wr_rst_busy;
assign upstream_ready = ~prog_full & ~wr_rst_busy;

为什么要提前? 上游模块可能是多级流水线,当它收到"停"的信号后,已经在流水线中的数据还会继续流出几个周期。如果用 full 做反压,这些在途数据会溢出。prog_full 提前 N 个数据预警,正好容纳在途数据。

阈值计算公式:

复制代码
prog_full 阈值 = FIFO 深度 - 上游流水线延迟拍数 - 安全裕量(2~5)
11.2.3 读端反压(下游反压 FIFO)

读端也有反压:下游处理不过来时,拉低 rd_ready,FIFO 就不读,水位自然上升,最终通过 prog_full 反压到上游。

verilog 复制代码
// 读端:下游 ready 时才读
assign rd_en = ~empty & ~rd_rst_busy & downstream_ready;

这样就形成了从下游到上游的完整反压链

复制代码
下游忙 → downstream_ready=0 → rd_en=0 → FIFO 水位上升
→ prog_full=1 → upstream_ready=0 → 上游停发

11.3 AXI4-Stream 接口下的反压

如果 FIFO 配置为 AXI4-Stream 接口,反压是协议内置的:

信号 方向 含义
s_axis_tvalid 输入 上游数据有效
s_axis_tready 输出 FIFO 可接收(反压信号,= ~prog_full)
m_axis_tvalid 输出 FIFO 输出数据有效
m_axis_tready 输入 下游可接收(下游反压 FIFO)

握手规则 :数据传输发生在 tvalid && tready 同时为高的时钟沿。

verilog 复制代码
// AXI-Stream 从端(写 FIFO):FIFO 满时 tready 拉低,反压上游
// 这是 IP 核内部自动实现的,用户无需额外逻辑

// AXI-Stream 主端(读 FIFO):下游 tready 拉低时,FIFO 停止弹出
// 也是 IP 核内部自动实现的

AXI4-Stream FIFO 的反压是自动的,s_axis_tready 内部关联到 prog_fullfullm_axis_tvalid 关联到 empty。用户只需正确连接上下游即可。

11.4 反压的层级传递(流水线反压)

在多级流水线系统中,反压需要逐级传递,不能只靠最后一级 FIFO。

复制代码
模块A ──▶ FIFO1 ──▶ 模块B ──▶ FIFO2 ──▶ 模块C
  ▲                                        │
  └──────────── 反压链 ────────────────────┘

正确做法:每一级都有自己的反压信号,逐级传递:

verilog 复制代码
// 模块B 的输入反压
assign b_ready = ~fifo1_prog_full;

// 模块B 的输出反压(来自 FIFO2)
assign b_out_valid = b_processing_done & ~fifo2_prog_full;

// 模块B 内部:如果输出被反压,暂停处理(输入数据还在 FIFO1 中缓冲)
always @(posedge clk) begin
    if (b_out_valid && ~fifo2_prog_full) begin
        // 正常处理
    end else if (fifo2_prog_full) begin
        // 输出被反压,暂停从 FIFO1 读数据
        b_pause <= 1'b1;
    end
end

关键原则:反压信号必须能传递到数据源,中间任何一级"只进不出"都会导致数据丢失。

11.5 反压的常见陷阱

陷阱 1:反压路径上有寄存器导致延迟

如果 upstream_ready(反压信号)经过一级寄存器才到上游,上游会多收到 1 拍数据,prog_full 阈值必须把这个延迟算进去。

复制代码
prog_full ──▶ 寄存器 ──▶ upstream_ready(延迟 1 拍)

解决:反压路径尽量用组合逻辑,或者阈值多减延迟拍数。

陷阱 2:死锁(Deadlock)

上下游互相等待导致死锁:

  • 上游等下游 ready 才发数据
  • 下游等上游 valid 才拉高 ready

AXI-Stream 死锁示例

复制代码
上游:tvalid 依赖下游 tready(错!tvalid 应该独立)
下游:tready 依赖上游 tvalid(错!tready 应该独立)

解决tvalidtready 必须各自独立产生,不能互相依赖。数据来源决定 tvalid,缓冲空间决定 tready

陷阱 3:反压时丢失在途数据

上游是多拍流水线,反压信号到达后,流水线中已有的数据还会流出。如果 FIFO 剩余空间不够,就会溢出。

解决 :用 prog_full 提前反压,阈值 = 深度 - 流水线深度 - 裕量。

陷阱 4:复位期间反压信号不确定

复位期间 full / prog_full 可能是任意值,上游可能误判。

解决 :反压逻辑中加入 wr_rst_busy

verilog 复制代码
assign upstream_ready = ~prog_full & ~wr_rst_busy;

11.6 其他高级使用技巧

技巧 1:预填充(Prefill)与低水位唤醒

某些场景(如数据包处理)希望 FIFO 攒到一定量才开始读,避免读端频繁启停。用 prog_empty 实现:

verilog 复制代码
// 水位低于低阈值时停止读,攒数据
// 水位高于高阈值时恢复读
reg reading;

always @(posedge rd_clk) begin
    if (rst) begin
        reading <= 1'b0;
    end else begin
        case (reading)
            1'b0: if (~prog_empty) reading <= 1'b1;  // 攒够了,开始读
            1'b1: if (empty)      reading <= 1'b0;  // 读空了,暂停攒数据
        endcase
    end
end

assign rd_en = reading & downstream_ready & ~empty;

这本质上是一个双阈值迟滞:低阈值启动,读空停止。

技巧 2:用双阈值 prog_full 做滞回控制

单阈值 prog_full 在水位附近波动时会频繁跳变,导致上游时停时发。用双阈值(Assert/Negate)引入滞回:

复制代码
Assert Value = 450(水位 ≥ 450 时反压)
Negate Value = 350(水位 < 350 时解除反压)

这样上游一旦停发,要等水位降到 350 以下才恢复,中间 350~450 的区间保持停发状态,避免抖动。

技巧 3:包边界感知的反压

如果传输的是数据包(帧),反压时最好在包边界暂停,避免发送半个包。

verilog 复制代码
// 仅在包边界(sop 或 eop)处响应反压
reg pkt_in_progress;

always @(posedge wr_clk) begin
    if (wr_en && din_is_sop) pkt_in_progress <= 1'b1;
    if (wr_en && din_is_eop) pkt_in_progress <= 1'b0;
end

// 包发送中途不响应反压,保证包完整性
assign wr_en = upstream_valid & (~prog_full | pkt_in_progress) & ~wr_rst_busy;

注意:这样做要求 FIFO 剩余空间至少能容纳一个最大包长,否则仍会溢出。

技巧 4:水位监控与性能统计

利用 wr_data_count / rd_data_count 做实时水位监控,评估 FIFO 深度是否合理:

verilog 复制代码
// 记录最高水位(写时钟域)
reg [8:0] max_water_level;
always @(posedge wr_clk) begin
    if (rst) max_water_level <= 0;
    else if (wr_data_count > max_water_level)
        max_water_level <= wr_data_count;
end

如果 max_water_level 经常接近深度,说明 FIFO 太浅,需要加深;如果远低于深度,说明可以减省资源。

技巧 5:异步 FIFO 的跨域反压延迟补偿

异步 FIFO 中,prog_full 同步于写时钟,但实际水位变化受读端(另一个时钟域)影响,存在跨域同步延迟(通常 2~3 个写时钟周期)。

补偿方法prog_full 阈值额外减去跨域延迟:

复制代码
阈值 = 深度 - 上游流水线延迟 - 跨域同步延迟(2~3拍)- 裕量
技巧 6:用 almost_full 做最后一级保护

almost_full(剩余 ≤ 1 时拉高)可以作为"最后防线",在 prog_full 失效时紧急停止:

verilog 复制代码
assign wr_en = upstream_valid & ~prog_full & ~almost_full & ~full & ~wr_rst_busy;

虽然正常情况下 prog_full 已经够了,但多加一层保护可以在异常时避免溢出。

技巧 7:FWFT 模式下的反压注意

FWFT 模式下,valid 信号表示 dout 上有数据。下游反压时(downstream_ready=0),rd_en=0,数据保持在 dout 上不弹出,FIFO 水位不会继续下降,最终反压传递到上游。

verilog 复制代码
// FWFT 读端反压
assign rd_en = valid & downstream_ready;  // 有数据且下游就绪才弹出

FWFT 模式下 rd_en 本质上就是"弹出确认",数据已经在输出口上了,不需要先发读请求再等数据。


附录 A:FIFO 深度估算公式

对于读写速率不同的场景,FIFO 最小深度估算:

复制代码
最小深度 = 突发数据量 × (1 - 读速率/写速率) + 裕量

示例:写速率 10 Gbps,读速率 8 Gbps,最大突发 1000 个 32bit 数据:

复制代码
最小深度 = 1000 × (1 - 8/10) = 200
实际选 512(留余量,且为 2 的幂)

附录 B:常用配置速查

应用场景 时钟 资源 位宽 深度 读模式 关键标志
跨时钟域数据桥 异步 BRAM 32 512 Standard full/empty/rst_busy
AXI-Stream 缓冲 异步 BRAM 64 1024 FWFT valid/ready/prog_full
小容量指令缓存 同步 Distributed 8 64 Standard full/empty
极浅流水线对齐 同步 Shift Reg 16 16 Standard full/empty
高速数据率匹配 异步 BRAM 64→32 2048→4096 FWFT prog_full/prog_empty/data_count

文档说明:本文基于 Vivado 17.4(FIFO Generator v13.2)编写,操作界面在更高版本中基本一致。具体参数取值范围和行为请以 Xilinx 官方文档 PG057 为准。

相关推荐
明德扬19 小时前
高速采集选 Direct RF 还是外置 ADC/FMC?从采样率、JESD204B 和开发周期对比
fpga开发·fpga
明德扬19 小时前
机器人为什么需要“感知—控制—计算”一体化?FPGA 多传感器底板设计要点
fpga开发·机器人·fpga
MDYFPGA5 天前
明德扬 Kintex-7 FPGA核心板及多接口扩展底板介绍
fpga开发·fpga
FPGA小迷弟5 天前
FPGA复位策略深度解析:异步复位、同步释放与设计最佳实践
fpga开发·fpga
MDYFPGA5 天前
Arria 10 10AX027 核心板适合哪些高速数据处理项目
fpga开发·fpga
芯路行者7 天前
4.4 HDLBits —— 更多时序逻辑电路
fpga开发·verilog·fpga·hdlbits
芯路行者8 天前
7 HDLBits —— 验证:编写测试激励文件
fpga开发·verilog·fpga·hdlbits
芯路行者8 天前
4.1 HDLBits —— 时序逻辑电路之锁存器与触发器
fpga开发·verilog·fpga·hdlbits
芯路行者8 天前
5 HDLBits —— 构建更大的电路
fpga开发·verilog·fpga·hdlbits