verilog 异步fifo设计及仿真

一、核心思想

FIFO即先进先出,不能随机读写随即地址的数据,只能挨个读写。异步fifo是指读写时钟不同,读和写分别在各自的时钟域下,适用于不同时钟域下的数据传输。缓冲等。

二、读写时序

1、写操作

当写信号有效且fifo非满就写到写地址对应的位

2、读操作

当读信号有效且fifo非空既可以进行读地址对应数据

三、设计难点

1、如何判断FIFO的空满

  • 根据网上的例子,常规方法都是在读写地址指针加一个额外的状态位。当读或写够一轮状态位就取反。
  • 当写地址指针与读地址指针低八位完全相等,而写地址领先读地址一轮,则表示写满了。(二进制情况下)
  • 当写地址指针与读地址指针低八位完全相等,而读地址追上了写地址,则表示读空了。(二进制与格雷码情况下)
  • 但是将二进制转换成格雷码后判断满的标志则为,格雷码状态下高两位相反,其余相同

2、如何解决不同时钟下两个地址指针的比较,降低亚稳态

  • 利用格雷码进行地址指针的判断:因为格雷码每加1,只有一位在变。
  • 加两级寄存器,将读地址转换为格雷码再同步到写时钟域下,判断fifo是否为满;将写地址转化为格雷码同步到读时钟域下,判断fifo是否为空。

四、代码

fifo_async.v

