异步FIFO

2 篇文章 0 订阅

异步FIFO

  异步FIFO的写时钟和读时钟为异步时钟,FIFO内部的写逻辑和读逻辑的交互需要异步处理,异步FIFO常用于跨时钟域交互。异步FIFO的设计重点有两个,第一个就是跨时钟域的转换及同步;第二个就是状态信号的产生采用相对保守的方式。

异步FIFO接口

异步 FIFO的输入/输出 ,如下图所示:
在这里插入图片描述
异步FIFO端口说明:

  • wclk : 该时钟为异步FIFO写操作的工作时钟。
  • w_rst_ : 该信号为异步FIFO的写时钟域复位信号,低电平有效。
  • wren : 该信号为异步FIFO的写使能。
  • full : 该信号为FIFO已满标志。如果 FIFO 为满状态,则禁止再写数据。
  • wdata : 该总线为写数据总线。
  • rclk : 该时钟为异步FIFO读操作的工作时钟。
  • r_rst_: 该信号为异步FIFO的读时钟域复位信号,低电平有效。
  • rden: 该信号为异步FIFO的读使能。
  • empty: 该信号为异步FIFO的空标志。
  • rdata: 该总线为读数据总线。

   FIFO还提供其他标识,如almost_full和almost_empty,用于提供关于FIFO再写人多少会满以及再读出多少会空的信息。

异步FIFO结构

   异步FIFO基本上分为7个部分,分别是写时钟域的地址管理、读时钟域的地址管理、读时钟域读地址到写时钟域的格雷码方式同步、写时钟域写地址到读时钟域的格雷码方式同步、写时钟域的满和将满信号的产生、读时钟域的空和将空信号产生以及FIFO Memory。
   
在这里插入图片描述

   

异步FIFO设计思想

   复位后,读和写地址指针均指在0地址处,同时almost empty 和empty信号均有效。
   在写时钟域,当写请求wren有效时,如果此时写时钟域的状态正常(即还未满),则RAM写使能信号wr_ram有效,数据将会写入RAM里,同时在下一个clk原来的写地址加1,指向下一个写地址。
   在读时钟域侧,当读请求rden有效时,如果此时读时钟域的状态正常(即还未空),则RAM读使能信号rd_ram有效,数据将会从RAM里被读出,同时在下一个clk原来的读地址加1,指向下一个读地址。
   在写时钟域FIFO状态的判断及产生是使用了格雷码同步的方法实现多比特跨时钟域的转换、把读地址从读时钟rd_clk同步到wr_clk,然后判断写地址和读地址之间的差从而判断FIFO是否被写满。
   在读时钟域FIFO状态的判断及产生也使用了格雷码同步的方法实现多比特跨时钟域的转换、把写地址从写时钟wr_clk到rd_clk的转换,然后判断写地址和读地址之差进而得出FIFO是否为空的结论。

双口RAM接口

   在 FIFO 中常用的RAM包括单口RAM、简单双口RAM、真双口RAM、单口ROM、双口ROM这5种类型的RAM,也可以使用寄存器来实现FIFO的存储器。寄存器实现的方式比较适合深度和位宽较小的情况,一般在FPGA里还是推荐使用Block RAM来实现异步FIFO的存储器。这里介绍简单双口RAM来做FIFO内部的RAM,双口RAM的接口如下图所示。
在这里插入图片描述
双口RAM端口说明:

  1. wclk : 该时钟为双口RAM写操作的工作时钟。
  2. wren : 该信号为双口RAM的写使能。
  3. waddr : 该总线为双口RAM的写地址总线。
  4. wdata : 该总线为双口RAM的写数据总线。
  5. rclk : 该时钟为双口RAM读操作的工作时钟。
  6. rden : 该信号为双口RAM的读使能。
  7. raddr : 该总线为双口RAM的读地址总线。
  8. rdata: 该总线为双口RAM的读数据总线。

