异步FIFO的设计详解(格雷码计数+两级DFF同步)

文章目录

  • 一、异步FIFO介绍
    • 1.1.空满判断
    • 1.2.跨时钟域问题
    • 1.3.格雷码转换
    • 1.4.格雷码计数器
  • 二、代码code

一、异步FIFO介绍

  FIFO有同步和异步两种,同步即读写时钟相同,同步FIFO用的少,可以作为数据缓存;异步即读写时钟不相同,异步FIFO可以 解决跨时钟域的问题,在应用时需根据实际情况考虑好fifo深度即可。
  与同步FIFO相同,异步FIFO也主要由五大模块组成,不同的是,异步FIFO的读写逻辑控制还包括了格雷码转换和时钟同步部分:
    (1)、 FIFO写逻辑控制——产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;
    (2)、 FIFO读逻辑控制——产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号;
    (3)、 时钟同步逻辑——通过两级DFF分别将写时钟域的写指针同步到读时钟域,将读时钟域的读指针同步到写时钟域;
    (4)、 格雷码计数器——格雷码计数器中二进制计数器的低(n-1)位可以直接作为FIFO存储单元的地址指针;
    (3)、 FIFO存储体(如Memory,reg等)。
    
其逻辑结构如下所示:
异步FIFO的设计详解(格雷码计数+两级DFF同步)_第1张图片

1.1.空满判断

对于异步FIFO采用地址扩展一位的方式对FIFO进行读写计数,进而判断空满。  
   读空信号:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址)
       当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效。
   写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效

当最高位相同,其余位相同认为是读空
当最高位不同,其余位相同认为是写满

该方法试用的是二进制数之间的空满比较判断,详情参见同步FIFO设计方法2。

因为异步FIFO采用格雷码计数,而格雷码是镜像对称的,若只根据最高位是否相同来区分是读空还是写满是有问题的。如下如图所示:
异步FIFO的设计详解(格雷码计数+两级DFF同步)_第2张图片
因此用格雷码判断是否为读空或写满时应使用理论 2,看最高位和次高位是否相等,具体如下:
当最高位和次高位相同,其余位相同认为是读空
当最高位和次高位不同,其余位相同认为是写满

  通俗的讲:当FIFO空时即读指针赶上写指针,此时两个格雷码完全相同(包括扩展位)。当FIFO写满时候需要考虑如下3个条件

  • 写指针的格雷码与同步到写时钟域的读指针格雷码的最高位不同
  • 写指针的格雷码与同步到写时钟域的读指针格雷码的次高位不相等
  • 写指针的格雷码与同步到写时钟域的读指针格雷码的其余位都相等

1.2.跨时钟域问题

  由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?

  跨时钟域的问题:上面我们已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较。

