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流程图
下面这张图,是我自己画的状态跳转关系。你看一眼就明白了。
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,基本都能覆盖。你只要记住:状态是给别人看的,不是给自己看的。设计状态的时候,多想想你的上下游模块需要什么信息。