实现代码

module  dual_port_RAM #(
    parameter DEPTH = 16,
    parameter WIDTH = 8
)(
	input                           wclk ,   //写数据时钟
	input                           wenc ,   //写使能
	input    [$clog2(DEPTH)-1:0]    waddr,   //写地址
	input    [WIDTH-1:0]            wdata,   //输入数据
	input                           rclk ,   //读数据时钟
	input                           renc ,   //读使能
	input    [$clog2(DEPTH)-1:0]    raddr,   //读地址
	output  reg    [WIDTH-1:0]      rdata 	 //输出数据
);


    /********************参数定义********************/
    
    /*********************IO 说明********************/

    /********************** 内部信号声明 **********************/
    reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];  //RAM大小定义
	
    /*************************功能定义*************************/	
    //RAM写操作
    always @(posedge wclk) 
    begin
    	if(wenc)
    		RAM_MEM[waddr] <= wdata;
    end 
  
	//RAM读操作
    always @(posedge rclk) 
    begin
    	if(renc)
    		rdata <= RAM_MEM[raddr];
    end 
    
    endmodule  

地址管理

写时钟域的地址管理

   主要在写时钟域中产生FIFO Memory写地址和写有效信号,当写使能有效且FIFO没有写满时,FIFO写地址的递增应。实现代码如下:

	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (wren && !full)
			w_bin_waddr <= w_bin_waddr + 1'd1;
		else
		    w_bin_waddr <= w_bin_waddr;
	end

读时钟域的地址管理

   读控制逻辑主要产生FIFO Memory读地址和读有效信号,当读使能有效且FIFO没有读空时,FIFO读地址的递增应。实现代码如下:

	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (rden && !empty)
			r_bin_raddr <= r_bin_raddr + 1'd1;
		else
		    r_bin_raddr <= r_bin_raddr;
	end

格雷码方式同步

格雷码

