异步FIFO学习

一、概述

在大规模ASIC或FPGA设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使相异时钟域数据传输的时序要求变得宽松,也提高了它们之间的传输效率。

异步FIFO是指一种FIFO设计,其中将数据从一个时钟域写入FIFO缓冲区,并从另一个时钟域的同一FIFO缓冲区中读取数据,这两个时钟域彼此异步。使用异步FIFO可以将数据安全地从一个时钟域传递到另一个时钟域。

进行异步FIFO设计的方法有很多,在设计的过程中一些细节没有注意可能会使设计存在问题和缺陷,可能存在问题但仿真时大部分时间也能够正常工作导致这些问题难以检测和调试。所以本文讨论了一种FIFO设计风格和进行异步FIFO设计时必须考虑的重要细节。

进行FIFO设计时的困难与生成FIFO指针以及找到确定FIFO上的满状态和空状态的可靠方法有关

二、异步FIFO的设计基础

2.1 FIFO指针

同步FIFO空满判断的一种实现:读写计数
对于同步FIFO设计(在同一时钟域中进行FIFO缓冲区的读写操作的FIFO),一种实现方式是对FIFO缓冲区的写入和读取次数进行计数(FIFO写入但不读取时), 递减(在FIFO读取但不写入时)或保持(无写入和读取,或同时进行读写操作)FIFO缓冲区的当前填充值。 当FIFO计数器达到预定的全值时FIFO已满,当FIFO计数器为零时FIFO为空。但对于异步FIFO设计,不能使用增减FIFO计数器,因为将需要两个不同的异步时钟控制计数器。

异步FIFO空满判断的实现:读写指针
为了确定异步FIFO设计的满状态和空状态,必须比较写入和读取指针。

在FIFO写操作中,写指针所指向的存储位置被写入,然后写指针递增以指向下一个要写的位置。同样,读取指针始终指向要读取的当前FIFO字。 在复位时,两个指针都复位为零,FIFO为空,而读指针指向无效数据(因为FIFO为空并且声明了空标志)。

将第一个数据字写入FIFO后,写指针就会递增,清空标志,并且仍在寻址第一个FIFO存储字内容的读指针会立即将第一个有效字驱动到FIFO数据输出端口,由接收器读取。读取指针始终指向要读取的下一个FIFO字,这意味着,接收器逻辑不必使用两个时钟周期来读取数据字。(若接收器在读取FIFO数据字之前首先必须增加读取指针,则接收器将计时一次以从FIFO输出数据字,并再次计时以将数据字捕获到接收器中。 这将降低效率)

当读写指针相等时,FIFO为空。 当在复位操作期间两个指针都复位为零时,或者当读取指针从FIFO读取了最后一个字时,读指针追上写指针时,就会发生这种情况。又或者当写指针已经追上了读指针。因此单纯指针相等,不能确定FIFO是空的还是满的用来区分是FIFO空满状态的一种设计技术是为每个指针添加一个额外的位。 当写指针增加到最终FIFO地址之后,写指针将使未使用的MSB递增,同时将其余位设置回零,如图1所示。 使用读取指针完成相同操作。 如果两个指针的MSB不同,则意味着写指针比读指针多绕了一次。 如果两个指针的MSB相同,则意味着两个指针的绕行次数相同。

在这里插入图片描述
使用n位指针(其中n-1位是访问整个FIFO存储缓冲区所需的地址位数),当两个指针(包括MSB)相等时,FIFO为空。 当两个指针(MSB除外)相等时,FIFO已满。

2.2 格雷码的使用

2.2.1 二进制码存在的问题

将二进制计数值从一个时钟域同步到另一个时钟域将存在亚稳态问题,因为一个n-bit计数器的每个位都可以同时更改(例如,二进制数7-> 8的值为0111-> 1000,所有位都更改了)。

解决问题的一种方法:握手信号

解决该问题的一种方法是将周期二进制计数值采样并保存在一个保持寄存器中,然后将异步就绪信号传递到新的时钟域。 识别就绪信号后,接收时钟域将同步的确认信号发送回发送时钟域。 在从接收时钟域接收到确认信号之前,采样指针不得更改。

