天天看点

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

1.FIFO

1.1简介

FIFO(First In First Out),即先入先出存储器,就可以把它当成一个管道,数据比作管道里的水,先流进去的就先流出去。FIFO存储器分为写入专用区和读取专用区。读操作与写操作可以同步(同一时钟控制)或异步(不同频率时钟控制)进行,写入区数据可按照写入的顺序从读取端读出,类似于吸收写入端与读出端速度差的一种缓冲器。

1.2功能

1)对连续的数据流进行缓存,防止在进机和存储操作时丢失数据;

2)数据集中起来进行进机和存储,可避免频繁的总线操作,减轻CPU的负担;

3)允许系统进行DMA操作(Direct Memory Access,直接内存操作),提高数据的传输速度。若不采用DMA操作,数据传输将达不到传输要求,而且大大增加CPU的负担,无法同时完成数据的存储工作。

2.空满状态

FIFO作为一个存储器,就要有写入数据和读取数据的功能,存储器内为空状态时不再继续读取数据,存储器为满状态时不再继续写入数据,变成只读存储器,因此空满状态判断还是特别重要的。

如果要写入数据,就要判断当前存储器的状态,就要将写指针与读指针进行比较,那么读指针就需要送进写电路中,进行时钟跨越,就要用到同步器。众所周知,跨越时钟域的时候希望出现错误的概率越低越好,这时格雷码就无疑是最优的一种计数方式,毕竟十进制数加一时,格雷码只变动一位,而通常用的二进制码就可能变动多位,例如十进制从7变成8,二进制码就从0111变成了1000,四位全都发生了变化,所以4位都有可能出现亚稳态,从而在同步器的输出端出现难以预料的情况,而格雷码从 0100变成1100,只有一位发生了变化,这样经过同步器处理之后,就可能出现两种结果:0100和1100,1100是我们需要的结果,即使那一位出现亚稳态,我们得到的也是原数据0100,在数据稳定时,对系统也没有很大的影响,仅仅只是延时一段时间。 但是格雷码对计数并不十分友好,所以计数的时候还是得把格雷码再转换成二进制码计数,指针就用格雷码来表示。

2.1格雷码和二进制码转换

十进制 二进制 格雷码
N B3B2B1B0 G3G2G1G0
0 0 0 0 0 0 0 0
1 0 0 0 1 0 0 0 1
2 0 0 1 0 0 0 1 1
3 0 0 1 1 0 0 1 0
4 0 1 0 0 0 1 1 0
5 0 1 0 1 0 1 1 1
6 0 1 1 0 0 1 0 1
7 0 1 1 1 0 1 0 0
8 1 0 0 0 1 1 0 0
9 1 0 0 1 1 1 0 1
10 1 0 1 0 1 1 1 1
11 1 0 1 1 1 1 1 0
12 1 1 0 0 1 0 1 0
13 1 1 0 1 1 0 1 1
14 1 1 1 0 1 0 0 1
15 1 1 1 1 1 0 0 0

二进制转换格雷码代码:

格雷码转换二进制代码:

...
parameter size=4;
integer i;
always @(gray)
for(i=0;i<size,i=i+1)
	bin <= ^(gray>>i);
...
           

2.2空满状态判断

判断空满状态我们通常使用附加位比较法。实际操作起来就是给每个指针增加一个附加位,如果读和写指针完全相同,则说明读和写指针经历了相同次数的循环,即FIFO存储器当前处于空状态,如果读和写指针的最高位不同而其他位相同,说明写指针比读指针多循环了一次,即FIFO存储器当前处于满状态。因此读和写指针都变成了n位指针,其中低n-1位存放FIFO存储器的地址,最高位用来判断空满状态。

对于二进制来说,上述方法可行,但对于格雷码来说就不太妙,就拿4位二进制码来打比方,低3位存地址,那么存储容量就为8,从十进制7到8变化时,最高位从0变成1,低3位归零。而格雷码从十进制7到8变化时,最高位从0变成1,低3位却没有归零。另外,对于两个格雷码指针现都是0100,写指针加1变成1100,此时低3位相同,而最高位不同,FIFO存储器满标志将置位,这也比较头疼。这时我们就需要使用双重格雷码计数器------既能产生n位格雷码又能产生n-1位格雷码计数器的计数器。