module fifo_async#(parameter dwidth = 8,awidth = 4)(
	input 						wclk	,
	input 						rclk	,
	input						alrst	,//全局复位 读写指针归0,数据归0
	input 						wr_req	,
	input						rd_req	,
	input  		[dwidth-1:0]	wr_data	,
	output	reg [dwidth-1:0]	rd_data	,
	output						empty	,
	output						full	,
	output		[awidth:0]		cnt
);
	reg			[awidth:0]		wr_addr_p;//9位的写地址指针
	reg			[awidth:0]		rd_addr_p;//9位的读地址指针
	reg			[awidth:0]		rd_addr_p0;
	reg			[awidth:0]		rd_addr_p1;			
	
	wire		[awidth-1:0]	wr_addr	;//8位写地址
	wire		[awidth-1:0]	rd_addr	;//8位读地址
		
	wire		[awidth:0]		wr_addr_p_gray;
	reg			[awidth:0]		wr_addr_p_gray0;//同步到读时钟域下的格雷码写地址指针
	reg			[awidth:0]		wr_addr_p_gray1;
	wire		[awidth:0]		rd_addr_p_gray;//同步到写时钟域下的读地址指针
	reg			[awidth:0]		rd_addr_p_gray0;
	reg			[awidth:0]		rd_addr_p_gray1;
	wire		[awidth:0]		fifo_depth		;	
	reg			[dwidth-1:0]	fifo_ram[{(awidth){1'b1}}:0];
	integer 					i				;
	
	//fifo_depth
	assign fifo_depth = {(awidth){1'b1}}+1'b1;//计算出fifo的深度,以便后面计算
	
	//wr_addr_p
	always @(posedge wclk or posedge alrst)begin
		if(alrst)begin
			wr_addr_p <= 1'b0;
		end
		else if(wr_req&&!full)begin//当写请求有效且非满就可以进行写操作
			wr_addr_p <= wr_addr_p + 1'b1;
		end
		else 
			wr_addr_p <= wr_addr_p;
	end
	
	//rd_addr_p
	always @(posedge rclk or posedge alrst)begin
		if(alrst)begin
			rd_addr_p <= 1'b0;
		end
		else if(rd_req&&!empty)begin
			rd_addr_p <= rd_addr_p + 1'b1;//当读请求有效且非空就可以进行读操作
		end
		else 
			rd_addr_p <= rd_addr_p;
	end
	
	//wr_addr
	assign wr_addr = wr_addr_p[awidth-1:0];//把低位给地址
	
	//rd_addr
	assign rd_addr = rd_addr_p[awidth-1:0];//把低位给地址
	
		//格雷码转换
	assign wr_addr_p_gray 	= (wr_addr_p>>1)^wr_addr_p;
	assign rd_addr_p_gray 	= (rd_addr_p>>1)^rd_addr_p;
	
	
	//将读指针同步到写指针域下来进行full判断
	always @(posedge wclk or posedge alrst)begin
		if(alrst)begin
			rd_addr_p_gray0 <= 'b0;
			rd_addr_p_gray1 <= 'b0;
		end
		else 
			rd_addr_p_gray0 <= rd_addr_p_gray;
			rd_addr_p_gray1 <= rd_addr_p_gray0;
	end
	
	//将写指针同步到读指针域下来进行empty判断
	always @(posedge rclk or posedge alrst)begin
		if(alrst)begin
			wr_addr_p_gray0 <= 1'b0;
			wr_addr_p_gray1 <= 1'b0;
		end
		else 
			wr_addr_p_gray0 <= wr_addr_p_gray;
			wr_addr_p_gray1 <= wr_addr_p_gray0;
	end
	
	//将读指针同步到写时钟下
	always @(posedge wclk or posedge alrst)begin
		if(alrst)begin
			rd_addr_p0 <= 1'b0;
			rd_addr_p1 <= 1'b0;
		end
		else 
			rd_addr_p0 <= rd_addr_p;
			rd_addr_p1 <= rd_addr_p0;
	end
	
	//在读时钟域下计算写指针与读指针的差即为数据量
	assign cnt = wr_addr_p - rd_addr_p1;
	

	
	//当写领先读一轮,则说明满了;当读赶上写,则说明空了。
	assign full = (wr_addr_p_gray=={~rd_addr_p_gray1[awidth-:2],rd_addr_p_gray1[awidth-2:0]})?1:0;
	assign empty = (rd_addr_p_gray==wr_addr_p_gray1)?1:0;
	
	//wr_data
	always @(posedge wclk or posedge alrst)begin
		if(alrst)begin
			for(i=0;i<=fifo_depth;i=i+1)
				fifo_ram[i] <= {(dwidth){1'b0}};
		end
		else if(wr_req&&!full)begin
			fifo_ram[wr_addr] <= wr_data;
		end	
	end
	//rd_data
	always @(posedge rclk or posedge alrst)begin
		if(alrst)begin
			rd_data <= 1'b0;
		end
		else if(rd_req&&!empty)begin
			rd_data <= fifo_ram[rd_addr];
		end	
	end
	
endmodule

fifo_async_tb.v

`timescale 1ns/1ps
module fifo_async_tb();
	reg 					tb_wclk		;
    reg 					tb_rclk		;
    reg						tb_alrst	;
    reg 					tb_wr_req	;
    reg						tb_rd_req	;
    reg  	[7:0]			tb_wr_data	;
    wire	[7:0]			tb_rd_data	;
    wire					tb_empty	;
    wire					tb_full		;
	wire	[4:0]			tb_cnt     	;
	
	fifo_async u_fifo_async(
	.wclk					(tb_wclk	),
	.rclk					(tb_rclk	),
	.alrst					(tb_alrst	),
	.wr_req					(tb_wr_req	),
	.rd_req					(tb_rd_req	),
	.wr_data				(tb_wr_data	),
	.rd_data				(tb_rd_data	),//数据量
	.empty					(tb_empty	),
	.full					(tb_full	),
	.cnt                    (tb_cnt		)     
	
	
);
	defparam u_fifo_async.dwidth = 8,
			 u_fifo_async.awidth = 4;
	
	parameter   WR_CLOCK_CYCLE = 20,//50Mhz的写时钟
				RD_CLOCK_CYCLE = 10;//100Mhz的读时钟
	
	initial tb_wclk = 1'b0;//初始写化时钟
	always #(WR_CLOCK_CYCLE/2) tb_wclk = ~tb_wclk;
	
	initial tb_rclk = 1'b1;//初始读化时钟
	always #(RD_CLOCK_CYCLE/2) tb_rclk = ~tb_rclk;
	
	integer j=0;
	initial begin
		tb_alrst = 1'b0;
		tb_wr_req = 1'b0;
		tb_rd_req = 1'b0;
		tb_wr_data = 8'd0;
		#(20*WR_CLOCK_CYCLE);
		tb_alrst = 1'b1;
		#(20*WR_CLOCK_CYCLE);
		tb_alrst = 1'b0;
		
		repeat(10)begin
			for(j=0;j<10;j = j+1)begin
				#2;
				tb_wr_req = {$random};
				tb_wr_data = {$random}%255;
				#(WR_CLOCK_CYCLE*2);
			end
			for(j=0;j<20;j = j+1)begin
				#2;
				tb_rd_req = {$random};
				#(RD_CLOCK_CYCLE*2);
			end
		end

	$stop;
	end
endmodule

五、总结

1、异步时钟下的数据交互和对比很容易产生亚稳态,在这里先将二进制转换为格雷码在进行跨时钟域同步,并且是加了两级寄存器来进行同步,能很好得消除亚稳态。
2、当二进制最高位相反,其余位相同时,其对应的格雷码是高两位相反,其余相同。这一点要注意。!!!
3、仿真还是不太靠谱,可能会有隐藏的问题没看出来。
4、tb文件写的不太合理,当fifo的空满标志位拉高时,还在送数据进去。
5、数据余量那边算的不太对,应该用打拍后的格雷码回转成二进制再进行计算的,后续会改正---------2021.6.20

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值