一个DC FIFO的仿真实验

66 篇文章 23 订阅
28 篇文章 11 订阅

咱们FPGA里面开发遇到FIFO一般分单时钟和双时钟FIFO,双时钟FIFO是读写各自不同的时钟。由于时钟相位频率关系不可预知,所有也有人叫做异步FIFO。真正的异步FIFO不是这样,是没有时钟的,写入信号通过写信号的上升边缘实现,类似早期的asram。

双时钟FIFO的实现有两大重点:使用格雷码,跨域时钟域要打两个拍子。这其实不就是CDC的基本原则嘛。格雷码能保证多位中每次只有一个BIT发生变化,打两个拍子能防止亚稳态。这样能将跨越时钟的数据出错概率降到最低。这两个重点主要是用在信号传递部分,而具体的数值保存部分也有专门的硬件保证,就是双时钟RAM。读写双时钟的RAM如下实现:

module generic_dpram  #(
parameter aw = 4,
parameter dw= 8 
) (
	input 		rclk,
	input       rd_rst,
	input       rce,oe,
	
	input [aw-1:0]raddr,waddr, 
	output reg [dw-1:0] do,
	input wclk,wrst,wce,we,rrst,
	input [dw-1:0]di 
	);

localparam DEPTH = 1<<aw-1 ;
	
reg [7:0] mem[0:DEPTH-1] ; integer i;initial begin for (i=0;i<DEPTH;i=i+1) mem[i] =0; end 

always @ ( posedge wclk ) if (we ) mem[waddr]<=di;
always @ ( posedge rclk ) if (rce) do <= mem[raddr];

endmodule 

www.opencore.org上有一个FIFO的项目,内有多个FIFO实现,其中一个使用格雷码的双时钟FIFO,这篇文章咱们就仿真。

下面是这个双时钟FIFO的代码。

 上图是此FIFO实例化双时钟RAM,我们没有在项目里面找到具体的实现,所以写了我这篇文字开头的那个generic_dpram 。

其实输入应该没有什么可以看时序的,主要看输出的时序,也主要是看看re和dout的时序对应关系。

写一下测试平台,给两个不一样的时钟,设置FIFO的地址是4,位宽是8位,也就说FIFO有16个单元。写入5个数据,之后再读出5个数据。

 
`timescale 1ns/1ns

module tb ;

reg rst,clr,wr_clk,rd_clk,we,re;
initial  { rst,clr,wr_clk,rd_clk,we,re }= 0 ;

always #4 wr_clk = ~wr_clk ;
always #5 rd_clk = ~rd_clk ;

integer i ;

initial begin 

$dumpfile("tb_dc_fifo.vcd");
$dumpvars;

#10;
rst=1;clr=1;#10;
rst=0;clr=0;#10;

@(posedge wr_clk);
for(i=0;i<5;i=i+1) begin 
we=1;@(posedge wr_clk);
end 
we=0;  //write 5 data to fifo 

#200 ;
@(posedge rd_clk);
for(i=0;i<5;i=i+1) begin 
re=1;@(posedge rd_clk);
end 
re=0;  //read 5 data from fifo 

#200 ;
$finish ;




end 
wire [7:0]dout;
reg [7:0] din = 0 ; 
always @(posedge wr_clk) if (we)din<=din+1;
 //generic_fifo_dc   #(.aw(4),.dw(8)) generic_fifo_dc_gray (	
 generic_fifo_dc_gray #(.aw(4),.dw(8)) generic_fifo_dc_gray (	
 .rd_clk(rd_clk) , 
 .wr_clk(wr_clk) , 
 .rst(~rst) , 
 .clr(clr) , 
 .din(din ) , 
 .we(we) ,
 .dout(dout ) , 
 .re(re ) , 
 .full(full) , 
 .empty(empty )// , 
 //.wr_level(wr_level) , 
 //.rd_level(rd_level)  
 );
 
endmodule 
 

这里我再给出这个dcfifo的代码:

/
                                                             
  Universal FIFO Dual Clock, gray encoded                    
                                                             
                                                             
  Author: Rudolf Usselmann                                   
          rudi@asics.ws                                      
                                                             
                                                             
  D/L from: http://www.opencores.org/cores/generic_fifos/    
                                                             
/
                                                             
 Copyright (C) 2000-2002 Rudolf Usselmann                    
                         www.asics.ws                        
                         rudi@asics.ws                       
                                                             
 This source file may be used and distributed without        
 restriction provided that this copyright statement is not   
 removed from the file and that any derivative work contains 
 the original copyright notice and the associated disclaimer.
                                                             
     THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY     
 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED   
 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS   
 FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR      
 OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,         
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES    
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE   
 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR        
 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF  
 LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT  
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT  
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE         
 POSSIBILITY OF SUCH DAMAGE.                                 
                                                             
/



//  CVS Log
//
//  $Id: generic_fifo_dc_gray.v,v 1.2 2004-01-13 09:11:55 rudi Exp $
//
//  $Date: 2004-01-13 09:11:55 $
//  $Revision: 1.2 $
//  $Author: rudi $
//  $Locker:  $
//  $State: Exp $
//
// Change History:
//               $Log: not supported by cvs2svn $
//               Revision 1.1  2003/10/14 09:34:41  rudi
//               Dual clock FIFO Gray Code encoded version.
//
//
//
//
//


//`include "timescale.v"

