数字 06 verilog_关于异步FIFO

为了应对不同时钟域模块之间的通信,fifo诞生了。

fifo就是first-in-first-out,先进先出。

通常会使用写指针和读指针来判断fifo是空还是满,为了方便,一般设计会将指针设置为比位宽多1Bit,用最高位的数据来判断是写指针追上了读指针还是读指针追上了写指针。

先说一下自己掉的坑:

1、reg [width-1:0] mem[depth-1:0],这里的depth是mem中width位寄存器的总个数,一共有depth个而不是2的depth次方个。

比如depth是8,那么mem总共有8个width位宽的寄存器而不是256个。(怪我看书不认真)

2、搞清楚哪些是同步信号哪些是异步信号

3、

设计之前要知道两个概念

1、格雷码

2、缓冲(2级D触发器)

这两个概念的设计都是为了减少模块的亚稳态概率。

1、格雷码

简单来说,格雷码是源码左移1Bit然后和源码本身异或。

    格雷码的作用是避免亚稳态的产生,因为格雷码在源码从0一直+1到N时,格雷码每次只会变化本身的一个位。

第二列就是格雷码

用了格雷码之后,满信号和空信号的判别就需要重新思考了,这里直接放结论:

Gray码的最高位和次高位为空满标志位,满:读写地址的最高位和次高位相反,空:读写地址的最高位和次高位相同

2、两级D触发器缓冲

 

因为触发器进入亚稳态的时间,可以用参数 MTBF(Mean Time Between Failures,平均故障间隔时间)来描述,采用两级触发器可以让这个时间延长很久,也就是让故障的概率降低到很低很低。具体见:

为什么两级级联能降低亚稳态出现的概率

作者:龚黎明
链接:https://www.zhihu.com/question/43571892/answer/95940354
来源:知乎

 

模块设计框图

我自己设计的时候是按照模块来写的,先写了左边FIFO写模块,再写了右边FIFO读模块,然后是中间的FIFO存储器模块,最后是整个系统的连线,在连线的时候我发现,需要补上上图框图中的两根线,才是完整的。我的程序可以实现仿真,但是没有全面测试过,还在学习如何完善。

1、写模块

根据设计要求,设计了格雷码和2级d触发器。

module fifo_w(
	input wire wclk,			//fifo写模块时钟
	input wire wen,				//写使能
	input wire[8:0] wq2_rptr,	//从读模块传过来的读指针位置
	input wire wrst_n,			//复位信号
	
	output wire[8:0] wptr,		//写指针的位置
	output wire[8:0] waddr,		//写地址
	output wire wfull			//fifo已经被写满了

);
	reg[8:0] waddr_ptr;		//写地址	
	reg[8:0] dfilp1,dfilp2;	//两级D触发器,减小亚稳态发生
	
	
initial//初始置位
		begin

			waddr_ptr<=	9'b0_0000_0000;
			dfilp1	<=	9'b0_0000_0000;
			dfilp1	<=	9'b0_0000_0000;		
		end
	

always@(posedge wclk or negedge wrst_n)//上升沿
	begin
	
		dfilp1	<=	wq2_rptr;
		dfilp2	<=	dfilp1;	
		
		if(wrst_n==0)//复位
			begin
				waddr_ptr<=	9'b0_0000_0000;			
			end
		else if((wfull!=1)&&(wen==1))//正常写情况就地址指针+1
			begin
				waddr_ptr	<=	waddr_ptr+1;
			end
		else
			begin   //如果满了或者不允许写,那就地址指针位置不变
				waddr_ptr	<=	waddr_ptr;
			end
	end

assign 	wptr	=	(waddr_ptr>>1)^waddr_ptr;//生成格雷码
assign	wfull	=	((wptr[8]!=dfilp2[8])&&(wptr[7]!=dfilp2[7])&&(wptr<<2==dfilp2<<2))?1'b1:1'b0;//满信号判断
assign	waddr	=	waddr_ptr;//

Endmodule

写模块做了个小测试

格雷码正确

我把读地址写成了0,写指针转了一圈之后回来,正确产生了满信号

2、读模块

module fifo_r(
	input wire rclk,				//fifo读模块时钟
	input wire ren,				//读使能
	input wire[8:0] rq2_wptr,	//从写模块传过来的写指针位置
	input wire rrst_n,			//复位信号
	
	output wire[8:0] rptr,		//读指针的位置
	output wire[8:0] raddr,		//读地址
	output wire rempty			//fifo

);

	reg[8:0] raddr_ptr;		//读地址	
	reg[8:0] dfilp1,dfilp2;	//两级D触发器,减小亚稳态发生
	
	
initial//初始置位
		begin

			raddr_ptr<=	9'b0_0000_0000;
			dfilp1	<=	9'b0_0000_0000;
			dfilp1	<=	9'b0_0000_0000;		
		end
	

always@(posedge rclk or negedge rrst_n)//上升沿
	begin
	
		dfilp1	<=	rq2_wptr;
		dfilp2	<=	dfilp1;	
		
		if(rrst_n==0)//复位
			begin
				raddr_ptr<=	9'b0_0000_0000;
			
			end
		else if((rempty!=1)&&(ren==1))//允许读 且不是空状态,就读,然后指针+1
			begin
				raddr_ptr	<=	raddr_ptr+1;
			end
		else
			begin//不允许读 或者是空状态,就不变
				raddr_ptr	<=	raddr_ptr;
			end
	end