使用此技术,可以将具有多个更改位的计数值安全地传输到新的时钟域。 收到确认信号后,发送时钟域有权清除就绪信号并重新采样二进制计数值。使用此技术,二进制计数器值会定期进行采样,并且并非所有二进制计数器值都可以传递给新的时钟域。

同步采样的读写指针此时并不一定是实际的读写地址(同步采样指针滞后于实际指针),所以不用担心FIFO上溢或者下溢的情况:
不需要需要担心二进制计数器可能继续增加采样计数器值使得FIFO出现上溢或下溢的情况。当写指针赶上同步采样的读指针时,FIFO已满。 同步采样的读取指针可能无法反映实际读取指针的当前值,但是写入指针将不会尝试计数超过同步读取指针值的值。 不会发生溢出。当读指针赶上同步采样的写指针时,FIFO空。 同步采样的写指针可能无法反映实际写指针的当前值,但读指针将不会尝试计数超过同步写指针值的值。 不会发生下溢。

解决问题的另一种方法:格雷码
见 2.2.2

2.2.2 格雷码计数器

格雷码的特点
1、任何两个相邻编码的差别仅有1位(从一个格雷计数到下一个格雷计数只能改变一位)。
2、格雷码计数器的计数序列为2^n。可以使格雷码计数器计数偶数个序列,但是与这些序列之间的转换通常不像标准格雷码那样简单。

二进制码转格雷码:移位异或
gray[n-1:0] = (bin[n-1:0] >>1) ^ bin[n-1:0]

格雷码转二进制码:
bin[i] = ^(gray[n-1:0] >>i)
eg:4位二进制转格雷码
bin[3] = gray[3];
bin[2] = gray[3] ^ gray[2];
bin[1] = gray[3] ^ gray[2] ^ gray[1];
bin[0] = gray[3] ^ gray[2] ^ gray[1] ^ gray[1] ^ gray[0];

双n位格雷码计数器
为了之后的需要,这里介绍“双n位格雷码计数器”(一个通用的n位格雷码计数器修改第二个MSB以形成一个具有共享LSB的(n-1)位格雷码计数器)

为了理解n-bit格雷码转换到(n-1) bit格雷码的问题,以4位、3位格雷码计数器为例。4位格雷码后半部分的第二个MSB将在4位序列的三个LSB中产生所需的3位格雷码序列。 唯一的一个问题是带有额外MSB的3位格雷码不再是真正的格雷码,因为当序列从7(灰色0100)变为8(〜灰色1000),然后又从15(〜灰色1100)变为0时, (灰色0000),两位在变化,而不是一位。 真正的格雷码在计数之间只改变一位。

实现1:
图3是一种双n位格雷码计数器的框图。格雷码计数器假定寄存器位的输出本身就是格雷码值(ptr,wptr或rptr)。 然后将格雷码的输出传递到格雷二进制转换器(bin),再将其传递给条件二进制值增量器以生成下一个二进制计数值(bnext),然后将其传递给格雷码转换器生成next-Gray-count-value(gnext),该值传递到寄存器输入。

图3框图的上半部分显示了所描述的逻辑流程,而下半部分则显示了与下一节所述的第二个格雷码计数器相关的逻辑。
在这里插入图片描述
该双n位格雷码计数器既生成n位格雷码序列又生成(n-1)位格雷码序列。(n-1)位格雷码通过对n位格雷码的两个MSB进行异或运算以生成用于(n-1)位格雷码的MSB,它与n位格雷码计数器的(n-2)个LSB组合在一起,形成(n-1)位格雷码计数器。

