异步FIFO设计

异步FIFO的概念

对于实现异步FIFO的技术方式来说,其重难点在于其空满信号的判断,它涉及到内部跨时钟域细节的实现、格雷码的转换以及空满信号的比较

FIFO的实现架构框图仅供参考

异步FIFO的RTL实现

通过RTL实现异步FIFO之前,需要明白异步FIFO的几个重要的参数,也是我们设计的重点:

  • FIFO的深度:通俗地说,就是异步FIFO可以存多少个数据的意思!
  • FIFO的宽度:上面说异步FIFO的深度是表示能存放多少数据的概念,那宽度便是每个数据有多少位,也就是我们通常所说的数据有多宽!
  • FIFO空:表示FIFO里面数据被读完了;
  • FIFO满:表示FIFO里面填满了数据;
  • FIFO写指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0);
  • FIFO读指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0);
  • FIFO读时钟:表示读取数据使用的时钟,一般设计时钟的上升沿为有效沿,有效沿读取数据;
  • FIFO写时钟:表示写入数据时使用的时钟,一般上升沿为有效沿,当然也可以设计下降沿为有效沿

第一个问题:读写指针与空满条件的判断之间的关系

解决方案:

增加一位表示读写指针,例如FIFO的深度为8,我们原来用3位表示读写指针即可,但是我们增加到4位,这样只要读写指针的最高位不相等,即便二者剩下的其他位相等也不能表示指针相等,也就是不能说FIFO为空,相反FIFO为满

第二个问题:判断FIFO的空满,需要读写指针跨时钟域传输(读写指针的跨时钟域问题)

怎么处理呢?由于读写指针有多位,对于多比特数据的CDC问题,我们一般不会直接两级同步过去,两级同步适用于单比特变化的数据!

但是如上图的实现方式,好像还就是两级同步,这是什么原因呢?

如果非要使用两级寄存器同步的方式,我们就要控制每次只有1比特数据发生变化,如何实现呢?

解决方案

使用格雷码对读写指针计数值进行编码

补:

二进制计数器实现指针的弊端

读写时钟分别和各自的时钟同步,彼此异步,使用二进制计数器实现指针,在空满信号指针比较的时候,由于计数器的值发生多位翻转,提高了亚稳态的可能性,错误的指针会带来错误的数据传输。因此,强烈避免使用二进制计数器实现读写指针

二进制转换为格雷码的方法:

 assign gray_value = binary_value ^ (binary_value>>1);//伪代码

第三个问题:读写指针的格雷码形式判断空满

二进制码转换成了格雷码并跨时钟域到了另一个时钟域,那接下来就是读写指针的格雷码形式的对比了,二进制的对比很简单,就是如果二者所有的位全部相等, 则表示空;如果二者最高位不同,但其他位相同,则表示满

解决方案

格雷码判断空满的方法:

对于空的判断:格雷码完全相同表示空