此时,对于4位格雷码指针来说,判断写满的标准就是:后两位相同,前两位异或值均为0,最高位不相同。

3.子模块设计

本次FIFO存储器设计共分为:读指针控制模块、写指针控制模块、存储器RAM模块、读指针同步到写时钟域模块和写指针同步到读时钟域模块。

3.1读指针同步到写时钟域

module sync_r2w(wrptr2,rptr,wclk,wrst_n);
parameter ADDRSIZE=4;
output [ADDRSIZE:0] wrptr2;//同步后的读指针
input [ADDRSIZE:0] rptr;//同步前的读指针
input wclk,wrst_n;
reg [ADDRSIZE:0] wrptr2,wrptr1;//中间寄存器

always @(posedge wclk or negedge wrst_n)
if(!wrst_n)
	{wrptr2,wrptr1}<=0;//复位
else
	{wrptr2,wrptr1}<={wrptr1,rptr};//寄存器串联

endmodule
           

3.2写指针同步到读时钟域

module syne_w2r(rwptr2,wptr,rclk,rrst_n);
parameter ADDRSIZE=4;
output [ADDRSIZE:0] rwptr2;//同步后的写指针
input [ADDRSIZE:0] wptr;//同步前的写指针
input rclk,rrst_n;
reg [ADDRSIZE:0] rwptr2,rwptr1;//中间寄存器

always @(posedge rclk or negedge rrst_n)
if(!rrst_n)
	{rwptr2,rwptr1}<=0;//复位
else
	{rwptr2,rwptr1}<={rwptr1,wptr};//寄存器串联

endmodule
           

3.3存储器RAM

module fifomem(rdata,wdata,waddr,raddr,wclken,wclk,rclken,rclk);
parameter DATASIZE=8;//数据宽度
parameter ADDRSIZE=4;//地址宽度
output [DATASIZE-1:0] rdata;//读出数据
input [DATASIZE-1:0] wdata;//写入数据
input wclken,wclk,rclken,rclk;
input [ADDRSIZE-1:0] waddr,raddr;//写地址、读地址
reg [DATASIZE-1:0] rdata;

reg [DATASIZE-1:0] MEM [0:(1<<ADDRSIZE)-1];//存储体

always @(posedge rclk)//读时钟读出数据
if(rclken)
	rdata = MEM[raddr];

always @(posedge wclk)//写时钟写入数据
if(wclken)
	MEM[waddr] <= wdata;

endmodule
           

测试模块:

module tbmem;
parameter DATASIZE=8;
parameter ADDRSIZE=4;
wire [DATASIZE-1:0] rdata;
reg [DATASIZE-1:0] wdata;
reg wclken,wclk,rclken,rclk;
reg [ADDRSIZE-1:0] waddr,raddr;
integer seed1;

initial
begin
	wclk=0;rclk=0;seed1=20;
	waddr=0;raddr=0;

end

always #9 wclk = ~wclk;
always #11 rclk = ~rclk;

always @(posedge wclk)
	wdata <= {$random(seed1)/256};

initial
begin
	wclken=1;rclken=0;
	repeat (10) @(posedge wclk);
	wclken=0;rclken=1;
	repeat (6) @(posedge rclk);
	wclken=1;rclken=1;
	#99 $stop;
end

always @(posedge wclk)
	if(wclken==1)
		waddr=waddr+1;
always @(posedge rclk)
	if(rclken==1)
		raddr=raddr+1;

fifomem fifomem(rdata,wdata,waddr,raddr,wclken,wclk,rclken,rclk);

endmodule
           

仿真结果:

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

3.4读指针控制

