导读:
异步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中的位置。
- 采用格雷码的FIFO:地址指针通常使用格雷码来表示。格雷码是一种二进制码,相邻的两个值只有一个位元发生变化。使用格雷码可以减少地址指针在变化时的不稳定性,从而减少在时钟边沿时的不稳定状态。
- 地址指针的更新方式:
- 标准范式的FIFO:地址指针在每个时钟周期朝一个方向递增或递减,用于确定要读取或写入的位置。在写入数据时,写指针增加;在读取数据时,读指针增加。
- 采用格雷码的FIFO:格雷码地址指针的更新方式相对复杂一些。格雷码的特性使得在更新时只有一个位发生变化,这样可以减少指针变化的不稳定性。在读写操作时,格雷码地址指针的更新可能需要一些额外的逻辑。
- 时序稳定性:
- 标准范式的FIFO:由于二进制地址指针的性质,通用FIFO在时序上需要额外的同步逻辑,以确保地址指针的稳定传递。这尤其在不同时钟域的情况下需要考虑。
- 采用格雷码的FIFO:格雷码的特性减少了地址变化的不稳定性,因此在一些时序方面可能更容易处理。但格雷码的使用可能需要更多的逻辑来实现。
选择使用哪种FIFO设计取决于具体的应用需求和时序约束。格雷码的FIFO在一些特定情况下可能提供一些优势,但也需要权衡设计的复杂性。
-
fifo
+关注
关注
3文章
388浏览量
43678 -
信号
+关注
关注
11文章
2791浏览量
76763 -
时钟域
+关注
关注
0文章
52浏览量
9536 -
异步FIFO
+关注
关注
0文章
20浏览量
8359
发布评论请先 登录
相关推荐
评论