Verilog实现深度8同步FIFO:计数器法设计与仿真验证

发布时间:2026/10/17 15:06:10
Verilog实现深度8同步FIFO:计数器法设计与仿真验证 做数字逻辑设计的人迟早都会遇到FIFO。这个基础组件几乎出现在每一个稍微像样的系统里跨模块数据缓冲、流水线解耦、速率匹配、测量数据的暂存处处都有它的身影。这篇文章要落地的是一个很经典的小项目用Verilog实现一个同步FIFO存储深度为8读写使用同一个时钟。我会从设计思路、RTL代码、仿真验证到工程化注意事项把整个流程完整走一遍。我见过不少初学者一上来就抄一个FIFO代码仿真跑通就觉得自己会了真到项目里配RAM、调时序、查数据错位的时候又一脸懵。所以这篇文章不只是贴代码更重要的是把“为什么这么做”讲清楚为什么同步FIFO用计数器判断空满最省事、为什么读数据输出要打一拍、写满之后wr_en要不要保护这些细节才是日常工程里真正决定成败的地方。如果你刚学完基础Verilog语法想找一个小而完整的模块练手或者你已经写过一些模块但对FIFO的边界情况空读、满写、同时读写把握不准这篇文章都适合你。8位深度的规模不大波形一眼能看全正好用来把原理吃透深度大了之后逻辑完全一样只是寄存器多一些而已。1. 项目概述这个FIFO到底解决什么问题1.1 什么是FIFO为什么系统里离不开它FIFO的全称是First In First Out先入先出队列。你可以把它想象成水管里排队流过去的水先进去的水先流出来顺序绝对不会乱。它本质上是两个角色之间的缓冲通道一个角色负责往里面写数据叫写端另一个角色负责从里面取数据叫读端。写端和读端不需要知道对方在干什么只需要各自跟FIFO的接口打交道这就把两边的时序耦合解开了。举一个实际场景。某个模块计算完一组结果数据有效信号拉高一个周期但下一级处理模块因为调度原因可能三个周期后才有空接收中间这两个周期的数据如果没人接就丢了。在两级之间塞一个FIFO产生数据的模块只要FIFO不满就写消费数据的模块只要FIFO非空就取两侧不用互相等待问题就解决了。FIFO还有更重要的用途就是处理数据速率不匹配。比如一个模块突发地一次性产生100个数据处理端平均每个时钟只能处理1个突发之间还有空闲那这个FIFO的深度只要扛得住单次突发量整个系统就能顺畅运转。如果完全没有FIFO处理端必须跟着生产端的节奏走系统性能被最弱的环节锁死。1.2 “同步8位深度”这个标题怎么理解先把概念掰开。同步FIFO指读写两端使用同一个时钟每个时钟沿上读指针、写指针、状态标志一起更新逻辑天然是安全的不需要做跨时钟域处理。反之读写时钟不同的叫异步FIFO那要用格雷码、两级同步器等一整套专门手段复杂度完全不一样。“8位深度”是指存储单元的个数是8个也就是能暂存8个数据。有些同学会把“8位”误以为是数据位宽其实这里说的是深度数据位宽是可配置的本例默认取8 bit也就是一个8×8的存储阵列能存8个8比特的数据。如果你需要32 bit宽的数据直接把数据位宽参数改掉深度逻辑不受影响。之所以从8这个深度入手是因为存储地址只需要3比特空满、计数、指针回绕这些行为在波形上看得清清楚楚调试成本极低。等这一套逻辑熟透了往256、1024深扩展只是改参数的事核心结构一行都不用动。1.3 这个设计适合谁可以用在哪些地方如果你是一个正在学Verilog的初学者这个项目是你把组合逻辑、时序逻辑、状态机思维整合起来的一次很好的练习。FIFO里有读指针、写指针、计数器、RAM阵列、组合逻辑判断一个模块把时序电路最常见的要素全涵盖了。如果你已经有一定基础这个设计还能当做一个通用的IP核来用挂在系统总线上做数据缓冲或者作为子模块嵌入到图像处理、通信协议处理这些更大的设计里。后面我会给出的代码是参数化的DATA_WIDTH和ADDR_WIDTH都可以调实际项目里想直接用的话改一下端口映射就能接入。2. 同步FIFO的设计思路与方案选型2.1 整体架构存储阵列、读写指针、计数状态三件套同步FIFO的结构可以拆成三个核心部分。第一部分是存储阵列就是一个一块小型的双端口RAM一边写一边读读写可以同时进行第二部分是写指针和读指针分别指向下一次写入和下一次读出的地址第三部分是状态判断逻辑向外输出空、满、当前存储数量等信号。读写指针的配合规则非常直观。每写入一个数据写指针加1数据落到当前写指针指向的那个单元里每读出一个数据读指针加1把当前读指针指向的单元内容送出去。两个指针从0开始到7之后回绕到0所以本质上就是3比特计数器在循环。FIFO是否为空、是否为满完全取决于这两个指针的差。存储阵列可以用两种方式实现。一种是直接用寄存器数组也就是常说的分布式RAM适合深度不大的情况8深度直接用寄存器堆综合出来的面积和走线都很好控制另一种是例化专用的block RAM适合深度大、位宽宽的场景。本例因为深度只有8我直接用reg数组让综合工具自行推断即可。2.2 空满判断的两种常见方案指针比较法与计数器法空满判断是FIFO设计的核心方案也五花八门但归纳起来大致就两类。指针比较法思路是当写指针追上读指针即两者相等时数学上既可能是空也可能是满需要用额外一位比特来区分。最常见的做法是让指针位宽比地址位宽多1位最高位作为回绕附加位写指针和读指针的低位地址相同、最高位不同表示写指针多绕了一圈那就是满完全相等就是空。这种方案在高深度异步FIFO里几乎是标配因为它不需要一个随时加减的计数器在跨时钟域同步时也有优势。计数器法思路更直白专门维护一个fifo_count信号记录当前FIFO里有多少个有效数据。写一个加1读一个减1读写同时发生则保持不变。当fifo_count等于0的时候就是空等于深度值的时候就是满。选哪个方案取决于场景。同步FIFO里读写指针在同一时钟下更新计数器不存在跨时钟域一致性问题所以计数器法简单又直观异步FIFO必须用指针比较法因为读写指针分别在不同的时钟域里计数器如果跨时钟域加减会产生竞争。2.3 为什么同步FIFO优先选计数器法很多教科书上默认讲指针比较法因为通用性强但同步场景下我建议你用计数器法理由有三条。第一是直观。fifo_count直接告诉你FIFO里有多少数据仿真波形上一眼就看明白做覆盖率统计、突发长度检查、剩余空间判断都极其方便。指针比较法只有空和满两个状态中间还剩多少数据你得自己算调试效率低一截。第二是省事。指针比较法要想区分空和满指针必须比地址多一位所有读写地址比较都要处理回绕逻辑对新手来说很容易在边界上写错。计数器法完全不用管回绕一个朴素的正负1计数就干完了。第三是天然支持almost full这种中间状态。数据处理类的系统经常需要 not full才能写或者需要提前一个周期告警计数器法直接把剩余空间算给你想做阈值判断瞬间就能实现。指针比较法要额外做加减法反而绕远路。当然计数器法也有代价就是fifo_count这个信号本身要用寄存器保存还要做加减面积上多个十几二十个触发器对8深度这种小FIFO完全无感但如果你做上万深度的巨型FIFO这点开销会明显变大那时候再用指针比较法也不迟。工程上不存在银弹选方案就是选约束。3. RTL实现核心代码与关键细节3.1 模块接口定义与参数化设计代码直接给出来我用的模块名是fifo_sync参数做了两级可配DATA_WIDTH是数据位宽默认8 bitADDR_WIDTH是地址位宽满足2^ADDR_WIDTH存储深度默认3即深度恰好是8。这样你后续想改成16深或32深只要把ADDR_WIDTH改成4或5即可。module fifo_sync #( parameter DATA_WIDTH 8, parameter ADDR_WIDTH 3 )( input wire clk, input wire rst_n, input wire wr_en, input wire rd_en, input wire [DATA_WIDTH-1:0] wr_data, output reg [DATA_WIDTH-1:0] rd_data, output wire fifo_full, output wire fifo_empty, output wire [ADDR_WIDTH:0] fifo_count );接口上有几个点需要提前说清楚。wr_en和rd_en都是高电平有效wr_data是写数据rd_data是被读出的数据。fifo_full和fifo_empty分别是满标志和空标志。fifo_count比地址位宽多1位因为深度8最大计到8需要4比特来表达0到8这9个值。关于rd_data我这里把它做成了寄存器输出读使能有效的那一拍数据并不会立刻出现在rd_data上而是下一拍才能读到。这个时序特性非常重要后面仿真章节会专门说你现在只要记住FIFO的读端口通常是有读延迟的。3.2 存储阵列与写操作逻辑存储阵列直接用reg数组声明深度由ADDR_WIDTH自动推导出来配合localparam使用避免手写具体深度导致参数不一致。localparam DEPTH 1 ADDR_WIDTH; // 8 units reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; reg [ADDR_WIDTH-1:0] wr_ptr; reg [ADDR_WIDTH-1:0] rd_ptr; reg [ADDR_WIDTH:0] count;写操作逻辑的完整代码是这样的always (posedge clk or negedge rst_n) begin if (!rst_n) begin wr_ptr {ADDR_WIDTH{1b0}}; end else if (wr_en !fifo_full) begin mem[wr_ptr] wr_data; wr_ptr wr_ptr 1b1; end end这段逻辑有两个关键细节值得展开说。第一写入有前提条件wr_en有效还不够必须fifo_full为低才真正写入。这相当于给FIFO加了一层自我保护的写阀门防止写满之后数据继续落到RAM里把旧数据覆盖。实际项目里即使上游逻辑保证不会写满这层保护也建议保留因为上游一旦出错被覆盖的数据会导致整个调试过程变得极其痛苦。第二wr_ptr的位宽正好是3比特从0写到7后自动回绕到0这是让指针循环的关键。如果你把wr_ptr的位宽加宽到4比特写深度就变成了16存储阵列的访问地址位宽也要一起变所以这里用ADDR_WIDTH参数统一控制保证指针和地址永远匹配。3.3 读操作与读数据寄存器读操作逻辑和写操作几乎对称always (posedge clk or negedge rst_n) begin if (!rst_n) begin rd_ptr {ADDR_WIDTH{1b0}}; rd_data {DATA_WIDTH{1b0}}; end else if (rd_en !fifo_empty) begin rd_data mem[rd_ptr]; rd_ptr rd_ptr 1b1; end endrd_data在这里是时序输出不是组合逻辑直通。这意味着读使能拉高的那个时钟沿FIFO内部把mem[rd_ptr]的值锁存到rd_data寄存器里外部电路要到下一个时钟沿之后才能采样到稳定的读数据。为什么这么设计两个原因。一是这种带一级输出寄存器的读端口能显著提高时序收敛性RAM读出的数据经过一级寄存器打拍后路径上的组合逻辑被切短不容易出现建立时间违例二是它和绝大多数片上RAM的读行为一致方便综合工具映射到真正的存储器硬核上。代价是读延迟一拍外部用这个FIFO时需要在时序上配合好。我把rd_ptr的复位值也显式归零了。很多代码在复位时只清指针不清数据这没问题但rd_data在复位时最好也归零这样仿真时读空FIFO返回的数据是干净的0而不是一个X态后面排查起来省很多心。3.4 计数器与空满信号的生成计数器是同步FIFO的状态核心。完整代码如下always (posedge clk or negedge rst_n) begin if (!rst_n) begin count h0; end else begin case ({wr_en ~fifo_full, rd_en ~fifo_empty}) 2b10: count count 1b1; // write only 2b01: count count - 1b1; // read only 2b11: count count; // read and write simultaneously default: count count; endcase end end assign fifo_empty (count 0); assign fifo_full (count DEPTH); assign fifo_count count;先注意case条件里的细节wr_en和rd_en并不是裸着进case的而是分别用fifo_full和fifo_empty做了门控。也就是说当FIFO已满时即使wr_en为高写侧也被视为无效当FIFO已空时即使rd_en为高读侧也被视为无效。计数器只统计真实发生的读写这个门控必须加否则满之后写请求还会把count加1变成9深度就乱套了。fifo_count的输出我是直接assign给count的没有打拍。这是因为空满信号需要尽可能快地反映状态供外部逻辑做组合判断。如果你的下游电路对这两个信号有严格的时序要求可以在输出端再加一级寄存器变成registered版本但代价是空满判断晚一个周期内部写入保护也会跟着晚一拍取舍要权衡好。3.5 读写同时发生时计数器怎么走同步FIFO允许在一个时钟周期里同时读和写也就是wr_en和rd_en同时为高。这时候问题来了同一拍既加1又减1count到底加还是减答案是保持不变。写入1个、读出1个FIFO里的有效数据数不变所以count维持原值。这个行为我在case里用2b11分支专门表达了。还要注意另一种情况FIFO深度不为0也不为满但同一拍wr_en和rd_en同时为高读写地址会不会冲突不会。写指针和读指针各自独立只要count不为0读指针指向的是已写入的有效数据写作的是另一个空闲单元两者互不干扰。这也正是双端口RAM的价值一个周期能同时完成一次写和一次读。但如果FIFO恰好是满的wr_en仍然为高同时rd_en也为高会发生什么写阀门把写操作拦住了只有读真实发生count减1。这个场景在case里其实已经覆盖了因为wr_en ~fifo_full此时为0case只看到读侧有效。这种边角情况的正确性就是我坚持在case前做门控的原因。4. 仿真验证从测试平台到波形分析4.1 Testbench框架搭建代码写完之后不能直接上板先得仿真。Testbench的核心思路是构造一个可控的读写场景观察FIFO的空满标志和数据是否对得上。我的测试框架如下module tb_fifo_sync; reg clk; reg rst_n; reg wr_en; reg rd_en; reg [7:0] wr_data; wire [7:0] rd_data; wire fifo_full; wire fifo_empty; wire [3:0] fifo_count; fifo_sync #( .DATA_WIDTH(8), .ADDR_WIDTH(3) ) uut ( .clk (clk), .rst_n (rst_n), .wr_en (wr_en), .rd_en (rd_en), .wr_data (wr_data), .rd_data (rd_data), .fifo_full (fifo_full), .fifo_empty (fifo_empty), .fifo_count (fifo_count) ); initial clk 0; always #5 clk ~clk; initial begin rst_n 1b0; wr_en 1b0; rd_en 1b0; wr_data 8h00; repeat (5) (posedge clk); rst_n 1b1; repeat (3) (posedge clk); // 测试场景在这里展开 #200; $finish; end initial begin $dumpfile(fifo_sync.vcd); $dumpvars(0, tb_fifo_sync); end endmodule时钟周期设成10 ns半周期5 ns。复位至少保持5个时钟再释放让所有寄存器稳定进入复位态。空间上的例化模块用了命名端口连接虽然麻烦一点但可读性和可维护性比位置连接强得多工程上建议养成这个习惯。4.2 基本读写场景先写满再读完第一个测试场景是经典的先写后读。连续写入8个数据此时FIFO应该满然后再连续读8个数据FIFO应该空。向FIFO里写入的数据我建议用有规律但又能分辨顺序的序列比如0x01、0x02一直到0x08这样从rd_data上读出什么立刻就能判断顺序有没有错。// 连续写满8个数据 repeat (8) begin (posedge clk); wr_en 1b1; wr_data wr_data 8h01; end (posedge clk); wr_en 1b0; // 检查fifo_full和fifo_count的值 if (fifo_full ! 1b1 || fifo_count ! 4d8) $display([ERROR] Full flag mismatch at %0t, $time); // 连续读空8个数据 repeat (8) begin (posedge clk); rd_en 1b1; end (posedge clk); rd_en 1b0; if (fifo_empty ! 1b1 || fifo_count ! 4d0) $display([ERROR] Empty flag mismatch at %0t, $time);这里要提醒一点repeat块里的(posedge clk)实际上让wr_en在时钟上升沿到来之前已经稳定上升沿采样时wr_en为高数据在沿后写入。所以repeat(8)循环8次正好写进8个数据。读侧注意rd_data的时序。读使能在第N个时钟沿有效rd_data要到第N1个时钟沿才能更新为被读出的数据。所以如果你在第8个读周期结束后立刻检查rd_data看到的还是倒数第1个数据的值要再多等一拍。这个特性在设计验证脚本时特别容易踩我建议在这样的关键位置打印时加上$display的时间信息配合波形确认。4.3 边界场景满后继续写、空后继续读、同时读写只测基本读写是不够的边界场景才是隐藏bug的地方。我最常构造的有三个场景。第一个是满后继续写。先写满8个数据然后继续把wr_en拉高试图写第9个此时由于fifo_full为高写阀门生效mem里不应有任何变化count稳定保持8wr_ptr不增加。检查方法是记录写满时的wr_ptr再尝试写几次后确认wr_ptr没动。第二个是空后继续读。FIFO读空后继续拉高rd_en此时fifo_empty为高读阀门生效rd_ptr不增加count保持0rd_data维持复位后的值不会变成X。第三个是读写同时发生。这个场景最容易被忽略但最符合实际使用。让FIFO里先有4个数据然后在一个时钟周期里同时拉高wr_en和rd_encount应该保持4不变。连续做几次同时读写等count到达满或空的边缘再验证空满标志是否和计数器一致。// 写入4个数据后开启同时读写10个周期 repeat (4) begin (posedge clk); wr_en 1b1; wr_data wr_data 8h01; end repeat (10) begin (posedge clk); wr_en 1b1; rd_en 1b1; wr_data wr_data 8h01; if (fifo_count ! 4d4) $display([ERROR] Count should stay 4 at %0t, $time); end这段逻辑里我同时拉高了wr_en和rd_enFIFO内部每个周期写入一个新数据同时读出一个旧数据count如果始终保持4说明同时读写的case处理正确。这是同步FIFO最容易出错的地方务必单独跑一遍。4.4 用计数信号快速定位故障fifo_count这个信号在调试阶段的价值远超你的预期。如果测试里发现数据顺序错乱最有效的做法不是硬盯波形而是把fifo_count和rd_data放在一张波形图里对比。比如FIFO里先写了8个数据还没读你看到fifo_count8同时fifo_full1那写侧大概率没问题。如果此时你强制rd_en连续拉高8拍fifo_count应该每拍减1到第8拍变成0且fifo_empty1。假如在某拍fifo_count从5直接跳到3那说明读路径上有两拍都触发了读或者rd_en的门控写错了。另一个实用技巧是在Testbench里加入自动比对。写入时把数据同时压进一个行为级的参考队列读取时从参考队列弹出数据和rd_data比对任何不匹配立即打印错误信息。这种参考模型方法在小型设计验证里极其好用代码也不复杂本质就是用系统级的可靠逻辑去校验RTL实现。5. FPGA实现与工程化注意事项5.1 综合工具对存储阵列的推断8个深度的reg数组在综合时FPGA工具通常会把它推断成分布式RAM也就是使用查找表加触发器来实现不会消耗块RAM资源。原因很简单深度这么小用块RAM是浪费而且块RAM的读延迟特性和这个设计不完全一致。如果你把ADDR_WIDTH改成6或7深度变成64或128综合工具就更倾向于推断为块RAM。此时需要注意块RAM的输出本身带有一级或两级寄存器可能导致读延迟变成2到3拍和你的RTL行为不符。解决方法是利用综合工具的原语或属性强制控制RAM的推断方式并且重新确认读时序。我在工程里通常的做法是深度小于16直接不管让工具自由处理深度超过64则显式例化平台相关的RAM IP把读延迟作为参数传进设计避免RTL行为与物理实现漂移。5.2 复位策略与初始值问题这个FIFO使用异步复位rst_n一旦拉低所有指针和计数器立即归零。这里有一个工程上的细节FIFO的存储阵列mem不需要复位因为只要count为0读路径永远被阀门拦住任何人都读不到mem里的内容所以mem里的旧数据完全不影响行为。这为你省下了一大批复位树的走线资源。但rd_data不一样我把它在复位时清零了是因为仿真时如果读到未初始化的RAM内容rd_data会出现X态X在波形上一旦出现后续所有比较判断都会变得不可信。虽然这只是仿真问题综合后的实际电路中复位后rd_data的寄存器值取决于实现工具但统一清零能让前后端行为保持一致。复位释放的时刻也要小心。如果rst_n在时钟上升沿附近释放且FIFO内部记录有有效数据读写指针可能会比预期多走一个沿导致第一个数据丢失。稳妥的做法是在Testbench中让复位释放与时钟上升沿错开或者使用同步释放的复位逻辑这在工程上是一个成熟实践。5.3 数据有效性与时序约束我设计里没有单独输出一个rd_valid信号因为读数据的有效周期是确定的rd_en为高的下一拍rd_data上就是这一次读出的数据。外部电路只要用rd_en打一拍去对齐rd_data就能正确取数。如果FIFO被用在一个高速总线上建议把fifo_count或者空满标志打一拍输出并且对这两条路径做时序约束避免长走线导致建立时间违例。尤其是fifo_full它同时作为内部写阀门的一部分如果它从RAM配置位、指针比较和计数器综合出来的路径太长写数据的建立时间会变得非常紧这是同步FIFO在FPGA上比较常见的时序瓶颈。遇到这种情况工程上常用的解法和我说过的保持一致先把full信号寄存一拍再用它去门控写操作代价是外部多等一个周期但时序压力大幅缓解。这个改动涉及接口行为变更要在设计文档里明确标注。6. 常见问题与踩坑实录6.1 空读返回什么数据很多第一次接触FIFO的人会纠结FIFO为空时强行读rd_data到底是什么在我的设计里空读时读阀门不打开rd_ptr不动rd_data保持上一次的值或者复位值不会出现X态也不会有任何数据损失。这个行为是安全的但你要清楚它也有代价下游电路如果误以为读出了一个有效数据就会拿到一个旧值。所以使用FIFO时外部逻辑必须把fifo_empty作为读数据有效的条件之一不能只靠rd_en。6.2 指针位宽和数据位宽别混淆这个坑我在指导新人时见过不止一次。有同学把wr_ptr定义成DATA_WIDTH位宽把指针和数据位宽弄混结果写地址逐渐溢出到错误的位置读出来的数据顺序完全错乱。指针位宽必须等于ADDR_WIDTH数据位宽由DATA_WIDTH决定两者相互独立不要因为默认都是8就以为它们是同一个东西。6.3 计数器加法溢出隐患count是从0到8需要4比特位宽也就是ADDR_WIDTH1。如果你在例化时不小心把fifo_count的端口位宽定义成3比特那么当计数到达8时会自动回绕成0fifo_full逻辑立刻失效整个空满系统崩溃。这种错误仿真时往往表现为“永远不满”或者“满之后还能继续写”排查半天最后发现是端口位宽接错了。接口位宽千万不要凭感觉写ADDR_WIDTH加1这是固定规则。6.4 仿真X态传播怎么破如果你用的仿真工具对未初始化RAM会输出X哪怕逻辑设计正确波形上也会出现一大片红色。最直接的解决办法是我前面说的在复位时清零rd_data更彻底的办法是在Testbench里通过force语句把mem的全部单元初始化为确定值或者使用$readmemh加载一个初始化文件。这样仿真环境干净调试效率会高很多。6.5 “8位深度”的歧义澄清再回到标题本身。8位深度指的是存储深度为8个存储单元这是对存储体量的描述不是数据位宽。如果你把32 bit的数据放进这个FIFO深度依然是8如果你希望深度为16把ADDR_WIDTH改成4即可。标题里“同步”限定了单时钟域如果未来要做跨时钟域场景就得在异步FIFO方向扩展代码结构需要重新设计。我把这个项目里最典型的几个问题整理成了一张速查表方便你调试时对照现象可能原因排查方法FIFO永远不满fifo_count位宽不足或比较逻辑写错检查fifo_count端口位宽是否为ADDR_WIDTH1读出顺序错乱指针位宽与数据位宽混淆检查wr_ptr、rd_ptr位宽是否等于ADDR_WIDTHcount在同时读写时漂移case分支未完整覆盖2b11检查case条件中对读写的门控是否一致rd_data出现X态读取未初始化RAM单元复位清零rd_data或初始化mem数组full信号有时不拉高比较器中DEPTH取值类型不匹配确认DEPTH与count位宽对齐无隐式截断7. 后续扩展方向7.1 异步FIFO与格雷码同步FIFO熟悉之后下一个必须迈过的台阶就是异步FIFO。读写时钟不同指针跨时钟域同步时要避免多比特信号产生亚稳态导致误判最经典的做法就是把指针转成格雷码让每个时钟周期只有1个比特变化再经过两级同步器打拍。格雷码的编码规则和指针回绕方式都和同步FIFO完全不同建议单独开一个项目仔细做一遍。7.2 参数化接口与总线协议适配现在的代码只提供了最朴素的读写信号。真实系统里更常见的是把FIFO封装成AXI-Stream接口用tvalid、tready、tdata这些握手信号去控制读写。那时候wr_en会变成tvalid与tready的组合rd_en会变成tvalid和tready方向的对称逻辑这套封装适配不难但握手信号优先级、背压处理都要重新考虑。7.3 增加阈值告警与FWFT模式这个版本只做了空满两个标志。工程上经常还需要almost_full、almost_empty用于提前阻塞突发写入或触发转移逻辑。有了fifo_count加两个比较器就能实现。另外现在读端口有一拍延迟如果你想要First Word Fall Through模式也就是第一个数据在空转非空时立刻出现在rd_data上不需要等待读使能那读指针和读数据逻辑要重构这也是一个很有价值的进阶练习。我个人在实际项目里反复体会到FIFO这种模块看起来简单真正把它用对、用稳靠的是对每一个边界条件的精确控制。这篇先把同步8位深度扎扎实实走通指针、计数、空满、阀门这些基本功练到位后面无论是扩展到异步还是做到总线接口都是水到渠成的事。如果你照着代码仿真时遇到什么奇怪现象对照第六节的排查表先自查一遍多数问题都是位宽或门控这两类原因。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询