module rptr_empty(rempty,raddr,rptr,rwptr2,rinc,rclk,rrst_n);
parameter ADDRSIZE=4;//地址宽度
input [ADDRSIZE:0] rwptr2;//同步后的写指针
input rinc,rclk,rrst_n;
output rempty;//输出空状态
output [ADDRSIZE:0] rptr;//读指针
output [ADDRSIZE-1:0] raddr;//读地址
reg [ADDRSIZE:0] rptr,rbin,rgnext,rbnext;//读时钟域格雷码和读时钟域二进制码
reg rempty,raddrmsb;//读地址最高位

always @(posedge rclk or negedge rrst_n)
begin
if(!rrst_n)
   begin
	rptr <= 0;
	raddrmsb <= 0;//复位
   end
else
   begin
	rptr <= rgnext;//下一个读地址
	raddrmsb <= rgnext[ADDRSIZE]^rgnext[ADDRSIZE-1];//地址最高位为指针最高位和次高位异或
   end
end

always @(rptr or rinc)
begin:Gray_inc//块名为Gray_inc
   integer i;
   for(i=0;i<=ADDRSIZE;i=i+1)
	rbin[i]=^(rptr>>i);//格雷码转换为二进制码
   if(!rempty)
	rbnext=rbin+rinc;//增加FIFO计数
   else
	rbnext=rbin;
   	rgnext=(rbnext>>1)^rbnext;//二进制码转换为格雷码
end

always @(posedge rclk or negedge rrst_n)
if(!rrst_n)
   rempty<=1'b1;//复位输出空
else
   rempty<=(rgnext==rwptr2);//判断是否满足条件判断空状态

assign raddr={raddrmsb,rptr[ADDRSIZE-2:0]};//读地址指针

endmodule
           

测试模块:

module tbrptr;
parameter ADDRSIZE=4;
wire rempty;
wire [ADDRSIZE-1:0] raddr;
wire [ADDRSIZE:0] rptr;
reg [ADDRSIZE:0] rwptr2;
reg rinc,rclk,rrst_n;

initial
begin
   rclk=0;rrst_n=1;rinc=0;
   rwptr2=13;
   #3 rrst_n=0;
   #4 rrst_n=1;
   #6 rinc=1;
end

always #11 rclk=~rclk;

initial
begin
   repeat (5) @(posedge rclk);
   @(posedge rempty);
   #20 $stop;
end

rptr_empty rptr_empty(rempty,raddr,rptr,rwptr2,rinc,rclk,rrst_n);

endmodule
           

仿真结果:

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

3.5写指针控制

module wptr_full(wfull,waddr,wptr,wrptr2,winc,wclk,wrst_n);
parameter ADDRSIZE=4;//地址宽度
input [ADDRSIZE:0] wrptr2;//同步后的读指针
input winc,wclk,wrst_n;
output wfull;//输出满状态
output [ADDRSIZE:0] wptr;
output [ADDRSIZE-1:0] waddr;
reg [ADDRSIZE:0] wptr,wbin,wgnext,wbnext;
reg wfull,waddrmsb;
wire w_2ndmsb,wr_2ndmsb;//判断信号

always @(posedge wclk or negedge wrst_n)
begin
if(!wrst_n)
   begin
	wptr <= 0;
	waddrmsb <= 0;//复位
   end
else
   begin
	wptr <= wgnext;
	waddrmsb <= wgnext[ADDRSIZE]^wgnext[ADDRSIZE-1];//写地址最高位
   end
end

always @(wptr or winc)
begin:Gray_inc
   integer i;
   for(i=0;i<=ADDRSIZE;i=i+1)
	wbin[i] = ^(wptr >> i);//格雷码转二进制
   if(!wfull)
	wbnext = wbin + winc;
   else
	wbnext = wbin;
   	wgnext = (wbnext>>1) ^ wbnext;//二进制转格雷码
end

assign w_2ndmsb = wgnext[ADDRSIZE] ^ wgnext[ADDRSIZE-1];//格雷码写指针前两位异或
assign wr_2ndmsb = wrptr2[ADDRSIZE] ^ wrptr2[ADDRSIZE-1];//同步后的读指针前两位异或

always @(posedge wclk or negedge wrst_n)
begin
if(!wrst_n)
	wfull <= 0;
