FIFO那些事儿

0.引言

FIFO尤其是异步FIFO几乎是数字IC设计工程师面试必备,几乎每年都有9~10月份都能听到关于异步FIFO的讨论。而异步FIFO在接口电路设计或高速数据传输中也非常常用,在实际工程应用中,一般很少去自己设计异步FIFO,因为其太复杂,处理很繁琐,容易出错;一般是使用DW的IP,使用FPGA的也有对应的FIFO的IP供免费使用。

好的FIFO有两条标准:写满不溢出,读空不多读。一定要避免出现既满又空的情况出现。

1.同步FIFO的设计

同步FIFO的设计相对简单,读写时钟为同一个时钟,因此读写地址是同步的,可通过计数的方式,当写但不读时,cnt自增,当读不写时,cnt自减,当又读又写或不读不写,cnt不变。直接通过cnt的值可以进行空满判断。

1.1空满标志产生

满标志:当cnt为Deepth_fifo或为(Deepth_fifo-1)但正在写,满标准为1,其他情况为0;

空标志:当cnt为0或为1但正在读,空标志为1,其他情况为0;

实际情况下可以设置上下水限来产生almost_full、almost_empty;当cnt<下水限,产生almost_empty;当cnt>上水限,产生almost_full;可以进行参数化设计使得上下水限可编程。当上水限=Deepth_fifo,下水限=0即为最极限的情况。

1.2读写使能产生

在读写使能信号的产生上,可采用自我保护的方式:

assign write_allow = write_enable && !full
assign read_allow  = read_enable && !empty

1.3读写地址产生

地址产生可以采用简单的2进制计数的方式,当读使能有效,在时钟作用下,读地址加1,写使能有效,写地址加1.当FIFO深度较大,且对FIFO速度要求比较高的时候,可以采用线性反馈移位寄存器(LFSR)来产生地址,它的速度比二进制计数器快。

wire read_linearfeedback, write_linearfeedback;
assign read_linearfeedback = ! (read_addr[8] ^ read_addr[4]);
assign write_linearfeedback = ! (write_addr[8] ^ write_addr[4]);

always @(posedge clock or posedge fifo_gsr)
if (fifo_gsr) read_addr <= 9'h0;
else if (read_allow)
read_addr <= { read_addr[7], read_addr[6], read_addr[5],
read_addr[4], read_addr[3], read_addr[2],
read_addr[1], read_addr[0], read_linearfeedback };
always @(posedge clock or posedge fifo_gsr)
if (fifo_gsr) write_addr <= 9'h0
else if (write_allow)
write_addr <= { write_addr[7], write_addr[6], write_addr[5],
write_addr[4], write_addr[3], write_addr[2],
write_addr[1], write_addr[0], write_linearfeedback };

LFSR:Nbit的LFSR可以用来产生2^N-1个周期长度的不重复的伪随机数。不同长度的LFSR有不同的特定的生成多项式使其达到最大长度2^N-1。

2.异步FIFO设计

异步是指读、写时钟完全独立且不一致,或者不同频率,或者同频但不同相。读地址和空标志由读时钟产生,写地址和满标志由写时钟产生。空满标志需要对读写地址进行比较,这是跨时钟域的异步信号比较问题。使用二进制计数的话,可能会产生地址增1时出现多位地址线变化,而且每一位跳变时间不一致,因此会产生一些中间值,因此在比较时,可能会产生误判断,导致逻辑错误。

为避免上述问题,通常会使用格雷码产生地址。(二进制转格雷码:G=(B>>1)^B),格雷码相邻两个值跳变的地址线只有一位,这样地址变化时间段,极大提高了比较精度。

图 2-1 格雷码FIFO基本原理图

FIFO那些事儿_第1张图片

2.1空满标志产生

格雷码不能进行加减产生空满标志,通常是利用地址的相对关系,对现在、将来、过去的地址进行保存,利用rd_addr产生rd_next_gray_addr,rd_next_gray_addr延时一拍产生rd_gray_addr,rd_gray_addr再延时一拍得到rd_last_gray_addr。在时间上,3个地址有时间关系,并相差1.写地址的格雷码产生类似。

空标志:当读指针追上写指针即为读空。

empty =( rd_gray_addr == wt_gray_addr )&&( rd_next_gray_addr ==wt_gray_addr && read_allow ) ;

图 2-2 空标志产生

FIFO那些事儿_第2张图片

满标志:写指针追上读指针一圈。