assign 	rptr	=	(raddr_ptr>>1)^raddr_ptr;//生成格雷码
assign	rempty	=	(rptr==dfilp2)?1'b1:1'b0;//空信号判断
assign	raddr	=	raddr_ptr;//

Endmodule

测试仿真

格雷码正确,也生成了空信号

3、存储器模块,双口ram

module fifo_dualram(

	input[7:0] wdata,
	input[7:0] waddr,
	input[7:0] raddr,
	
	input wfull,//满状态,1为满状态,0是不满的状态
	input rempty,//空状态,1为空状态,0是不空的状态
	
	input wen,//写使能,1为使能态
	input ren,//读使能,1为使能态

	input wclk,
	input rclk,
	
	input ramrst_n,//复位信号
	
	output reg[7:0] rdata
);

	wire wclken;//这个信号是(满信号取反)和(写使能)的逻辑与
	wire rclken;//这个信号是(空信号取反)和(读使能)的逻辑与
	reg[7:0] fifo_dualram[256:0];
	
//存储器初始化
genvar i;
generate 
	for(i=0;i<256;i=i+1)
		begin:initfifo
			initial
				begin
					fifo_dualram[i]	<=	8'h00;		
				end
		end

endgenerate
	
//输出数据初始化
initial
	begin
		rdata	<=	8'h00;
	end

//复位
genvar j;
generate 
	for(j=0;j<256;j=j+1)
		begin
			always@(negedge ramrst_n )	
				begin
					if(!ramrst_n)
						begin
							fifo_dualram[j]	<=	8'h00;		
						end
				end
		end
endgenerate	
//写模块
always@(posedge wclk)	
	begin
		if(wclken!=0)
			begin
				fifo_dualram[waddr]<=wdata;//如果可以写的正常状态,就写数据
			end
		else
			begin
				fifo_dualram[waddr]<=fifo_dualram[waddr];//否则内容不变
			end
	end
always@(posedge rclk)
	begin
		if(rclken!=0)
			begin
				rdata<=fifo_dualram[raddr];//如果可以读的正常状态,就读数据
			end	
		else
			begin
				rdata	<=	8'h00;//否则输出数据置0
			end
	end

assign wclken=(wen&(!wfull));//这个信号是(满信号取反)和(写使能)的逻辑与
assign rclken=(ren&(!rempty));//这个信号是(空信号取反)和(读使能)的逻辑与
endmodule 

按照地址的循序写了,01  03  中间02好像被跳过了,总是可以读得出来,循序也对

4、系统整合  模块

module sys_fifo(
	input wen,
	input wclk,
	input wrst_n,
	input[7:0] wdata,
	
	input ren,
	input rclk,
	input rrst_n,
	output[7:0] rdata,
	
	input ramrst_n
	

);


	wire[8:0] wq2_rptr;
	wire[8:0] rq2_wptr;
	
	wire[8:0] waddr;
	wire[8:0] raddr;
	
	wire wfull;
	wire rempty;
	
fifo_w w(
.wen(wen),
.wclk(wclk),
.wrst_n(wrst_n),
.wq2_rptr(wq2_rptr),
.wptr(rq2_wptr),
.waddr(waddr),
.wfull(wfull));

fifo_r r(
.ren(ren),
.rclk(rclk),
.rrst_n(rrst_n),
.rq2_wptr(rq2_wptr),
.rptr(wq2_rptr),
.raddr(raddr),
.rempty(rempty));

fifo_dualram dualram(
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wfull(wfull),
.wen(wen),
.ren(ren),
.rempty(rempty),
.wclk(wclk),
.rclk(rclk),
.ramrst_n(ramrst_n),
.rdata(rdata)
);
endmodule

	

5、简单的测试模块,tb

module tb_fifodualram;

	reg[7:0] wdata;
	wire[7:0] rdata;
	
	reg wen;
	reg wclk;
	reg wrst_n;
	
	reg ren;
	reg rclk;
	reg rrst_n;

	reg ramrst_n;
	
initial
	begin
		wclk	<=	1'b0;
		rclk	<=	1'b0;
		
		wen		<=	1'b0;
		ren		<=	1'b0;
		
		wrst_n	<=	1'b0;
		rrst_n	<=	1'b0;
		ramrst_n<=	1'b0;
		
		wdata<=	8'b0000_0000;
		
	end
always #5	wclk	<=	~wclk;
always #20	rclk	<=	~rclk;

initial
	fork
			#1	wrst_n	=	1'b1;
			#2	ramrst_n=	1'b1;
			#3	rrst_n	=	1'b1;

			#10	wdata	=	8'b0000_0001;
			#20	wdata	=	8'b0000_0010;
			#30	wdata	=	8'b0000_0100;
			#40	wdata	=	8'b0000_1000;
			#11	wen	=	1'b1;
			//#102	always@ #5 wdata	=	wdata+1;
			#100	wen	=	1'b0;
			#120	ren =	1'b1;
			#540	ren =	1'b0;
			#550	ramrst_n = 1'b0;
			#600	ramrst_n = 1'b1;	
			#650	ren =	1'b1;			
	join
	
sys_fifo  sys(.wen(wen),.ren(ren),.wclk(wclk),.rclk(rclk),.wrst_n(wrst_n),.rrst_n(rrst_n),.ramrst_n(ramrst_n),.wdata(wdata),.rdata(rdata));
endmodule

系统测试仿真图

猜你喜欢

转载自blog.csdn.net/qq_41034231/article/details/106079926