异步FIFO的verilog设计

1.    异步FIFO的概念

异步FIFO为读取与写入采用不同的时钟,使用异步FIFO用于在不同的时钟域传输数据,主要用于跨时钟域传输多bit数据。

2.    异步FIFO的设计难点

同步异步信号,避免亚稳态数据的危害

设计合适的FIFO指针,判断FIFO满或者空状态

3.    同步FIFO的指针

同步FIFO有一个计数器用于计数存储的数目和读取的数目。当FIFO只有写操作没有读操作计数值增加,当FIFO只有读操作没有写操作的时候计数值减小,当没有读写操作或者同时在进行读写操作的时候计数值保持不变。当计数器的计数值达到FIFO的总长度的时候FIFO满,当计数值为0的时候那么FIFO空。

但是在异步FIFO设计中无法使用一个计数器来统计FIFO的数据的个数,因为两个异步的时钟需要去控制同一个计数器。因此在设计异步FIFO时候需要比较FIFO的读写指针位置。

4.    异步FIFO的指针

异步FIFO的写指针总是指向下一个要被写入的地址,当复位或者清空时候读写指针都指向FIFO缓冲区的0地址。同样的读指针总是指向当前FIFO中要被读出的数据所在的地址,当清空或者复位是指针指向0,当发生一次读操作时读指针所指的地址的数据被取出,然后指针地址增加。

FIFO空的标志发生时是当读的指针和写的指针相等时。当读写指针复位到0的时候如清空FIFO等操作的时候,或者是当读的指针赶上了写指针。

FIFO满的标志也是当读指针和写指针相等的时候。当写指针已经环绕一周,然后追上读指针的时候。因此就存在一个问题当读写指针相等的时候如何判断是读空还是写满。

为了区分是读空还是写满,一种设计的方式是给读写指针添加一个额外的位。当写指针增加了以后超过了FIFO的存储空间的最后的地址,这时候指针自加就会使得额外添加的最高位翻转。同样的读指针也会进行相同的操作。因此当两个最高位是不同的时候就可以判断是写指针已经环绕追上了读指针,那么FIFO是处于写满状态,如果最高位是相同的那么是读指针追赶上了写指针,FIFO处于读空状态。

异步FIFO的verilog设计_第1张图片

图 1添加MSB用于区分写满与读空

5.    读写指针计数器设计

对于一个二进制计数器,当他的计数值从一个时钟域同步到另外一个时钟域的时候是危险的,因为可能存在n-bits数据同时变化的情况,如7计数到8对应的二进制是0111计数到1000,那么对应的所有的位均发生变化。

一种通用的解决FIFO计数器的问题的方法是使用格雷码进行计数,格雷码对于相邻的两个计数值只允许一个位发生变化。因此就解决了欲在同一个时钟边沿同步多个bits的问题。

6.    格雷码计数器问题引入

考虑4位和3位的格雷码计数器如图 2。通过对二进制码的分析可以得出对于4位格雷码上半部分与下半部分是最高位反向其余位关于中点镜像。为了将4位的格雷码转化为3位的格雷码,同时使得最高位为附加位,用于区分写满与读空。我们希望4位格雷码的低3位重复第一部分的低3位,而不是关于中点的镜像。如果直接将下半部分的低3位格雷码编码改为上半部分的低3位一样的编码那么就不是真格雷码应为从7到8以及15到0时候会出现两bits发生变化。而真格雷码应该是在相邻的两个计数之间只有一个位发生变化。

异步FIFO的verilog设计_第2张图片

图 2四位格雷码

7.     格雷码计数器设计

格雷码计数器是不存在奇数长度的,因此设计出来的额FIFO的深度就是  。 二进制计数器咋每次自加都会检测是否为满或者空状态,从而保证不会出现溢出或者下溢。

在图 3所示的格雷码计数器中二进制计数器的低(n-1)位可以直接作为FIFO存储单元的地址指针,将二进制数转化为格雷码传输给另外一个时钟域。

异步FIFO的verilog设计_第3张图片

图 3格雷码计数器

8.    格雷码转二进制与二进制转格雷码

二进制B[n:0]转化为格雷码G[n:0]

