今天讲一个非常基本的知识,关于异步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的基础。
欢迎大家关注我的微信公众号:半导学社。