• 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转换示意图

     

  • 相关阅读:
    缓存问题
    基情探测器心得
    新手最常见的误解和错误
    C语言书籍推荐
    初学者编程实战指南 (4) 由一个简单的例子学习抽象
    数据结构的动画演示
    利用IDE使你的代码风格好看一些
    初学者编程实战指南 (2) 避免逻辑的重复
    入门编程语言的选择问题
    关于ACM集训队
  • 原文地址:https://www.cnblogs.com/lkiller/p/4785621.html
Copyright © 2020-2023  润新知