3. FIFO读写指针原理:写指针与读指针的生成逻辑、指针回绕机制

各位同学,今天我们来聊聊FIFO的核心——读写指针。说实话,很多人在调试FIFO数据丢失问题时,第一反应就是去看full和empty信号,但往往忽略了指针本身。我个人的经验是,指针出问题,数据必丢。这可不是吓唬你。

3.1 写指针(wr_ptr)的生成逻辑

写指针,顾名思义,就是控制数据往哪儿写的。它的本质是一个计数器,每来一个写使能(wr_en),它就加1。

但这里有个细节——写指针指向的是下一个要写入的地址,而不是当前正在写的地址。为什么这么设计?你想想看,如果指向当前地址,那读操作怎么知道数据已经写进去了?

核心要点:写指针永远指向下一个空闲的存储单元。

我在项目中遇到过这样一个坑:有人把写指针设计成“先写后加”,结果读指针读到的总是旧数据。调试了整整两天,最后发现是时序错了一拍。嗯,这种低级错误,新手最容易犯。

写指针的生成代码,我习惯这样写:

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        wr_ptr <= 0;
    else if (wr_en && !full)
        wr_ptr <= wr_ptr + 1;
    else
        wr_ptr <= wr_ptr;
end

注意看,这里有个!full的判断。为什么要加?因为FIFO满了你还写,数据就覆盖了。我曾经见过一个案例,就是因为忘了加这个条件,导致FIFO满的时候还在写,结果旧数据被冲掉,读出来的全是错的。

3.2 读指针(rd_ptr)的生成逻辑

读指针和写指针是对称的。每来一个读使能(rd_en),读指针就加1。它指向的是当前要读出的数据地址。

这里有个容易混淆的地方:读指针指向的是当前数据,写指针指向的是下一个位置。两者差一拍。我建议你在画时序图的时候,把这个关系标清楚,否则后面分析空满标志时会晕。

always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        rd_ptr <= 0;
    else if (rd_en && !empty)
        rd_ptr <= rd_ptr + 1;
    else
        rd_ptr <= rd_ptr;
end

你看,读指针也有个!empty的保护。FIFO空了你还读,读出来的就是垃圾数据。说白了,这就是最基本的保护机制。

3.3 指针回绕(Wrap Around)机制

好,接下来是重点——指针回绕。为什么需要回绕?因为FIFO的深度是有限的。假设深度是8,地址范围0~7。写指针加到7之后,下一个该写哪儿?当然是回到0。

这就是回绕。说白了,就是计数器溢出后自动归零。

小技巧:我习惯用格雷码来实现指针,这样可以减少跨时钟域时的亚稳态风险。但这是后话了,今天我们先讲二进制指针。

回绕的实现其实很简单:

// 假设FIFO深度为8,地址位宽为3
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        wr_ptr <= 3'b0;
    else if (wr_en && !full)
        wr_ptr <= wr_ptr + 1;  // 加到7后自动回绕到0
end

但是!这里有个大坑。你想想看,如果只用3位地址,怎么判断空和满?写指针和读指针相等时,到底是空还是满?

嗯,这就是经典问题。我刚开始做FIFO时,就被这个问题坑过。解决方案是:扩展一位额外的位来区分空和满。

比如深度为8的FIFO,地址用3位,但指针用4位。最高位用来标记回绕次数:

  • 空标志:wr_ptr == rd_ptr(所有位都相等)
  • 满标志:wr_ptr[3] != rd_ptr[3] 且 wr_ptr[2:0] == rd_ptr[2:0]

说白了,就是看最高位是否相同。相同就是空,不同就是满。这个技巧,我建议你记牢。

警告:千万不要用简单的地址相等来判断空满!否则你会遇到“假满”或“假空”的问题,数据丢失就在所难免了。

3.4 指针回绕的SVG流程图

下面我用一张图来展示指针回绕的完整过程。这张图我画了很多遍,希望能帮你理清思路。

FIFO指针回绕机制示意图 地址0 地址1 地址2 ... 地址7 写指针 wr_ptr 读指针 rd_ptr 回绕路径(写指针从7回到0) 额外位扩展 深度8 → 地址3位 → 指针4位 最高位标记回绕次数 相同=空,不同=满 空满判断逻辑 空:wr_ptr == rd_ptr 满:最高位不同,低位相同

这张图里,我特意把回绕路径画成了虚线。你注意看,写指针从地址7回到地址0时,最高位会翻转。这个翻转就是判断满的标志。

3.5 实战中的常见问题

最后,我分享几个实战中遇到的指针问题:

  1. 指针不同步:异步FIFO中,读写指针跨时钟域传递时,如果没有用格雷码或双级同步,会出现亚稳态,导致空满判断错误。我曾经因为这个原因,数据丢了整整一帧。
  2. 指针溢出:如果FIFO深度不是2的幂次方,回绕逻辑会变得复杂。我建议你尽量用2的幂次方深度,省心。
  3. 复位状态不一致:复位后,读写指针必须都归零。如果有一个没复位干净,空满标志就乱了。

避坑指南:我曾经在调试一个高速FIFO时,发现写指针偶尔会跳变。查了三天,最后发现是时钟抖动导致的。解决方案是给指针加一个同步使能信号。嗯,这种问题,没有经验真的很难想到。

好了,关于读写指针的原理,我们就讲到这里。记住一句话:指针是FIFO的灵魂,回绕是指针的核心。搞懂了这些,你离FIFO数据丢失问题的真相就不远了。

可可精品资料库