【转】同步FIFO和异步FIFO的Verilog实现
2011-10-1010:59:26|分类:FPGA学习|标签:fifoverilogfpga|字号大中小订阅
FIFO是英文FirstInFirstOut的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO一般用于不同时钟域之间的数据传输,比如FIFO的一端是AD数据采集,另一端是计算机的PCI总线,假设其AD采集的速率为16位100KSPS,那么每秒的数据量为100K×16bit=1.6Mbps,而PCI总线的速度为33MHz,总线宽度32bit,其最大传输速率为1056Mbps,在两个不同的时钟域间就可以采用FIFO来作为数据缓冲。另外对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
FIFO的分类根均FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。同步FIFO是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
FIFO设计的难点FIFO设计的难点在于怎样判断FIFO的空/满状态。为了保证数据正确的写入或读出,而不发生益处或读空的状态出现,必须保证FIFO在满的情况下,不能进行写操作。在空的状态下不能进行读操作。怎样判断FIFO的满/空就成了FIFO设计的核心问题。
.........................................................................................................................................
同步FIFO的Verilog代码之一
在modlesim中验证过。
/******************************************************
Afifocontrollerverilogdescription.
******************************************************/
modulefifo(datain,rd,wr,rst,clk,dataout,full,empty);
input[7:0]datain;
inputrd,wr,rst,clk;
output[7:0]dataout;
outputfull,empty;
wire[7:0]dataout;
regfull_in,empty_in;
reg[7:0]mem[15:0];
reg[3:0]rp,wp;
assignfull=full_in;
assignempty=empty_in;
//memoryreadout
assigndataout=mem[rp];
//memorywritein
always@(posedgeclk)begin
if(wr&&~full_in)mem[wp]<=datain;
end
//memorywritepointerincrement
always@(posedgeclkornegedgerst)begin
if(!rst)wp<=0;
elsebegin
if(wr&&~full_in)wp<=wp+1'b1;
end
end
//memoryreadpointerincrement
always@(posedgeclkornegedgerst)begin
if(!rst)rp<=0;
elsebegin
if(rd&&~empty_in)rp<=rp+1'b1;
end
end
//Fullsignalgenerate
always@(posedgeclkornegedgerst)begin
if(!rst)full_in<=1'b0;
elsebegin
if((~rd&&wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf)))
full_in<=1'b1;
elseif(full_in&&rd)full_in<=1'b0;
end
end
//Emptysignalgenerate
always@(posedgeclkornegedgerst)begin
if(!rst)empty_in<=1'b1;
elsebegin
if((rd&&~wr)&&(rp==wp-1||(rp==4'hf&&wp==4'h0)))
empty_in<=1'b1;
elseif(empty_in&&wr)empty_in<=1'b0;
end
end
endmodule
...........................................................................................................................
同步FIFO的Verilog代码之二
这一种设计的FIFO,是基于触发器的。宽度,深度的扩展更加方便,结构化跟强。以下代码在modelsim中验证过。
modulefifo_cell(sys_clk,sys_rst_n,read_fifo,write_fifo,fifo_input_data,
next_cell_data,next_cell_full,last_cell_full,cell_data_out,cell_full);
parameterWIDTH=8;
parameterD=2;
inputsys_clk;
inputsys_rst_n;
inputread_fifo,write_fifo;
input[WIDTH-1:0]fifo_input_data;
input[WIDTH-1:0]next_cell_data;
inputnext_cell_full,last_cell_full;
output[WIDTH-1:0]cell_data_out;
outputcell_full;
reg[WIDTH-1:0]cell_data_reg_array;
reg[WIDTH-1:0]cell_data_ld;
regcell_data_ld_en;
regcell_full;
regcell_full_next;
assigncell_data_out=cell_data_reg_array;
always@(posedgesys_clkornegedgesys_rst_n)
if(!sys_rst_n)
cell_full<=#D0;
elseif(read_fifo||write_fifo)
cell_full<=#Dcell_full_next;
always@(write_fifoorread_fifoornext_cell_fullorlast_cell_fullorcell_full)
casex({read_fifo,write_fifo})
2'b00:cell_full_next=cell_full;
2'b01:cell_full_next=next_cell_full;
2'b10:cell_full_next=last_cell_full;
2'b11:cell_full_next=cell_full;
endcase
always@(posedgesys_clkornegedgesys_rst_n)
if(!sys_rst_n)
cell_data_reg_array[WIDTH-1:0]<=#D0;
elseif(cell_data_ld_en)
cell_data_reg_array[WIDTH-1:0]<=#Dcell_data_ld[WIDTH-1:0];
always@(write_fifoorread_fifoorcell_fullorlast_cell_full)
casex({write_fifo,read_fifo,cell_full,last_cell_full})
4'bx1_xx:cell_data_ld_en=1'b1;
4'b10_01:cell_data_ld_en=1'b1;
default:cell_data_ld_en=1'b0;
endcase
always@(write_fifoorread_fifoornext_cell_fullorcell_fullorlast_cell_fullorfifo_input_dataornext_cell_data)
casex({write_fifo,read_fifo,next_cell_full,cell_full,last_cell_full})
5'b10_x01:cell_data_ld[WIDTH-1:0]=fifo_input_data[WIDTH-1:0];
5'b11_01x:cell_data_ld[WIDTH-1:0]=fifo_input_data[WIDTH-1:0];
default:cell_data_ld[WIDTH-1:0]=next_cell_data[WIDTH-1:0];
endcase
endmodule
modulefifo_4cell(sys_clk,sys_rst_n,fifo_input_data,write_fifo,fifo_out_data,
read_fifo,full_cell0,full_cell1,full_cell2,full_cell3);
parameterWIDTH=8;
parameterD=2;
inputsys_clk;
inputsys_rst_n;
input[WIDTH-1:0]fifo_input_data;
output[WIDTH-1:0]fifo_out_data;
inputread_fifo,write_fifo;
outputfull_cell0,full_cell1,full_cell2,full_cell3;
wire[WIDTH-1:0]dara_out_cell0,data_out_cell1,data_out_cell2,
data_out_cell3,data_out_cell4;
wirefull_cell4;
fifo_cell#(WIDTH,D)cell0
(.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.fifo_input_data(fifo_input_data[WIDTH-1:0]),
.write_fifo(write_fifo),
.next_cell_data(data_out_cell1[WIDTH-1:0]),
.next_cell_full(full_cell1),
.last_cell_full(1'b1),
.cell_data_out(fifo_out_data[WIDTH-1:0]),
.read_fifo(read_fifo),
.cell_full(full_cell0)
);
fifo_cell#(WIDTH,D)cell1
(.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.fifo_input_data(fifo_input_data[WIDTH-1:0]),
.write_fifo(write_fifo),
.next_cell_data(data_out_cell2[WIDTH-1:0]),
.next_cell_full(full_cell2),
.last_cell_full(full_cell0),
.cell_data_out(data_out_cell1[WIDTH-1:0]),
.read_fifo(read_fifo),
.cell_full(full_cell1)
);
fifo_cell#(WIDTH,D)cell2
(.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.fifo_input_data(fifo_input_data[WIDTH-1:0]),
.write_fifo(write_fifo),
.next_cell_data(data_out_cell3[WIDTH-1:0]),
.next_cell_full(full_cell3),
.last_cell_full(full_cell1),
.cell_data_out(data_out_cell2[WIDTH-1:0]),
.read_fifo(read_fifo),
.cell_full(full_cell2)
);
fifo_cell#(WIDTH,D)cell3
(.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.fifo_input_data(fifo_input_data[WIDTH-1:0]),
.write_fifo(write_fifo),
.next_cell_data(data_out_cell4[WIDTH-1:0]),
.next_cell_full(full_cell4),
.last_cell_full(full_cell2),
.cell_data_out(data_out_cell3[WIDTH-1:0]),
.read_fifo(read_fifo),
.cell_full(full_cell3)
);
assigndata_out_cell4[WIDTH-1:0]={WIDTH{1'B0}};
assignfull_cell4=1'b0;
endmodule
..........................................................................................................................
异步FIFO的Verilog代码之一
这个是基于RAM的异步FIFO代码,个人认为代码结构简单易懂,非常适合于考试中填写。记得10月份参加威盛的笔试的时候,就考过异步FIFO的实现。想当初要是早点复习,可能就可以通过威盛的笔试了。
与之前的用RAM实现的同步FIFO的程序相比,异步更为复杂。增加了读写控制信号的跨时钟域的同步。此外,判空与判满的也稍有不同。
modulefifo1(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n);
parameterDSIZE=8;parameterASIZE=4;
output[DSIZE-1:0]rdata;
outputwfull;
outputrempty;
input[DSIZE-1:0]wdata;
inputwinc,wclk,wrst_n;
inputrinc,rclk,rrst_n;
regwfull,rempty;
reg[ASIZE:0]wptr,rptr,wq2_rptr,rq2_wptr,wq1_rptr,rq1_wptr;
reg[ASIZE:0]rbin,wbin;
reg[DSIZE-1:0]mem[0:(1<<ASIZE)-1];
wire[ASIZE-1:0]waddr,raddr;
wire[ASIZE:0]rgraynext,rbinnext,wgraynext,wbinnext;
wirerempty_val,wfull_val;
//-----------------双口RAM存储器--------------------
assignrdata=mem[raddr];
always@(posedgewclk)
if(winc&&!wfull)mem[waddr]<=wdata;
//-------------同步rptr指针-------------------------
always@(posedgewclkornegedgewrst_n)
if(!wrst_n){wq2_rptr,wq1_rptr}<=0;
else{wq2_rptr,wq1_rptr}<={wq1_rptr,rptr};
//-------------同步wptr指针---------------------------
always@(posedgerclkornegedgerrst_n)
if(!rrst_n){rq2_wptr,rq1_wptr}<=0;
else{rq2_wptr,rq1_wptr}<={rq1_wptr,wptr};
//-------------rempty产生与raddr产生-------------------
always@(posedgerclkornegedgerrst_n)//GRAYSTYLE2pointer
begin
if(!rrst_n){rbin,rptr}<=0;
else{rbin,rptr}<={rbinnext,rgraynext};
end
//Memoryread-addresspointer(okaytousebinarytoaddressmemory)
assignraddr=rbin[ASIZE-1:0];
assignrbinnext=rbin+(rinc&~rempty);
assignrgraynext=(rbinnext>>1)^rbinnext;
//FIFOemptywhenthenextrptr==synchronizedwptroronreset
assignrempty_val=(rgraynext==rq2_wptr);
always@(posedgerclkornegedgerrst_n)
begin
if(!rrst_n)rempty<=1'b1;
elserempty<=rempty_val;
end
//---------------wfull产生与waddr产生------------------------------
always@(posedgewclkornegedgewrst_n)//GRAYSTYLE2pointer
if(!wrst_n){wbin,wptr}<=0;
else{wbin,wptr}<={wbinnext,wgraynext};
//Memorywrite-addresspointer(okaytousebinarytoaddressmemory)
assignwaddr=wbin[ASIZE-1:0];
assignwbinnext=wbin+(winc&~wfull);
assignwgraynext=(wbinnext>>1)^wbinnext;
assignwfull_val=(wgraynext=={~wq2_rptr[ASIZE:ASIZE-1],wq2_rptr[ASIZE-2:0]});//:ASIZE-1]
always@(posedgewclkornegedgewrst_n)
if(!wrst_n)wfull<=1'b0;
elsewfull<=wfull_val;
endmodule
..........................................................................................................................
异步FIFO的Verilog代码之二
与前一段异步FIFO代码的主要区别在于,空/满状态标志的不同算法。
第一个算法:CliffordE.Cummings的文章中提到的STYLE#1,构造一个指针宽度为N+1,深度为2^N字节的FIFO(为便方比较将格雷码指针转换为二进制指针)。当指针的二进制码中最高位不一致而其它N位都相等时,FIFO为满(在CliffordE.Cummings的文章中以格雷码表示是前两位均不相同,而后两位LSB相同为满,这与换成二进制表示的MSB不同其他相同为满是一样的)。当指针完全相等时,FIFO为空。
这种方法思路非常明了,为了比较不同时钟产生的指针,需要把不同时钟域的信号同步到本时钟域中来,而使用Gray码的目的就是使这个异步同步化的过程发生亚稳态的机率最小,而为什么要构造一个N+1的指针,CliffordE.Cummings也阐述的很明白,有兴趣的读者可以看下作者原文是怎么论述的,CliffordE.Cummings的这篇文章有Rev1.1\Rev1.2两个版本,两者在比较Gray码指针时的方法略有不同,个Rev1.2版更为精简。
第二种算法:CliffordE.Cummings的文章中提到的STYLE#2。它将FIFO地址分成了4部分,每部分分别用高两位的MSB00、01、11、10决定FIFO是否为goingfull或goingempty(即将满或空)。如果写指针的高两位MSB小于读指针的高两位MSB则FIFO为“几乎满”,若写指针的高两位MSB大于读指针的高两位MSB则FIFO为“几乎空”。
它是利用将地址空间分成4个象限(也就是四个等大小的区域),然后观察两个指针的相对位置,如果写指针落后读指针一个象限(25%的距离,呵呵),则证明很可能要写满,反之则很可能要读空,这个时候分别设置两个标志位dirset和dirrst,然后在地址完全相等的情况下,如果dirset有效就是写满,如果dirrst有效就是读空。
这种方法对深度为2^N字节的FIFO只需N位的指针即可,处理的速度也较第一种方法快。
这段是说明的原话,算法一,还好理解。算法二,似乎没有说清楚,不太明白。有兴趣的可以查查论文,详细研究下。
总之,第二种写法是推荐的写法。因为异步的多时钟设计应按以下几个原则进行设计:
1,尽可能的将多时钟的逻辑电路(非同步器)分割为多个单时钟的模块,这样有利于静态时序分析工具来进行时序验证。
2,同步器的实现应使得所有输入来自同一个时钟域,而使用另一个时钟域的异步时钟信号采样数据。
3,面向时钟信号的命名方式可以帮助我们确定那些在不同异步时钟域间需要处理的信号。
4,当存在多个跨时钟域的控制信号时,我们必须特别注意这些信号,保证这些控制信号到达新的时钟域仍然能够保持正确的顺序。
modulefifo2(rdata,wfull,rempty,wdata,
winc,wclk,wrst_n,rinc,rclk,rrst_n);
parameterDSIZE=8;
parameterASIZE=4;
output[DSIZE-1:0]rdata;
outputwfull;
outputrempty;
input[DSIZE-1:0]wdata;
inputwinc,wclk,wrst_n;
inputrinc,rclk,rrst_n;
wire[ASIZE-1:0]wptr,rptr;
wire[ASIZE-1:0]waddr,raddr;
async_cmp#(ASIZE)async_cmp(.aempty_n(aempty_n),
.afull_n(afull_n),
.wptr(wptr),.rptr(rptr),
.wrst_n(wrst_n));
fifomem2#(DSIZE,ASIZE)fifomem2(.rdata(rdata),
.wdata(wdata),
.waddr(wptr),
.raddr(rptr),
.wclken(winc),
.wclk(wclk));
rptr_empty2#(ASIZE)rptr_empty2(.rempty(rempty),
.rptr(rptr),
.aempty_n(aempty_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n));
wptr_full2#(ASIZE)wptr_full2(.wfull(wfull),
.wptr(wptr),
.afull_n(afull_n),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n));
endmodule
modulefifomem2(rdata,wdata,waddr,raddr,wclken,wclk);
parameterDATASIZE=8;//Memorydatawordwidth
parameterADDRSIZE=4;//Numberofmemoryaddressbits
parameterDEPTH=1<<ADDRSIZE;//DEPTH=2**ADDRSIZE
output[DATASIZE-1:0]rdata;
input[DATASIZE-1:0]wdata;
input[ADDRSIZE-1:0]waddr,raddr;
inputwclken,wclk;
`ifdefVENDORRAM
//instantiationofavendor'sdual-portRAM
VENDOR_RAMMEM(.dout(rdata),.din(wdata),
.waddr(waddr),.raddr(raddr),
.wclken(wclken),.clk(wclk));
`else
reg[DATASIZE-1:0]MEM[0:DEPTH-1];
assignrdata=MEM[raddr];
always@(posedgewclk)
if(wclken)MEM[waddr]<=wdata;
`endif
endmodule
moduleasync_cmp(aempty_n,afull_n,wptr,rptr,wrst_n);
parameterADDRSIZE=4;
parameterN=ADDRSIZE-1;
outputaempty_n,afull_n;
input[N:0]wptr,rptr;
inputwrst_n;
regdirection;
wirehigh=1'b1;
wiredirset_n=~((wptr[N]^rptr[N-1])&~(wptr[N-1]^rptr[N]));
wiredirclr_n=~((~(wptr[N]^rptr[N-1])&(wptr[N-1]^rptr[N]))|
~wrst_n);
always@(posedgehighornegedgedirset_nornegedgedirclr_n)
if(!dirclr_n)direction<=1'b0;
elseif(!dirset_n)direction<=1'b1;
elsedirection<=high;
//always@(negedgedirset_nornegedgedirclr_n)
//if(!dirclr_n)direction<=1'b0;
//elsedirection<=1'b1;
assignaempty_n=~((wptr==rptr)&&!direction);
assignafull_n=~((wptr==rptr)&&direction);
endmodule
modulerptr_empty2(rempty,rptr,aempty_n,rinc,rclk,rrst_n);
parameterADDRSIZE=4;
outputrempty;
output[ADDRSIZE-1:0]rptr;
inputaempty_n;
inputrinc,rclk,rrst_n;
reg[ADDRSIZE-1:0]rptr,rbin;
regrempty,rempty2;
wire[ADDRSIZE-1:0]rgnext,rbnext;
//---------------------------------------------------------------
//GRAYSTYLE2pointer
//---------------------------------------------------------------
always@(posedgerclkornegedgerrst_n)
if(!rrst_n)begin
rbin<=0;
rptr<=0;
end
elsebegin
rbin<=rbnext;
rptr<=rgnext;
end
//---------------------------------------------------------------
//incrementthebinarycountifnotempty
//---------------------------------------------------------------
assignrbnext=!rempty?rbin+rinc:rbin;
assignrgnext=(rbnext>>1)^rbnext;//binary-to-grayconversion
always@(posedgerclkornegedgeaempty_n)
if(!aempty_n){rempty,rempty2}<=2'b11;
else{rempty,rempty2}<={rempty2,~aempty_n};
endmodule
modulewptr_full2(wfull,wptr,afull_n,winc,wclk,wrst_n);
parameterADDRSIZE=4;
outputwfull;
output[ADDRSIZE-1:0]wptr;
inputafull_n;
inputwinc,wclk,wrst_n;
reg[ADDRSIZE-1:0]wptr,wbin;
regwfull,wfull2;
wire[ADDRSIZE-1:0]wgnext,wbnext;
//---------------------------------------------------------------
//GRAYSTYLE2pointer
//---------------------------------------------------------------
always@(posedgewclkornegedgewrst_n)
if(!wrst_n)begin
wbin<=0;
wptr<=0;
end
elsebegin
wbin<=wbnext;
wptr<=wgnext;
end
//---------------------------------------------------------------
//incrementthebinarycountifnotfull
//---------------------------------------------------------------
assignwbnext=!wfull?wbin+winc:wbin;
assignwgnext=(wbnext>>1)^wbnext;//binary-to-grayconversion
always@(posedgewclkornegedgewrst_nornegedgeafull_n)
if(!wrst_n){wfull,wfull2}<=2'b00;
elseif(!afull_n){wfull,wfull2}<=2'b11;
else{wfull,wfull2}<={wfull2,~afull_n};
endmodule