第四章: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
这里要注意几个问题:
- 回绕(Wrap-around):指针到达最大值后,下一个时钟应该回到0。对于2的幂次深度,加法器自动回绕。但如果不是2的幂次,需要手动判断。
- 空满判断:同步FIFO的空满标志,通常通过比较读写指针的差值来实现。我个人习惯用「指针相等」判空,「指针差值为深度」判满。
- 格雷码指针:异步FIFO必须用格雷码,因为二进制指针多bit同时变化时,跨时钟域采样会出问题。格雷码每次只变1bit,安全得多。
避坑指南:我曾经在异步FIFO中直接用二进制指针做跨时钟域比较,结果仿真没问题,上板后偶尔读错数据。查了三天才发现是二进制指针多bit跳变导致的亚稳态。从那以后,所有异步FIFO我都老老实实用格雷码。
4.4 整体控制逻辑框图
下面我用一张SVG图来展示读写控制逻辑的整体结构。这张图我画了很多遍,算是比较清晰的一个版本。
从这张图可以清楚看到:写使能和读使能分别控制指针的递增,指针值送到地址译码器生成RAM地址,同时指针差值用于生成空满标志,空满标志又反过来控制使能信号——形成一个闭环。
4.5 面积与功耗的权衡
最后聊点实际的。读写控制逻辑虽然不大,但在面积和功耗上也有优化空间:
| 优化方向 | 面积影响 | 功耗影响 | 我的建议 |
|---|---|---|---|
| 二进制指针 | 小(加法器简单) | 中(多bit翻转) | 同步FIFO首选 |
| 格雷码指针 | 中(需要转换逻辑) | 低(每拍只变1bit) | 异步FIFO必选 |
| 组合译码 | 小 | 中(组合逻辑动态功耗) | 深度小的时候用 |
| 分块译码 | 中(多级译码器) | 低(只有选中块翻转) | 深度大时推荐 |
个人经验:如果功耗是首要约束,优先用格雷码指针+分块译码。虽然面积会大10%~15%,但动态功耗能降30%以上。我在一个低功耗IoT芯片项目中就是这么做的,效果很明显。
好了,读写控制逻辑就讲到这里。记住三个核心点:使能信号要跟状态标志做与、地址译码要考虑时序、指针递增要处理好回绕。下一节我们聊聊存储体的选择——用寄存器还是SRAM,这里面的门道也不少。
可可精品资料库