/*

Description
===========

I/Os
----
rd_clk	Read Port Clock
wr_clk	Write Port Clock
rst	low active, either sync. or async. master reset (see below how to select)
clr	synchronous clear (just like reset but always synchronous), high active
re	read enable, synchronous, high active
we	read enable, synchronous, high active
din	Data Input
dout	Data Output

full	Indicates the FIFO is full (driven at the rising edge of wr_clk)
empty	Indicates the FIFO is empty (driven at the rising edge of rd_clk)

wr_level	indicates the FIFO level:
		2'b00	0-25%	 full
		2'b01	25-50%	 full
		2'b10	50-75%	 full
		2'b11	%75-100% full

rd_level	indicates the FIFO level:
		2'b00	0-25%	 empty
		2'b01	25-50%	 empty
		2'b10	50-75%	 empty
		2'b11	%75-100% empty

Status Timing
-------------
All status outputs are registered. They are asserted immediately
as the full/empty condition occurs, however, there is a 2 cycle
delay before they are de-asserted once the condition is not true
anymore.

Parameters
----------
The FIFO takes 2 parameters:
dw	Data bus width
aw	Address bus width (Determines the FIFO size by evaluating 2^aw)

Synthesis Results
-----------------
In a Spartan 2e a 8 bit wide, 8 entries deep FIFO, takes 97 LUTs and runs
at about 113 MHz (IO insertion disabled). 

Misc
----
This design assumes you will do appropriate status checking externally.

IMPORTANT ! writing while the FIFO is full or reading while the FIFO is
empty will place the FIFO in an undefined state.

*/


module generic_fifo_dc_gray(	rd_clk, wr_clk, rst, clr, din, we,
		dout, re, full, empty, wr_level, rd_level );

parameter dw=8;
parameter aw=4;

input			rd_clk, wr_clk, rst, clr;
input	[dw-1:0]	din;
input			we;
output	[dw-1:0]	dout;
input			re;
output			full; 
output			empty;
output	[1:0]		wr_level;
output	[1:0]		rd_level;


//
// Local Wires
//

reg	[aw:0]		wp_bin, wp_gray;
reg	[aw:0]		rp_bin, rp_gray;
reg	[aw:0]		wp_s, rp_s;
reg			full, empty;

wire	[aw:0]		wp_bin_next, wp_gray_next;
wire	[aw:0]		rp_bin_next, rp_gray_next;

wire	[aw:0]		wp_bin_x, rp_bin_x;
reg	[aw-1:0]	d1, d2;