else
	wfull <= ((wgnext[ADDRSIZE] !== wrptr2[ADDRSIZE])&&(w_2ndmsb == wr_2ndmsb)&&(wgnext[ADDRSIZE-2:0] == wrptr2[ADDRSIZE-2:0]));//同时满足三个判断条件则写满
end

assign waddr = {waddrmsb,wptr[ADDRSIZE-2:0]};//写地址指针

endmodule
           

测试模块:

module tbwptr;
parameter ADDRSIZE=4;
wire wfull;
wire [ADDRSIZE-1:0] waddr;
wire [ADDRSIZE:0] wptr;
reg [ADDRSIZE:0] wrptr2;
reg winc,wclk,wrst_n;

initial
begin
   wclk=0;wrst_n=1;winc=0;
   wrptr2=12;
   #3 wrst_n=0;
   #4 wrst_n=1;
   #6 winc=1;
end

always #11 wclk = ~wclk;

initial
begin
   repeat (5) @(posedge wclk);
   @(posedge wfull);
   #20 $stop;
end

wptr_full wptr_full(wfull,waddr,wptr,wrptr2,winc,wclk,wrst_n);

endmodule
           

仿真结果:

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

3.6综合仿真

module fifo_asyn(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n,wclken,rclken);
parameter DSIZE=8;
parameter ASIZE=4;
output [7:0] rdata;
output       wfull;
output       rempty;
input  [7:0] wdata;
input        winc,wclk,wrst_n,wclken;
input        rinc,rclk,rrst_n,rclken;
wire   [3:0] waddr,raddr;
wire   [4:0] wptr,rptr,wrptr2,rwptr2;

sync_r2w sync_r2w(wrptr2,rptr,wclk,wrst_n);
syne_w2r syne_w2r(rwptr2,wptr,rclk,rrst_n);
fifomem fifomem(rdata,wdata,waddr,raddr,wclken,wclk,rclken,rclk);
rptr_empty rptr_empty(rempty,raddr,rptr,rwptr2,rinc,rclk,rrst_n);
wptr_full wptr_full(wfull,waddr,wptr,wrptr2,winc,wclk,wrst_n);

endmodule
           

测试模块:

module tbfifo;
parameter DSIZE=8;
parameter ASIZE=4;
wire [7:0] rdata;
wire       wfull;
wire       rempty;
reg  [7:0] wdata;
reg        winc,wclk,wrst_n,wclken;
reg        rinc,rclk,rrst_n,rclken;
integer seed;

initial
begin
	wclk=0;rclk=0;seed=11;
	winc=0;rinc=0;wclken=1;rclken=1;
	wrst_n=1;rrst_n=1;
	#2 wrst_n=0;rrst_n=0;
	#3 wrst_n=1;rrst_n=1;
end

always #9 wclk=~wclk;
always #11 rclk=~rclk;

always @(posedge wclk)
	wdata <= {$random(seed)/256};

initial
begin
	#20 winc=1;
	repeat (12)@(posedge wclk);
	winc=0;rinc=1;
	repeat (6)@(posedge rclk);
	winc=1;rinc=1;
	@(posedge wfull);
	winc=0;rinc=1;
	@(posedge rempty);
	#20 $stop;
end

fifo_asyn fifo_asyn(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n,wclken,rclken);

endmodule
           

仿真结果:

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

4.小结

这个案例是我参考着《verilog HDL数字系统设计及仿真》一步步做的,不断学习不断回顾,但出的错还是不老少,心态差点被搞崩,还好我没有放弃555,就发现了一些很细微的点,我就罗列下来避免以后再犯。

异步FIFO的verilog实现1.FIFO2.空满状态3.子模块设计4.小结

这段代码看起来平平无奇,但我就是搞了一周,就是因为repeat后面的分号!!!加上之后,modelsim才能仿真正确,不加的话就会出现各种结果(我尝试加begin…end没有卵用,错的更离谱)

还有就是要是仿真的时候,变量的波形是红线,那就是没有给它初始化,或者就是模块太多,把自己弄迷了,可能忘了在测试代码中给出相关控制变量。ok就到这了。

继续阅读