FPGA 进阶 06:FIFO 设计——同步/异步 FIFO、almost 水位与深度计算
本文是
fpga/进阶层第 10 篇。异步 FIFO 篇 专攻跨时钟域的 Gray 码指针,这篇把 FIFO 当工程对象做选型与设计总览:同步还是异步、标准还是 FWFT、水位标志怎么设、深度怎么算、用什么资源实现、IP 怎么配。FIFO IP 在 常用 IP 核 有总览,深度通用算法可对照 FIFO 深度计算。
本文要回答的问题
- 同步 FIFO 和异步 FIFO 什么时候用哪个?
- FWFT 和标准读模式差在哪,为什么数据流设计爱用 FWFT?
- almost/prog full/empty 水位怎么定,为什么不能等满才反压?
- FIFO 深度到底怎么算?
- BRAM、分布式 RAM、FF 实现 FIFO 怎么选?
一、同步 FIFO vs 异步 FIFO
| 同步 FIFO | 异步 FIFO | |
|---|---|---|
| 时钟 | 读写同一时钟 | 读时钟、写时钟独立/异步 |
| 指针跨域 | 不需要(同域) | 需要 Gray 码 + 两级同步器 |
| 空满判断 | 二进制指针直接比 | Gray 指针跨域比较、n+1 位 |
| 逻辑/延迟 | 少、时序好 | 多 Gray 转换和同步延迟 |
| 典型用途 | 同域削峰缓冲、模块解耦、排队 | 跨时钟域传数据流 |
| Xilinx IP | FIFO Generator Common Clock | Independent Clocks |
| Intel IP | SCFIFO | DCFIFO |
单时钟域里做缓冲用同步 FIFO 即可,别用异步 FIFO 做同域缓冲——白白多出 Gray 转换和跨域同步的逻辑/延迟。跨时钟域传多位数据流才用异步 FIFO(原理见 进阶 08)。
同步 FIFO 核心逻辑骨架
单时钟下指针不用 Gray,空满直接比较:
// 同步 FIFO(FWFT 略),深度 DEPTH(2^n),指针 n+1 位
reg [W:0] wptr, rptr; // 多 1 位区分空满
wire [W:0] wcnt = wptr + 1'b1;
assign full = (wcnt == rptr); // 写追上读一圈
assign empty = (wptr == rptr); // 读追上写
always @(posedge clk) begin
if (!rst_n) begin wptr<=0; rptr<=0; end
else begin
if (wr_en && !full) begin
mem[wptr[W-1:0]] <= wdata;
wptr <= wcnt;
end
if (rd_en && !empty)
rptr <= rptr + 1'b1;
end
end二、读模式:标准 vs FWFT
| 标准(normal) | FWFT(First Word Fall Through) | |
|---|---|---|
| 数据出现 | rd_en 后下一拍 rdata 有效 | 非空即队首在 rdata、立即可见 |
| rd_en 语义 | 「给我读这个地址」 | 「我取走当前队首,弹下一个」 |
| 对接 valid/ready | 要自己对齐 rd_en 和 rdata(差 1 拍) | rdata+valid 直接给下游,ready 当 rd_en |
| 适用 | 严格按地址读、省逻辑 | 绝大多数数据流/握手 |
FWFT 下 FIFO 输出天然就是「数据 + 有效」,下游 ready 直接连读使能,衔接 AXI-Stream/valid-ready 最省事(握手细节见 valid/ready 握手)。
三、水位标志:almost / prog full/empty
除了二值的 full/empty,实用 FIFO 都要可编程水位:
- almost full / prog full:占用达到阈值(如 ≥ 深度−余量)就拉高,用来提前反压上游;
- almost empty / prog empty:占用低于阈值就拉高,用来通知下游预取/上报将空。
为什么不能等 full 才反压:异步 FIFO 满信号跨到写域要经过两级同步(约 2 拍),且上游收到反压到真正停手还有响应延迟,这段时间数据还在灌——等真满再停必然 overflow 丢数。同步 FIFO 也有下游响应延迟。所以:
prog_full 阈值 ≈ 深度 −(反压响应期间最多新增数据量)− 安全余量例:深度 512,上游收到反压前最多再灌 8 拍数据,跨域+响应留 4 拍余量 → occupancy ≥ 500 就拉 prog full 反压。almost empty 对称地为下游预取留余量。IP 里 prog full/empty 阈值可配常量或动态端口。
四、深度计算
FIFO 要吸收「写快读慢」期间的最大积压(通用推导见 FIFO 深度计算):
最小深度 ≈ 突发写入数 − 突发期间读走的数
写快读慢:depth ≥ B − B × (读吞吐/写吞吐)
(B = 一次最大连续突发个数;结果向上取整到 2^n,再加水位余量)例:写 100MHz、每次最多连续突发 120 个数据,读 80MHz:突发期最多读走 120 × 80/100 = 96,积压 ≈ 24,取深度 32;若还要反压余量可上 64。还要综合:突发长度上限、读写时钟频率比与异步漂移、是否允许丢帧/反压、almost 水位余量。过浅→频繁反压/丢数;过深→白耗 BRAM。
五、资源实现:BRAM / 分布式 RAM / FF
| 实现资源 | 适合 | 特点 | IP 选项 |
|---|---|---|---|
| Block RAM / URAM | 深 FIFO(几百~几千)、宽数据 | 原生双口、不占 LUT/FF、效率高;有读延迟 | FIFO Gen 选 Block RAM |
| 分布式 RAM(LUT) | 浅~中 FIFO、位宽窄 | 延迟低、可布在使用点旁 | 选 Distributed RAM |
| FF / SRL | 极浅弹性缓冲(≤几~十几) | skid buffer、fall-through,最快 | Distributed RAM/Shift Register |
六、IP 配置对照(Vivado FIFO Generator / Quartus SCFIFO·DCFIFO)
| 配置项 | 含义 / 建议 |
|---|---|
| Clock domain | Common(同步)/ Independent(异步) |
| 读写宽度 | 可同宽;异步支持 mixed width(写窄读宽等) |
| 深度 | 2 的幂;按第四节估 + 水位余量 |
| Read mode | FWFT(数据流推荐)/ Standard |
| prog full/empty | 配阈值做提前反压/预取(见第三节) |
| almost full/empty | 固定或可编程水位 |
| data_count | 占用计数,注意异步 FIFO 里是本域近似值 |
| overflow/underflow 标志 | 建议使能,调试时一眼看出是否写满/读空 |
| 复位 | 复位期间不读写;异步 FIFO 注意读写复位与 复位策略 一致 |
七、典型现象与调试
| 现象 | 可能原因 | 排查 |
|---|---|---|
| 偶发丢数 | 写满仍写(overflow) | 看 overflow 标志;prog full 提前反压 |
| 读出重复/旧数据 | 读空仍读(underflow) | 看 underflow;FWFT 下 rd_en 只在 valid&ready 时拉 |
| 反压了还丢 | 反压太晚(等真满) | 水位阈值减去响应延迟 |
| 数据流对不上拍 | FWFT/标准选错、rd_en 对齐错 | 明确读模式;画 valid/ready 时序 |
| 资源暴涨/时序差 | 深 FIFO 误用 FF,或浅 FIFO 误用 BRAM | 按第五节换实现资源 |
| 异步 FIFO 报时序违例 | 漏 set_clock_groups | 补约束(IP 通常自带) |
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 同域缓冲用异步 FIFO | 浪费逻辑/延迟 | 同步用 Common Clock/SCFIFO |
| 等 full 才反压 | overflow 丢数 | prog full 提前(深度−响应余量) |
| 深度拍脑袋 | 丢数或浪费 BRAM | 按突发×(1−读速/写速) 估、取 2^n |
| FWFT 当标准用 | rd_en/rdata 错位 | FWFT 下 rdata 直接有效,rd_en=取走 |
| 深 FIFO 用 FF | LUT/FF 爆、时序差 | 深用 BRAM/URAM、浅用分布式 RAM/FF |
| 不看 overflow/underflow | 丢数不自知 | 使能标志,上板先验证无溢出 |
相关与延伸
前置:异步 FIFO(Gray 码原理)、复位策略、valid/ready 握手。 后续:DSP48 与算术、BRAM/URAM 存储设计——FIFO 底层正是 BRAM。 通用:FIFO 深度计算与设计;IP 总览见 常用 IP 核。
一句话总结
FIFO 选型先看时钟和资源:同域缓冲用同步 FIFO(Common Clock/SCFIFO,指针二进制直接比、无 Gray 无同步器),跨时钟域才用异步 FIFO(Independent Clocks/DCFIFO,Gray 指针,见进阶 08);读模式优先 FWFT——非空即队首在 rdata、rd_en 当「取走」,对接 valid/ready 最自然,标准模式省逻辑但要自己对齐 1 拍读延迟;反压必须用 prog/almost full 提前拉,阈值≈深度−(反压响应+跨域同步期间还会灌入的数据量)−余量,等真满才停必然 overflow;深度按「突发数 ×(1−读速/写速)」估、向上取 2^n 再加水位余量;实现资源上深 FIFO 用 Block RAM/URAM(原生双口、不占 LUT/FF)、浅 FIFO/握手 skid buffer 用分布式 RAM 或 FF(延迟低、布在使用点旁);务必使能 overflow/underflow 标志,上板先确认无写满读空——FIFO 是数据通路的「减压阀」,水位和深度配对了才能既不丢数又不浪费。