对于满的判断:最高位,次高位都不同,但是其余位相同

    RTL:
		module asyn_fifo#(
		    parameter DATA_WIDTH = 8,
		    parameter DATA_DEPTH = 32
		    )(
		    // write port
		    input   wr_clk,
		    input   wr_rst,
		    input   wr_en,
		    input   [DATA_WIDTH-1:0] wr_data,
		    output  reg wr_full,
		    
		    //read port
		    input   rd_clk,
		    input   rd_rst,
		    input   rd_en,
		    output  reg   [DATA_WIDTH-1:0] rd_data,
		    output  reg rd_empty    
		    );
		    
		   reg [DATA_WIDTH-1:0] fifo_buffer [0:DATA_DEPTH-1];
		    //增加一位来判断读写指针的空满状态
		   reg [$clog2(DATA_DEPTH):0] wr_pointer;
		   reg [$clog2(DATA_DEPTH):0] rd_pointer; 
		   
		   
		   always@(posedge wr_clk) begin
		        if(wr_en) begin         
		            if(wr_pointer<16)
		                fifo_buffer[wr_pointer] <= wr_data;     
		            else
		                fifo_buffer[wr_pointer-16] <= wr_data;                
		        end
		   end
		   
		   always@(posedge rd_clk) begin
		      if(rd_en) begin    
		        if(rd_pointer<16)
		            rd_data <= fifo_buffer[rd_pointer];     
		        else
		            rd_data <= fifo_buffer[rd_pointer-16];  
		      end     
		   end    
		 
		
		
		  //用格雷码来判断空满
		  //1、二进制转换为格雷码
		   wire [$clog2(DATA_DEPTH):0] wr_ptr_g;
		   wire [$clog2(DATA_DEPTH):0] rd_ptr_g;   
		  
		   assign wr_ptr_g = wr_pointer ^ (wr_pointer>>>1);
		   assign rd_ptr_g = rd_pointer ^ (rd_pointer>>>1);
		   
		   //2、跨时钟域同步读写指针
		   
		   reg [$clog2(DATA_DEPTH):0] wr_ptr_gr,wr_ptr_grr;
		   reg [$clog2(DATA_DEPTH):0] rd_ptr_gr,rd_ptr_grr; 
		   
		   always@(posedge wr_clk) begin
		        if(wr_rst) 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
		   
		   always@(posedge rd_clk) begin
		        if(rd_rst) 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@(rd_clk) begin
				if(rd_rst) 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
		
		
			//rd_pointer after gray coding synchronize into  write clock region
			always@(wr_clk) begin
				if(wr_rst) 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
			*/
		
		   //3、读写指针的格雷码形式判断空满
		   //判空
		   always@(posedge rd_clk) begin
		        if(rd_rst)
		            rd_empty <= 0;
		        else if(rd_ptr_g == wr_ptr_grr)
		            rd_empty <= 1;
		        else
		            rd_empty <= 0;
		   end
		   //判满
		   always@(posedge wr_clk) begin
		        if(wr_rst)
		            wr_full <= 0;
		        else if(wr_ptr_g[$clog2(DATA_DEPTH)]!= rd_ptr_grr[$clog2(DATA_DEPTH)] && 
		                wr_ptr_g[$clog2(DATA_DEPTH)-1]!= rd_ptr_grr[$clog2(DATA_DEPTH)-1]         &&
		                wr_ptr_g[$clog2(DATA_DEPTH)-2:0]== rd_ptr_grr[$clog2(DATA_DEPTH)-2:0])
		                wr_full <= 1;  
		        else
		            wr_full <= 0;               
		   end   
		 
		  
		//对写满的限制
		always@(posedge wr_clk or posedge wr_rst) begin
		    if(wr_rst) begin
		        wr_pointer <= 0;
		    end
		    else if(wr_en) begin
		       if(!((rd_ptr_grr[$clog2(DATA_DEPTH) - 2 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 2 : 0])
		 			&& ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) && ( rd_ptr_grr[$clog2(DATA_DEPTH) - 1] != wr_ptr_g[$clog2(DATA_DEPTH) - 1] ))) begin
		           wr_pointer <= wr_pointer + 1;
		        end
		        else begin
		            wr_pointer <= wr_pointer;
		        end
		    end
		    else  begin
		        wr_pointer <= wr_pointer;
		    end
		end
		
		
		
		//对读空的限制
		always@(posedge rd_clk or posedge rd_rst) begin
		    if(rd_rst) begin
		        rd_pointer <= 0;
		    end
		    else if(rd_en) begin
		        if(wr_ptr_grr != rd_ptr_g) begin
		            rd_pointer <= rd_pointer + 1;
		        end
		        else begin
		            rd_pointer <= rd_pointer;
		        end
		    end
		    else  begin
		        rd_pointer <= rd_pointer;
		    end
		
		end
		
		endmodule
		
		
		
		
		
		
		
	Testbench:
		`timescale 1ns / 1ps
		
		module tb_asyn_fifo();
		
		    parameter DATA_WIDTH = 8;
		    parameter DATA_DEPTH = 16;
		
		    //write ports    
			reg wr_clk;
			reg wr_rst;
			reg wr_en;
			reg [DATA_WIDTH - 1 : 0] wr_data;
			wire full;
		    
		    //read ports
			reg rd_clk;
			reg rd_rst;
			reg rd_en;
			wire [DATA_WIDTH - 1 : 0] rd_data;
			wire empty;
		
		
		
		    initial begin
		        wr_clk = 0;
		        forever begin
		            #2 wr_clk = ~wr_clk;
		        end
		    end
		
		    initial begin
		        rd_clk = 0;
		        forever begin
		            #5 rd_clk = ~rd_clk;
		        end
		    end
		
		    initial begin
		        wr_rst = 1'b1;
		        rd_rst = 1'b1;
		        wr_en = 1'b0;
		        rd_en = 1'b0;
		
		        #10
		        wr_rst = 0;
		        rd_rst = 0;
		
		        #10
		        wr_en = #(0.2) 1'b1;
		        wr_data = #(0.2) $random; 
		        repeat(5) begin
		            @(posedge wr_clk);
		                wr_data = #(0.2) $random;  
		        end
		
		        @(posedge wr_clk); 
		        wr_en = #(0.2) 1'b0;
		        wr_data = #(0.2) $random;
		
		        #10
		        rd_en = #(0.2) 1'b1;
		        repeat(5) begin
		            @(posedge rd_clk);  
		        end
		
		        @(posedge rd_clk);
		        rd_en = #(0.2) 1'b0;
		
		        #10
		        wr_en = #(0.2) 1'b1;
		        wr_data = #(0.2) $random; 
		        repeat(16) begin
		            @(posedge wr_clk);  
		                wr_data = #(0.2) $random;
		        end
		        @(posedge wr_clk); 
		        wr_en = #(0.2) 1'b0;
		        wr_data = #(0.2) $random;                
		
		
		    end
		
		
		
		
		asyn_fifo#(
		    .DATA_WIDTH ( DATA_WIDTH ),
		    .DATA_DEPTH ( DATA_DEPTH )
		)u_asyn_fifo(
		    .wr_clk     ( wr_clk     ),
		    .wr_rst     ( wr_rst     ),
		    .wr_en      ( wr_en      ),
		    .wr_data    ( wr_data    ),
		    .wr_full       ( full       ),
		    .rd_clk     ( rd_clk     ),
		    .rd_rst     ( rd_rst     ),
		    .rd_en      ( rd_en      ),
		    .rd_data    ( rd_data    ),
		    .rd_empty      ( empty      )
		);
		
		
		
		
		
		endmodule
		
		
		

 

 

 

 

 

 Double sync 之前必须在前一级时钟域register out(格雷码)

 最好是寄存器输出(full/empty  wrdy/rrdy)

问题补充:异步fifo,source端频率很快会出错吗?

  • 由于快采慢,CDC转换不会有什么问题。因此满的判断不会有问题。
  • 判断空的方法是拿read_pointer和做完CDC转换过来的write_pointer做比较。由于慢采快,读到的write_pointer是离散的,但只要是格雷码跨CDC,跨过去的值只可能比真实值相等或者小,不可能比真实值大,所以此时的空信号是很保守的空(也可以叫假空)!也就是说异步fifo明明还有数,读时钟域就判断出空,暂时停止读取数据。但是这并不会导致出错,因为这种保守的空判断,只是降低了读的效率,并没有导致读出错误的数据或者不存在的数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay丶ke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值