实现2:
FIFO实现使用这种格雷码计数器,该格雷码计数器实际上使用了两组寄存器,从而无需将格雷指针值转换为二进制值。 第二组寄存器(二进制寄存器)还可用于直接寻址FIFO存储器,而无需将内存地址转换为格雷码。 仍需要n位格雷码指针将指针同步到相反的时钟域,但是n-1位二进制指针可用于直接寻址内存。 如果需要,二进制指针还使运行计算更容易以生成“"almost-full”和“almost-empty”的标志位。
在这里插入图片描述
格雷码指针和二进制指针的比较
较格雷码指针,二进制指针的一些优点:
•将多bit 采样到保持寄存器中并使用同步握手控制信号将多位值传递到新的时钟域中的技术可用于跨时钟域传递任何任意多位值。该技术可用于传递FIFO指针或任何多bit值。
•每个同步格雷码指针需要2n个触发器(每个指针位2个)。采样的多位寄存器需要2n + 4个触发器(每个时钟域中每个保持寄存器位1个,2个触发器用于同步就绪位,2个触发器用于同步确认位)。任何一种指针样式经历亚稳态的机会都没有明显的差异。
•采样的多位二进制寄存器允许任意更改指针。格雷码指针只能递增和递减。
•采样的多位寄存器技术允许任意FIFO深度。而格雷码指针需要2^n FIFO深度。如果设计需要至少132个字的FIFO深度,则使用标准格雷码指针将采用256个字的FIFO深度。由于大多数实例化的双端口RAM块的深度都是2的幂,因此这可能不是问题。
•使用二进制指针可以轻松地使用之间的简单二进制算法来计算“almost full”和“almost empty”状态位,

较格雷码指针使用二进制指针的一个缺点是:
•采样并持有二进制FIFO指针,然后在时钟边界上进行握手,可能会使新采样的捕获与接收时钟域至少相隔两个时钟沿。和来自发送时钟域的另外两个时钟边沿。此等待时间通常不是问题,但通常会在断言为full和empty时增加更多悲观,并且可能需要额外的FIFO深度来补偿所增加的悲观情绪。 由于大多数FIFO通常都是用过大的深度指定的,因此不太可能需要额外的寄存器或更大的双端口FIFO缓冲区大小。

在选择实现FIFO设计的方法时,上述比较值得考虑。

2.3 空满条件的判断

在读时钟域中生成空标志,以确保在FIFO缓冲区为空时立即检测到空标志;在写时钟域中将生成满标志,以确保在FIFO缓冲区已满时立即检测到满标志。

2.3.1 空信号生成
如图1所示,当读指针和同步写指针相等时,FIFO为空。

使用比寻址FIFO所需的指针大一位的指针时, 当两个指针的额外位(指针的MSB)相等,则指针的回绕次数相同,加上读指针的其余部分等于同步的写指针,则FIFO为空。

写指针wptr 必须通过sync_w2r模块中的一对同步器寄存器将格雷码写指针同步到读时钟域进行比较。 因为使用格雷码指针一次只有一位改变,所以在时钟域之间同步多位转换是没有问题的。为了有效地记录剩余输出,实际上将同步的写指针与下一个格雷码读指针进行比较。
在这里插入图片描述

2.3.2 满信号生成
在这里插入图片描述
以8位深度的FIFO为例,有效地址为3bit,这里扩展一位来判断空满条件。当FIFO在前7个位置写入数据,然后读相同的七个字来清空FIFO,则两个指针将相等并指向地址Gray-7(FIFO为空)。 在下一个写操作中,写指针将使4位格雷码指针递增(请记住,只有3个LSB用于地址存储器),这使4位指针上的MSB有所不同,但其余写指针位将匹配读指针位,因此将声明FIFO满标志。

出现错误:不仅FIFO未满,而且3个LSB均未更改,这意味着寻址的存储器位置将覆盖最后写入的FIFO存储器位置。
(1、满信号错误;2、100地址位数据覆盖写入)

正确的满信号判断需要满足:
读指针与写指针的高2位不相等,其余位均相等;

在这里插入图片描述

三、异步FIFO设计实现

在这里插入图片描述
根据以上所诉内容,设计可以划分为以下六个Verilog模块:

•fifo1.v --顶层模块以实例化设计中使用的所有其他FIFO模块。如果将此FIFO用作较大的ASIC或FPGA设计的一部分,则可能会舍弃此顶层模块,以允许将其他FIFO模块分组到各自的时钟域中,以改善综合和静态时序分析。
•fifomem.v–这是FIFO存储器缓冲区,可通过写入和读取时钟域进行访问。该缓冲区很可能是实例化的同步双端口RAM。
•sync_r2w.v–同步器模块,用于将读指针同步到写入时钟域。wptr_full模块将使用同步的读取指针来生成FIFO已满条件。
•sync_w2r.v–同步器模块,用于将写指针同步到读时钟域中。rptr_empty模块将使用同步的写指针来生成FIFO空条件。该模块中没有其他逻辑。
•rptr_empty.v–该模块与read-clock domain完全同步,并且包含FIFO读指针和空标志逻辑。
•wptr_full.v–该模块与write-clockdomain完全同步,并且包含FIFO写指针和满标志逻辑

