FIFO设计代码规范实战 - 第1节
1. FIFO基础概念:同步FIFO与异步FIFO的区别
做数字IC设计这些年,FIFO可以说是打交道最多的模块之一了。说白了,FIFO就是个先进先出的数据缓冲器。你写进去的数据,按顺序读出来,不会乱序。嗯,这个大家应该都懂。
但真正让我觉得有意思的,是同步FIFO和异步FIFO的区别。我刚开始做设计时,总觉得两者差不多,不就是读写控制嘛。直到有一次项目里把异步FIFO当同步用,结果时序跑不过...好,咱们来仔细说说。
1.1 同步FIFO
同步FIFO,读写时钟是同一个。这意味着什么?你想想看,所有控制信号都在同一个时钟域里,设计起来相对简单。
核心特点:
- 读写共用一个时钟
- 不需要跨时钟域处理
- 空满判断直接用计数器
- 延迟可控,时序容易收敛
我个人习惯在数据通路内部用同步FIFO。比如某个模块处理完数据,需要暂存一下再送给下一级,这时候同步FIFO就够用了。我记得有个项目是做视频处理流水线,中间插了七八个同步FIFO做乒乓缓冲,跑得很稳。
// 同步FIFO的简单空满判断
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
fifo_cnt <= 0;
else if (wr_en && !full && !(rd_en && !empty))
fifo_cnt <= fifo_cnt + 1;
else if (rd_en && !empty && !(wr_en && !full))
fifo_cnt <= fifo_cnt - 1;
end
assign full = (fifo_cnt == FIFO_DEPTH);
assign empty = (fifo_cnt == 0);
1.2 异步FIFO
异步FIFO就复杂多了。读写时钟不同,这就带来了跨时钟域的问题。我曾经在一个多时钟域的项目里,因为异步FIFO的格雷码同步没做好,导致空满标志出错,数据直接丢了。嗯,那会儿加班到凌晨三点才找到问题。
注意:异步FIFO的核心难点在于空满判断。读写指针在不同时钟域,直接比较会出问题。必须用格雷码+两级同步器来处理。
为什么用格雷码?因为格雷码相邻两个值只有一位变化。你想想看,如果直接用二进制指针,多位同时变化时同步器可能采到中间态,那就乱套了。格雷码把风险降到最低。
// 格雷码转换示例
function [ADDR_WIDTH-1:0] binary_to_gray;
input [ADDR_WIDTH-1:0] binary;
begin
binary_to_gray = binary ^ (binary >> 1);
end
endfunction
// 两级同步器
always @(posedge rd_clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr_sync1 <= 0;
wr_ptr_sync2 <= 0;
end else begin
wr_ptr_sync1 <= wr_ptr_gray;
wr_ptr_sync2 <= wr_ptr_sync1;
end
end
2. FIFO的常见应用场景
2.1 跨时钟域数据传递
这是异步FIFO最经典的应用。两个模块工作在不同频率,比如一个跑100MHz,另一个跑150MHz。数据怎么传?直接连肯定不行。
我做过一个项目,CPU总线是100MHz,DDR控制器是400MHz。中间必须用异步FIFO做桥接。CPU写数据进FIFO,DDR控制器从另一边读。这里要注意深度选择——如果写快读慢,FIFO会满;读快写慢,FIFO会空。深度不够就丢数据,深度太大浪费面积。
深度估算小技巧:异步FIFO深度 = (写时钟频率 / 读时钟频率) × 最大连续写入数据量。实际项目中我一般再加20%余量。
2.2 数据缓冲与速率匹配
同步FIFO在这场景下用得最多。比如一个模块处理数据是突发的,一会儿发一堆,一会儿不发。后面模块处理速度恒定。这时候FIFO就起到蓄水池的作用。
我记得有个图像处理的项目,前端传感器一帧数据是连续写入的,但后端算法模块需要逐行处理。中间放个同步FIFO,传感器写满一行,算法读一行,配合得刚刚好。
常见缓冲场景:
- AXI总线写数据通道的缓冲
- 视频像素数据的行缓冲
- 网络数据包的暂存
- CPU与DMA之间的数据中转
2.3 位宽转换
这个很多人会忽略。FIFO不仅能缓冲数据,还能做位宽转换。比如输入8位,输出32位。或者反过来。
怎么做?其实很简单。FIFO的读写数据位宽可以不同。写入时8位数据存一次,读出时32位数据读一次。内部地址控制会自动处理。我有个项目里,ADC输出12位数据,但DSP需要64位输入,中间就是靠FIFO做位宽转换加缓冲,一举两得。
// 位宽转换FIFO配置示例
// 写入位宽: 8位, 读出位宽: 32位
// 深度: 64 (实际存储8位数据64个, 32位数据16个)
fifo_wrapper #(
.DATA_WIDTH_WR(8),
.DATA_WIDTH_RD(32),
.FIFO_DEPTH (64)
) u_fifo (
.wr_clk (clk_a),
.rd_clk (clk_b),
.wr_en (wr_en),
.rd_en (rd_en),
.wr_data(wr_data_8bit),
.rd_data(rd_data_32bit),
.full (full),
.empty (empty)
);
3. 知识体系总览
说了这么多,咱们用一张图来总结一下FIFO的基础知识体系。这张图把同步FIFO和异步FIFO的区别、应用场景都串起来了。
我的建议:新手先从同步FIFO入手,把读写控制逻辑搞明白。等同步FIFO玩熟了,再挑战异步FIFO。异步FIFO的格雷码同步和空满判断,是面试必考题,也是实际项目最容易出bug的地方。
好了,FIFO的基础概念就聊到这儿。记住一句话:同步FIFO简单但局限,异步FIFO灵活但复杂。选型时先问自己——读写时钟是不是同一个?如果不是,老老实实用异步FIFO,别想着偷懒。
可可精品资料库