ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FIFO Generate IP核写时序与Status Flags阈值配置实战指南

FIFO Generate IP核写时序与Status Flags阈值配置实战指南 做FPGA的兄弟对Vivado里的FIFO Generate IP核应该都不陌生项目里跨时钟域、数据缓冲、位宽转换拿它一拖就出来。但说实话很多人在Basic页把深度一填、时钟一选Generate就完事了结果一到仿真或者上板要么数据写不进去要么FIFO的满标志乱跳查了半天最后发现是Status Flags页配置没搞明白或者压根没理解FIFO写操作侧的握手时序。这篇就把这块彻底捋一遍重点说两件事FIFO的写操作时序到底是怎么回事以及Status Flags页里那些Almost Full、Programmable Full阈值到底应该怎么配。文章适合刚开始用Xilinx FIFO IP核的同学也适合已经用了很久但一直没把标志位配置吃透的老手。1. 先建立一个FIFO的整体认知1.1 FIFO IP核到底帮你做了什么FIFO的全称是First In First Out先入先出队列。你在FPGA里自己用Verilog写一个FIFO其实也不难无非是一个双口RAM加读写指针再加一套满空判断逻辑。但一旦牵扯到跨时钟域比如写时钟100MHz、读时钟75MHz满标志要在写时钟域产生、空标志要在读时钟域产生指针还要做格雷码跨域同步这块自研的成本和踩坑概率就明显上来了。FIFO Generate IP核就是把这些脏活累活都封装好让你通过GUI填参数就能拿到一个经过验证的存储队列。这个IP核能解决的典型场景很明确跨时钟域数据交换、数据缓冲削峰、位宽匹配比如32位写入、8位读出、命令帧的缓存与速率解耦。它的本质功能是接收写侧输入的数据按照写入顺序存下来再按同样顺序输出给读侧同时通过full、empty、almost_full、prog_full这些标志位把内部水位状态透明地汇报给你。1.2 写侧接口信号逐个认识不管你是用Native接口还是AXI4-Stream接口写操作侧打交道最频繁的就是下面这几个信号。信号名方向作用wr_clk输入写时钟所有写侧信号在这个时钟域工作wr_en输入写使能高电平有效配合有效时钟沿写入数据din输入写数据总线宽度由Write Width参数决定full输出满标志为高时代表FIFO已满不能再写wr_ack输出写应答每成功写入一个数据会拉高一个周期wr_data_count输出写侧数据计数指示当前FIFO中已有的数据个数overflow输出溢出标志当在FIFO已满时还尝试写数据会拉高一个周期wr_rst_busy输出写侧复位忙信号复位过程中或复位释放后为高此时不能进行写操作写握手协议其实只有一句话在wr_clk上升沿如果wr_en为高、full为低则din上的数据被写入FIFO同时wr_ack拉高一个周期。反过来如果full已经拉高了你的wr_en即使保持为高数据也进不去并且overflow会给出一个周期的脉冲提示这种非法写入行为。这里我必须先把wr_rst_busy单独拎出来讲。很多新手第一次看仿真波形复位拉低之后立刻就把wr_en置高开始写数据结果数据全部丢失半天找不出原因。Vivado生成的FIFO IP核复位释放之后内部还要经过一段复位同步和状态初始化时间这段时间wr_rst_busy维持在逻辑高。你必须等wr_rst_busy拉低之后再开始写操作。最好的做法就是把wr_rst_busy取反之后和你的写请求信号做与逻辑这样能最大程度避免复位尾巴上的写丢失。2. FIFO写操作时序详解2.1 Standard FIFO模式下的写时序行为Native接口下最常用的就是Standard FIFO模式这也是FIFO IP核默认的读模式。理解这个模式的写时序核心就是盯住wr_clk上升沿上wr_en、din、full三者的配合。在稳定写入的阶段通常是这样wr_en拉高din在时钟沿前已经稳定建立full保持低电平那么每个wr_clk上升沿都能写进去一个数据wr_ack也会跟着拉高一个周期。这里有一个界面参数容易忽略就是Basic页里的“Write Data Count”和“Read Data Count”是否勾选。如果你不勾选wr_data_count是取不到的。数据建立时间在时序上很容易被忽视。din必须在wr_clk有效沿之前满足数据建立时间在有效沿之后满足保持时间这是FPGA时序收敛的基本要求。在FPGA工程里只要你的写侧逻辑和FIFO写端口同属一个时钟域这套约束工具会自动帮你分析。但如果你是跨时钟域把数据送到din上的那就不能依赖工具自动保证必须在外部做好信号同步和打拍处理否则一上板就会出现偶发性丢数。2.2 First Word Fall Through模式对写操作的影响First Word Fall ThroughFWFT模式和Standard模式的核心区别在读侧表现为第一个写入的数据不需要rd_en拉高就会直接出现在dout上也就是“透传”或“预读”模式。很多工程师以为FWFT会影响写时序其实从写侧看写入的合法条件仍然是wr_en配合full为低数据照常被写入内部存储。但FWFT有一个实际工程中需要警惕的地方FIFO为空的初始状态下FWFT会把内部第一个存储单元的状态提前输出这意味着写侧只要写入一个数据dout几乎会同时看到这个数据的值。如果你的下游逻辑在FIFO空的时候就把dout当有效数据使用会产生误触发。所以在写操作侧你需要确保empty标志被正确使用读侧逻辑必须等empty拉低后再采样dout。2.3 写满边界与溢出保护写满边界是最容易出bug的地方。full信号不是在你写入第Depth个数据的瞬间立刻拉高的而是内部写指针走到最后一个位置、FIFO中数据个数等于设置深度之后由控制逻辑产生。在同步FIFO中full的产生和wr_clk同域相对及时但也会有几个逻辑门的组合延迟。在异步FIFO中full信号需要把读侧指针同步到写时钟域这里涉及多级寄存器的打拍同步通常会产生2到3个写时钟周期的延迟。所以实际中full拉高的时刻严格来说比你FIFO真正存满的时刻要晚一点点。换句话说你在full还没拉高之前继续写可能已经写到了深度上限这时候数据并不一定溢出因为FIFO内部还有同步延迟的余量。但反过来如果你完全依赖full作为停止写信号在异步FIFO高频率连续写的情况下存在一个风险窗口——full还没来得及拉高而你的写使能还在继续新数据有可能被丢弃或者覆盖未读数据。应对办法有两种一是给FIFO深度留出余量比如实际需要缓存500个数据深度选1024而不是512二是使用Almost Full或Programmable Full这类预警标志在full拉高之前提前停止写操作。2.4 复位过程与写侧的交互FIFO IP核初始化相关配置里复位类型可以选择异步复位或同步复位。不管选哪种务必保证复位信号的有效电平与所选类型匹配。以常见的Active High异步复位为例复位拉高后内部读写指针清零、输出标志复位然后复位释放wr_rst_busy保持高一段时间之后拉低写侧才进入可工作状态。这里有一个很重要的实操细节如果你想在仿真里尽快开始写数据通常在全局复位释放后再额外等待wr_rst_busy变低。很多人直接在复位释放后延迟个100ns就开始写这在慢时钟下可能碰巧没问题但如果时钟频率高wr_rst_busy还没完全拉低你的第一批数据就丢了。用逻辑门控是最稳妥的避免纯靠延时去猜。另外复位信号的有效宽度也要留意太短的复位脉冲可能导致FIFO内部状态没有完全复位干净标志位出现异常。3. Status Flags页配置实战3.1 整页选项和Flags类型总览打开FIFO Generator的配置界面在Basic页确定好接口类型、时钟模式和深度之后点进Status Flags页会看到三个区域Standard Flags、Other Flags、Programmable Flags。Standard Flags指的就是基础的full和empty两者默认勾选且无法取消。Other Flags里是Almost Full和Almost EmptyProgrammable Flags里是Programmable Full和Programmable Empty。我的建议是除非你只是做个临时缓存、完全靠full和empty控制读写否则Almost Full或者Programmable Full至少配一个Almost Empty或者Programmable Empty至少配一个。因为实际开发里完全依赖full和empty做控制相当于在悬崖边开车full拉高才能停手但跨时钟延迟已经让FIFO处于满状态的边缘。Almost Full和Programmable Full相当于提前给你刹车距离这才是工程上稳妥的做法。3.2 Almost Full与Almost Empty的阈值设置Almost Full Threshold Value就是当FIFO里已有数据量达到你设置的这个数值时almost_full立刻拉高。这个值可以设置为一组双值即Assert Value和Negate Value。Assert Value是断言阈值达到这个值almost_full拉高Negate Value是解除阈值当FIFO内数据量回落到这个值以下时almost_full拉低。为什么要分成两个值而不是同一个值这是为了避免标志位在阈值附近反复跳变引入滞回特性。举个例子一个深度512的FIFO你可以设置Almost Full Assert Value为508Negate Value为504。那么当FIFO里数据量增长到508个时almost_full拉高通知上游停止写入或者降低写入速率写侧持续读数据直到数据量降到504以下almost_full才拉低。508和504之间的这4个数据差距就是为了防止在满状态附近抖动。Almost Empty同理比如Assert Value设为4Negate Value设为8数据量降到4个以下时almost_empty拉高读侧收到信号后可以减速或者等待数据数据量升到8以上再解除。关于阈值的单位不同版本IP核的界面描述有细微差别有的明确写“Number of Words”有的写“Threshold Value”。以Vivado较新的版本为例这个阈值就是FIFO中已存储的数据个数不是剩余空间数。配置完最好做一次仿真确认用波形去验证阈值对应的拉高时刻避免单位和语义理解错。3.3 Programmable Full与Programmable Empty的配置细节Programmable Flags比Almost Flags更灵活。Programmable Full支持Single Constant、Multiple Constant、Single Programmable Threshold三种模式区别在于阈值是否固定、是否可以通过外部输入端口动态修改。最常见的模式是Single Constant也就是程序化满阈值固定为一个常量界面里需要填写Programmable Full Threshold Value。这个值的含义和Almost Full类似也是FIFO中已存数据个数。假设FIFO深度是2048你的写侧突发长度大约是128个数据也就是最多一次性连续写128个数据不能停。如果你希望FIFO快满之前留出这128个数据的余量那满阈值应该设置为2048减128也就是1920。当FIFO数据量达到1920时prog_full拉高此时离真正满还有128个位置的缓冲足够写侧完成一次完整突发也不会溢出。Programmable Empty的配置逻辑对称考虑的是FIFO里最少还剩多少数据时给读侧报警。如果读侧不希望频繁被empty打断可以设置Prog Empty Threshold为32也就是当FIFO数据量降到32以下时prog_empty拉高。这里要说句实在话Programmable Empty在实际工程里用得相对少因为empty本身就是精确的读侧标志读侧只要等empty拉低后读即可很多时候不需要提前预警。但如果是做流水线控制需要提前调度读侧后级模块这种情况下Programmable Empty就有价值了。Programmable Flags还有一个特点就是可以在Basic页勾选对应的Threshold端口比如prog_full_thresh和prog_full_thresh_assert等这样你就能够在顶层模块里动态赋值不用每次改IP配置。Multiple Constant模式可以在不同阶段切换不同阈值常量但会增加资源并引入配置端口普通设计一般用不到。3.4 异步FIFO中Status Flags的跨时钟域注意点异步FIFO标志位的产生域和同步FIFO有明显区别。full、almost_full、prog_full是在写时钟域产生的empty、almost_empty、prog_empty是在读时钟域产生的。当你配置Status Flags页时界面上的阈值设置针对的是各自时钟域里观测到的FIFO水位。这里有个容易踩的坑这些标志不是“实时精确”的它们内部经过跨时钟域同步会有几个周期的延迟。full信号从读侧同步到写侧通常存在2到3个写时钟周期的延迟almost_full在异步FIFO中的响应时间更快一些因为它使用的同步逻辑相对简单但仍然存在不确定性。所以在异步FIFO场景下配置Almost Full或Programmable Full的阈值时必须把同步延迟预留进去。举例来说异步FIFO深度1024跨时钟域的同步延迟约3个写时钟如果写侧可能连续写4个数据那么阈值至少应设为1024减4减3否则可能在prog_full拉高之前就已经溢出。除此之外异步FIFO如果配置了Almost Flags建议在界面勾选“Synchronous”相关选项时看清说明。某些选项是针对标志输出与读/写时钟的同步性配错可能导致标志位在跨时钟域后出现毛刺进而影响外部逻辑。至少要在仿真里验证标志位变化时是否存在亚稳态窗口必要时在IP外部再加一级寄存器同步确保下游逻辑采样到的标志位是稳定的。3.5 用表格看清Status Flags页常用组合标志类型经典用途推荐阈值设置方式注意事项full强制停止写入固定不可配置异步FIFO下延迟2-3周期empty强制停止读取固定不可配置读侧精确标志almost_full提前预警停止写Assert/ Negate 双值建议配置滞回区间almost_empty提前预警停止读Assert/ Negate 双值适合流水线调度prog_full突发长度可调的写预警深减突发长度适合固定突发场景prog_empty读侧预调度固定阈值非必需按需配置如果你的设计里同时使用Almost Full和Programmable Full需要注意两者的优先级。FIFO内部逻辑会同时计算谁先到阈值谁先拉高。工程上建议不要让两个预警值靠得太近否则会出现两个标志同时拉高、同时拉低的现象外部逻辑反而不知道按哪个处理。一般Almost Full阈值比Programmable Full阈值更接近满值让prog_full先拉高做粗粒度预警almost_full后拉高做紧急刹车这样控制层次更清晰。4. 完整配置与仿真验证实例4.1 创建一个同步FIFO并配置Flags我用Vivado 2022.1版本操作一遍给大家做个参考。IP Catalog里搜索FIFO Generator双击打开配置界面。Basic页先选择Native接口Read Width和Write Width都设为32Read Depth设为512时钟模式选择Common Clock也就是同步FIFORead Mode选Standard FIFO。这一页的Data Count Ports里勾上Write Data Count和Read Data Count方便调试时观察水位。然后切到Status Flags页Standard Flags里full和empty默认勾选。Other Flags里勾选Almost Full和Almost Empty并按前面所说设置为双值。Almost Full Assert Value设为508Negate Value设为504Almost Empty Assert Value设为4Negate Value设为8。Programmable Flags里勾选Programmable FullType选Single ConstantThreshold Value设置为480。这里480的含义是深度512预留32个数据的缓冲当FIFO数据量达到480时prog_full拉高距离真正满还有32个位置足够写侧做最后一拍突发。4.2 实例化代码与顶层连接要点配置完成后点击Generate生成IP。在顶层模块里例化时有几个关键连线必须做对。wr_rst_busy必须接上而且写侧逻辑要用它做门控din位宽要与配置一致wr_en不能一直拉高要由上游valid信号打一拍后生成wr_data_count接到一个寄存器实时监控水位。下面给一个简化的顶层例化代码片段位宽32、深度512的同步FIFO供参考wire rst_n; wire wr_clk; wire [31:0] din; wire wr_en; wire full; wire almost_full; wire prog_full; wire wr_ack; wire overflow; wire [9:0] wr_data_count; wire rd_en; wire [31:0] dout; wire empty; wire almost_empty; wire prog_empty; wire [9:0] rd_data_count; wire wr_rst_busy; wire rd_rst_busy; wire valid; fifo_512x32 u_fifo ( .rst (~rst_n), .wr_clk (wr_clk), .rd_clk (wr_clk), .din (din), .wr_en (wr_en ~wr_rst_busy), .rd_en (rd_en), .dout (dout), .full (full), .almost_full (almost_full), .prog_full (prog_full), .wr_ack (wr_ack), .overflow (overflow), .wr_data_count (wr_data_count), .empty (empty), .almost_empty (almost_empty), .prog_empty (prog_empty), .rd_data_count (rd_data_count), .valid (valid), .wr_rst_busy (wr_rst_busy), .rd_rst_busy (rd_rst_busy) );wr_en信号这里做了与门控也就是只有wr_rst_busy拉低之后写请求才真正生效。这个写法的好处是即便上游逻辑在复位释放后立刻拉高wr_en也不会丢掉数据同时避免在复位过程中向FIFO写入非法数据。如果你使用的是FIFO IP核的默认例化模板通常会自动生成一个wr_rst_busy信号建议一定不要悬空把它用上。4.3 写操作Testbench验证思路写完例化代码如果不跑仿真就上板等于盲调。我习惯先写一个简单的仿真testbench专门把写侧行为和标志位行为验证一遍。核心验证点有三个写数据是否完整进入FIFO、prog_full和almost_full的拉高时机是否正确、连续写到满时full和overflow是否符合预期。下面是简化的testbench片段演示如何产生连续的写数据和观察标志位reg rst_n; reg wr_clk; reg [31:0] din; reg wr_en; wire full; wire almost_full; wire prog_full; wire [9:0] wr_data_count; wire wr_rst_busy; reg rd_en; initial begin wr_clk 0; forever #5 wr_clk ~wr_clk; // 100MHz end initial begin rst_n 0; wr_en 0; din 0; rd_en 0; #100; rst_n 1; // 等待wr_rst_busy拉低 wait (wr_rst_busy 1b0); // 连续写入600个数据观察full行为 repeat (600) begin (posedge wr_clk); wr_en 1; din $random; end (posedge wr_clk); wr_en 0; // 观察一段时间后停止仿真 #1000; $finish; end这里需要特别说明等待wr_rst_busy拉低之后再开始写是关键一步。如果使用wait (wr_rst_busy 1b0)写侧请求会自动避开复位尾巴。测试向FIFO里连续写入600个数据而配置深度只有512因此预期在写入过程中full会拉高后续写使能即使持续为高数据也不会进入overflow会拉高。同时wr_data_count最高到512不会超过深度。你可以把这个计数器的最大值打印出来做断言检查防止FIFO数据量出现超过深度的问题。4.4 生产环境集成时的几个易错点从例化模板到真正集成到工程里至少有四个坑值得提前避开。第一IP核自带的复位信号有效电平和极性要和你工程全局复位保持一致不一致时不要直接连加一级反相或者逻辑转换。第二异步FIFO场景下读写时钟域各自的复位信号都要按对应时钟域做异步复位同步释放不要在写时钟域直接送一个读时钟域产生的复位信号。第三wr_data_count在异步FIFO中属于写时钟域它只是FIFO水位的近似指示跨时钟同步延迟下读数据个数的变化会滞后不要在关键路径上对它做过于严格的时序判断。第四如果配置了Programmable Full Threshold外部端口比如prog_full_thresh需要在顶层给常量赋值不赋值的时候IP内部会按照配置的常量工作但一旦端口存在悬空可能造成仿真不定态。5. 常见问题与排查技巧实录5.1 写不进去数据时的排查路径遇到写侧完全写不进数据不要一上来就怀疑FIFO IP损坏按照下面的顺序排查。第一步看wr_rst_busy是不是一直为高。如果复位信号一直有效或者复位释放后IP内部复位状态机卡住wr_rst_busy会一直高此时写使能无论怎么拉高都没用。第二步看full是什么时候拉高的如果FIFO里已经有了等于深度的数据量full自然为高wr_en即使有效也写不进去。第三步确认wr_en、din、wr_clk三个信号在仿真波形里是否对齐到同一个时钟沿。有一种常见错误是din在时钟沿之后才变化导致建立时间不满足仿真里可能看不出问题上板偶发丢数就非常头疼。我实测中遇到最多的情况是仿真波形里wr_en确实拉高了din也有效但wr_ack没有跟着拉高。这时候十有八九是full在写使能到达之前已经拉高了。异步FIFO场景下尤其明显因为full产生跨时钟域同步写侧看到的full状态总比读侧真实状态晚。如果外部逻辑没有给FIFO预留缓冲空间很容易出现“FIFO看起来还能写实际上内部已经满了”的诡异现象。5.2 标志位阈值不生效或拉高时机不对配置Almost Full Threshold为508仿真里却看到FIFO数据量到505左右almost_full就拉高了这时不要慌先确认你使用的是Assert Value还是Negate Value。在双值配置下almost_full并不是严格在Assert Value这个点触发而是内部逻辑综合考虑到同步延迟、多周期路径等实际条件产生时刻可能与配置值存在一定偏差。另外一个很常见的坑是配置页面的单位有些版本里Threshold以“剩余空间数”为单位有些版本里以“已用数据个数”为单位。你在界面上写数字之前一定要确认这个参数对应的语义。仿真确认阈值是否正确有一个标准做法把wr_data_count拉出来显示找到almost_full拉高的位置观察wr_data_count当时的数值把这个值和你的Assert Value对比。如果相差超过几个时钟周期再去看是不是同步延迟导致或者是不是你配置的两个阈值之间滞回区间太小。5.3 异步FIFO丢数和读写同时使能问题异步FIFO丢数通常不是FIFO本身的问题而是跨时钟域写侧逻辑和标志位配合不当。比如写时钟100MHz、读时钟25MHz写侧突发连续写数据FIFO深度只有16那么full或almost_full会因为读侧时钟慢而很快拉高。如果你的写侧逻辑没有基于标志位做流控而是盲目连续写数据丢失就是必然结果。所以在异步FIFO设计里写侧一定要实现“写请求有效且非满才写入”的握手逻辑。读写同时使能也是个容易忽略的点。有些设计里读写使能同时有效时数据既写入又读出此时FIFO内数据个数不变化这是正常的但如果你的外部控制逻辑没理解这个行为可能会认为FIFO始终处于满或空的状态。尤其在使用w r_data_count做状态判断时要考虑到同时读写时计数器保持不变的场景。5.4 常见问题速查表现象可能原因检查方法写使能有效但数据进入不了FIFOwr_rst_busy仍为高或full已拉高查看wr_rst_busy和full波形数据偶发丢失din建立时间不足或wr_en未按握手机制检查din与wr_clk时序almost_full提前拉高阈值语义理解错误或同步延迟对比wr_data_count与配置值full长时间不拉高读侧从未使能FIFO内数据堆积检查rd_en和empty状态overflow频繁报错写侧未做流控持续写满FIFO检查wr_en是否在full后仍拉高wr_data_count数值波动同拍同时读写导致计数不变结合读写使能一起观察标志位出现毛刺复位极性或跨时钟域未处理检查复位配置与标志同步级数这套排查逻辑对我来说非常实用很多时候问题不在IP核本身而是外部逻辑对FIFO标志位的行为理解有偏差。把FIFO当成一个有延迟、有内部状态的模块来看待不要把它想成即时响应的组合逻辑很多怪现象就都能解释了。最后再分享一个我自己的习惯。每次在新工程里使用FIFO Generate IP核我都会把生成的example design跑一遍仿真它是官方提供的完整参考里面有规范的读写时序和Flag行为。先照着example design把波形看懂再去改自己的配置比直接上手改参数要稳妥得多。配置Status Flags页时花五分钟把阈值语义和留白余量算清楚后续调试能省下好几个小时。
返回列表