3.1 fifo1

顶层FIFO模块将所有子模块实例化(建议使用端口命名的方式实例化)。

module fifo1 #(parameter DSIZE = 8,     parameter ASIZE = 4)  
(	output [DSIZE-1:0] rdata,   
	output             wfull,   
	output             rempty,   
	input  [DSIZE-1:0] wdata,   
	input              winc, wclk, wrst_n,   
	input              rinc, rclk, rrst_n
	);  
	
wire   [ASIZE-1:0] waddr, raddr;  
wire   [ASIZE:0]   wptr, rptr, wq2_rptr, rq2_wptr;  

sync_r2w      sync_r2w  (
							.wq2_rptr(wq2_rptr), 
							.rptr(rptr),      
							.wclk(wclk), 
							.wrst_n(wrst_n)); 
							 
sync_w2r      sync_w2r  (	.rq2_wptr(rq2_wptr), 
							.wptr(wptr), 
							.rclk(rclk), 
							.rrst_n(rrst_n));  

fifomem #(DSIZE, ASIZE) fifomem (
							.rdata(rdata), 
							.wdata(wdata),  
							.waddr(waddr), 
							.raddr(raddr), 
							.wclken(winc), 
							.wfull(wfull),  
							.wclk(wclk));  
							
rptr_empty #(ASIZE)     rptr_empty   (
							.rempty(rempty), 
							.raddr(raddr),
							.rptr(rptr),
							.rq2_wptr(rq2_wptr),  
							.rinc(rinc), 
							.rclk(rclk),
							.rrst_n(rrst_n)); 
							 
wptr_full  #(ASIZE)     wptr_full  (
							.wfull(wfull), 
							.waddr(waddr), 
							.wptr(wptr), 
							.wq2_rptr(wq2_rptr),      
							.winc(winc), 
							.wclk(wclk),  
							.wrst_n(wrst_n));
endmodule

3.2 fifomem

module fifomem #(parameter  DATASIZE = 8, // Memory data word width		
				parameter  ADDRSIZE = 4) // Number of mem address bits  
(	output [DATASIZE-1:0] rdata,   
	input  [DATASIZE-1:0] wdata,   
	input  [ADDRSIZE-1:0] waddr, raddr,   
	input                 wclken, wfull, wclk);  
`ifdef VENDORRAM    // instantiation of a vendor's dual-port RAM    
	vendor_ram mem (	.dout(rdata), 
						.din(wdata),                    
						.waddr(waddr), 
						.raddr(raddr), 
						.wclken(wclken), 
						.wclken_n(wfull), 
						.clk(wclk));  
`else    // RTL Verilog memory model    
	localparam DEPTH = 1<<ADDRSIZE;    
	reg [DATASIZE-1:0] mem [0:DEPTH-1];   
	 
	assign rdata = mem[raddr];    
	always @(posedge wclk)      
		if (wclken && !wfull) mem[waddr] <= wdata;  
