第四章:FIFO读写控制逻辑

各位同学,今天我们来聊聊FIFO的核心——读写控制逻辑。说白了,这部分就是FIFO的「大脑」,它决定了数据什么时候能写进去、什么时候能读出来。我在项目中见过不少新手,一上来就写使能信号乱拉,结果数据全乱了。嗯,咱们今天就把这块彻底讲透。

4.1 读使能(Read Enable)与写使能(Write Enable)

先看最基本的两个信号:wr_en 和 rd_en。它们就像门卫,一个管进,一个管出。

  • 写使能(wr_en):高电平时,数据从输入总线写入FIFO。但要注意——不是每次拉高都能写!如果FIFO满了,写使能必须被忽略。
  • 读使能(rd_en):高电平时,数据从FIFO输出到读总线。同样,空的时候读使能无效。

核心原则:写使能只在非满时有效,读使能只在非空时有效。这是所有FIFO设计的铁律。

我个人习惯把使能信号和状态标志(full/empty)做逻辑与,再送给存储体。举个例子:

// 实际写入信号
wire actual_wr_en = wr_en & !full;
// 实际读取信号
wire actual_rd_en = rd_en & !empty;

你想想看,如果不加这个保护,满的时候还往里写,数据就会覆盖掉还没读走的内容。我曾经在一个项目中遇到过这种bug,查了整整两天才定位到——就是因为写使能没跟满标志做与操作。

4.2 地址译码器

地址译码器,说白了就是把读写指针翻译成存储单元的选通信号。对于同步FIFO,通常用二进制计数器做指针,然后直接作为地址送给RAM。

但这里有个坑:二进制地址的译码延迟。如果FIFO深度很大(比如1024),地址译码器的组合逻辑路径会很长,影响时序。我建议的做法是:

  • 深度 ≤ 64:直接用二进制地址,简单高效
  • 深度 64~256:考虑用格雷码指针,减少跨时钟域问题
  • 深度 > 256:建议用分块译码,或者直接用寄存器阵列

小技巧:对于深度是2的幂次的FIFO,地址译码其实就是取指针的低N位。比如深度16,只需要取指针的低4位。这样译码器几乎零延迟。

我记得有一次做视频缓存FIFO,深度设了512。刚开始用二进制地址直接译码,时序报告里地址译码路径占了关键路径的40%。后来改成两级译码——先译高3位选块,再译低6位选单元——时序立马就收敛了。

4.3 指针递增逻辑

读写指针的递增,看似简单,其实门道不少。先看基本代码:

// 写指针递增
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    wr_ptr <= 0;
  else if (actual_wr_en)
    wr_ptr <= wr_ptr + 1;
end

// 读指针递增
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    rd_ptr <= 0;
  else if (actual_rd_en)
    rd_ptr <= rd_ptr + 1;
end

这里要注意几个问题:

  1. 回绕(Wrap-around):指针到达最大值后,下一个时钟应该回到0。对于2的幂次深度,加法器自动回绕。但如果不是2的幂次,需要手动判断。
  2. 空满判断:同步FIFO的空满标志,通常通过比较读写指针的差值来实现。我个人习惯用「指针相等」判空,「指针差值为深度」判满。
  3. 格雷码指针:异步FIFO必须用格雷码,因为二进制指针多bit同时变化时,跨时钟域采样会出问题。格雷码每次只变1bit,安全得多。

避坑指南:我曾经在异步FIFO中直接用二进制指针做跨时钟域比较,结果仿真没问题,上板后偶尔读错数据。查了三天才发现是二进制指针多bit跳变导致的亚稳态。从那以后,所有异步FIFO我都老老实实用格雷码。

4.4 整体控制逻辑框图

下面我用一张SVG图来展示读写控制逻辑的整体结构。这张图我画了很多遍,算是比较清晰的一个版本。

FIFO读写控制逻辑结构图 写使能控制 wr_en & !full 读使能控制 rd_en & !empty 写指针计数器 递增/回绕 读指针计数器 递增/回绕 地址译码器 二进制/格雷码 → RAM地址 空/满标志生成 比较读写指针差值 图例: 使能信号 指针/地址 状态反馈

从这张图可以清楚看到:写使能和读使能分别控制指针的递增,指针值送到地址译码器生成RAM地址,同时指针差值用于生成空满标志,空满标志又反过来控制使能信号——形成一个闭环。

4.5 面积与功耗的权衡

最后聊点实际的。读写控制逻辑虽然不大,但在面积和功耗上也有优化空间:

优化方向 面积影响 功耗影响 我的建议
二进制指针 小(加法器简单) 中(多bit翻转) 同步FIFO首选
格雷码指针 中(需要转换逻辑) 低(每拍只变1bit) 异步FIFO必选
组合译码 小 中(组合逻辑动态功耗) 深度小的时候用
分块译码 中(多级译码器) 低(只有选中块翻转) 深度大时推荐

个人经验:如果功耗是首要约束,优先用格雷码指针+分块译码。虽然面积会大10%~15%,但动态功耗能降30%以上。我在一个低功耗IoT芯片项目中就是这么做的,效果很明显。

好了,读写控制逻辑就讲到这里。记住三个核心点:使能信号要跟状态标志做与、地址译码要考虑时序、指针递增要处理好回绕。下一节我们聊聊存储体的选择——用寄存器还是SRAM,这里面的门道也不少。


可可精品资料库