精华内容
下载资源
问答
  • 异步FIFO原理及其实现

    千次阅读 多人点赞 2020-08-07 22:38:56
    FPGA(一):异步FIFO实现(包含源码和仿真文件) 一、异步FIFO的重要参数及其作用 1、FIFO:First Input First Output,即先入先出队列,本质是RAM。 FIFO有几个最重要的参数: 2、wr_clk:写时钟,所有与写有关的...


    本次设计主要介绍异步FIFO中读写指针和格雷码的原理及其实现,最后会有代码和仿真文件

    一、异步FIFO的重要参数及其作用

    FIFO有几个最重要的参数:
    1、FIFO:First Input First Output,即先入先出队列,本质是RAM。
    2、wr_clk:写时钟,所有与写有关的操作都是基于写时钟;
    3、rd_clk:读时钟,所有与读有关的操作都是基于读时钟;
    4、FIFO_WIDTH: FIFO的位宽,即FIFO中每个地址对应的数据的位宽;
    5、FIFO_DEPTH: FIFO的深度,即FIFO中能存入多少个(位宽为FIFO_WIDTH的)数据;
    6、full:FIFO发出的满信号,当FIFO满了之后,将full拉高;
    7、empty:FIFO发出的空信号,当FIFO空了之后,将empty拉高;
    8、wr_en:主机发送给FIFO的写使能,一般受制于FIFO发出的full信号,若full信号为高,一般主机会拉低写使能信号,防止新数据覆盖原来的数据;
    9、rd_en:主机发送给FIFO的读使能,一般受制于FIFO发出的empty信号,若empty信号为高,一般主机会拉低读使能信号,防止从FIFO中读出不确定的数据。

    异步FIFO主要用作跨时钟域的数据缓存。

    二、设计要点

    异步FIFO设计中,最重要的就是空满判断,格雷码是现在使用最多用于判断空满的一种码制,虽然都知道用格雷码,那为什么要用格雷码?

    先说一说空满判断:
    :读快于写时,读指针追上写指针时,写入的数据被读完,读指针和读指针相同,FIFO为空。
    :当写快于读时,当写指针追上读指针,写入的数据比读出的数据多FIFO_DEPTH个,即写指针比读指针大一个FIFO_DEPTH时,此时FIFO为满。

    还有就是为什么读写指针要比FIFO_DEPTH多一位,以及格雷码在这之中起到的重要作用,这个在说完格雷码之后会有解释。

    格雷码:因1953年公开的弗兰克·格雷专利 “Pulse Code Communication”而得名。
    格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
    在同步时,出现亚稳态的概率也比二进制低。

    二进制转换为格雷码
    在这里插入图片描述
    Verilog代码描述:gray = binary ^ (binary >> 1)

    在这里插入图片描述
    注:以上两幅关于格雷码的图片均不是原创,不知道出处,若有侵权,请联系删除

    1、首先是读写指针为什么可以多一位,对读写地址有没有影响

    答案是,没有影响
    如上图,假如FIFO_DEPTH为8,那么指针就有16个值
    普通的三位的地址从000写到111,然后再写入的话,地址又为000,一直写到111,重复上述操作。
    因为我们取指针的低三位作为读写地址,如图,可以看出,即使是四位的指针,因为取的低三位,所以也是在000-111中往复循环,不会出现地址溢出的情况。

    2、其次是为什么要用格雷码

    由于以上原因,指针多一位对读写地址没有影响,而多一位又可以很好地利用格雷码的特性进行空满判断。
    如上图,3和4, 5和6, 7和8之间有什么关系呢
    可以看出,3的高两位与4的高两位相反低两位相同;5和6,7和8也有这种关系。
    其实格雷码还是一种对称码,比如说3位的格雷码,可以先写出一位的格雷码,0 和 1;然后将这两位对称一下,写出 0 1 1 0,然后在前两个前面填上0,后两个前面添上1,得到两位格雷码 00 01 11 10,然后再对称得到 00 01 11 10 10 11 01 00,再在前四个前添上0,后四个 前面添上1,得到三位的格雷码,000 001 011 010 110 111 101 100.

    而3和4之间还有什么关系呢,那就是他们的数值之间相差8,即一个FIFO_DEPTH,所以可以用这个来判断满。
    空的判断很简单,格雷码一样就是空。

    假设FIFO_DEPTH == 8
    空的判断:empty =(wr_gray == rd_gray)?1:0;
    满的判断:full = ({~wr_gray[3:2],wr_gray[1:0]} == rd_gray)?1:0;

    3、空满信号的同步
    因为空信号是对读操作有影响,所以,将空信号在rd_clk下同步(多采用两级寄存器)
    因为满信号是对写操作有影响,所以,将满信号在wr_clk下同步(多采用两级寄存器)

    三、源代码及仿真

    1、源代码

    
    `timescale 1ns / 1ns
    module asynchronous_fifo #(	
    	parameter 						FIFO_WIDTH = 8,
    	parameter 						FIFO_DEPTH = 8
    )(
    	input							wr_clk,				//写时钟
    	input							rd_clk,				//读时钟
    	input							wr_en,				//写使能
    	input							rd_en,				//读使能
    	input							wr_rst_n,			//写复位
    	input							rd_rst_n,			//读复位
    	
    	input		[FIFO_WIDTH-1:0]	wr_data,			//写入的数据		
    	
    	output	reg	[FIFO_WIDTH-1:0]	rd_data,			//输出的数据
    	output	reg						wr_full,
        output	reg						rd_empty,
        output									wr_fifo_en,
    	output									rd_fifo_en,
    	output	reg		[$clog2(FIFO_DEPTH):0]	wr_gray_g2,		// 写指针
        output	reg		[$clog2(FIFO_DEPTH):0]	rd_gray_g2
        );
        
        
        // reg define
        reg		[$clog2(FIFO_DEPTH):0]		wr_pointer = 0;		// 写指针
    	reg		[$clog2(FIFO_DEPTH):0]		rd_pointer = 0;		// 读指针
    	reg		[$clog2(FIFO_DEPTH):0]		wr_gray_g1;  		// 写格雷码,用格雷码来判断空满
    	reg		[$clog2(FIFO_DEPTH):0]		rd_gray_g1; 
    	
    
        wire	[$clog2(FIFO_DEPTH):0]		wr_gray;  		// 写格雷码,用格雷码来判断空满
    	wire	[$clog2(FIFO_DEPTH):0]		rd_gray;  		// 读格雷码,用格雷码来判断空满 
    	wire	[$clog2(FIFO_DEPTH)-1:0]  	wr_addr;
    	wire	[$clog2(FIFO_DEPTH)-1:0]  	rd_addr;
    	wire								full_1;
    	wire								empty_1;
        
        
         //ram define
       //(*ram_style = "distributed"*) reg [FIFO_WIDTH - 1 : 0] 	fifo_buffer	[FIFO_DEPTH - 1:0];			//寄存器组当FIFO
        reg [FIFO_WIDTH - 1 : 0] 	fifo_buffer	[FIFO_DEPTH - 1:0];			//寄存器组当FIFO
        
       	assign	wr_fifo_en = wr_en && !full_1;
        assign	rd_fifo_en = rd_en && !empty_1;
        
        assign	wr_gray = wr_pointer^(wr_pointer>>1);
        assign	rd_gray = rd_pointer^(rd_pointer>>1);
        
        assign	wr_addr = wr_pointer[$clog2(FIFO_DEPTH)-1:0]; 
        assign	rd_addr = rd_pointer[$clog2(FIFO_DEPTH)-1:0];
        
        assign	full_1 = ({~rd_gray_g2[$clog2(FIFO_DEPTH):$clog2(FIFO_DEPTH)-1], rd_gray_g2[$clog2(FIFO_DEPTH)-2:0]} == wr_gray)?1:0;
        assign	empty_1 = (wr_gray_g2 == rd_gray)?1:0;
    
       
        //将rd_gray在写时钟域与wr_gray比较,如果wr_gray与rd_gray高两位相反,低位相等,则写满
        always@(posedge wr_clk or negedge wr_rst_n) begin
        	if(!wr_rst_n) begin
        		rd_gray_g1 <= 0;
        		rd_gray_g2 <= 0;
        	end
        	else begin
        		rd_gray_g1 <= rd_gray;
        		rd_gray_g2 <= rd_gray_g1;
        	end
        end
        
         always@(posedge wr_clk or negedge wr_rst_n) begin
        	if(!wr_rst_n)
        		wr_full <= 1'b0;
        	else if(wr_full && wr_en)
        		wr_full <= 1'b1;
        	else 
        		wr_full <= full_1;
        end
        
        //将wr_gray在读时钟域与rd_gray比较,相等为FIFO空
         always@(posedge rd_clk or negedge rd_rst_n) begin
        	if(!rd_rst_n) begin
        		wr_gray_g1 <= 0;
        		wr_gray_g2 <= 0;
        	end
        	else begin
        		wr_gray_g1 <= wr_gray;
        		wr_gray_g2 <= wr_gray_g1;
        	end
        end
        
        always@(posedge rd_clk or negedge rd_rst_n) begin
        	if(!rd_rst_n)
        		rd_empty <= 1'b0;
        	else if(rd_empty && rd_en)
        		rd_empty <= 1'b1;
        	else 
        		rd_empty <= empty_1;
        end
        
        
        // 写数据指针计数
       	always@(posedge wr_clk or negedge wr_rst_n) begin
        	if(!wr_rst_n) begin
        		fifo_buffer[wr_addr] <= 'bz;
        		wr_pointer <= 0;
        	end
        	else if(wr_fifo_en) begin
        		fifo_buffer[wr_addr] <= wr_data;
        		wr_pointer <= wr_pointer + 1'b1;
        	end
        	else begin
        		fifo_buffer[wr_addr] <= fifo_buffer[wr_addr];
        		wr_pointer <= wr_pointer;
        	end
        end
        
        // 读数据指针计数
       	always@(posedge rd_clk or negedge rd_rst_n) begin
        	if(!rd_rst_n) begin
        		rd_data <= 'bz;
        		rd_pointer <= 0;
        	end
        	else if(rd_fifo_en) begin
        		rd_data <= fifo_buffer[rd_addr];
        		rd_pointer <= rd_pointer + 1'b1;
        	end
        	else begin
        		rd_data <= fifo_buffer[rd_addr];
        		rd_pointer <= rd_pointer;
        	end
        end
    
        
    endmodule
    

    2、仿真文件

    改变clk_period_wr和clk_period_rd就可以实现读写快慢切换

    
    `timescale 1ns / 1ns
    `define	 clk_period_wr 50
    `define	 clk_period_rd 20
    
    
    module asynchronous_fifo_tb();
    
    
    parameter		FIFO_WIDTH = 8;
    parameter		FIFO_DEPTH = 16;
    
    
    reg							wr_clk;				
    reg							rd_clk;				
    reg							wr_en;			
    reg							rd_en;				
    reg							wr_rst_n;
    reg							rd_rst_n;
    reg		[FIFO_WIDTH-1:0]	wr_data;
    
    wire	[FIFO_WIDTH-1:0]	rd_data;			
    wire						wr_full;    
    wire						rd_empty;
    wire						wr_fifo_en;
    wire						rd_fifo_en;
    wire	[$clog2(FIFO_DEPTH):0]		wr_gray_g2;
    wire	[$clog2(FIFO_DEPTH):0]		rd_gray_g2;
    
    assign wr_fifo_en = wr_en && !wr_full;
    assign rd_fifo_en = rd_en && !rd_empty;
    
    initial begin
    	wr_clk = 0;
    	forever begin
    		#(`clk_period_wr/2) wr_clk = ~wr_clk;
    	end
    end
    initial begin
    	rd_clk = 0;
    	forever begin
    		#(`clk_period_rd/2) rd_clk = ~rd_clk;
    	end
    end
    
    initial begin
    	wr_rst_n = 1;
    	rd_rst_n = 1;
    	wr_en = 0;
    	rd_en = 0;
    	#5;
    	wr_rst_n = 0;
    	rd_rst_n = 0;
    	#5;
    	wr_rst_n = 1;
    	rd_rst_n = 1;
    end
    
    
    initial begin
    	//第一段仿真
    	@(negedge wr_clk) wr_en = 1;wr_data = $random;
    	repeat(7) begin   		
    		@(negedge wr_clk)
    		wr_data = $random;
    	end
    	@(negedge wr_clk) wr_en = 0;
    	@(negedge rd_clk) rd_en = 1;
    	
    	repeat(8) begin
    		@(negedge rd_clk); 
    	end 
    	@(negedge rd_clk) rd_en = 0;
    	# 150
    	
    	//第二段仿真
    	@(negedge wr_clk)
    	wr_en = 1;
    	wr_data = $random;
    	repeat(18) begin
    	@(negedge wr_clk)
    		wr_data = $random;
    	end
    	@(negedge wr_clk) wr_en = 0;	
    	@(negedge rd_clk) rd_en = 1;
    	repeat(18) begin
    		@(negedge rd_clk);
    	end
    	rd_en = 0;
    	
    	// 第三段仿真
    	repeat(9) begin
    	@(negedge wr_clk)
    		wr_data = $random;wr_en = 1;
    	end
    	rd_en = 1;
    	repeat(19) begin
    	@(negedge wr_clk)
    		wr_data = $random;
    	end
    	@(negedge wr_clk) wr_en = 0;	
    	@(negedge rd_clk) rd_en = 0;
    
    
    	#20 $finish;	
    
    
    end
    	
    asynchronous_fifo #(
    		.FIFO_WIDTH	(FIFO_WIDTH),
    		.FIFO_DEPTH	(FIFO_DEPTH)
    		)
    	asynchronous_fifo (	
    		.wr_clk			(wr_clk),
    		.rd_clk			(rd_clk),
    		.wr_rst_n		(wr_rst_n),
    		.rd_rst_n		(rd_rst_n),
    		.wr_en			(wr_fifo_en),
    		.rd_en			(rd_fifo_en),
    		.wr_data		(wr_data),
    		
    		.wr_full		(wr_full),
    		.rd_empty		(rd_empty),
    		.rd_data		(rd_data)
    	);
    
    endmodule
    

    3、仿真截图

    读比写快
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    写比读快
    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    这个代码也还有些问题,希望大家多多包容并且指出错误或者可以改善的地方,一起进步。

    展开全文
  • 异步FIFO原理

    2021-12-31 15:44:45
    异步FIFO原理 一,FIFO概念及用途 FIFO即英文First In First Out 的缩写,是一种先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出...

    异步FIFO原理


    一,FIFO概念及用途
    FIFO即英文First In First Out 的缩写,是一种先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。按读写是否为相同时钟域分为同步和异步FIFO,这里主要介绍异步FIFO,主要用于跨时钟域传输数据。
    二,FIFO的常见参数
    宽度:THE WIDTH,FIFO一次读写操作的数据位,
    深度:THE DEEPTH,FIFO可以存储多少个N位的数据(如果宽度为N)。
    满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
      空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
      读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
      写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
      读指针:指向下一个读出地址,读完后自动加1。
      写指针:指向下一个要写入的地址的,写完自动加1。
    读写指针其实就是读写的地址,只不过这个地址不能任意选择,而是连续的。
    三,FIFO的关键:读写指针和空满标志
    1,当读写指针相等时,表明FIFO为空:
    在这里插入图片描述

    2,当写指针转了一圈又追上了读指针,当读写指针再次相等时,表明FIFO为满:
    在这里插入图片描述

    所以空满都是读写指针相等,如何区分呢?对于二进制数来说,解决方法就是读写指针的位宽分别增加一个bit,当最高位相同,其余位相同认为是读空;当最高位不同,其余位相同认为是写满。但是这里会引入一个新的问题,就是读写指针位于不同的时钟域,二者需要同步后才可以比较,同步的过程有两个:
    (1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号;
    (2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号;
    异步FIFO的写指针和读指针分属不同时钟域,这样指针在进行同步过程中很容易出错,比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,并不能确定会出现哪个值,那出错的概率非常大,怎么办呢?到了格雷码发挥作用的时候了,而格雷码的编码特点是相邻位每次只有 1 位发生变化, 这样在进行指针同步的时候,只有两种可能出现的情况:1.指针同步正确,正是我们所要的;2.指针同步出错。举例假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变保持不变,那么这个错误会不会导致读空判断出错?答案是不会,最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。所以gray码保证的是同步后的读写指针即使在出错的情形下依然能够保证FIFO功能的正确性。
    四,格雷码
    在这里插入图片描述

    (1)二进制码转换成格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。
    在这里插入图片描述

    (2)格雷码转换成二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。
    在这里插入图片描述

    使用gray码同时也带来另一个问题,即在格雷码域如何判断空与满。对于“空”的判断依然依据二者完全相等;而对于“满”的判断,如下图,由于gray码除了最高位外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了最高位,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:
    (1)读指针和写指针的最高位不相等
    (2)读指针和写指针的次高位也不相等
    (3)剩下的其余位完全相等。如下图位置7和位置15,转化为二进制对应的是0111和1111
    在这里插入图片描述

    五,补充问题:由于设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?
    异步FIFO通过比较读写指针进行空满判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一定不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错。同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,也不会出现满写的情况。所以FIFO空之后不能继续读取,FIFO满之后不能继续写入的原则依然可以得到保证。总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

    六,FIFO功能点简单总结

    1. 同时读写,读写数据正确检查
    2. FIFO满标志位检查
    3. FIFO空标志位检查
    4. 写过程中发生写复位,写数据和FIFO满标志位被清空
    5. 读过程中发生读复位,读数据和FIFO空标志位被清空
    6. 读写时钟相位相同,异步FIFO能正常工作
    7. 读写时钟相位不同,异步FIFO能正常工作
    8. 写时钟等于读时钟,异步FIFO能正常工作
    9. 写时钟快于读时钟,异步FIFO能正常工作
    10. 写时钟慢于读时种,异步FIFO能正常工作
    11. 写过程中发生写复位以后,异步FIFO能继续正常工作
    12. 读过程中发生读复位以后,异步FIFO能继续正常工作
    13. FIFO满以后,继续往FIFO写数据,异步FIFO不会被卡死,数据被读走以后,异步FIFO能继续正常工作
    14. FIFO空以后,不可以再读出数据
    展开全文
  • FIFO在硬件上是一种地址依次自增的Single Dul RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO,其中同步FIFO是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换;异步FIFO通常情况下是...

            FIFO在硬件上是一种地址依次自增的Simple Dual Port RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO,其中同步FIFO是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换;异步FIFO通常情况下是指读时钟和写时钟频率有差异,即由两个异步时钟驱动的FIFO,由于读写操作是独立的,故常用于多比特数据跨时钟域处理。本文仅讨论异步FIFO的设计。

            因为FIFO的硬件本质是一块Simple Dual Port RAM,无论它的内部结构和原理如何复杂,最核心的部分都只是对这个RAM的读写操作而已,所以我们先设计RAM部分的RTL。一个Simple Dual Port RAM应该由读写时钟、读写使能、读写地址总线和读写数据总线接口构成,这里为了在功能仿真中初始化读数据寄存器,再加入一个读复位接口。Verilog代码如下:

    module DPRAM #
    	(
    		parameter WIDTH = 16 ,					// DPRAM数据总线宽度
    		parameter DEPTH = 16 ,					// DPRAM存储深度
    		parameter ADDR  = 4 					// DPRAM地址总线宽度
    
    	)
    	(
    		input					wrclk	,		// 写时钟
    		input					rdclk	,		// 读时钟
    		input					rd_rst_n,		// 读复位
    		input					wr_en	,		// 写使能
    		input					rd_en	,		// 读使能
    		input		[WIDTH-1:0]	wr_data	,		// 写数据
    		output	reg [WIDTH-1:0] rd_data	,		// 读数据
    		input	    [ADDR-1:0]  wr_addr	,		// 写地址
    		input	    [ADDR-1:0]  rd_addr			// 读地址
        );
    
    
    	reg [WIDTH-1:0] DPRAM [DEPTH-1:0];
    
    
    	// RAM写数据
    	always @(posedge wrclk) begin
    		if (wr_en)
    			DPRAM[wr_addr] <= wr_data;
    	end
    
    
    	// RAM读数据
    	always @(posedge rdclk or negedge rd_rst_n) begin
    		if(!rd_rst_n)
    			rd_data <= 'b0;
    		else if (rd_en)
    			rd_data <= DPRAM[rd_addr];
    	end
    
    
    endmodule

    其中 WIDTH 是RAM数据总线的位宽,DEPTH 是RAM的存储深度(即RAM中可以存下 DEPTH 个宽度为 WIDTH 的数据),ADDR 是地址总线的宽度(即DEPTH = 2^ADDR ,异步FIFO中深度必须是2^n,原因在后面阐述)。

    接下来需要解决的是如何控制这个RAM来实现异步FIFO的功能,在实现这部分功能前先来捋一捋异步FIFO的一些重要概念:

    1、FIFO数据宽度:FIFO一次读写的数据位宽。(与RAM数据位宽相同)

    2、FIFO存储深度:FIFO可存储的固定位宽数据的个数。(与RAM存储深度相同)

    3、读时钟:在每个读时钟的边沿来临时读数据。

    4、写时钟:在每个写时钟的边沿来临时写数据。

    5、读指针:指向下一个要读的地址,读完后自动加1。

    6、写指针:指向下一个要写的地址,写完后自动加1。

    读写指针其实就是读写的地址,只不过不能任意设置,只能连续自增。

    7、空/满标志:为了保证FIFO的正确读写,而不发生写溢出或读空的情况,需要提供写满和读空的标志来提醒外部控制器此状态下不能再进行写/读操作。

    根据上述重要概念可以定义出异步FIFO的基本对外接口:写时钟、读时钟、写使能、读使能、写满标志、读空标志、写入数据总线、读出数据总线以及读/写复位。因为我们所设计的是异步FIFO,它的读写部分不是在同一个时钟域内工作,所以可以将它们划分为写时钟域和读时钟域,在两个时钟域各自控制本时钟域内的信号,并将两个时钟域内的一些有关信号进行跨时钟域处理来联合判断FIFO状态。

    一、异步FIFO结构简述

     

     上图为异步FIFO结构示意图,这里简述一下其结构,不懂也没关系,下面会详细讲解各部分的原理和作用。

    由图可见,异步FIFO的核心部件就是一个 Simple Dual Port RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及解格雷码;下面的两对D触发器 sync_r2w 和 sync_w2r 是同步器,负责将写地址同步至读时钟域、将读地址同步至写时钟域。

     

    二、各自时钟域内的二进制地址自增控制

    RAM的地址由自然二进制表示,让其地址随着时钟自增即可满足FIFO指针递增的要求;注意各自时钟域开启使能端并且没有写满/读空的情况下才能让地址自增。Verilog代码如下:

    // ******************************** 写时钟域 ******************************** //
    	// 二进制写地址递增
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if (!wr_rst_n) begin
    			wr_bin <= 'b0;
    		end
    		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
    			wr_bin <= wr_bin + 1'b1;
    		end
    		else begin
    			wr_bin <= wr_bin;
    		end
    	end
    
    
    // ******************************** 读时钟域 ******************************** //
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if (!rd_rst_n) begin
    			rd_bin <= 'b0;
    		end
    		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
    			rd_bin <= rd_bin + 1'b1;
    		end
    		else begin
    			rd_bin <= rd_bin;
    		end
    	end

     

    三、二进制地址转格雷码地址

    异步FIFO是通过比较读指针和写指针的位置来判断FIFO是否写满或读空,但是不可以直接比较两个指针,因为他们属于不同时钟域,直接相比可能会产生亚稳态从而引起误判,这就需要将两个指针分别进行跨时钟域处理,然后再判断。但是存在一个问题,自然二进制编码的地址在状态翻转的时候是多位变化,这就可能会产生竞争现象并有可能被另一个时钟域的触发器采样到,从而引发误判。最容易的解决方法就是将自然二进制编码的地址转为格雷码编码的地址。

    从上图可以看出,格雷码如果每2^n个数一循环,首尾两个格雷码仍然是只有一位变化,如果不是2^n个数,那么首尾数据就不是仅有一位变化,那就不是真正的格雷码,所以这也是异步FIFO的存储深度只能是2^n的原因。

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

    虽然将二进制转成了格雷码,但仍存在一个隐藏的竞争冒险问题,如下图所示:

    上图是从二进制地址自增和转格雷码的RTL连接图。可以看出,A点输出的是二进制数据,可能存在多位变化,但是经过转格雷码电路 bin to gray (组合逻辑电路)就不会产生竞争现象了吗,仔细想想,当A点二进制数据处于变化中的中间态时,由于 bin to gray 是组合逻辑电路,那么B点也会出现中间态二进制数据对应的格雷码数据,这个中间数据就有可能被读时钟域采样到,从而再次引发误判,解决方法就是在二进制转格雷码电路后打一拍转为时序逻辑电路,这样在写时钟的驱动下,C点数据每次更新必然只有一位在变化,就避免了出现冒险现象。有人可能会问了,上面不是说写满和读空标志都是由两个地址指针判断的吗?对格雷码地址打一拍会不会引起标志位来的迟了,这里先说一下标志位是不会来迟的,原因在后面的标志位判断方法中会讲到。

    二进制转格雷码Verilog代码如下:

    // 写地址:二进制转格雷码
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if (!wr_rst_n) begin
    			wr_gray <= 'b0;
    		end
    		else begin
    			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
    		end
    	end
    
    
    // 读地址:二进制转格雷码
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if (!rd_rst_n) begin
    			rd_gray <= 'b0;
    		end
    		else begin
    			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
    		end
    	end

     

    四、格雷码地址跨时钟域 

    将二进制地址处理为格雷码地址的目的就是为了将其同步到另一个时钟域时不会引起竞争冒险,在前面的异步FIFO结构图中也可以看见两个D触发器构成的同步器对格雷码地址打两拍从而同步至另一个时钟域,这里直接给出同步器的Verilog代码:

    // 格雷码读地址同步至写时钟域
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if(!wr_rst_n) begin
    			rd_gray_ff1 <= 'b0;
    			rd_gray_ff2 <= 'b0;
    		end
    		else begin
    			rd_gray_ff1 <= rd_gray;
    			rd_gray_ff2 <= rd_gray_ff1;
    		end
    	end
    
    
    // 格雷码写地址同步至读时钟域
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if(!rd_rst_n) begin
    			wr_gray_ff1 <= 'b0;
    			wr_gray_ff2 <= 'b0;
    		end
    		else begin
    			wr_gray_ff1 <= wr_gray;
    			wr_gray_ff2 <= wr_gray_ff1;
    		end
    	end

     

    五、 对同步后的格雷码地址解码

    为什么要对格雷码解码?因为二进制下数据的规律更明显,便于后续判断标志位。

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

     其电路图应如下所示:

    在数电书中我们曾学过串行进位加法器,它是一种后一位计算依靠前一位进位的组合逻辑电路,上图的解格雷码电路也与其类似,前一位的输出依靠后一位的异或结果,这会带来更大的组合链延迟并产生竞争现象,不过在位宽不大的情况下对于正确产生标志位的影响概率较小,可以使用组合逻辑进行解格雷,当然也可以对解格雷后的二进制数据打一拍消除竞争现象,这里因为地址总线只有4位,仅使用组合逻辑解格雷。Verilog代码如下:

        parameter WIDTH = 16,		// FIFO数据总线位宽
    	parameter PTR   = 4			// FIFO存储深度(bit数,深度只能是2^n个)	
    
    
        // 解格雷码电路循环变量
    	integer i ;
    	integer j ;
    
    
        // 同步后的写地址解格雷
    	always @(*) begin
    		wr_bin_rd[PTR] = wr_gray_ff2[PTR];
    		for ( j=PTR-1; j>=0; j=j-1 )
    			wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j];
    	end
    
    
    
    	// 同步后的读地址解格雷
    	always @(*) begin
    		rd_bin_wr[PTR] = rd_gray_ff2[PTR];
    		for ( i=PTR-1; i>=0; i=i-1 )
    			rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
    	end

    这里推荐使用for语句(仅这里),和C/C++不同,Verilog中的for语句是将所以可能的结构全部展开成电路(因为属于组合逻辑,在仿真中第0ns就展开完毕),并且可以通过改变 parameter变量的值来改变电路层级,较为方便,但其他地方慎用for语句,因为可能会综合出较大面积的电路,浪费LUT资源。

     

    六、“写满” 与 “读空” 标志的产生

    “读空” 标志的产生比较好理解,如果读指针=写指针,说明FIFO就已经读空了,意思是读指针赶上了写指针,那么写指针之前的地址空间都已经被读指针遍历了,就说明FIFO已经读空了。

    在讲“写满” 标志之前,先来看一张图:

     这张图是五位二进制和格雷码的对应表,可以看出,低四位二进制每16个数据一循环,而超出16个数据后最高位会从0变成1,这第五位意味着什么?意味着这一位可以表示循环的次数,即这一位为1就代表了低二进制已经循环过16个数据了,正在处于第二轮循环中。

    这给如何判断 “写满” 标志带来了启发,何谓 “写满” ?就是写指针正好超过了读指针一整个存储深度并同处于同一个位置,就好比两个运动员绕着运动场跑步,第一个运动员盖过了第二个运动员一圈,即使他们处于操场的同一个位置,但第一个运动员实际上比第二个运动员多跑了一圈。如何表示写指针盖过了读指针一圈呢?就使用上面说的第五位来判断,其实有效地址只有四位,第五位是用来存储盖过的圈数的。所以可以看见,前面的代码中,地址总线宽度其实都是5位的。

    所以判断 “写满” 标志的方法就是:写指针和读指针最高位的数据不同,而其他位都相同。

    这里给出两个标志判断的Verilog代码:

    reg [PTR:0] rd_bin_wr	;					// 同步到写时钟域的二进制读地址
    reg [PTR:0] wr_bin_rd	;					// 同步到读时钟域的二进制写地址
    reg [PTR:0] rd_bin		;					// 二进制读地址
    reg [PTR:0] wr_bin		;					// 二进制写地址
    
    
    // 写时钟域产生写满标志
    	always @(*) begin
    		if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) 
        begin
    			wr_full = 1'b1;
    		end
    		else begin
    			wr_full = 1'b0;
    		end
    	end
    
    
    // 读时钟域产生读空标志
    	always @(*) begin
    		if( wr_bin_rd == rd_bin )
    			rd_empty = 1'b1;
    		else
    			rd_empty = 1'b0;
    	end

     

    七、Modelsim 功能仿真

    先给出异步FIFO实现的全部Verilog代码

    `timescale 1ns / 1ns
    //
    // Company: Xidian University
    // Engineer: Xumingwei
    // 
    // Create Date: 2020/11/06 20:52:45
    // Design Name: 异步FIFO控制器
    // Module Name: ASFIFO
    // Project Name: ASFIFO
    // Target Devices: XC7K325T
    // Tool Versions: Vivado 2019.2
    // Description: 本模块为异步FIFO控制器,将RAM操作为ASFIFO
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //
    
    
    module ASFIFO#
    	(
    		parameter WIDTH = 16,		// FIFO数据总线位宽
    		parameter PTR   = 4			// FIFO存储深度(bit数,深度只能是2^n个)
    	)
    	(
    		// write interface
    		input					wrclk	,		// 写时钟
    		input					wr_rst_n,		// 写指针复位
    		input	[WIDTH-1:0]		wr_data	,		// 写数据总线
    		input					wr_en	,		// 写使能
    		output  reg				wr_full	,		// 写满标志
    
    		//read interface
    		input					rdclk	,		// 读时钟
    		input					rd_rst_n,		// 读指针复位
    		input					rd_en	,		// 读使能
    		output	[WIDTH-1:0]		rd_data	,		// 读数据输出
    		output	reg				rd_empty		// 读空标志
        );
    
    
    	// 写时钟域信号定义
    	reg [PTR:0] wr_bin		;					// 二进制写地址
    	reg [PTR:0] wr_gray		;					// 格雷码写地址
    	reg [PTR:0] rd_gray_ff1 ;					// 格雷码读地址同步寄存器1
    	reg [PTR:0] rd_gray_ff2 ;					// 格雷码读地址同步寄存器2
    	reg [PTR:0] rd_bin_wr	;					// 同步到写时钟域的二进制读地址
    
    
    	// 读时钟域信号定义
    	reg [PTR:0] rd_bin		;					// 二进制读地址
    	reg [PTR:0] rd_gray		;					// 格雷码读地址
    	reg [PTR:0] wr_gray_ff1 ;					// 格雷码写地址同步寄存器1
    	reg [PTR:0] wr_gray_ff2 ;					// 格雷码写地址同步寄存器2
    	reg [PTR:0] wr_bin_rd	;					// 同步到读时钟域的二进制写地址
    
    
    	// 解格雷码电路循环变量
    	integer i ;
    	integer j ;
    
    
    	// DPRAM控制信号
    	wire  				dpram_wr_en		 ;		// DPRAM写使能
    	wire [PTR-1:0]		dpram_wr_addr    ;		// DPRAM写地址
    	wire [WIDTH-1:0] 	dpram_wr_data	 ;		// DPRAM写数据
    	wire  				dpram_rd_en		 ;		// DPRAM读使能
    	wire [PTR-1:0]		dpram_rd_addr    ;		// DPRAM读地址
    	wire [WIDTH-1:0] 	dpram_rd_data	 ;		// DPRAM读数据
    
    
    
    	// ******************************** 写时钟域 ******************************** //
    	// 二进制写地址递增
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if (!wr_rst_n) begin
    			wr_bin <= 'b0;
    		end
    		else if ( wr_en == 1'b1 && wr_full == 1'b0 ) begin
    			wr_bin <= wr_bin + 1'b1;
    		end
    		else begin
    			wr_bin <= wr_bin;
    		end
    	end
    
    
    	// 写地址:二进制转格雷码
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if (!wr_rst_n) begin
    			wr_gray <= 'b0;
    		end
    		else begin
    			wr_gray <= { wr_bin[PTR], wr_bin[PTR:1] ^ wr_bin[PTR-1:0] };
    		end
    	end
    
    
    	// 格雷码读地址同步至写时钟域
    	always @(posedge wrclk or posedge wr_rst_n) begin
    		if(!wr_rst_n) begin
    			rd_gray_ff1 <= 'b0;
    			rd_gray_ff2 <= 'b0;
    		end
    		else begin
    			rd_gray_ff1 <= rd_gray;
    			rd_gray_ff2 <= rd_gray_ff1;
    		end
    	end
    
    
    	// 同步后的读地址解格雷
    	always @(*) begin
    		rd_bin_wr[PTR] = rd_gray_ff2[PTR];
    		for ( i=PTR-1; i>=0; i=i-1 )
    			rd_bin_wr[i] = rd_bin_wr[i+1] ^ rd_gray_ff2[i];
    	end
    
    
    	// 写时钟域产生写满标志
    	always @(*) begin
    		if( (wr_bin[PTR] != rd_bin_wr[PTR]) && (wr_bin[PTR-1:0] == rd_bin_wr[PTR-1:0]) ) begin
    			wr_full = 1'b1;
    		end
    		else begin
    			wr_full = 1'b0;
    		end
    	end
    
    
    	// ******************************** 读时钟域 ******************************** //
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if (!rd_rst_n) begin
    			rd_bin <= 'b0;
    		end
    		else if ( rd_en == 1'b1 && rd_empty == 1'b0 ) begin
    			rd_bin <= rd_bin + 1'b1;
    		end
    		else begin
    			rd_bin <= rd_bin;
    		end
    	end
    
    
    	// 读地址:二进制转格雷码
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if (!rd_rst_n) begin
    			rd_gray <= 'b0;
    		end
    		else begin
    			rd_gray <= { rd_bin[PTR], rd_bin[PTR:1] ^ rd_bin[PTR-1:0] };
    		end
    	end
    
    
    	// 格雷码写地址同步至读时钟域
    	always @(posedge rdclk or posedge rd_rst_n) begin
    		if(!rd_rst_n) begin
    			wr_gray_ff1 <= 'b0;
    			wr_gray_ff2 <= 'b0;
    		end
    		else begin
    			wr_gray_ff1 <= wr_gray;
    			wr_gray_ff2 <= wr_gray_ff1;
    		end
    	end
    
    
    	// 同步后的写地址解格雷
    	always @(*) begin
    		wr_bin_rd[PTR] = wr_gray_ff2[PTR];
    		for ( j=PTR-1; j>=0; j=j-1 )
    			wr_bin_rd[j] = wr_bin_rd[j+1] ^ wr_gray_ff2[j];
    	end
    
    
    	// 读时钟域产生读空标志
    	always @(*) begin
    		if( wr_bin_rd == rd_bin )
    			rd_empty = 1'b1;
    		else
    			rd_empty = 1'b0;
    	end
    
    
    	// RTL双口RAM例化
    	DPRAM
    		# ( .WIDTH(16), .DEPTH(16), .ADDR(4) )
    		U_DPRAM
    		(
    			.wrclk		(wrclk		 	),
    			.rdclk		(rdclk			),
    			.rd_rst_n   (rd_rst_n		),
    			.wr_en 		(dpram_wr_en	),
    			.rd_en 		(dpram_rd_en	),
    			.wr_data 	(dpram_wr_data	),
    			.rd_data 	(dpram_rd_data	),
    			.wr_addr 	(dpram_wr_addr	),
    			.rd_addr 	(dpram_rd_addr	)
    		);
    
    
    	// 产生DPRAM读写控制信号
    	assign dpram_wr_en   = ( wr_en == 1'b1 && wr_full == 1'b0 )? 1'b1 : 1'b0;
    	assign dpram_wr_data = wr_data;
    	assign dpram_wr_addr = wr_bin[PTR-1:0];
    
    	assign dpram_rd_en   = ( rd_en == 1'b1 && rd_empty == 1'b0 )? 1'b1 : 1'b0;
    	assign rd_data = dpram_rd_data;
    	assign dpram_rd_addr = rd_bin[PTR-1:0];
    
    
    endmodule
    

    以及testbench代码

    `timescale 1ns / 1ns
    //
    // Company: Xidian University
    // Engineer: Xumingwei
    // 
    // Create Date: 2020/11/06 23:54:40
    // Design Name: ASFIFO's testbench
    // Module Name: ASFIFO_tb
    // Project Name: 
    // Target Devices: 
    // Tool Versions: 
    // Description: 异步FIFO的激励文件
    // 
    // Dependencies: 
    // 
    // Revision:
    // Revision 0.01 - File Created
    // Additional Comments:
    // 
    //
    
    
    module ASFIFO_tb;
    
    	parameter WIDTH = 16;
    	parameter PTR   = 4;
    
    	// 写时钟域tb信号定义
    	reg					wrclk		;
    	reg					wr_rst_n	;
    	reg	[WIDTH-1:0]		wr_data		;
    	reg 				wr_en		;
    	wire				wr_full		;
    
    	// 读时钟域tb信号定义
    	reg					rdclk		;
    	reg					rd_rst_n	;
    	wire [WIDTH-1:0]	rd_data		;
    	reg					rd_en		;
    	wire				rd_empty	;
    
    	// testbench自定义信号
    	reg					init_done	;		// testbench初始化结束
    
    
    
    	// FIFO初始化
    	initial	begin
    		// 输入信号初始化
    		wr_rst_n  = 1	;
    		rd_rst_n  = 1	;
    		wrclk 	  = 0	;
    		rdclk 	  = 0	;
    		wr_en 	  = 0	;
    		rd_en 	  = 0	;
    		wr_data   = 'b0 ;
    		init_done = 0	;
    
    		// FIFO复位
    		#30 wr_rst_n = 0;
    			rd_rst_n = 0;
    		#30 wr_rst_n = 1;
    			rd_rst_n = 1;
    
    		// 初始化完毕
    		#30 init_done = 1;
    	end
    
    
    
    	// 写时钟
    	always
    		#2 wrclk = ~wrclk;
    
    	// 读时钟
    	always
    		#4 rdclk = ~rdclk;
    
    
    
    	// 读写控制
    	always @(*) begin
    		if(init_done) begin
    			// 写数据
    			if( wr_full == 1'b1 )begin
    				wr_en = 0;
    			end
    			else begin
    				wr_en = 1;
    			end
    		end
    	end
    
    	always @(*) begin
    		if(init_done) begin
    			// 读数据
    			if( rd_empty == 1'b1 )begin
    				rd_en = 0;
    			end
    			else begin
    				rd_en = 1;
    			end
    		end
    	end
    
    
    
    	// 写入数据自增
    	always @(posedge wrclk) begin
    		if(init_done) begin
    			if( wr_full == 1'b0 )
    				wr_data <= wr_data + 1;
    			else
    				wr_data <= wr_data;
    		end
    		else begin
    			wr_data <= 'b0;
    		end
    	end
    
    
    
    	// 异步fifo例化
    	ASFIFO
    		# ( .WIDTH(16), .PTR(4) )
    		U_ASFIFO
    		(
    			.wrclk	 	(wrclk		),
    			.wr_rst_n	(wr_rst_n	),
    			.wr_data	(wr_data	),
    			.wr_en		(wr_en		),
    			.wr_full	(wr_full	),
    
    			.rdclk		(rdclk		),
    			.rd_rst_n	(rd_rst_n	),
    			.rd_data	(rd_data	),
    			.rd_en		(rd_en		),
    			.rd_empty	(rd_empty	)
    		);
    
    
    endmodule
    

     Modelsim 仿真波形如下:

    可见读数据对写数据实现了多比特跨时钟域处理,这是异步FIFO的一个常用用途。



    八、拓展问题

    1、对格雷码编码电路打拍和跨时钟域的打两拍会导致其中一个时钟域跨过来的地址数据滞后,这样会导致FIFO工作异常吗?

    不会。在写时钟域中,写地址是本地数据,无延迟,读地址是跨时钟域而来,有一定的延迟。“写满” 的判断条件是读写指针高位不同,其他位相同,当 “写满” 信号出现时,意味着写指针和读指针的低四位已经相同,而此时的读指针可能不是真正的读指针,也就是假设参与判断的读指针是5时,实际的读指针可能已经到8了,这说明读出的数据更多了,也就是 “写满” 标志来早了,其实这并不是真正的写满,而是 “假满” 。

    在读时钟域中,读地址是本地数据,无延迟,写地址是跨时钟域而来,有一定的延迟。“读空” 的判断条件是读写指针相同,当 “读空” 信号出现时,意味着读指针和写指针已经相同,而此时的写指针可能不是真正的写指针,也就是假设参与判断的写指针是3时,实际的写指针可能已经到6了,这说明写入的数据更多了,也就是 “读空” 标志来早了,其实这并不是真正的读空,而是 “假空” 。

    但 “写满” 和 “读空” 标志来早并不会影响异步FIFO的正常工作,仅相当于FIFO的有效深度减小了,也可以看做是对FIFO的一种保护,防止写溢出或读溢出。

     

    2、异步FIFO的读写时钟频率不同,在进行两级跨时钟同步时,慢时钟会对快时钟域下的数据产生 “漏采” 现象,这会影响异步FIFO的功能吗?

    不会。当读慢写快时,将格雷码格式的写指针同步到读时钟域,可能会发生漏采现象。比如写指针从1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8,而读时钟仅采样到了2 -> 4 -> 6,但这些漏掉的读指针并不会影响FIFO的逻辑功能,当读时钟采样到6时,它以为采样到了最新的数据6,而实际的最新的数据已经写到了8,和第一个问题一样,这也会让 “读空” 标志来早,从而保护FIFO。

     

     

     

    展开全文
  • 实现异步FIFO的基本原理总结一、概述二、基本原理1、写满与读空2、格雷编码三、FIFO实现方案1、整体模块划分2、读写FIFO控制子模块内部框图 一、概述 前文中我们通过调用XILINX提供的FIFO IP核熟悉了FIFO的具体功能...

    一、概述

    前文中我们通过调用XILINX提供的FIFO IP核熟悉了FIFO的具体功能,后续我们将用verilog HDL自己实现一个异步FIFO,更彻底地搞懂FIFO的基本原理。我们知道整体设计是具体实现的前提,因此在用HDL实现异步FIFO前,将后续实现FIFO中涉及的基本知识和FIFO内部组成模块设计等内容总结如下。

    二、基本原理

    1、写满与读空

    在上文中,FIFO有两个非常重要的输出信号,w_full(写满信号):即写指针追上对读指针;和r_empty(读空信号),即读指针追上写指针,FIFO读写使能可以用这个两个信号做为其中之一的判断条件。一般我们将FIFO深度的地址空间扩展1位,用该扩展位来判断写满还是读空。
    例如:以深度为16的FIFO为例,地址空间用4bit表示,扩展后为5bit,因此写满或读空信号的判断条件可以表示为:
    当{~w_addr[4],w_addr[3:0]}==r_addr[4:0]时为写满;
    当w_addr[4:0]=r_addr[4:0]时为读空;
    由于FIFO空间是循环寻址的,这也是FIFO深度为2的整数次幂的原因。

    2、格雷编码

    异步FIFO读写时钟非同源,在判断写满或读空时,涉及到跨时钟域读取多bit的地址信息,因此需要对w_addr和r_addr进行格雷编码,根据格雷码的特性前后传输的数据只有单bit不同,因此可以按照单bit信号打两拍的方式大幅降低亚稳态(通过时钟沿采集信号时不满足信号建立时间和保持时间要求,采集信号高低电平随机出现或出现振荡情况)出现的概率。
    格雷编码具体实现比较简单:
    assign w_gaddr=(waddr>>1)&waddr。
    由于需要先地址信号进行了格雷编码,因此需要注意写满或读空信号的判断条件为:
    当{~w_gaddr[4:3],w_gaddr[2:0]}==r_gaddr[4:0]时为写满;
    当w_gaddr[4:0]=r_gaddr[4:0]时为读空。

    三、FIFO实现方案

    1、整体模块划分

    FIFO内部主要由读写FIFO控制模块和双口RAM模块组成,如下图所示。其中写RAM的使能信号w_ram_en由写数据使能和写满信号共同决定,当写满时后不能再向RAM中写数据。
    在这里插入图片描述

    2、读写FIFO控制子模块内部框图

    FIFO控制子模块内部实现框图如下图所示。其中,格雷编码模块为组合逻辑,使用两个寄存器的目的是输出的读写RAM地址与生成的格雷编码地址保持同步。
    在这里插入图片描述
    需要注意的是:w_gaddr和r_addr需要打两拍才能避免亚稳态,虽然是两拍之前的状态,但是读写指针还可以继续移动,仍然可以判断读空或写满状态。

    展开全文
  • 同步FIFO与异步FIFO的基本原理

    千次阅读 2020-11-18 10:49:22
    FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据, 其数据地址由内部读写指针...
  • 这里写自定义目录标题异步FIFO读和写使用格雷码传输指针 异步FIFO 本文代码参考Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc 异步FIFO是读写时钟...
  • 异步fifo时序原理

    2017-09-07 10:56:59
    在大规模ASIC或FPGA设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使相异时钟域数据传输的时序...
  • FPGA之异步FIFO

    2021-04-15 20:54:16
    FPGA之异步FIFO篇1 异步FIFO的用途1.1 解决读写时钟不一致的数据传输1.2 解决数据宽度不一致的接口问题2 异步FIFO的关键原理3 异步FIFO代码 1 异步FIFO的用途 异步FIFO的用途可以总结为两块: 1.1 解决读写时钟不...
  • 异步FIFO,Verilog源码

    2018-10-31 10:22:54
    异步FIFO,Verilog源码实现异步FIFO异步FIFO原理,
  • 当年的获奖论文啊,公认的经典 经典英文 CummingsSNUG2002SJ_FIFO1.pdf
  • 本设计以对大量实时采集数据进行缓存为背景,硬件采用Micron公司的1GB SODIMM DDR3 和Kintex-7系列FPGA的片上FIFO,软件通过研究DDR3的基本工作原理编写用户接口模块,同时结合片上FIFO的控制模块完成异步FIFO缓存...
  • 根据FIFO工作的时钟域分为同步/异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟在时钟沿来临时同时发生读写。异步FIFO读写时钟不一致,读写相互独立。读写指针的工作原理读指针:总是指向下一个将要读取的单元,...
  • 异步fifo设计,有同步有异步,有verilog,有vhdl。
  • 设计一个异步FIFO,深度为8,位宽也是8. 代码是学习Simulation and Synthesis Techniques for Asynchronous FIFO Design Clifford E. Cummings, Sunburst Design, Inc.这篇文章的 一、FIFO介绍 FIFO是英文First I
  • 异步FIFO的设计原理如图: 其中主要的难点在于空满标志的产生,这里采用方法(1),其中地址宽度较FIFO深度所需地址宽度宽1位: 空标志产生:将写地址waddr--> 转换为格雷码waddr_gray-->两级同步至读时钟...
  • 异步FIFO是一种在电子系统中得到广泛应用的器件,多数情况下它都是以一个独立芯片的方式在系统中应用。本文介绍一种充分利用FPGA内部的RAM资源,在FPGA内部实现异步FIFO模块的设计方法。这种异步FIFO比外部FIFO 芯片...
  • 介绍同步FIFO原理,并且提供了verilog源代码;详细介绍了异步FIFO原理和两种实现方法,并提供verilog源代码。
  • 异步FIFO.pdf

    2020-02-26 14:26:21
    异步FIFO的工作原理、同步化分析处理、跨时钟域解决方案以及fpga代码示例讲解,适合初学者学习使用。
  • 数字设计中经常设计到跨时钟域同步的问题,其中最为广泛采用的方法就是异步fifo实现多数据同步,文档里提供了一种实现方法。
  • 异步FIFO设计原理及Verliog源代码

    万次阅读 多人点赞 2015-12-04 21:08:28
    1.什么是FIFOFIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址...
  • 异步fifo简介

    千次阅读 2019-07-18 21:57:20
    在大规模ASIC或FPGA设计中,多时钟系统往往是不可避免的,这样就产生了不同时钟域数据传输的问题,其中一个比较好的解决方案就是使用异步FIFO来作不同时钟域数据传输的缓冲区,这样既可以使相异时钟域数据传输的时序...
  • 异步FIFO原理以及可综合的Verilog代码 一、FIFO的定义 二、FIFO的应用场景 三、FIFO的分类 FIFO的参数 FIFO的设计难点 一、FIFO的定义 _ First In First Out 【FIFO】先进先出的缓存器,它与普通存储器的区别是...
  • 异步FIFO的全面理解

    2021-05-26 15:33:32
    异步FIFO的组成及原理 异步FIFO主要有四个部分,读写时钟、读写指针、读写数据、空满标志。 复位的时候,读写指针都为0,读指针总是指向要读的位置,读完自加1,写指针总是指向要写的下一个位置,写完自加1。当写...
  • 计算 FIFO 深度是设计 FIFO 中常遇到的问题。常识告诉我们,当读速率慢于写速率时(瞬时速率),FIFO 便可被用作系统中的缓冲元件或队列。FIFO 的大小取决于读写数据的...异步 FIFO 最小深度计算原理 IFO 用于缓...
  • 同步FIFO与异步FIFO

    千次阅读 2019-11-28 18:07:36
    FIFO一般用于不同时钟域之间的数据传递,FIFO根据FIFO工作的时钟域,可以分为同步FIFO与异步FIFO:同步FIFO是读时钟与写时钟为同一时钟,在时钟上升沿同时发生读写操作。异步FIFO是读写时钟为不同时钟,读写时钟彼此...
  • FIFO(四):异步FIFO的最小深度计算

    万次阅读 多人点赞 2019-05-31 18:10:55
    1.1异步FIFO最小深度计算原理 1.2 异步FIFO最小深度常用计算公式 1. 2.1假如读写FIFO是同时进行的 1.2.2 读写FIFO不是同时进行的情况 2. 异步FIFO最小深度计算实例 2.1 用于SDRAM中的读写FIFO 2.2异步时钟数据...
  • 5-异步FIFO结构.pdf

    2020-06-27 12:59:21
    该文件主要是包括了FIFO原理以及设计结构,包括相应的设计代码。对几种FIFO的设计结构进行了充分的阐述,是找工作准备期间比较好的借鉴资料。
  • 计算FIFO深度是设计FIFO中常遇到的问题。常识告诉我们,当读速率慢于写速率时,FIFO便可被用作系统...一、异步FIFO最小深度计算原理 如果数据流连续不断则FIFO深度无论多少,只要读写时钟不同源同频则都会丢数; FI...
  • 异步FIFO的一些小事·0】异步FIFO同步化设计

    千次阅读 多人点赞 2017-09-03 22:09:03
    异步FIFO设计

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,830
精华内容 6,332
关键字:

异步fifo原理