导读:
异步FIFO包含"读"和"写“两个部分,写操作和读操作在不同的时钟域中执行,这意味着Write_Clk和Read_Clk的频率和相位可以完全独立。异步FIFO的原理很简单,写操作是在写使能有效时,写地址指针(Write_Pointer)逐渐递增,将数据写入存储器的相应位置。读操作是在读使能信号有效时,读地址指(Read_Pointer)逐渐递增,从存储器的相应位置读取数据。
但异步FIFO有一个难点就是—满和空的产生。写操作,我们得判断FIFO是不是满了,满了就不能继续往里面写,不然就会覆盖还没取走的数据。对于读操作,我们得判断FIFO是不是空了,空了就不能接着取,不然旧的数据会被取多次。
触发异步FIFO的满和空,是拿读和写的Pointer做比较得到的。问题在于:写操作下的Write_Pointer和读操作下的Read_Pointer属于两个不同的时钟域信号。两个不同的时钟域信号是不能直接做运算的,需要同步到同一个时钟域之后才行,因为有这个CDC同步器的开销,导致FIFO出现 真 满空和 假 满空。
接下来我们一起来看看标准异步FIFO的”真“满空和“假“满空如何产生,以及采用格雷码异步FIFO跟标准FIFO有什么区别。(这里所谓标准异步FIFO是指不使用特殊编码方式(如格雷码)的异步FIFO,即使用常规的二进制地址指针。)
标准异步FIFO
标准异步FIFO结构如上图,假设FIFO的深度为8,addr(地址范围)为0 ~ 7。那么设计读写地址指针都是4bit,即w_ptr[3:0],r_ptr[3:0],其中最高bit是扩展位。ptr取值范围是0 ~15,要比FIFO地址(addr)多一倍。为什么这么设计呢?因为满和空本质上是读和写指向了FIFO的同一个存储单元,但是“空”是读指针追上了写指针,“满”是写指针超过了读指针整整一圈。其中最高位就是用来确定是 “谁追上了谁”
如果两个ptr低位全等,最高位不等,就是“满”;
如果两个Ptr低位全等,最高位相等,就是“空”。
如图所示,将w_ptr通过CDC同步之后,送到读时钟域,得到w_ptr_syn,然后再将它和r_ptr作比较,就可以得到“空”信号,代码如下:
assign empty = (w_ptr_syn[3:0]== r_ptr[3:0]);
同理,将r_ptr通过CDC同步之后,送到写时钟域,得到r_ptr_syn,然后将它和w_ptr作比较,就可以得到满信号,代码如下:
assign full = (w_ptr[3] != r_ptr_syn[3]) && (w_ptr[2:0] == r_ptr_syn[2:0]);
大家觉得上面的代码得到的满和空是“ 真”满空吗?
答案是否定的。因为CDC同步器本身也需要开销,一般简单的两级同步器需要目标时钟域两个时钟周期。当我们判断满信号的时候,我们是在 写时钟域 ,用w_ptr和同步过来的r_ptr_syn做比较。r_ptr_syn要比真正的r_ptr要滞后,导致判满的逻辑并不完全准确。当FIFO接近满的时候,full信号就会为1,从而阻止对FIFO继续写入(脑补:“满”意味着写比读块,写指针马上赶上读指针一圈,此刻还与滞后同步过来的读指针比较,是不是快满还没满时就满足full条件了?)。同理,当FIFO接近空,但是实际可能还没空的时候,empty信号就会为1。
这种假满空并不会导致FIFO的行为出错,只会导致FIFO的利用率并非百分百,相当于FIFO的深度少了那么一两层。
那么FIFO能得到真满空吗?
答案肯定是可以的。如果我们在写时钟域判断“空”信号,在读时钟域判断“满”信号呢?(在写时钟域,通过滞后的r_ptr_syn都得到了“空”信号,那说明实际的r_ptr必然真的赶上了w_ptr,此刻FIFO绝对空了。在读时钟域,通过滞后的w_ptr_syn都得到了“满”信号,那说明实际的w_ptr必然真的超过了r_ptr一圈,此刻FIFO绝对满了)。
assign empty_real = (w_ptr[3:0] == r_ptr_syn[3:0]);
assign full_real = (r_ptr[3] != w_ptr_syn[3]) && (r_ptr[2:0] == w_ptr_syn[2:0]);
这个“真”满空信号,用到的时候并不多。但是理解“真”满空和“假”满空,是理解异步FIFO的基础。
采用格雷码的异步FIFO
采用格雷码的异步FIFO判断“满”和“空”的原理没变,跟标准FIFO是一样的,即在写时钟域判断满条件,在读时钟域判断空条件:
如果两个ptr低位全等,最高位不等,就是“满”;
如果两个Ptr低位全等,最高位相等,就是“空”。
只不过,地址指针采用的是格雷码,如写地址指针:
assign nxt_wptr = (!full && wr_en) ? (wptr +1'b1):wptr;
assign nxt_wptr_gray = (nxt_wptr > >1)^nxt_wptr;
判断空条件:则在读时钟域,读地址指针格雷码 与 两级同步过来后的写地址指针格雷码(nxt_wptr_gray)进行比较:
always@(posedge rclk or negedge rst_n)
begin
if(!rst_n) begin
wptr_sp1<=6'b0;
wptr_sp2<=6'b0;
end
else begin
wptr_sp1<=nxt_wptr_gray;
wptr_sp2<=wptr_sp1;
end
end
assign empty=(rptr_gray==wptr_sp2);
同理,判断满条件:
assign full=(wptr_gray=={~rptr_sp2[3],rptr_sp2[2:0]});
理解到这儿,本质上两种FIFO似乎没什么区别,那为什么要用格雷码编码呢? 异步FIFO采用格雷码的主要原因是为了减少在异步时钟域中地址指针变化时可能出现的不稳定性,从而增强异步FIFO的可靠性和稳定性 。即使在亚稳态进行读写指针抽样也能进行正确的空满状态判断”。
下面两张图是采用格雷码的异步FIFO触发满和空条件的截图,从图示可看出,4根标红的格雷码地址指针 每一周期前后只有1位发生跳变,如wptr_gray:0010>0110>0111>0101>0100>1100>1101>1111>1110,这就是格雷码的特性,保证了相邻的两个值只有一个位元发生变化,因此在变化时不会出现多个位同时变化,减少了不稳定状态的可能性。
满条件
空条件
为什么2 进制指针做空满判断存在不稳定的可能呢?事实上 2 进制读指针在增减时,经常发生多位突变,比如 6 位地址 111111 会在下一时刻变成 000000 ,在实际电路中,这个变化过程要持续很长一段时间,会由 111111 经历 6 个状态转移到达 000000 。比如 111111-> 101111 -> 100111 ->100110 -> 100100 -> 000100-> 000000 。由于写时钟与读时钟不同步,异步的写时钟很可能会在状态不稳定的中间某个状态抽样,这样就会得到错误的读指针,进而做出错误的状态判断,导致系统异常。
当采用格雷码只有一个比特发生改变时,即使在中间状态抽样,其结果也不外乎两种:递增前原指针和递增后新指针。如果抽样到递增后的指针,预期结果跟设计一致。如果抽样到递增前的原指针,最坏的情况就是把“不满”判断成了“满”,但是这并不会对逻辑产生影响,只是带来了写操作的延迟。
总结(两种fifo的主要区别)
因此,标准范式的FIFO和采用格雷码的FIFO都存在一定的可能出现假满空。两者有一些区别,主要涉及到地址指针的表示和更新方式。以下是这两种FIFO的主要区别:
选择使用哪种FIFO设计取决于具体的应用需求和时序约束。格雷码的FIFO在一些特定情况下可能提供一些优势,但也需要权衡设计的复杂性。
全部0条评论
快来发表一下你的评论吧 !