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基本原理图
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 空标志产生
满标志:写指针追上读指针一圈。
full = (wt_gray_addr==rd_last_gray_addr) && (wt_next_gray_addr==rd_last_gray_addr && write_allow);
图 2-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
此时地址比较只需要比较高位地址ADDRA[9:2]与ADDRB[7:0];
4.块操作FIFO
当需要进行突发读、写N个深度的FIFO,此时地址也分为两个部分,高地址为块号,低地址为块内地址。空满标志由读写高位地址比较产生。
5.注意点
异步FIFO不适合做的太大,如果需要比较大的异步FIFO可以划分为大同步FIFO和一个异步小FIFO。小的FIFO可以用DFF作为内部结构,大的FIFO则使用双口ram作为内部存储结构。
图 5-1 FIFO转换示意图