full = (wt_gray_addr==rd_last_gray_addr) && (wt_next_gray_addr==rd_last_gray_addr && write_allow);

图 2-3 满标志产生

FIFO那些事儿_第3张图片

如果要产生几乎空、几乎满标志,可以多做几个格雷码的延时地址。利用特定的读、写格雷码间接远近关系产生几乎空几乎满。如果要在大空间内产生几乎空满标志(如差10),需要才用另外的方法。可以通过写地址减去读地址得到FIFO有多少个未读数据。具体可以看如下代码(几乎满为例):

//---turn the R addr to gray code to become w addr
always @(posedge read_clock or posedge fifo_gsr)
if(fifo_gsr)
Rd_truegray <= 8'h0;
else
Rd_truegray <=#1 {
(read_addr[7]),(read_addr[7]^read_addr[6]),
(read_addr[6]^read_addr[5]),(read_addr[5]^read_addr[4]),
(read_addr[4]^read_addr[3]),(read_addr[3]^read_addr[2]),
(read_addr[2]^read_addr[1]),(read_addr[1]^read_addr[0]) };

//---use w_clk to sync the r_addr----
always @(posedge write_clock or posedge fifo_gsr)
if(fifo_gsr)
Rag_wt_syn <= 8'h0;
else
Rag_wt_syn <=#1 Rd_truegray;
//---turn the gray code to bin code----
wire Ra_7_5 = Rag_wt_syn[7] ^ Rag_wt_syn[6] ^ Rag_wt_syn[5] ;
wire Ra_7_4 = Rag_wt_syn[7] ^ Rag_wt_syn[6] ^ Rag_wt_syn[5] ^ Rag_wt_syn[4];
wire Ra_3_1 = Rag_wt_syn[3] ^ Rag_wt_syn[2] ^ Rag_wt_syn[1] ;
assign  Ra_write_syn[7] = Rag_wt_syn[7]; //7
assign  Ra_write_syn[6] = Rag_wt_syn[7] ^ Rag_wt_syn[6]; //6
assign  Ra_write_syn[5] = Ra_7_5; //5
assign  Ra_write_syn[4] = Ra_7_4 ; //4
assign  Ra_write_syn[3] = Ra_7_4 ^ Rag_wt_syn[3] ; //3
assign  Ra_write_syn[2] = Ra_7_4 ^ Rag_wt_syn[3] ^ Rag_wt_syn[2]; //2
assign  Ra_write_syn[1] = Ra_7_4 ^ Ra_3_1 ; //1
assign  Ra_write_syn[0] = Ra_7_5 ^ Ra_3_1 ^ Rag_wt_syn[4] ^ Rag_wt_syn[0]; //0
//--delay wt_addr to one_clk_latency---
always @(posedge write_clock or posedge fifo_gsr)
if(fifo_gsr)
Wt_addr_p1 <=#1 0;
else
Wt_addr_p1 <=#1 write_addr ;
//Fifo_status--means the number of valid data in fifo
always @(posedge write_clock or posedge fifo_gsr)
if(fifo_gsr)
Fifo_status <= 8'h0;
else //if(!Full)
Fifo_status <= Wt_addr_p1 - Ra_write_syn;
always @(posedge write_clock or posedge fifo_gsr)
if(fifo_gsr)
Almostfull <=#1 1'b0;
//-- when left 16 depth report almost_full
else if (Fifo_status[7:4] == 4'hF)
Almostfull <=#1 1'b1;
else
Almostfull <=#1 1'b0;

几乎空,与几乎满类似,由读时钟产生。

3.位宽变换FIFO

有时候在应用中,需要进行数据位宽变换,如输入1024x16 bit,输出256x64 bit。

图 3-1 位宽变换FIFO

FIFO那些事儿_第4张图片

此时地址比较只需要比较高位地址ADDRA[9:2]与ADDRB[7:0];

4.块操作FIFO

当需要进行突发读、写N个深度的FIFO,此时地址也分为两个部分,高地址为块号,低地址为块内地址。空满标志由读写高位地址比较产生。

5.注意点

异步FIFO不适合做的太大,如果需要比较大的异步FIFO可以划分为大同步FIFO和一个异步小FIFO。小的FIFO可以用DFF作为内部结构,大的FIFO则使用双口ram作为内部存储结构。

图 5-1 FIFO转换示意图

FIFO那些事儿_第5张图片

 

你可能感兴趣的:(FIFO那些事儿)