Vivado FIFO Generator IP 核使用说明文档
适用平台 :Vivado 17.4(FIFO Generator v13.2)
适用器件 :Xilinx 7 系列 / UltraScale / UltraScale+(本文以 xcvu9p 为例)
参考手册:PG057(FIFO Generator Product Guide)
目录
- [FIFO 概述](#FIFO 概述)
- [FIFO 的分类](#FIFO 的分类)
- [Vivado 17.4 中 FIFO IP 核创建步骤](#Vivado 17.4 中 FIFO IP 核创建步骤)
- 各选项卡参数详解
- 水位概念详解(重点)
- 接口信号说明
- [读模式:Standard vs First Word Fall Through](#读模式:Standard vs First Word Fall Through)
- 使用示例代码
- 复位与初始化注意事项
- 常见问题与最佳实践
- 反压(Backpressure)与高级使用技巧
1. FIFO 概述
1.1 什么是 FIFO
FIFO(First In First Out,先进先出)是一种没有外部地址线的存储缓冲器,数据按写入顺序依次读出。本质上由 RAM + 读写指针控制逻辑 构成。
与 RAM 的区别:
| 特性 | RAM | FIFO |
|---|---|---|
| 地址线 | 有,可随机访问 | 无,内部指针自动递增 |
| 读写顺序 | 任意地址 | 严格先进先出 |
| 数据读出后 | 仍然保留 | 被消耗,不再存在 |
| 适用场景 | 需要随机寻址 | 数据缓冲、跨时钟域、速率匹配 |
1.2 FIFO 的典型用途
- 跨时钟域数据传输:异步 FIFO 是不同时钟域之间安全传递多 bit 数据的标准方法
- 读写速率匹配:写端和读端带宽不同时做缓冲(如 ADC 高速采样 → 低速处理)
- 数据位宽转换:写位宽和读位宽可以不同,实现宽窄口转换
- 突发数据缓冲:吸收短时数据突发,平滑数据流
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 核创建步骤
- 在 Flow Navigator → IP Catalog 中搜索
FIFO,双击 FIFO Generator - 填写组件名称(如
fifo_32x512),进入配置界面 - 依次配置 5 个选项卡:
- Basic:接口类型、时钟域、实现资源
- Native Ports:数据位宽、深度、读模式、ECC、复位
- Status Flags:空满标志、Almost 标志、握手信号
- Data Counts:数据计数(水位计)
- Summary:配置汇总核对
- 点击 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_count 和 rd_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_count 和 rd_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_busy 和 rd_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=1 或 rst_busy=1)写入的数据不会被正确存储,必须等复位完全结束后再开始操作。
9.3 热复位
运行中重新复位 FIFO 时:
- 拉低
wr_en和rd_en - 拉高
rst至少几个时钟周期 - 释放
rst - 等待
wr_rst_busy和rd_rst_busy都拉低 - 恢复正常读写
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_count 和 rd_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 最佳实践
- 写端必须检查
full或prog_full,禁止溢出 - 读端必须检查
empty,禁止下溢 - 异步 FIFO 复位后等
rst_busy拉低再操作 - 水位控制用
prog_full/prog_empty做阈值判断,比轮询data_count更省逻辑 - 需要精确水位值时才开
data_count,不需要就关掉,节省资源 - 跨时钟域必须加异步时钟约束,否则实现可能报时序违例
- 位宽转换时注意数据字节序:写端高位先入,读端对应关系见 PG057
- FWFT 模式建议开启
valid信号,读端逻辑更简洁 - FIFO 深度选择:至少能容纳最大突发长度 + 跨域延迟裕量,一般留 1.5~2 倍余量
- 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_full或full,m_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 应该独立)
解决 :tvalid 和 tready 必须各自独立产生,不能互相依赖。数据来源决定 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 为准。