异步FIFO原理及其实现

目录

    • 一、异步FIFO的重要参数及其作用
    • 二、设计要点
    • 三、源代码及仿真

本次设计主要介绍异步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”而得名。
格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
在同步时,出现亚稳态的概率也比二进制低。

二进制转换为格雷码
异步FIFO原理及其实现_第1张图片
Verilog代码描述:gray = binary ^ (binary >> 1)

异步FIFO原理及其实现_第2张图片
注:以上两幅关于格雷码的图片均不是原创,不知道出处,若有侵权,请联系删除

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原理及其实现_第3张图片
异步FIFO原理及其实现_第4张图片
异步FIFO原理及其实现_第5张图片

写比读快
异步FIFO原理及其实现_第6张图片

异步FIFO原理及其实现_第7张图片

异步FIFO原理及其实现_第8张图片

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

你可能感兴趣的:(Verilog设计实例,verilog,fpga,fifo)