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
仿真结果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR9UNjRUT4FlaOBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2MDN1UzM0cTM5EDNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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
仿真结果:
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
仿真结果:
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
仿真结果:
4.小结
这个案例是我参考着《verilog HDL数字系统设计及仿真》一步步做的,不断学习不断回顾,但出的错还是不老少,心态差点被搞崩,还好我没有放弃555,就发现了一些很细微的点,我就罗列下来避免以后再犯。
这段代码看起来平平无奇,但我就是搞了一周,就是因为repeat后面的分号!!!加上之后,modelsim才能仿真正确,不加的话就会出现各种结果(我尝试加begin…end没有卵用,错的更离谱)
还有就是要是仿真的时候,变量的波形是红线,那就是没有给它初始化,或者就是模块太多,把自己弄迷了,可能忘了在测试代码中给出相关控制变量。ok就到这了。