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 Generator IP配置(选择FIFO模式);
  • 特性:
    • 容量小(单LUT6=64bit,总容量通常<1KB);
    • 异步访问(无时钟依赖,读写无延迟,但仅支持同步FIFO);
    • 占用逻辑资源(LUT),不占用BRAM;
  • 适用场景:小容量缓冲(比如串口1字节数据缓存、状态机临时数据缓冲)。

2. 块FIFO(基于BRAM实现)

  • 本质:用FPGA的专用BRAM(18Kb/36Kb)作为存储体,搭配FIFO控制逻辑(Xilinx硬核或软核);
  • 实现方式:通过Vivado的Block Memory Generator IP配置(选择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. 串口接收到1字节数据,产生rx_done=1(写使能wr_en=1);
  2. 先判断full==0(FIFO没满),允许写操作;
  3. 数据din=uart_rx_data存入FIFO的“写指针指向位置”;
  4. 写指针wr_ptr自动+1,计数器cnt+1;
  5. 若cnt达到FIFO深度(比如256),full=1,禁止后续写操作(避免溢出)。

3. 读操作流程(CPU处理侧)

  1. CPU空闲时,设置rd_en=1(读使能);
  2. 先判断empty==0(FIFO有数据),允许读操作;
  3. FIFO中“读指针指向位置”的数据输出到dout,传给CPU;
  4. 读指针rd_ptr自动+1,计数器cnt-1;
  5. 若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”为例,用于串口缓存):

  1. 新建工程 → 打开IP Catalog → 搜索“Block Memory Generator” → 双击打开;
  2. 配置界面:
    • 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”(可选,开启可编程满/空标志);
  3. 点击“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的使用~

posted @ 2025-12-13 21:01  lzx_拿命学fpga  阅读(2)  评论(0)    收藏  举报