reg			rd_rst, wr_rst;
reg			rd_rst_r, wr_rst_r;
reg			rd_clr, wr_clr;
reg			rd_clr_r, wr_clr_r;


//
// Reset Logic
//

always @(posedge rd_clk or negedge rst)
	if(!rst)	rd_rst <= 1'b0;
	else
	if(rd_rst_r)	rd_rst <= 1'b1;		// Release Reset

always @(posedge rd_clk or negedge rst)
	if(!rst)	rd_rst_r <= 1'b0;
	else		rd_rst_r <= 1'b1;

always @(posedge wr_clk or negedge rst)
	if(!rst)	wr_rst <= 1'b0;
	else
	if(wr_rst_r)	wr_rst <= 1'b1;		// Release Reset

always @(posedge wr_clk or negedge rst)
	if(!rst)	wr_rst_r <= 1'b0;
	else		wr_rst_r <= 1'b1;

always @(posedge rd_clk or posedge clr)
	if(clr)		rd_clr <= 1'b1;
	else
	if(!rd_clr_r)	rd_clr <= 1'b0;		// Release Clear

always @(posedge rd_clk or posedge clr)
	if(clr)		rd_clr_r <= 1'b1;
	else		rd_clr_r <= 1'b0;

always @(posedge wr_clk or posedge clr)
	if(clr)		wr_clr <= 1'b1;
	else
	if(!wr_clr_r)	wr_clr <= 1'b0;		// Release Clear

always @(posedge wr_clk or posedge clr)
	if(clr)		wr_clr_r <= 1'b1;
	else		wr_clr_r <= 1'b0;


//
// Memory Block
//

