3、FIFO状态定义:FULL、EMPTY、ALMOST_FULL、ALMOST_EMPTY、NORMAL状态的逻辑含义

好,咱们今天来聊聊FIFO的状态定义。说实话,很多新手写FIFO,上来就写读写指针,结果状态判断一塌糊涂。我见过不少项目,就是因为状态没定义清楚,最后仿真通过,上板子就出问题。

FIFO的状态,说白了就是告诉你:现在能不能写?能不能读?快满了还是快空了? 就这么简单。但实现起来,细节还挺多。

3.1 五种核心状态

我个人习惯把FIFO状态分成五类。你想想看,一个FIFO从空到满,中间会经历哪些阶段?

状态 逻辑含义 读写允许
EMPTY FIFO中没有任何数据 可写,不可读
ALMOST_EMPTY 数据即将被读空(还剩少量数据) 可写,可读(但要注意)
NORMAL 数据量适中,读写都安全 可写,可读
ALMOST_FULL 数据即将写满(还剩少量空间) 可读,可写(但要注意)
FULL FIFO已满,不能再写入 不可写,可读

嗯,这里要注意:ALMOST_FULL和ALMOST_EMPTY的阈值是可以配置的。我在项目中遇到过,有人把阈值设成1,结果ALMOST_FULL和FULL几乎同时触发,完全失去了预警的意义。

3.2 状态跳转的核心逻辑

状态怎么跳?核心看两个东西:写指针(wr_ptr)和读指针(rd_ptr)的差值。这个差值就是FIFO中当前存储的数据量。

我习惯用 fifo_cnt 来表示这个差值。当然,你也可以用指针比较的方式,但计数方式更直观。

// 伪代码示例:状态判断逻辑
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        fifo_state <= EMPTY;
    end else begin
        case (fifo_state)
            EMPTY: begin
                if (wr_en) 
                    fifo_state <= (DEPTH == 1) ? FULL : NORMAL;
            end
            NORMAL: begin
                if (fifo_cnt <= ALMOST_EMPTY_TH)
                    fifo_state <= ALMOST_EMPTY;
                else if (fifo_cnt >= DEPTH - ALMOST_FULL_TH)
                    fifo_state <= ALMOST_FULL;
            end
            // ... 其他状态类似
        endcase
    end
end

你看,状态跳转其实就是一个比较器加上有限状态机。但坑在哪里呢?

⚠️ 避坑指南:

我曾经在某个项目中,把ALMOST_FULL和ALMOST_EMPTY的阈值设成了相同的值。结果FIFO深度是16,阈值设成4,导致ALMOST_FULL和ALMOST_EMPTY之间只有8个数据的正常区间。后来调试了整整两天才发现问题。

建议:ALMOST_FULL阈值 + ALMOST_EMPTY阈值 < FIFO深度,否则会出现状态重叠。

3.3 状态定义的实际意义

你可能会问:为什么需要这么多状态?直接判断满和空不就行了吗?

嗯,这个问题问得好。我举个例子你就明白了。

假设你有一个FIFO,深度是1024。后端处理模块每处理一个数据需要10个时钟周期。如果等到FULL才停止写入,那写入端可能已经多写了几个数据,造成溢出。反过来,如果等到EMPTY才停止读取,读取端可能已经空读了几个周期,造成性能浪费。

ALMOST_FULL和ALMOST_EMPTY就是给上下游模块一个缓冲时间。

核心要点:

  • ALMOST_FULL:告诉写入端「快满了,准备停」
  • ALMOST_EMPTY:告诉读取端「快空了,准备等」
  • NORMAL:一切正常,放心读写

3.4 状态跳转的SVG流程图

下面这张图,是我自己画的状态跳转关系。你看一眼就明白了。

FIFO状态跳转关系图 EMPTY ALMOST_EMPTY NORMAL ALMOST_FULL FULL 写数据 写数据 写数据 写数据 读数据 读数据 读数据 读数据 写数据(正向跳转) 读数据(反向跳转)

3.5 状态定义的硬件实现

在实际的RTL代码中,我一般这样实现状态判断:

// 状态判断逻辑
assign fifo_cnt = wr_ptr - rd_ptr;  // 注意:这里假设指针是二进制计数

assign empty      = (fifo_cnt == 0);
assign full       = (fifo_cnt == DEPTH);
assign almost_empty = (fifo_cnt <= ALMOST_EMPTY_TH) && !empty;
assign almost_full  = (fifo_cnt >= DEPTH - ALMOST_FULL_TH) && !full;
assign normal     = !empty && !full && !almost_empty && !almost_full;

💡 个人经验:

我建议把ALMOST_EMPTY_TH设成2~4,ALMOST_FULL_TH设成2~4。这样既能起到预警作用,又不会太敏感。如果FIFO深度很小(比如8),那阈值可以设成1。

另外,千万不要用组合逻辑直接输出状态信号。我吃过这个亏——组合逻辑的毛刺会让下游模块误判。一定要用寄存器打一拍。

3.6 状态定义的小结

说白了,FIFO的五个状态就是一套水位预警系统:

  • EMPTY:干涸了,别读了
  • ALMOST_EMPTY:快干了,准备停
  • NORMAL:水位正常,放心用
  • ALMOST_FULL:快满了,准备停
  • FULL:满了,别写了

嗯,这套状态定义,我在十几个项目里都用过,从简单的UART FIFO到复杂的DDR缓存FIFO,基本都能覆盖。你只要记住:状态是给别人看的,不是给自己看的。设计状态的时候,多想想你的上下游模块需要什么信息。

可可精品资料库