异步FIFO简介及其原理
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序从顶部写入数据,从末尾读出数据。
异步FIFO 是指读写时钟不一致,读写时钟是互相独立的。
异步FIFO的组成和常见参数
异步FIFO主要由五部分组成:写控制端、读控制端、FIFO Memory和两个时钟同步端。
写控制端用于判断是否可以写入数据。
读控制端用于判断是否可以读取数据。
FIFO Memory用于存储数据。
两个时钟同步端用于将读写时钟进行同步处理。
满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
判断能否写入/读取数据关键在于:
写操作时,写使能信号有效且FIFO未满。
读操作时,读使能信号有效且FIFO未空。
FIFO设计的关键:产生可靠的FIFO读写指针 和 生成FIFO“空”/“满”状态标志。
空满判断
由于模块的设计,我们要对FIFO上游的写入端反馈写满信号,需要在写时钟域进行反馈,而需要判断我的FIFO写满了,就需要比较读写指针,来判断我的FIFO的剩余量。
因此出现了:
在写时钟域下,需要判断写指针与读指针的关系。在读时钟域下,需要判断读指针与写指针的关系。
在写时钟域下的写指针好办,就是读指针需要去从读时钟域获取;同样,读时钟域下的读指针可以直接拿到,但写指针需要从写时钟域获取。这就涉及到了跨时钟域传输。
读指针:总是指向下一个将要被写入的单元,复位时,指向第一个单元(编号为0)。
写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
FIFO空标志位:很简单,只要读写指针相等即可。即wr_ptr ==rd_ptr。
FIFO满标志:与空标志不同的是,写指针要比读指针多走一圈FIFO的深度,此时虽然读写指针指向了同一个位置,但为了与读空状态作区别,我们引入比地址位多一位的地址声明。
在指针中添加一个额外的位,当写指针增加并越过最后一个FIFO地址时,就将未用的最高位(MSB)加1。对读指针也进行同样的操作。此时,对于深度为2^n的FIFO,需要的读写指针位宽为(n+1)位。
如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。
这样,FIFO满的标志就变成了:MSB位不同,而其余位相同。
例如:对同一个位置0010,代表FIFO中第2个数,当写指针从0010→0011→0100→0101→0110→0111→1000(此时回到FIFO的底部从0开始,意味着2以上的数据满了)→1001→1010(读写指针汇合)。
此时的0010与1010就满足FIFO满的标志:首位不同,其余三位相同。据此,就可以区分出空满标志。
但此时还会出现的问题是,亚稳态的问题。
将一个二进制的数从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。
使用格雷码
亚稳态的问题在此不进行赘述,总是我们在进行FIFO读写信号传输时,是跨了时钟域的,并且此时使用二进制计数,常常会出现变化的位数太多,出现多bit的跨时钟域传输问题,简单理解。就是这样不好,传输不稳定,我们可以接受单bit的跨时钟域传输,这样出现的亚稳态问题我们容易解决。
二进制转格雷码:
从左边第二位开始,依次将每一位与左邻一位异或,作为对应位的格雷码值,最左一位符号位不变。
格雷码转二进制:
从左边第二位开始,依次将每一位与左邻一位异或后的值再进行异或,作为对应位的值,最左一位符号位不变。
传输读写指针之前,将其先由二进制转为格雷码,然后再将其发送到对方的时钟域下。
格雷码下的空满判断
对于“空”的判断依然依据二者完全相等(包括MSB);
对于“满”的判断:
(1)wptr和rptr的MSB不相等。
(2)wptr与rptr的次高位不相等。
(3)剩下的其余位完全相等。
assign full = (wr_addr_gray == { ~(rd_addr_gray_d2[addr_width-:2]) , rd_addr_gray_d2[addr_width-2:0]} ) ; // 高两位不同,其余位相同。对高两位取反,再与低位进行合并。
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
时钟同步
在同步FIFO设计中,因为读写指针在同一个时钟下,因此可以直接进行比较。
但在异步FIFO中,由于读写指针在不同的时钟下,因此需要将两个地址指针进行时钟同步操作。
在异步FIFO中,常用的同步方法是两级同步打拍延迟,同步地址指针的大致过程如上图。
//写指针同步到读时钟域
always @ (posedge rd_clk or negedge rd_rstn)begin
if(!rd_rstn) begin
wr_ptr_gr <= 0;
wr_ptr_grr <= 0;
end
else begin
wr_ptr_gr <= wr_ptr_g; //非阻塞赋值,综合为两级触发器
wr_ptr_grr <= wr_ptr_gr;
end
end
//读指针同步到写时钟域
always @ (posedge wr_clk or negedge wr_rstn) begin
if(!wr_rstn) begin
rd_ptr_gr <= 0;
rd_ptr_grr <= 0;
end
else begin
rd_ptr_gr <= rd_ptr_g;
rd_ptr_grr <= rd_ptr_gr;
end
end