高云FPGA异步FIFO IP核配置与Verilog驱动实战

发布时间:2026/7/4 7:43:28

高云FPGA异步FIFO IP核配置与Verilog驱动实战 高云FPGA异步FIFO IP核配置与Verilog驱动实战最近在做一个FPGA项目需要把摄像头采集的数据传给另一个时钟域的图像处理模块中间的数据缓冲和跨时钟域传输让我头疼了一阵。后来我用了高云FPGA自带的异步FIFO IP核问题迎刃而解。今天我就把这个从IP核配置到Verilog驱动、再到仿真验证的完整流程分享给大家手把手教你搞定异步FIFO。1. 异步FIFO是什么为什么需要它1.1 FIFO基础数据流的“排队缓冲区”咱们先打个比方。FIFO就像银行排队叫号系统先来的人先办业务先进先出。在FPGA里FIFO就是一个数据缓冲区用来临时存放数据流解决数据生产者和消费者速度不匹配的问题。可能有人会问这和我学单片机时用的堆栈Stack有啥区别我刚开始也混淆过这里简单说下FIFO就像排队队尾进队头出。适合处理连续的数据流比如视频流、网络数据包。在FPGA里它常用于数据缓冲、流量控制。堆栈就像叠盘子你只能从最上面放和取后进先出。单片机里主要用来管理函数调用、保存临时变量。简单说FIFO管“流水线”堆栈管“函数调用”。咱们今天的主角是前者。一个FIFO有几个关键参数你得知道深度能存多少个数据。好比排队大厅有多少个座位。宽度每个数据有多少位比如8位、16位。好比每个人手里拿的号牌大小。空/满标志告诉你缓冲区是空的没数据可读还是满的没空间可写防止读错或写爆。读写指针内部用来记录读到哪、写到哪的“记号笔”。1.2 同步FIFO vs 异步FIFOFIFO按时钟分两种这是核心区别同步FIFO读写都用同一个时钟。好比整个银行只有一个挂钟所有人按同一个时间节奏办事。控制简单但只能用在同一个时钟域。异步FIFO读和写用两个独立的时钟。好比取号机和柜台有各自的钟速度可能不一样。它就是为了解决跨时钟域数据传递而生的内部有专门的电路比如双端口RAM、格雷码计数器来安全地同步读写指针避免数据出错。什么时候用异步FIFO当你FPGA设计里有两个模块一个在50MHz时钟下产生数据另一个在75MHz时钟下处理数据中间就得用异步FIFO来做缓冲和时钟域隔离。图像处理、音频编解码、高速接口如DDR、PCIe的数据缓存都离不开它。2. 高云云源软件里的FIFO IP核高云FPGA的开发软件Gowin云源提供了现成的FIFO IP核咱们不用从零写直接配置调用就行省时省力还可靠。打开软件在IP核生成器里你能找到几种FIFOFIFO就是咱们要用的异步FIFO双时钟。FIFO SC同步FIFO单时钟。FIFO HS / FIFO SC HS支持更高时钟频率的版本。双击“FIFO”就进入了配置界面。别被一堆选项吓到我带你一个个看关键的。2.1 核心参数配置首先给IP核起个名别用默认的fifo_top因为顶层模块通常叫top。我这里改成fifo_ip。1. Options基本选项Write Depth/Width写深度和写数据位宽。比如我设深度128宽度8位就是能存128个8位数据。Read Depth/Width读深度和读数据位宽。注意异步FIFO支持读写位宽不同比如你写是8位读可以设成16位IP核会自动计算。公式是Read Data Width (Write Depth * Write Data Width) / Read Depth。位宽和深度别设太大会浪费FPGA里宝贵的Block RAM资源。Output Register Select勾上后输出数据会延迟一个时钟周期能改善时序一般建议勾选。Controlled by RdEn输出数据是否受读使能控制。勾选后只有RdEn有效时数据才出现在输出端口。2. FIFO Implementation实现方式就是用FPGA里的哪种存储资源Block SRAM (BSRAM)块RAM容量大适合深度大于1KB的情况。Shadow SRAM (SSRAM)分布式RAM灵活适合小容量FIFO小于1KB。REG用寄存器和查找表搭极浅的FIFO才用。 通常按建议来小的用SSRAM大的用BSRAM。3. Read Mode读模式Standard FIFO标准模式。需要读使能有效才会把下一个数据输出。First-Word Fall-Through (FWFT)首字直通模式。第一个写入的数据会立刻出现在输出端不需要等读使能。后续数据才需要读使能控制。这种模式延迟更低。4. Data Number Reset数据计数与复位Read/Write Data Num勾选后IP核会输出Wnum和Rnum信号告诉你当前FIFO里存了多少个数据写计数和还能读多少个数据读计数调试时非常有用。En_Reset使能复位。勾选后读写时钟域有独立的复位信号WrReset和RdReset。Reset_Synchronization如果选了En_Reset这个选项让你选择是否使用一个统一的复位信号Reset。为了简单咱们通常选这个。5. Flag Control标志位控制除了基本的空(Empty)/满(Full)标志还可以使能“几乎满/几乎空”标志让你提前知道缓冲区快满了或快空了方便做流控。Almost Full Flag使能半满标志Almost_Full。阈值可以设成固定值Constant Parameter或者通过输入端口动态设置Input Parameter。比如我设固定值120当FIFO里的数据达到120个时Almost_Full就拉高。Almost Empty Flag同理使能半空标志Almost_Empty。我设固定值10当FIFO里数据少于等于10个时Almost_Empty拉高。其他如ECC校验一般数据位宽1-64位时可选用于检错纠错根据需求来。配置完点击Generate软件就会生成一个可用的FIFO模块.v文件和对应的例化模板。3. 手把手编写Verilog驱动模块IP核生成好了但它只是个“黑盒子”。我们需要写控制逻辑来告诉它什么时候写、什么时候读。咱们把读写控制分开成两个模块结构更清晰。3.1 写控制模块 (fifo_wr.v)这个模块负责产生写使能和要写入的数据。逻辑是当FIFO为空时开始写入当写计数达到我们设定的最大值比如128时停止写入。module fifo_wr( input wr_clk , // 写时钟信号 input wr_rst_n , // 复位信号低电平有效 input [7:0] fifo_Wnum_count , // 从IP核来的写计数当前FIFO中数据量 input fifo_empty , // 从IP核来的空标志 output reg fifo_wr_en , // 输出给IP核的写使能 output reg [6:0] fifo_wr_data // 输出给IP核的写入数据 ); parameter WR_DATA_MAX 7d127; // 我们要写入的最大数据值0-127 // 写使能控制逻辑 always (posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) fifo_wr_en 1b0; // 复位时关闭写使能 else if(fifo_empty) // 如果FIFO空了就启动写入 fifo_wr_en 1b1; else if(fifo_Wnum_count 8d128) // 如果已经写满了128个就停止 fifo_wr_en 1b0; else fifo_wr_en fifo_wr_en; // 其他情况保持原状态 end // 写入数据生成逻辑简单地从0递增到127 always (posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) fifo_wr_data 7d0; else if(fifo_wr_en (fifo_wr_data WR_DATA_MAX)) fifo_wr_data fifo_wr_data 7d1; // 使能有效且未到最大值数据加1 else fifo_wr_data 7d0; // 否则如写停止或复位数据归零 end endmodule注意这里fifo_Wnum_count是8位最大值是255。我们设定的FIFO深度是128所以当计数到128时表示真的满了。fifo_wr_data是7位范围0-127这是我们实际写入的数据内容。3.2 读控制模块 (fifo_rd.v)这个模块负责产生读使能。逻辑是当FIFO为满时开始读取当可读数据量快被读空比如只剩1个时停止读取防止读空。module fifo_rd( input rd_clk , // 读时钟信号 input rd_rst_n , // 复位信号 input [7:0] fifo_Rnum_count , // 从IP核来的读计数当前可读数据量 input fifo_full , // 从IP核来的满标志 output reg fifo_rd_en // 输出给IP核的读使能 ); parameter RD_EMPTY_TH 8d1; // 读空阈值当可读数据小于等于1时停读 always (posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) fifo_rd_en 1b0; else if(fifo_full) // FIFO满了赶紧开始读 fifo_rd_en 1b1; else if(fifo_Rnum_count RD_EMPTY_TH) // 快读空了停 fifo_rd_en 1b0; else fifo_rd_en fifo_rd_en; // 保持 end endmodule3.3 顶层模块整合 (fifo_top.v)现在我们把IP核和两个控制模块“连线”起来。这个顶层模块就是整个设计的“总指挥部”。module fifo_top( input sys_clk, // 系统时钟这里为了简化读写用同一时钟实际可接不同时钟 input sys_rst_n, // 系统复位低电平有效 output [6:0] fifo_rd_data // FIFO读出的数据引到顶层输出便于观察 ); // 内部连线声明 wire fifo_wr_en; wire fifo_rd_en; wire [6:0] fifo_wr_data; wire almost_full; wire almost_empty; wire fifo_full; wire fifo_empty; wire [7:0] fifo_Wnum_count; wire [7:0] fifo_Rnum_count; // 例化FIFO IP核 fifo_ip u_fifo_ip ( .Data (fifo_wr_data), // input [6:0] .Reset (~sys_rst_n), // input, 注意IP核复位是高有效我们系统复位是低有效所以取反 .WrClk (sys_clk), // input .RdClk (sys_clk), // input, 实际异步应用时这里接不同的时钟 .WrEn (fifo_wr_en), // input .RdEn (fifo_rd_en), // input .Wnum (fifo_Wnum_count), // output [7:0] .Rnum (fifo_Rnum_count), // output [7:0] .Almost_Empty (almost_empty), // output .Almost_Full (almost_full), // output .Q (fifo_rd_data), // output [6:0] .Empty (fifo_empty), // output .Full (fifo_full) // output ); // 例化写控制模块 fifo_wr u_fifo_wr ( .wr_clk (sys_clk), .wr_rst_n (sys_rst_n), .fifo_Wnum_count (fifo_Wnum_count), .fifo_empty (fifo_empty), .fifo_wr_en (fifo_wr_en), .fifo_wr_data (fifo_wr_data) ); // 例化读控制模块 fifo_rd u_fifo_rd ( .rd_clk (sys_clk), .rd_rst_n (sys_rst_n), .fifo_Rnum_count (fifo_Rnum_count), .fifo_full (fifo_full), .fifo_rd_en (fifo_rd_en) ); endmodule提示在这个例子里为了仿真方便读写时钟都接了sys_clk。在实际的异步FIFO应用中WrClk和RdClk必须接两个不同源或不同频率的时钟信号这才是异步FIFO的价值所在。4. Modelsim仿真验证代码写完了对不对上仿真用Modelsim跑一下最放心。这里有个高云FPGA仿真的坑要特别注意如果设计里用了IP核或者一些底层原语必须在仿真测试文件里例化一个GSR单元否则仿真会报错。timescale 1ns / 1ps // 定义仿真时间单位/精度 module fifo_tb(); // ---- 关键必须添加的GSR实例化 ---- GSR GSR_inst (.GSRI(1b1)); // --------------------------------- // 产生测试时钟和复位 reg sys_clk; reg sys_rst_n; always #10 sys_clk ~sys_clk; // 产生50MHz时钟周期20ns initial begin sys_clk 1b0; sys_rst_n 1b0; // 开始复位 #200; // 复位保持200ns sys_rst_n 1b1; // 释放复位 #5000; // 仿真运行5000ns $stop; // 结束仿真 end // 连接被测设计 wire [6:0] rd_data; fifo_top u_fifo_top ( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .fifo_rd_data (rd_data) ); endmodule把上面这个测试文件、以及我们写的三个模块(fifo_top.v,fifo_wr.v,fifo_rd.v)和IP核生成的fifo_ip.v一起加到Modelsim工程里编译仿真。看波形关键点复位后fifo_empty应为高fifo_full为低写计数和读计数为0。启动写入fifo_empty变低后fifo_wr_en拉高fifo_wr_data从0开始递增。同时fifo_Wnum_count写计数增加。触发半满当fifo_Wnum_count增加到我们设定的阈值120时almost_full信号应该拉高。这说明FIFO快满了。写满停写当fifo_Wnum_count达到128深度时fifo_full拉高同时我们的写控制模块应关闭fifo_wr_en。启动读取fifo_full拉高后fifo_rd_en应拉高开始读出数据。fifo_Rnum_count读计数减少fifo_rd_data输出数据。触发半空当fifo_Rnum_count减少到我们设定的阈值10时almost_empty拉高。读空停读当fifo_Rnum_count为0时fifo_empty拉高读使能fifo_rd_en关闭。波形如果完全按这个顺序变化恭喜你仿真通过了5. 上板实测与在线调试仿真通过后就可以综合、布局布线生成比特流下载到高云FPGA开发板了。引脚约束根据你的板子把顶层模块的时钟(sys_clk)和复位(sys_rst_n)信号分配到实际的晶振引脚和按键引脚上。输出数据fifo_rd_data可以接到LED或者用在线逻辑分析仪看。高云在线逻辑分析仪GAO这是调试利器。把fifo_wr_en,fifo_rd_en,fifo_wr_data,fifo_rd_data,almost_full,almost_empty,full,empty这些关键信号加进去观察。上板抓到的波形应该和仿真一致。可能会看到数据在almost_full和almost_empty阈值附近有些毛刺或抖动这是正常的因为在线采样和时钟域不同步。只要主要逻辑正确就没问题。6. 实际项目中的几点心得深度计算要留余量别可丁可卯地设深度。比如你计算出来需要缓存100个数据最好设成128或256防止偶尔的数据突发导致溢出。异步时钟要约束如果读写真是异步时钟一定要在设计约束文件里定义这两个时钟并设置set_clock_groups -asynchronous来告诉工具它们是异步的避免工具做无谓的时序优化。标志位延迟empty和full标志的产生有延迟通常1-2个读时钟或写时钟周期。所以你的控制逻辑不能看到full才停写看到empty才停读要提前用almost_full/empty或留出安全余量。复位处理异步FIFO的复位需要特别小心确保复位信号在各自时钟域下被正确同步和释放。用IP核的话它内部一般会处理好。好了从IP核配置、Verilog驱动到仿真上板的完整流程就是这样。异步FIFO是FPGA跨时钟域通信的基石把这个练熟了后面做图像、通信项目就顺手多了。遇到问题多看看波形理解数据流和标志位的变化调试起来就不难了。

相关新闻