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中的具体应用:

  1. 写指针转格雷码:在写时钟域,把二进制写指针转成格雷码
  2. 同步到读时钟域:用两级触发器同步格雷码写指针
  3. 读指针转格雷码:在读时钟域,把二进制读指针转成格雷码
  4. 同步到写时钟域:用两级触发器同步格雷码读指针
  5. 比较判断满空:在各自时钟域比较同步后的指针

我的个人习惯:

写验证激励时,我会在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的灵魂,没有它,跨时钟域就是一场灾难。

本章知识体系:

FIFO读写时序 同步FIFO读写时序 写操作时序 读操作时序 满/空标志判断 异步FIFO读写时序 跨时钟域同步 格雷码应用 满/空标志判断 二进制↔格雷码转换 核心:跨时钟域安全 + 格雷码同步

可可精品资料库