解决方法:两级寄存器同步 + 格雷码
  (1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号
  (2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号

  如果直接用二进制编码的读写指针去完成上述的两种同步是不行的,使用格雷码更合适,为什么呢?

  因为二进制编码的指针在跳变的时候有可能是多位数据一起变化,如二进制的7–>8 即 0111 --> 1000 ,在跳变的过程中 4 位全部发生了改变,这样很容易产生毛刺,造成读写过程中数据出错。比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,你并不能确定会出现哪个值,那出错的概率非常大,而格雷码的编码特点是相邻位每次只有 1 位发生变化, 这样在进行指针同步的时候,就可以避免多个bit位同时跳变的情况。

  设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?

  异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

1.3.格雷码转换

  二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。

二进制B[n:0]转化为格雷码G[n:0]
G[n] = B[n]//保留最高位作为格雷码的最高位
G[n-1:0] = B[n-1:0]^B[n:1]//次高位格雷码为二进制码的高位与次高位相异或其余类似
异步FIFO的设计详解(格雷码计数+两级DFF同步)_第3张图片
我再换种更简单的描述
二进制数            1 0 1 1 0
二进制数右移1位,空位补0    0 1 0 1 1
异或运算            1 1 1 0 1
这样就可以实现二进制到格雷码的转换了,总结就是移位并且异或,verilog代码实现就一句:
assign wgraynext = ( wbinnext >> 1 ) ^ wbinnext;

格雷码G[n:0]转化为二进制B[n:0]
B[n] = G[n]//保留最高位作为二进制码的最高位
B[n-1:0] = G[n-1:0]^B[n:1]//次高位格雷码为二进制码的高位与次高位相异或其余类似
异步FIFO的设计详解(格雷码计数+两级DFF同步)_第4张图片

1.4.格雷码计数器

图中所示的格雷码计数器中二进制计数器的低(n-1)位可以直接作为FIFO存储单元的地址指针,将二进制数转化为格雷码传输给另外一个时钟域。
异步FIFO的设计详解(格雷码计数+两级DFF同步)_第5张图片

二、代码code

/*异步fifo 参考文献  Simulation and Synthesis Techniques for Asynchronous FIFO Design*/
//源码:https://github.com/DeamonYang/FPGA_SYNC_ASYNC_FIFO

module async_fifo(
		rst_n			,
		fifo_wr_clk	,
		fifo_wr_en	,
		r_fifo_full	,
		fifo_wr_data,
		
		fifo_rd_clk	,
		fifo_rd_en	,
		fifo_rd_data,
		r_fifo_empty	
		
//		fifo_wr_err,
//		fifo_rd_err
		
	);

		input          rst_n			;
		input          fifo_wr_en	;
		input	[15:0] fifo_wr_data;
		input          fifo_rd_en	;
		input          fifo_rd_clk;
		input          fifo_wr_clk;
		output reg     r_fifo_full	;
		output  [15:0] fifo_rd_data;
		output reg     r_fifo_empty	;
		
		
//		output reg fifo_wr_err;
//		output reg fifo_rd_err;

//中间信号internal singles		
		reg	[9:0]  rdaddress;  //RAM地址为9位地址 扩展一位用于同步
		reg	[9:0]  wraddress;  //RAM写地址
	
		wire	[9:0]	gray_rdaddress;      //格雷码读写地址
		wire	[9:0]	gray_wraddress;
		
		/*同步寄存器*/
		reg	[9:0] sync_w2r_r1,sync_w2r_r2;
		reg	[9:0] sync_r2w_r1,sync_r2w_r2;
		
		wire fifo_empty;
		wire fifo_full;
		
		/*二进制转化为格雷码计数器*/
		assign gray_rdaddress = (rdaddress >>1) ^ rdaddress;//(({1'b0,rdaddress[9:1]}) ^ rdaddress);
		
		/*二进制转化为格雷码计数器*/
		assign gray_wraddress = (({1'b0,wraddress[9:1]}) ^ wraddress);
		
		assign fifo_empty = (gray_rdaddress == sync_w2r_r2);  //格雷码所有bit位均相同
		
		assign fifo_full = (gray_wraddress == {~sync_r2w_r2[9:8],sync_r2w_r2[7:0]});  //高两位不同,其余位相同
//		
//		assign fifo_wr_err = (w_fifo_full && fifo_wr_en);
//		assign fifo_rd_err = (fifo_empty && fifo_rd_en);
	
		ram  ram(
			.data		(fifo_wr_data		),
			.rdaddress(rdaddress[8:0]),
			.rdclock	(fifo_rd_clk	),
			
			.wraddress(wraddress[8:0]),
			.wrclock	(fifo_wr_clk	),
			.wren		(fifo_wr_en	),
			.q			(fifo_rd_data)
			);	
		
		/*在读时钟域同步FIFO空 sync_w2r_r2 为同步的写指针地址 延迟两拍非实际写指针值 但是确保不会发生未写入数据就读取*/	
		always@(posedge fifo_rd_clk or negedge rst_n)
		if(!rst_n)
			r_fifo_empty <= 1'b1;
		else 
			r_fifo_empty <= fifo_empty;


			/*在写时钟域判断FIFO满 sync_r2w_r2 实际延迟两个节拍 可能存在非满判断为满 但不会导致覆盖*/
		always@(posedge fifo_wr_clk or negedge rst_n)
		if(!rst_n)
			r_fifo_full <= 1'b1;
		else 									
			r_fifo_full <= fifo_full;//格雷码判断追及问题			
			
			
		/*读数据地址生成*/
		always@(posedge fifo_rd_clk or negedge rst_n)
		if(!rst_n)
			rdaddress <= 10'b0;
		else if(fifo_rd_en && ~fifo_empty)begin
			rdaddress <= rdaddress + 1'b1;
		end
		
		/*写数据地址生成*/
		always@(posedge fifo_wr_clk or negedge rst_n)
		if(!rst_n)
			wraddress <= 10'b0;
		else if(fifo_wr_en && ~r_fifo_full)begin
			wraddress <= wraddress + 1'b1;
		end
		
		/*同步读地址到写时钟域*/
		always@(posedge fifo_wr_clk or negedge rst_n)
		if(!rst_n)begin
			sync_r2w_r1 <= 10'd0;
			sync_r2w_r2 <= 10'd0;
		end else begin
			sync_r2w_r1 <= gray_rdaddress;
			sync_r2w_r2 <= sync_r2w_r1;		
		end

		/*同步写地址到读时钟域, 同步以后 存在延迟两个节拍*/
		always@(posedge fifo_rd_clk or negedge rst_n)
		if(!rst_n)begin
			sync_w2r_r1 <= 10'd0;
			sync_w2r_r2 <= 10'd0;
		end else begin
			sync_w2r_r1 <= gray_wraddress ;
			sync_w2r_r2 <= sync_w2r_r1;		
		end		
endmodule

参考:https://blog.csdn.net/u014070258/article/details/90052281
https://www.cnblogs.com/aslmer/p/6114216.html

你可能感兴趣的:(数电,/,Verilog,fifo)