G[n] = B[n]//保留最高位作为格雷码的最高位

G[n-1:0] = B[n-1:0]^B[n:1]//次高位格雷码为二进制码的高位与次高位相异或其余类似

异步FIFO的verilog设计_第4张图片

格雷码G[n:0]转化为二进制B[n:0]

B[n] = G[n]//保留最高位作为二进制码的最高位

B[n-1:0] = G[n-1:0]^B[n:1]//次高位格雷码为二进制码的高位与次高位相异或其余类似

异步FIFO的verilog设计_第5张图片

9.    FIFO实现

将地址指针转化为格雷码以后对于相邻的两个地址只有一个位数据发生变化,因此可以使用触发器来同步异步时钟的数据。

异步FIFO的verilog设计_第6张图片

图 4 FIFO结构框图

10.    满和空信号的生成

读空信号是在读时钟域生成的,从而保证能够实时的没有延迟的确定读空。当读空发生时是读指针追赶上写指针,两个指针值相同(包括扩展的最高位)。此时其格雷码也是相同的,因此设计相对简单。

在FIFO设计中写满信号是在写时钟域生成的,从而保证能够实时的没有延迟的确定写满。当写指针追赶上读指针的时候发生,此时两个指针的二进制计数器的低(n-1)位相同最高位不同。但是由于扩展了格雷码,而对于n位格雷码其(n-1)位是关于中间值镜像对称的。

异步FIFO的verilog设计_第7张图片

图 5 有扩展位的格雷码编码

考虑图 5对于3位的格雷码地址添加一个附加位用于区分写满与读空成为4位的格雷码编码。当FIFO空时即读指针赶上写指针,此时两个格雷码完全相同(包括扩展位)。当FIFO写满时候需要考虑如下3个条件

    写指针的格雷码与同步到写时钟域的读指针格雷码的最高位不同

    写指针的格雷码与同步到写时钟域的读指针格雷码的次高位不相等

    写指针的格雷码与同步到写时钟域的读指针格雷码的其余位都相等

11.    不同的时钟速率同步的深入思考

当异步FIFO来同步两个不同的时钟域的时候,显然两个时钟的速度是不一样的,考虑当一个快的时钟域的信号同步到慢的时钟域的时候可能会存在计数值跳跃,因为在慢的时钟域的一个周期快的时钟域的计数值可能已经增加了多次了,因此就会导致如下的两个问题:

    在同步格雷码的时候如果格雷码的值增加了2个然而只采样了一次就会出现多个位的数据发生变化,这种情况下会导致多bit数据同步问题吗?

    答案是否定的。在同步多bit数据的时候当多个数据位在同步上升沿变化时会出现问题。但是对于快的时钟域的写指针的格雷码在一个时钟周期只改变一位,因此在慢的时钟域周围最多只会有一个位发生变化。

    在慢的时钟域一个周期里面快的时钟域的计数器可能已经增加了几个计数值了,那么会存在快的时钟域的已经从满状态增加到(满+1)的状态,而没能检测出满吗?

    答案是否定的,应为满状态是在写时钟域产生的,如果写的时钟比读取的时钟快,当写指针赶上从读时钟域同步过来的读指针后,满状态会马上置位,因此就不会出现溢出。

满与空状态能够准确的被置位,但是标志位清除有一定的延迟。

当我们在写时钟域产生FIFO满的状态,当写指针追赶上读指针的时候马上产生满标志,此时当读指针增长了以后FIFO就不再满了,但是写时钟域不能马上检测到读指针已经增加了,需要经过两个时钟周期,使得读指针的数据同步到写时钟域以后才能够清空满状态。这样能够保证FIFO不会溢出,相同的读FIFO也存在这样的问题。

/*异步fifo 参考文献  Simulation and Synthesis Techniques for Asynchronous FIFO Design*/

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;
		
		reg	[9:0]  rdaddress; //RAM地址为9位地址 扩展一位用于同步
		reg	[9:0]  wraddress;
	
		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);
		
		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

 

 

参考文献 Simulation and Synthesis Techniques for Asynchronous FIFO Design

你可能感兴趣的:(FPGA)