3. FIFO读写时序:同步FIFO的读写时序图、异步FIFO的读写时序图、格雷码在异步FIFO中的应用
大家好,我是你们的芯片验证讲师。今天咱们聊聊FIFO的读写时序。说实话,FIFO这东西在芯片设计里太常见了,但很多人写验证激励时,时序搞错了,仿真跑出来数据全乱套。我当年刚入行时也吃过这个亏,所以今天把这部分掰开揉碎了讲清楚。
3.1 同步FIFO的读写时序
同步FIFO,说白了就是读写时钟是同一个。你想想看,这样时序关系就简单多了。但简单归简单,该注意的点一个都不能少。
先看一个典型的同步FIFO写操作时序:
时钟周期: T0 T1 T2 T3 T4 T5
clk ▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁
wren ___▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
wdata XXXX D0 D1 D2 D3 XXXX
full ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
嗯,这里要注意:写使能wren必须在时钟上升沿之前稳定下来。我习惯在时钟上升沿前半个周期就把数据准备好,这样setup time才够用。
读操作时序类似:
时钟周期: T0 T1 T2 T3 T4 T5
clk ▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁
rden ___▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
rdata XXXX D0 D1 D2 D3 XXXX
empty ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
读使能rden拉高后,下一个时钟沿数据就出来了。这里有个坑:如果FIFO空了你还去读,读出来的数据是未知的。我在项目中遇到过这种情况,仿真时没注意,结果后仿发现数据全是X态,查了半天才发现是空标志没判断好。
同步FIFO读写关键点:
- 写操作:wren拉高 → 下一个时钟沿数据写入
- 读操作:rden拉高 → 下一个时钟沿数据读出
- 满标志:写指针追上读指针时拉高
- 空标志:读指针追上写指针时拉高
3.2 异步FIFO的读写时序
异步FIFO就复杂多了。读写时钟不同,跨时钟域处理是核心。我刚开始做异步FIFO验证时,总觉得不就是两个时钟域嘛,各写各的各读各的就行。结果呢?仿真跑出来全是亚稳态问题。
异步FIFO的读写时序图:
写时钟域:
wclk ▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁
wren ___▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
wdata XXXX D0 D1 D2 D3 XXXX
读时钟域:
rclk ▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁▔▁
rden ___▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
rdata XXXX D0 D1 D2 D3 XXXX
注意看,写时钟和读时钟的频率可能不同,相位关系也不固定。这就带来了一个核心问题:如何判断FIFO是满还是空?
你想想看,写指针在写时钟域更新,读指针在读时钟域更新。要比较它们,必须把指针同步到另一个时钟域。但同步过程有延迟,这就可能导致误判。
我曾经踩过的坑:
有一次做异步FIFO验证,写时钟是100MHz,读时钟是50MHz。我直接用二进制指针做跨时钟域同步,结果仿真时满标志经常提前拉高,导致写操作被阻塞。后来才发现,二进制指针多位同时变化时,同步到另一个时钟域会产生毛刺。这就是为什么异步FIFO要用格雷码。
3.3 格雷码在异步FIFO中的应用
格雷码,说白了就是相邻两个数之间只有一位不同。这个特性在跨时钟域同步时太有用了。我给大家画个图看看:
二进制: 000 → 001 → 010 → 011 → 100 → 101 → 110 → 111
格雷码: 000 → 001 → 011 → 010 → 110 → 111 → 101 → 100
看到了吗?二进制从011到100时,三位同时变化。如果这时候同步到另一个时钟域,可能采到000、001、010等任意值。而格雷码每次只变一位,同步时最多错一位,不会出现完全错误的值。
格雷码在异步FIFO中的具体应用:
- 写指针转格雷码:在写时钟域,把二进制写指针转成格雷码
- 同步到读时钟域:用两级触发器同步格雷码写指针
- 读指针转格雷码:在读时钟域,把二进制读指针转成格雷码
- 同步到写时钟域:用两级触发器同步格雷码读指针
- 比较判断满空:在各自时钟域比较同步后的指针
我的个人习惯:
写验证激励时,我会在testbench里同时监控二进制指针和格雷码指针。如果发现格雷码同步后的值和预期不一致,多半是跨时钟域同步逻辑有问题。这个方法帮我抓出过好几个bug。
格雷码转二进制和二进制转格雷码的代码实现:
// 二进制转格雷码
assign gray = (binary >> 1) ^ binary;
// 格雷码转二进制
assign binary[WIDTH-1] = gray[WIDTH-1];
generate
for (i = WIDTH-2; i >= 0; i = i-1) begin
assign binary[i] = gray[i] ^ binary[i+1];
end
endgenerate
嗯,这里要注意:格雷码转二进制是串行的,综合出来面积会大一些。但为了跨时钟域安全,这点代价值得。
最后,我给大家总结一下异步FIFO的满空判断逻辑:
| 条件 | 判断方法 |
|---|---|
| 空标志 | 同步后的写指针 == 读指针(格雷码比较) |
| 满标志 | 同步后的读指针 == 写指针取反最高位(格雷码比较) |
为什么满标志要取反最高位?因为格雷码的满状态和空状态在最高位不同。举个例子,深度为8的FIFO,空时指针都是000,满时写指针是100,读指针是000。如果不取反最高位,满和空就分不清了。
好了,FIFO读写时序这块就讲到这里。记住,同步FIFO看setup/hold,异步FIFO看跨时钟域同步。格雷码是异步FIFO的灵魂,没有它,跨时钟域就是一场灾难。
本章知识体系:
可可精品资料库