FPGA中的FIFO详解
掌握了ROM(只读、固定数据)和RAM(随机读写、临时数据)后,接下来的FIFO是FPGA设计中最常用的缓冲存储模块,核心定位是“解决数据传输的速率匹配、跨时钟域交互”——和ROM/RAM的“随机访问”不同,FIFO是“先进先出”的流式访问,无需地址线,靠“满/空标志”控制,专门应对“数据来了就存、要用就取”的场景(比如串口数据缓存、跨时钟域数据传递)。
下面从定义、核心特性、FPGA实现方式、关键信号、工作原理、实战场景、与ROM/RAM对比等维度,结合你的FPGA学习背景(串口、DPRAM、LUT/BRAM)详细讲解。
一、先明确:FIFO的本质是什么?
FIFO(First In First Out)——先进先出的存储缓冲器,本质是“带读写指针和满/空判断的RAM”:
- 没有像RAM那样的“地址线”(addra/addrb),不需要手动指定存储地址;
- 写操作:数据从“写端口”存入,写指针自动递增(不用管存在哪);
- 读操作:数据从“读端口”取出,读指针自动递增(不用管从哪读);
- 核心逻辑:“先存的数据先取”,避免数据混乱,靠“满标志(full)”和“空标志(empty)”防止写溢出(存太多撑爆)或读空(没数据还读)。
与ROM/RAM的核心差异
| 模块 | 核心访问方式 | 核心用途 | 关键标志 |
|---|---|---|---|
| ROM | 只读、随机地址访问 | 存固定数据(波形表、译码表) | 无(上电加载数据) |
| RAM | 随机读写、地址指定 | 存临时数据(缓存、参数存储) | 无(需手动管理地址) |
| FIFO | 流式读写、无地址 | 数据缓冲(速率匹配、跨时钟域) | full(满)、empty(空) |
二、FPGA中FIFO的两种实现方式(和LUT/BRAM呼应)
和ROM/RAM一样,FPGA中没有“物理FIFO芯片”,而是通过LUT或BRAM结合控制逻辑(指针、满/空判断)实现,对应两种常用FIFO:
1. 分布式FIFO(基于LUT实现)
- 本质:用多个LUT拼接成小容量RAM,再加上读写指针、满/空判断逻辑;
- 实现方式:通过Vivado的
Distributed Memory GeneratorIP配置(选择FIFO模式); - 特性:
- 容量小(单LUT6=64bit,总容量通常<1KB);
- 异步访问(无时钟依赖,读写无延迟,但仅支持同步FIFO);
- 占用逻辑资源(LUT),不占用BRAM;
- 适用场景:小容量缓冲(比如串口1字节数据缓存、状态机临时数据缓冲)。
2. 块FIFO(基于BRAM实现)
- 本质:用FPGA的专用BRAM(18Kb/36Kb)作为存储体,搭配FIFO控制逻辑(Xilinx硬核或软核);
- 实现方式:通过Vivado的
Block Memory GeneratorIP配置(选择FIFO模式); - 特性:
- 容量大(单BRAM可做18Kb FIFO,级联后可达MB级);
- 支持同步/异步FIFO(异步FIFO是跨时钟域的核心);
- 占用BRAM资源,不占用LUT;
- 功能丰富(支持数据位宽转换、可编程满/空阈值、中断输出);
- 适用场景:大容量缓冲(串口数据包缓存、ADC采样数据缓存、跨时钟域数据交互)。
关键选择原则(和ROM/RAM一致)
- 小容量(<1KB)、同步时钟 → 分布式FIFO(LUT实现);
- 大容量(>1KB)、异步时钟 → 块FIFO(BRAM实现)。
三、FIFO的核心信号(结合串口模块理解)
FPGA中FIFO的信号的非常固定,以常用的“异步FIFO”(写时钟≠读时钟)为例,对应串口数据缓存场景讲解:
| 信号类型 | 信号名 | 位宽 | 作用说明(结合串口场景) |
|---|---|---|---|
| 输入(写侧) | wr_clk | 1bit | 写时钟(如串口接收时钟115200波特率对应~104μs周期) |
| 输入(写侧) | wr_en | 1bit | 写使能(1有效):串口接收完成(rx_done=1)时,触发写FIFO |
| 输入(写侧) | din | Nbit | 写数据:串口接收的8位数据(din=uart_rx_data) |
| 输入(读侧) | rd_clk | 1bit | 读时钟(如系统50MHz时钟) |
| 输入(读侧) | rd_en | 1bit | 读使能(1有效):CPU/处理模块需要数据时,触发读FIFO |
| 输出(读侧) | dout | Nbit | 读数据:FIFO中读出的8位数据(传给CPU处理) |
| 状态标志 | full | 1bit | 满标志(1有效):FIFO存满了,禁止再写(避免数据溢出) |
| 状态标志 | empty | 1bit | 空标志(1有效):FIFO没数据了,禁止再读(避免读空无效数据) |
| 状态标志(可选) | prog_full | 1bit | 可编程满标志:比如FIFO存到80%时置1,提前触发处理模块准备读 |
| 状态标志(可选) | prog_empty | 1bit | 可编程空标志:比如FIFO剩20%数据时置1,提醒尽快补充数据 |
同步FIFO vs 异步FIFO
- 同步FIFO:wr_clk = rd_clk(同一时钟驱动),结构简单,无跨时钟域问题,适合速率匹配(比如50MHz系统时钟下,CPU处理速度慢于串口接收速度,用同步FIFO缓冲);
- 异步FIFO:wr_clk ≠ rd_clk(比如写时钟115200波特率,读时钟50MHz),核心用于跨时钟域数据交互(FPGA中最常用),需通过“格雷码编码”解决指针跨时钟域亚稳态问题(不用深入细节,知道用途即可)。
四、FIFO的核心工作原理(写操作+读操作+满/空判断)
以“串口接收数据写入FIFO,CPU读取FIFO数据”为例,拆解完整流程:
1. 核心内部结构
FIFO内部有两个关键指针和一个计数器:
- 写指针(wr_ptr):记录下一个要写入的位置,写一次自动+1;
- 读指针(rd_ptr):记录下一个要读出的位置,读一次自动+1;
- 计数器(cnt):记录FIFO中当前存储的数据个数(cnt=0→empty,cnt=深度→full)。
2. 写操作流程(串口接收侧)
- 串口接收到1字节数据,产生
rx_done=1(写使能wr_en=1); - 先判断
full==0(FIFO没满),允许写操作; - 数据
din=uart_rx_data存入FIFO的“写指针指向位置”; - 写指针wr_ptr自动+1,计数器cnt+1;
- 若cnt达到FIFO深度(比如256),
full=1,禁止后续写操作(避免溢出)。
3. 读操作流程(CPU处理侧)
- CPU空闲时,设置
rd_en=1(读使能); - 先判断
empty==0(FIFO有数据),允许读操作; - FIFO中“读指针指向位置”的数据输出到
dout,传给CPU; - 读指针rd_ptr自动+1,计数器cnt-1;
- 若cnt=0,
empty=1,禁止后续读操作(避免读空)。
4. 满/空判断(核心中的核心)
- 空标志(empty):cnt=0 → 读指针追上写指针,FIFO无数据;
- 满标志(full):cnt=FIFO深度 → 写指针追上读指针,FIFO无空闲空间;
- 为什么重要?:如果
full=1还写,会导致新数据覆盖旧数据(数据丢失);如果empty=1还读,会读出无效数据(错误),所以所有FIFO操作前必须先判断满/空标志。
五、FPGA中FIFO的实战场景(你一定会用到)
场景1:串口数据缓存(解决速率匹配问题)
- 问题:串口接收数据速率是115200波特率(约11.5KB/s),而FPGA系统时钟是50MHz(处理速度极快),但CPU可能在处理其他任务(比如驱动数码管),无法实时读取串口数据,导致数据丢失;
- 解决方案:用FIFO作为串口接收缓冲;
- 流程:
- 串口接收数据 →
rx_done=1→ wr_en=1 → 数据写入FIFO; - CPU空闲时 → 检查
empty==0→ rd_en=1 → 从FIFO读出数据处理; - 核心价值:FIFO像“数据蓄水池”,暂时存储串口数据,避免因处理速度不匹配导致丢失。
- 串口接收数据 →
场景2:跨时钟域数据交互(异步FIFO核心用途)
- 问题:ADC采样模块的采样时钟是10MHz(写时钟),而数字信号处理(DSP)模块的处理时钟是50MHz(读时钟),两个模块时钟不同步,直接传递数据会产生亚稳态(数据错误);
- 解决方案:用异步FIFO连接两个模块;
- 流程:
- ADC采样数据 → 10MHz时钟驱动wr_en=1 → 写入FIFO;
- DSP模块在50MHz时钟下 → 检查
empty==0→ rd_en=1 → 读出数据处理; - 核心价值:FIFO通过格雷码编码处理指针跨时钟域问题,实现“异步数据安全传输”(FPGA中跨时钟域的首选方案)。
场景3:DPRAM与串口之间的缓冲(优化你之前的ctrl模块)
- 问题:你之前的ctrl模块中,DPRAM的读地址addrb由tx_done触发自增,但如果串口发送速率(115200波特率)慢于DPRAM读速率(50MHz),会导致数据发送不连贯;
- 解决方案:在DPRAM和串口之间加一个FIFO;
- 流程:
- DPRAM读出数据 → 50MHz时钟驱动wr_en=1 → 写入FIFO;
- 串口发送完成(tx_done=1) → 检查
empty==0→ rd_en=1 → 从FIFO读出数据发送; - 核心价值:平衡DPRAM和串口的速率差异,让数据发送更稳定,避免卡顿。
六、FIFO与ROM/RAM的详细对比(表格总结)
| 对比维度 | FIFO | ROM | RAM(DPRAM/单端口RAM) |
|---|---|---|---|
| 访问方式 | 流式读写,无地址,先进先出 | 只读,随机地址访问 | 随机读写,需指定地址 |
| 核心用途 | 数据缓冲、跨时钟域交互、速率匹配 | 存储固定不变的数据(波形表、译码表) | 存储临时数据、参数配置、双端口交互 |
| 时钟依赖 | 同步(单时钟)/异步(双时钟) | 同步(BRAM ROM)/异步(LUT ROM) | 同步(BRAM RAM)/异步(LUT RAM) |
| 关键标志 | full(满)、empty(空) | 无 | 无(需手动管理地址和读写时序) |
| 容量特性 | 小容量(LUT)/大容量(BRAM) | 小容量(LUT)/大容量(BRAM) | 小容量(LUT)/大容量(BRAM) |
| 数据更新 | 运行时可随时写入(覆盖旧数据) | 仅上电时通过初始化文件加载,运行时不可写 | 运行时可随时读写(任意地址更新) |
| 典型场景 | 串口缓存、跨时钟域数据传递 | 数码管译码表、正弦波波形表 | 串口数据包缓存、CPU参数存储 |
七、FPGA实战:如何用Vivado生成FIFO IP(以块FIFO为例)
结合你的学习习惯,给出核心配置步骤(以“256深度、8位宽、异步FIFO”为例,用于串口缓存):
- 新建工程 → 打开IP Catalog → 搜索“Block Memory Generator” → 双击打开;
- 配置界面:
- Memory Type:选择“First In First Out (FIFO)”;
- FIFO Configuration:
- FIFO Type:选择“Asynchronous FIFO”(异步FIFO,跨时钟域);
- Write Clock:写时钟频率(比如115200波特率对应~104μs周期,填9600Hz,或直接填串口时钟频率);
- Read Clock:读时钟频率(比如50MHz);
- Port A(Write Port):
- Write Width:8(写数据位宽,对应串口8位数据);
- Write Depth:256(FIFO深度,可存储256字节数据);
- Port B(Read Port):
- Read Width:8(读数据位宽,和写位宽一致);
- Other Options:勾选“Enable Programmable Full/Empty Flags”(可选,开启可编程满/空标志);
- 点击“OK” → 生成IP,后续在顶层模块中例化即可。
简化例化代码(结合串口)
// FIFO IP例化(串口接收缓冲)
fifo_uart_rx fifo_uart_rx_inst (
.wr_clk(uart_rx_clk), // 串口接收时钟(115200波特率对应时钟)
.wr_en(rx_done), // 写使能:串口接收完成触发
.din(uart_rx_data), // 写数据:串口接收的8位数据
.rd_clk(sys_clk), // 读时钟:系统50MHz时钟
.rd_en(cpu_rd_en), // 读使能:CPU空闲时触发
.dout(fifo_rd_data), // 读数据:传给CPU的8位数据
.full(fifo_full), // 满标志:避免溢出
.empty(fifo_empty) // 空标志:避免读空
);
八、总结:FIFO的核心价值
FIFO是ROM和RAM的“互补模块”——ROM解决“固定数据存储”,RAM解决“随机访问临时数据”,FIFO解决“流式数据缓冲和跨时钟域交互”。
在FPGA设计中,只要遇到“数据传输速率不匹配”或“跨时钟域数据传递”,第一个想到的就是FIFO。比如:
- 串口/以太网等外设的数据缓存;
- ADC/DAC与处理模块之间的数据交互;
- 多模块之间的跨时钟域数据传递。
可以尝试在“串口多字节收发+DPRAM”项目中,加入FIFO作为串口接收/发送缓冲,观察数据传输的稳定性提升,这样能更快掌握FIFO的使用~

浙公网安备 33010602011771号