`endif

endmodule

3.3 sync_r2w

同步器模块,通过使用将FIFO写入时钟作为时钟源的一对寄存器,将n位指针从读取时钟域传递到写入时钟域。

module sync_r2w #(parameter ADDRSIZE = 4)  
(	output reg [ADDRSIZE:0] wq2_rptr,   
	input      [ADDRSIZE:0] rptr,   
	input                   wclk, wrst_n);  

reg [ADDRSIZE:0] wq1_rptr;  

always @(posedge wclk or negedge wrst_n)    
if (!wrst_n) 	{wq2_rptr,wq1_rptr} <= 0;    
else         	{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};

endmodule

3.4 sync_w2r

同步器模块,通过使用将FIFO写入时钟作为时钟源的一对寄存器,将n位指针从写时钟域传递到读时钟域。

module sync_w2r #(parameter ADDRSIZE = 4)  
(	output reg [ADDRSIZE:0] rq2_wptr,   
	input      [ADDRSIZE:0] wptr,   
	input                   rclk, rrst_n);  

reg [ADDRSIZE:0] rq1_wptr;  

always @(posedge rclk or negedge rrst_n)    
if (!rrst_n) 	{rq2_wptr,rq1_wptr} <= 0;    
else        	 {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};

endmodule

3.5 rptr_empty

该模块描述了在读取时钟域内生成的所有FIFO逻辑(同步器除外)。读取指针是双n位格雷码计数器输出。 n位格雷码读指针(rptr)通过sync_r2w模块传递到写时钟域。 (n-1)位二进制读指针(raddr)用于寻址FIFO缓冲区。当下一个rptr值等于同步的wptr值时,FIFO empty信号输出会在下一个rclk上升沿有效。

module rptr_empty #(parameter ADDRSIZE = 4)  
(	output reg                rempty,   
	output     [ADDRSIZE-1:0] raddr,   
	output reg [ADDRSIZE  :0] rptr,   
	input      [ADDRSIZE  :0] rq2_wptr,   
	input                     rinc, rclk, rrst_n);  

reg  [ADDRSIZE:0] rbin;  
wire [ADDRSIZE:0] rgraynext, rbinnext;  
//-------------------  
// GRAYSTYLE2 pointer  
//-------------------  
always @(posedge rclk or negedge rrst_n)    
if (!rrst_n) {rbin, rptr} <= 0;    
else         {rbin, rptr} <= {rbinnext, rgraynext};  

// Memory read-address pointer (okay to use binary to address memory)  
assign raddr     = rbin[ADDRSIZE-1:0];  
assign rbinnext  = rbin + (rinc & ~rempty);  
assign rgraynext = (rbinnext>>1) ^ rbinnext;  

//---------------------------------------------------------------  
// FIFO empty when the next rptr == synchronized wptr or on reset  //---------------------------------------------------------------  
assign rempty_val = (rgraynext == rq2_wptr);  

always @(posedge rclk or negedge rrst_n)    
if (!rrst_n) rempty <= 1'b1;    
else         rempty <= rempty_val;

endmodule

3.6 wptr_full

该模块描述了在写时钟域内生成的所有FIFO逻辑(同步器除外)。写指针是双n位格雷码计数器输出。 n位格雷码写指针(wptr)通过sync_w2r模块传递到读取时钟域。 (n-1)位二进制写指针(waddr)用于寻址FIFO缓冲区。

当下一个格雷码写指针与读时钟域同步过来的读指针相等时,在下一个clk上升沿产生full信号。

module wptr_full #(parameter ADDRSIZE = 4)  
(	output reg                wfull,   
	output     [ADDRSIZE-1:0] waddr,   
	output reg [ADDRSIZE  :0] wptr,   
	input      [ADDRSIZE  :0] wq2_rptr,   
	input                     winc, wclk, wrst_n
	);  

reg  [ADDRSIZE:0] wbin;  
wire [ADDRSIZE:0] wgraynext, wbinnext;  

// GRAYSTYLE2 pointer  
always @(posedge wclk or negedge wrst_n)    
if (!wrst_n) 	{wbin, wptr} <= 0;    
else         	{wbin, wptr} <= {wbinnext, wgraynext};  

// Memory write-address pointer (okay to use binary to address memory)  
assign waddr = wbin[ADDRSIZE-1:0];  
assign wbinnext  = wbin + (winc & ~wfull);  
assign wgraynext = (wbinnext>>1) ^ wbinnext;  

//------------------------------------------------------------------  // Simplified version of the three necessary full-tests:  
// assign wfull_val=((wgnext[ADDRSIZE]    !=wq2_rptr[ADDRSIZE]  ) &&  //                   (wgnext[ADDRSIZE-1]  !=wq2_rptr[ADDRSIZE-1]) &&  //                   (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));  //------------------------------------------------------------------  
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],                                   wq2_rptr[ADDRSIZE-2:0]});  

always @(posedge wclk or negedge wrst_n)    
if (!wrst_n) wfull  <= 1'b0;    
else         wfull  <= wfull_val;endmodule

endmodule

参考文献:
[1] C. E. Cummings and S. Design, “Simulation and Synthesis Techniques for Asynchronous FIFO Design,” pp. 1–23.

  • 3
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值