异步FIFO的”假”满空和”真”满空

今天讲一个非常基本的知识,关于异步FIFO的,同时也是不常被人注意的。

我们都知道异步FIFO有两个部分,写部分和读部分,分别在两个时钟域里面执行。比如说写操作在Write_Clk时钟下进行,读操作在Read_Clk时钟下进行。Write_Clk和Read_Clk这两个时钟的频率和相位没有任何关系。

异步FIFO别的都很简单 。对于写操作,无非是在写信号下产生累加的写地址Write_Pointer,然后用这个地址往存储器写数。在读信号下产生累加的读地址Read_Pointer,然后用这个地址从存储器取数据。

唯一的难点是:满和空的产生。对于写操作,得判断FIFO是不是满了,满了就不能继续往里面写,不然就会覆盖还没取走的数据。对于读操作,得判断FIFO是不是空了,空了就不能接着取,不然旧的数据会被取多次。

满和空的产生,是拿读和写的Pointer做比较得到的。问题在于:写操作下的Write_Pointer和读操作下的Read_Pointer属于不同的时钟域的信号。两个不同的时钟域的信号是不能直接做运算的,需要同步到同一个时钟域之后才行。

异步信号不能直接运算的原因,是因为会产生很多中间过渡毛刺,这些毛刺被下一级采样之后的输出是不确定的。比如说,下图的一根信号,虽然经过一些波动最终稳定到了高电平,但是如果你的采样点在时刻2,那么就会错误的得到一个低电平。

 

当然,也有些变态的异步FIFO,会拿格雷码的Read_Pointer和Write_Pointer在异步时钟下直接进行比较,得到的Full和Epmty信号再通过”去亚稳态”得到稳定的输出值。这种方法理论上也是可行的,但是我们不讲。

1,标准范式的异步FIFO

我们讲最通用的,标准范式的异步FIFO。

标准范式的异步FIFO结构如上图,假设FIFO的深度为16,地址范围为0~15。那么读写地址都是5bit,即WR_Pointer[4:0],RD_Pointer[4:0],其中最高bit是扩展位。Pointer取值范围是0~31,要比FIFO地址多一倍。这么做的原因在于:满和空本质上是读和写指向了FIFO的同一个存储单元,但是“空”是读指针追上了写指针,“满”是写指针超过了读指针整整一圈。怎么区别是谁追上了谁?最高位就是干的这个。

如果两个Pointer低位全等,最高位不等,那就是“满”;

如果两个Pointer低位全等,最高位也相等,那就是“空”。

 

2,假满空的产生

将WR_Pointer通过CDC同步之后,送到读时钟域,得到WR_Pointer_syn(上图的蓝线),然后再将其和RD_Pointer作比较,就可以得到“空”信号:

assign Empty = (WR_Pointer_syn[4:0]== RD_Pointer[4:0]);

将RD_Pointer通过CDC同步之后,送到写时钟域,得到RD_Pointer_syn(上图紫线),然后将其和WR_Pointer作比较,就可以得到满信号:

assign Full = (WR_Ponter[4] != RD_Pointer_syn[4]) && (WR_Ponter[3:0] == RD_Pointer_syn[3:0]);

接下来就是重点了。

上面的代码得到的满和空其实是假满空,并非真正的满空。原因在于,CDC同步本身也是需要开销的,一般简单的两级同步器需要目标时钟域两个T。当我们判断满信号的时候,我们用的是WR_Pointer和同步过来的RD_Pointer_syn做的比较。RD_Pointer_syn要比真正的RD_Pointer要滞后,导致判满的逻辑并不完全准确。当FIFO接近满的时候,Full信号就会为1,从而阻止对FIFO继续写入。

同理,Empty信号也不准确。当FIFO接近空,但是实际可能还没空的时候,Empty信号就会为1,从而阻止对FIFO数据的读取。

这种假满空并不会导致FIFO的行为出错,只会导致FIFO的效率略微有下降,相当于FIFO的层数少了那么一两层。本质上对FIFO起到了过保护的作用。

3,真满空的产生

那么怎么得到真满空?也不难。

之前我们是在写时钟域判断“满”信号,在读时钟域判断“空”信号,得到了“假”满空。

如果我们在写时钟域判断“空”信号,在读时钟域判断“满”信号,得到的就是“真”满空!

代码如下:

assign Empty_Real = (WR_Pointer[4:0] == RD_Pointer_syn[4:0]);

assign Full_Real = (RD_Pointer[4] != WR_Pointer_syn[4]) && (RD_Pointer[3:0] == WR_Pointer_syn[3:0]);

原理是这样的:

假如说,在写时钟域,通过滞后的RD_Pointer_syn都得到了“空”信号,那说明实际的RD_Pointer必然真的赶上了WR_Pointer,所以FIFO此刻绝对空了。

在读时钟域,通过滞后的WR_Pointer_syn都得到了“满”信号,那说明实际的WR_Ponter必然真的超过了RD_Pointer一圈,所以FIFO此刻绝对满了。

这个“真”满空信号,用到的时候并不多。但是理解“真”满空和“假”满空,是理解异步FIFO的基础,也是灵活运用异步FIFO的基础。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关