generic_dpram  #(.aw(aw),.dw(dw)) u0(
	.rclk(		rd_clk		),
	.rrst(		!rd_rst		),
	.rce(		1'b1		),
	.oe(		1'b1		),
	.raddr(		rp_bin[aw-1:0]	),
	.do(		dout		),
	.wclk(		wr_clk		),
	.wrst(		!wr_rst		),
	.wce(		1'b1		),
	.we(		we		),
	.waddr(		wp_bin[aw-1:0]	),
	.di(		din		)
	);


//
// Read/Write Pointers Logic
//

always @(posedge wr_clk)
	if(!wr_rst)	wp_bin <= {aw+1{1'b0}};
	else
	if(wr_clr)	wp_bin <= {aw+1{1'b0}};
	else
	if(we)		wp_bin <= wp_bin_next;

always @(posedge wr_clk)
	if(!wr_rst)	wp_gray <= {aw+1{1'b0}};
	else
	if(wr_clr)	wp_gray <= {aw+1{1'b0}};
	else
	if(we)		wp_gray <= wp_gray_next;

assign wp_bin_next  = wp_bin + {{aw{1'b0}},1'b1};
assign wp_gray_next = wp_bin_next ^ {1'b0, wp_bin_next[aw:1]};

always @(posedge rd_clk)
	if(!rd_rst)	rp_bin <= {aw+1{1'b0}};
	else
	if(rd_clr)	rp_bin <= {aw+1{1'b0}};
	else
	if(re)		rp_bin <= rp_bin_next;

always @(posedge rd_clk)
	if(!rd_rst)	rp_gray <= {aw+1{1'b0}};
	else
	if(rd_clr)	rp_gray <= {aw+1{1'b0}};
	else
	if(re)		rp_gray <= rp_gray_next;

assign rp_bin_next  = rp_bin + {{aw{1'b0}},1'b1};
assign rp_gray_next = rp_bin_next ^ {1'b0, rp_bin_next[aw:1]};


//
// Synchronization Logic
//

// write pointer
always @(posedge rd_clk)	wp_s <= wp_gray;

// read pointer
always @(posedge wr_clk)	rp_s <= rp_gray;


//
// Registered Full & Empty Flags
//

assign wp_bin_x = wp_s ^ {1'b0, wp_bin_x[aw:1]};	// convert gray to binary
assign rp_bin_x = rp_s ^ {1'b0, rp_bin_x[aw:1]};	// convert gray to binary

always @(posedge rd_clk)
        empty <= (wp_s == rp_gray) | (re & (wp_s == rp_gray_next));

always @(posedge wr_clk)
        full <= ((wp_bin[aw-1:0] == rp_bin_x[aw-1:0]) & (wp_bin[aw] != rp_bin_x[aw])) |
        (we & (wp_bin_next[aw-1:0] == rp_bin_x[aw-1:0]) & (wp_bin_next[aw] != rp_bin_x[aw]));


//
// Registered Level Indicators
//
reg	[1:0]		wr_level;
reg	[1:0]		rd_level;
reg	[aw-1:0]	wp_bin_xr, rp_bin_xr;
reg			full_rc;
reg			full_wc;

always @(posedge wr_clk)	full_wc <= full;
always @(posedge wr_clk)	rp_bin_xr <=  ~rp_bin_x[aw-1:0] + {{aw-1{1'b0}}, 1'b1};
always @(posedge wr_clk)	d1 <= wp_bin[aw-1:0] + rp_bin_xr[aw-1:0];

always @(posedge wr_clk)	wr_level <= {d1[aw-1] | full | full_wc, d1[aw-2] | full | full_wc};

always @(posedge rd_clk)	wp_bin_xr <=  ~wp_bin_x[aw-1:0];
always @(posedge rd_clk)	d2 <= rp_bin[aw-1:0] + wp_bin_xr[aw-1:0];

always @(posedge rd_clk)	full_rc <= full;
always @(posedge rd_clk)	rd_level <= full_rc ? 2'h0 : {d2[aw-1] | empty, d2[aw-2] | empty};


//
// Sanity Check
//

// synopsys translate_off
always @(posedge wr_clk)
	if(we && full)
		$display("%m WARNING: Writing while fifo is FULL (%t)",$time);

always @(posedge rd_clk)
	if(re && empty)
		$display("%m WARNING: Reading while fifo is EMPTY (%t)",$time);
// synopsys translate_on

endmodule



module generic_dpram  #(
parameter aw = 4,
parameter dw= 8 
) (
	input 		rclk,
	input       rd_rst,
	input       rce,oe,
	
	input [aw-1:0]raddr,waddr, 
	output reg [dw-1:0] do,
	input wclk,wrst,wce,we,rrst,
	input [dw-1:0]di 
	);

localparam DEPTH = 1<<aw-1 ;
	
reg [7:0] mem[0:DEPTH-1] ; integer i;initial begin for (i=0;i<DEPTH;i=i+1) mem[i] =0; end 

always @ ( posedge wclk ) if (we ) mem[waddr]<=di;
always @ ( posedge rclk ) if (rce) do <= mem[raddr];

endmodule 


之后在iverilog下仿真,并gtkwave 查看波形:

 这里看到写的状况,也可以看到wr_level和rd_level的变化。

wr_level	indicates the FIFO level:
		2'b00	0-25%	 full
		2'b01	25-50%	 full
		2'b10	50-75%	 full
		2'b11	%75-100% full

rd_level	indicates the FIFO level:
		2'b00	0-25%	 empty
		2'b01	25-50%	 empty
		2'b10	50-75%	 empty
		2'b11	%75-100% empty

empty和rd_level是rd_clk时钟域的信号,所以看上去没有和di对齐。

再看一下读FIFO的时序:

这里看到最关键的一点就是数据出现在FIFO读时钟之后一周期。empty伴随着最用一个输出出现。

这样一来时序就非常明确了,我们可以用在自己的代码里。

{{aAxvOXMOIvVUoXMxvoxiowMwWV8xxWTxoxOIOVIUUOvwVOUiIoUvvTMMVMwovWHWX8vOUOWOVOOTxiToVwToVO8UOMOxxUMiMoIiivmXIVWxix8vHOMoiHIoVU8VvmvIWXTvvOvv8xvMovOWMOOWUoMm8UiUZz}}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值