二进制码转换为格雷编通用电路

    二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。N位二进制码到N位格雷码的生成公式为:
{ G i = B i ⨁ B i + 1     ( i = 0 , 1 , . . . , n − 2 ) G n − 1 = B n − 1 \begin{cases}G_i = B_i \bigoplus B_{i+1} \ \ \ (i=0,1,...,n-2) \\ G_{n-1}=B_{n-1} \end{cases} {Gi=BiBi+1   (i=0,1,...,n2)Gn1=Bn1
实现代码如下:

module  binary_to_gray#(parameter	WIDTH = 8)
(
	input 	  [WIDTH-1:0] 	 binary_value,  //二进制编码数值

	output    [WIDTH-1:0]    gray_value     //格雷编码数值
);

	/*************************功能定义*************************/	
	generate 
	    genvar i;
		for(i=0;i<(WIDTH-1);i=i+1)
		begin: gray
		    assign    gray_value[i] = binary_value[i]^binary_value[i+1];
		end
    endgenerate
	
	assign   gray_value[WIDTH-1] = binary_value[WIDTH-1];
	
endmodule
格雷编转换为二进制码通用电路

   二进制格雷码转换成二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。
{ B n − 1 = G n − 1 B i = B i + 1 ⨁ G i     ( i = n − 2 , . . . , 1 ) \begin{cases} B_{n-1}=G_{n-1}\\B_{i} = B_{i+1} \bigoplus G_{i} \ \ \ (i=n-2,...,1) \end{cases} {Bn1=Gn1Bi=Bi+1Gi   (i=n2,...,1)
实现代码如下:

module  gray_to_binary #(parameter	WIDTH = 8)
(
	input 	  [WIDTH-1:0] 	 gray_value,     //格雷编码数值

	output    [WIDTH-1:0]    binary_value    //二进制编码数值
);


	/*************************功能定义*************************/	
    genvar i;
	generate
		for(i=0;i<(WIDTH-1);i=i+1)
		begin: binary
		    assign    binary_value[i] = gray_value[i]^binary_value[i+1];
		end
    endgenerate 
	
	assign   binary_value[WIDTH-1] = gray_value[WIDTH-1];
	
endmodule

格雷码方式同步原理

   由于异步FIFO读写时钟非同一个,那么写地址计数值在从写时钟域往读时钟域和读地址计数器在从读时钟域往写时钟域传递的过程中会存在重汇聚(re-convergence)的问题。异步FIFO的地址进行跨时钟域的电路示意图如下:
   
在这里插入图片描述
时序图如下所示在这里插入图片描述
   当FIFO地址的多bit信号在源时钟域同时翻转时,在夸时钟域传输时,由于线路延时的不同(Delay 0 和Delay 1 不同),造成各bit信号到达目的时钟域寄存器的时间不一致,一旦目的时钟正好在他们错开的时刻进行采样,就会出现错误。如上图中,本来传递过来的正确信号是11,但最后在目的时钟域采样成了10。
   而为了解决这样的问题就需要在进行跨时钟域之前将信号变成格雷码,但是必须保证进行跨时钟域之前的信号对应的二进制在源时钟域有效沿每次只会加1,这样其对应的格雷码才会保证每次只有1bit翻转。格雷码常用于总线地址信号,memory地址信号的跨时钟域传输。
   下图为格雷码与二进制编码的对应关系。可以看到二进制编码相邻转换会出现同时多bit翻转的时候,比如从0001->0010,低两位同时翻转;但是格雷码相邻状态转换每次只有一位发生翻转,就算因为延时问题出现出现采用错误,也只会是出现一种错误。例如二进码从0001跳转到0010,对应的格雷码是从0001跳变到0011,只有第二位发生了翻转,其在目的时钟域(读时钟域)被采样只有两种可能,分别是0001,0011。如果被采到了0001也不会出错,顶多就是在读时钟域觉得没有新数据写入进去(因为传过来的写地址没有变),但后来这个新的地址总会正确到来。只是满一点而已,不会造成异步FIFO的数据流错乱或数据丢失。
在这里插入图片描述
   Gray编码可以有效解决异步FIFO读/写地址采样不稳定的问题,提高系统的可靠性;缺点是有延迟(可提前判断来补偿),电路门数、复杂度增加。但是,用Gray编码来解决该问题也是异步FIFO设计最常用的方法。

写时钟域写地址到读时钟域的格雷码方式同步代码实现

	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U1
	(
	    .binary_value    (w_bin_waddr),
	    .gray_value      (w_gray_waddr) 
	);
    //写时钟域寄存
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    w_gray_waddr_reg <= w_gray_waddr;
	
	
	end
	//读时钟域两级同步
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		begin
		    r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    r_gray_waddr_sync1 <= w_gray_waddr_reg ;
			r_gray_waddr_sync2 <= r_gray_waddr_sync1 ;
		end
	end

读时钟域读地址到写时钟域的格雷码方式同步代码实现

//二进制码转格雷码
	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U2
	(
	    .binary_value    (r_bin_raddr),
	    .gray_value      (r_gray_raddr)
	);
    //读时钟域寄存
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    r_gray_raddr_reg <= r_gray_raddr;
	
	
	end
	//写时钟域两级同步
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		begin
		    w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    w_gray_raddr_sync1 <= r_gray_raddr_reg ;
			w_gray_raddr_sync2 <= w_gray_raddr_sync1 ;
		end
	end
	

空满标志判断

   当读写地址指针在复位操作期间被重置为零时,或者当读指针在从FIFO中读取了最后一个字之后赶上写指针时,此时读指针和写指针相等代表着FIFO为读空。而当写指针再次追赶上读指针时,此时读指针和写指针相等代表着FIFO为写满。也就是说当读写指针相等时,FIFO要么为空,要么为满。
   因此将地址位扩展一位,地址为二进制编码时,则用最高位来判断空满,其余低位还是正常用于读写地址索引。当写指针递增超过FIFO的最大地址时,写指针的MSB位将置为1,同时将其余低位设置回零。读指针也是如此。如果读指针和写指针的MSB不同,则意味着写指针比读指针多绕了一次,表示FIFO写满。如果两个指针的MSB相同,则表示两个指针的回绕次数相同,表示FIFO读空。采用二进制码时电路结构如下所示:
在这里插入图片描述
当判断almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志时,
当未回卷时(即读写地址最高位相同):未写入数据的地址数量 = FIFO深度 + radder - waddr
当回卷时(即读写地址最高位不相同):未写入数据的地址数量 = radder[N-2:0 ]- wadder[N-2:0],其中[N-2:0]代表除去最高位,剩余的低N-1位;
当未回卷时(即读写地址最高位相同):写入数据的地址数量 = wadder - raddr
当回卷时时(即读写地址最高位不相同):写入数据的地址数量 = FIFO深度 +wadder[N-2:0 ]- radder[N-2:0],其中[N-2:0]代表除去最高位,剩余的低N-1位;
在这里插入图片描述

   若不产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志。则可以在将地址扩展一位后,用地址为格雷码格雷码编码时,当写地址和读地址的格雷码的最高位和次高位相反,其余低位相同时,表示FIFO写满,当写地址和地读地址的格雷码的相同时,表示FIFO读空。采用格雷码时电路结构如下所示:
在这里插入图片描述

空满标志判断代码:

这里采用读写地址的格雷码进行比较,判断空满标志。实现代码如下所示:

	/*写满标志判断*/
	assign full = ({~w_gray_waddr_reg[ADDR_WIDTH:ADDR_WIDTH-1],w_gray_waddr_reg[ADDR_WIDTH-2:0]} == w_gray_raddr_sync2)?1'b1:1'b0;
	
	/*读空标志判断*/
	assign empty = (r_gray_raddr_reg == r_gray_waddr_sync2)? 1'b1:1'b0;

异步FIFO代码

实现一

不产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志采用读写地址的格雷码进行比较,判断空满标志;

module  async_fifo #(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk    , //写时钟
	input                   rclk    , //读时钟
	input 					w_rst_ 	, //写时钟域异步复位,低电平有效
	input 					r_rst_ 	, //写时钟域异步复位,低电平有效	
	input 					wren	, //写使能
	input 			 		rden	, //写使能
	input 		[WIDTH-1:0]	wdata	, //写数据

	output wire				full	, //写满信号
	output wire				empty	, //读空信号
	output wire [WIDTH-1:0]	rdata     //读数据
);

    /********************参数定义********************/

    /*********************IO 说明********************/

	/********************** 内部信号声明 **********************/
	localparam    ADDR_WIDTH = $clog2(DEPTH);   //地址位宽
	
	wire                        wenc              ;       //双端口RAM写使能
	wire                        renc              ;  	  //双端口RAM读使能
	reg     [ADDR_WIDTH:0]      w_bin_waddr       ;       //写地址(二进制)
	reg     [ADDR_WIDTH:0]      r_bin_raddr       ;       //读地址(二进制)
    wire    [ADDR_WIDTH:0]      w_gray_waddr      ;       //写地址(格雷码)
    wire    [ADDR_WIDTH:0]      r_gray_raddr      ;       //读地址(格雷码)
	reg     [ADDR_WIDTH:0]      w_gray_waddr_reg  ;       //写地址(格雷码)写时钟域暂存寄存器
	reg     [ADDR_WIDTH:0]      r_gray_raddr_reg  ;       //读地址(格雷码)读时钟域暂存寄存器
	reg     [ADDR_WIDTH:0]      r_gray_waddr_sync1;       //写地址(格雷码)写时钟域到读时钟域第一级同步
	reg     [ADDR_WIDTH:0]      r_gray_waddr_sync2;       //写地址(格雷码)写时钟域到读时钟域第二级同步
	reg     [ADDR_WIDTH:0]      w_gray_raddr_sync1;       //读地址(格雷码)读时钟域到写时钟域第一级同步
	reg     [ADDR_WIDTH:0]      w_gray_raddr_sync2;       //读地址(格雷码)读时钟域到写时钟域第二级同步
	/*************************功能定义*************************/	
    
	assign    wenc = wren && !full;
	assign    renc = rden && !empty;
	
	/*双端口RAM*/
    dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
	dual_port_RAM_U1
	(
	    .wclk     (wclk       ),   //写数据时钟
	    .wenc     (wenc       ),   //写使能
	    .waddr    (w_bin_waddr),   //写地址
	    .wdata    (wdata      ),   //输入数据
	    .rclk     (rclk       ),   //读数据时钟
	    .renc     (renc       ),   //读使能
	    .raddr    (r_bin_raddr),   //读地址
	    .rdata    (rdata      )    //输出数据
    );
   
   /*写控制逻辑*/
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (wren && !full)
			w_bin_waddr <= w_bin_waddr + 1'd1;
		else
		    w_bin_waddr <= w_bin_waddr;
	end
		
	/*读控制逻辑*/
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (rden && !empty)
			r_bin_raddr <= r_bin_raddr + 1'd1;
		else
		    r_bin_raddr <= r_bin_raddr;
	end
	
	/*写地址从写时钟域到读时钟域的格雷码方式同步*/
	//二进制码转格雷码
	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U1
	(
	    .binary_value    (w_bin_waddr),
	    .gray_value      (w_gray_waddr) 
	);
    //写时钟域寄存
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    w_gray_waddr_reg <= w_gray_waddr;
	
	
	end
	//读时钟域两级同步
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		begin
		    r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    r_gray_waddr_sync1 <= w_gray_waddr_reg ;
			r_gray_waddr_sync2 <= r_gray_waddr_sync1 ;
		end
	end
	
	/*读时钟域读地址到写时钟域的格雷码方式同步*/
	//二进制码转格雷码
	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U2
	(
	    .binary_value    (r_bin_raddr),
	    .gray_value      (r_gray_raddr)
	);
    //读时钟域寄存
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    r_gray_raddr_reg <= r_gray_raddr;
	
	
	end
	//写时钟域两级同步
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		begin
		    w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    w_gray_raddr_sync1 <= r_gray_raddr_reg ;
			w_gray_raddr_sync2 <= w_gray_raddr_sync1 ;
		end
	end
	
	/*写满标志判断*/
	assign full = ({~w_gray_waddr_reg[ADDR_WIDTH:ADDR_WIDTH-1],w_gray_waddr_reg[ADDR_WIDTH-2:0]} == w_gray_raddr_sync2)?1'b1:1'b0;
	
	/*读空标志判断*/
	assign empty = (r_gray_raddr_reg == r_gray_waddr_sync2)? 1'b1:1'b0;
	
endmodule

实现二

产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志采用读写地址的二进制码进行比较,判断空满标志;

module  async_fifo #(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16,
	parameter 	ALMOST_FULL_GAP = 3,  //离满还有ALMOST_FULL_GAP时,almost_full有效
	parameter 	ALMOST_EMPTY_GAP = 3  //离满还有ALMOST_EMPTY_GAP时,almost_empty有效
)(
	input 					wclk        , //写时钟
	input                   rclk        , //读时钟
	input 					w_rst_ 	    , //写时钟域异步复位,低电平有效
	input 					r_rst_ 	    , //写时钟域异步复位,低电平有效	
	input 					wren	    , //写使能
	input 			 		rden	    , //写使能
	input 		[WIDTH-1:0]	wdata	    , //写数据
    
	output wire				almost_full	, //将满信号
	output wire				almost_empty, //将空信号
	output wire				full	    , //写满信号
	output wire				empty	    , //读空信号
	output wire [WIDTH-1:0]	rdata         //读数据
);

    /********************参数定义********************/

    /*********************IO 说明********************/

	/********************** 内部信号声明 **********************/
	localparam    ADDR_WIDTH = $clog2(DEPTH);   //地址位宽
	
	wire                        wenc              ;       //双端口RAM写使能
	wire                        renc              ;  	  //双端口RAM读使能
	reg     [ADDR_WIDTH:0]      w_bin_waddr       ;       //写地址(二进制)
	reg     [ADDR_WIDTH:0]      r_bin_raddr       ;       //读地址(二进制)
    wire    [ADDR_WIDTH:0]      w_gray_waddr      ;       //写地址(格雷码)
    wire    [ADDR_WIDTH:0]      r_gray_raddr      ;       //读地址(格雷码)
	reg     [ADDR_WIDTH:0]      w_gray_waddr_reg  ;       //写地址(格雷码)写时钟域暂存寄存器
	reg     [ADDR_WIDTH:0]      r_gray_raddr_reg  ;       //读地址(格雷码)读时钟域暂存寄存器
	reg     [ADDR_WIDTH:0]      r_gray_waddr_sync1;       //写地址(格雷码)写时钟域到读时钟域第一级同步
	reg     [ADDR_WIDTH:0]      r_gray_waddr_sync2;       //写地址(格雷码)写时钟域到读时钟域第二级同步
	reg     [ADDR_WIDTH:0]      w_gray_raddr_sync1;       //读地址(格雷码)读时钟域到写时钟域第一级同步
	reg     [ADDR_WIDTH:0]      w_gray_raddr_sync2;       //读地址(格雷码)读时钟域到写时钟域第二级同步
	wire    [ADDR_WIDTH:0]      r_bin_waddr       ;       //读时钟域二进制写地址
	wire    [ADDR_WIDTH:0]      w_bin_raddr       ;       //写时钟域二进制读地址
	reg     [ADDR_WIDTH:0]      w_bin_raddr_reg   ;       //写时钟域读地址(二进制)暂存寄存器
	reg     [ADDR_WIDTH:0]      r_bin_waddr_reg   ;       //读时钟域写地址(二进制)暂存寄存器
    reg     [ADDR_WIDTH:0]      room_avail        ;       //FIFO内剩余空间
	reg     [ADDR_WIDTH:0]      data_avail        ;       //FIFO内已存入数据个数
	
	/*************************功能定义*************************/	
    /*RAM写使能*/
	assign    wenc = wren && !full;
	/*RAM读使能*/
	assign    renc = rden && !empty;
	
	/*双端口RAM*/
    dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH))
	dual_port_RAM_U1
	(
	    .wclk     (wclk       ),   //写数据时钟
	    .wenc     (wenc       ),   //写使能
	    .waddr    (w_bin_waddr),   //写地址
	    .wdata    (wdata      ),   //输入数据
	    .rclk     (rclk       ),   //读数据时钟
	    .renc     (renc       ),   //读使能
	    .raddr    (r_bin_raddr),   //读地址
	    .rdata    (rdata      )    //输出数据
    );
   
   /*写控制逻辑*/
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (wren && !full)
			w_bin_waddr <= w_bin_waddr + 1'd1;
		else
		    w_bin_waddr <= w_bin_waddr;
	end
		
	/*读控制逻辑*/
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}};
		else if (rden && !empty)
			r_bin_raddr <= r_bin_raddr + 1'd1;
		else
		    r_bin_raddr <= r_bin_raddr;
	end
	
	/*写地址从写时钟域到读时钟域的格雷码方式同步*/
	//二进制码转格雷码
	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U1
	(
	    .binary_value    (w_bin_waddr),
	    .gray_value      (w_gray_waddr) 
	);
    //写时钟域写地址(格雷码)寄存
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    w_gray_waddr_reg <= w_gray_waddr;
	end
	//读时钟域两级同步
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		begin
		    r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    r_gray_waddr_sync1 <= w_gray_waddr_reg ;
			r_gray_waddr_sync2 <= r_gray_waddr_sync1 ;
		end
	end
	//格雷码转二进制
	gray_to_binary #(.WIDTH(ADDR_WIDTH+1))
    gray_to_binary_U1
	(
	    .gray_value      (r_gray_waddr_sync2), 
	    .binary_value    (r_bin_waddr)

	);
	//读时钟域写地址(二进制)寄存
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_bin_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    r_bin_waddr_reg <= r_bin_waddr;
	end
	
	/*读时钟域读地址到写时钟域的格雷码方式同步*/
	//二进制码转格雷码
	binary_to_gray #(.WIDTH(ADDR_WIDTH+1))
	binary_to_gray_U2
	(
	    .binary_value    (r_bin_raddr),
	    .gray_value      (r_gray_raddr)
	);
    //读时钟域寄存
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    r_gray_raddr_reg <= r_gray_raddr;
	
	
	end
	//写时钟域两级同步
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		begin
		    w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ;
			w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ;
		end
		else
		begin
		    w_gray_raddr_sync1 <= r_gray_raddr_reg ;
			w_gray_raddr_sync2 <= w_gray_raddr_sync1 ;
		end
	end
	//格雷码转二进制
	gray_to_binary #(.WIDTH(ADDR_WIDTH+1))
    gray_to_binary_U2
	(
	    .gray_value      (w_gray_raddr_sync2), 
	    .binary_value    (w_bin_raddr)

	);
	//读时钟域写地址(二进制)寄存
	always@(posedge rclk or negedge r_rst_)
	begin
	    if(!r_rst_)
		    w_bin_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ;
		else
		    w_bin_raddr_reg <= w_bin_raddr;
	end
	
	/*FIFO内剩余空间计算*/
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    room_avail <= {(ADDR_WIDTH+1){1'b0}};
		else if(~w_bin_waddr[ADDR_WIDTH]==w_bin_raddr_reg[ADDR_WIDTH])//回卷时
		    room_avail <= w_bin_raddr_reg[ADDR_WIDTH-1:0]- w_bin_waddr[ADDR_WIDTH-1:0];
		else                                                          //未回卷时
		    room_avail <= DEPTH + w_bin_raddr_reg - w_bin_waddr;		    	    
	end
	
    /*FIFO内已存入数据个数计算*/
	always@(posedge wclk or negedge w_rst_)
	begin
	    if(!w_rst_)
		    data_avail <= {(ADDR_WIDTH+1){1'b0}};
		else if(~r_bin_raddr[ADDR_WIDTH]==r_bin_waddr_reg[ADDR_WIDTH])//回卷时
		    data_avail <= DEPTH + r_bin_waddr_reg[ADDR_WIDTH-1:0]- r_bin_raddr[ADDR_WIDTH-1:0];
		else                                                          //未回卷时
		    data_avail <= r_bin_waddr_reg - r_bin_raddr;		    	    
	end
	
	/*将满信号判断*/
	assign    almost_full = room_avail<ALMOST_FULL_GAP ? 1'b1:1'b0;
	
	/*将空信号判断*/
	assign    almost_empty = data_avail<ALMOST_EMPTY_GAP ? 1'b1:1'b0;	
	
	/*写满标志判断*/
	assign full = ({~w_bin_waddr[ADDR_WIDTH],w_bin_waddr[ADDR_WIDTH-1:0]} == w_bin_raddr_reg)?1'b1:1'b0;
	
	/*读空标志判断*/
	assign empty = (r_bin_raddr == r_bin_waddr_reg)? 1'b1:1'b0;
	
endmodule
  • 3
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值