数字IC秋招总结3-FIFO相关

目录

FIFO相关基础概念

什么是fifo

FIFO的基本参数

FIFO设计的要点

同步fifo

同步FIFO中almost_full信号

异步fifo

异步FIFO结构

空满判断

异步FIFO设计

注意事项

为什么用格雷码?出错怎么办?

假空假满

读写频率差距很大,会出问题嘛

为什么工程上的不用手撕的异步FIFO?

FIFO的深度必须是2^n吗?

如果深度就是要非2^n怎么办?

读写位宽的不同的FIFO

FIFO深度的计算


FIFO相关基础概念

什么是fifo

FIFO全称是First In First Out,即先进先出

FIFO主要用于以为下几个方面:

  • 跨时钟域传输
  • 将数据发送到芯片外之前进行缓冲,比如发送到SRAM和DRAM
  • 存储数据以备后用

FIFO的基本参数

FIFO的重要的基本参数有读时钟,写时钟,读指针,写指针,读使能,写使能,空标志,满标志

读时钟:读取FIFO数据所根据的时钟

写时钟:向FIFO中写数据所根据的时钟

读指针:指向读取数据的地址,读完自动加1

写指针:指向写入数据的地址,写完自动加1

读使能:读数据开始标志

写使能:写数据开始标志

空标志:FIFO为空的标志,一般判断条件是都指针与写指针相等

满标志:FIFO为满的标志,一般判断条件是都指针与写指针最高位不相等,剩余位相等(二进制下)

FIFO设计的要点

FIFO的设计功能上不能出错,即空了不能读,满了不能写,这样都会导致数据出错

FIFO设计的重要的一点是空满判断,我们用指针来判断FIFO是处于空状态还是处于满状态

空状态:每写一次数据,写指针加一,每读一次数据,都指针加一。当读指针追上写指针,二者处于相等的情况时,FIFO处于空的状态

满状态:每写一次数据写指针加一 ,当写指针转了一圈回来和写指针处于同一位置的时候,FIFO处于满的状态

同步fifo

同步FIFO就是FIFO的读和写操作都在同一时钟域下进行,即读写速率一样,FIFO的结构主要有读地址与写地址的变化,FIFO的读写数据,空满判断三个模块

always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            wr_ptr <= 0;
        else if(wr_en && !fifo_full)    //写使能,且fifo未写满
            wr_ptr <= wr_ptr + 1;
        else
            wr_ptr <= wr_ptr;
    end

    //读地址操作
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_ptr <= 0;
        else if(rd_en && !fifo_empty)   //读使能,且fifo不为空
            rd_ptr <= rd_ptr + 1;
        else
            rd_ptr <= rd_ptr;
    end

 以上是FIFO的读地址与写地址的变化,判断条件只有读写使能以及空满信号

    //写数据
    integer i;

    always @ (posedge clk or negedge rstn) begin
        if(!rstn) begin //复位清空fifo
            for(i = 0; i < depth; i = i + 1)
                fifo[i] <= 0;
        end
        else if(wr_en && !full)  //写使能时将数据写入fifo
            fifo[wr_ptr] <= wr_data;
        else    //否则保持
            fifo[wr_ptr] <= fifo[wr_ptr];
    end

    //读数据
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_data <= 0;
        else if (rd_en && !empty)
            rd_data <= fifo[rd_ptr];    //从fifo中读取数据
        else
            rd_data <= rd_data;
    end

以上是从FIFO的读写数据,需要注意的是在复位信号下用for循环来复位FIFO的每一个地址,然后根据使能信号进行读写。

    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            cnt <= 0;
        else if ((wr_en && !rd_en && !fifo_full)|(wr_en && rd_en && !fifo_full && fifo_empty))
            cnt <= cnt + 1;
        else if ((!wr_en && rd_en && !fifo_empty)|(wr_en && rd_en && fifo_full && !fifo_empty))
            cnt <= cnt - 1;
        else 
            cnt <= cnt;
    end

    //空满判断
    assign fifo_full = (cnt == depth)? 1 : 0;
    assign fifo_empty = (cnt == 0) ? 1 : 0;

wr_enrd_enfifo_fullfifo_emptycnt
0000不变
0001不变
0010不变
0011不变
0100cnt-1
0101不变
0110cnt-1
0111不存在
1000cnt+1
1001cnt+1
1010不变
1011不存在
1100不变
1101cnt+1
1110cnt-1

还剩一种情况没有列出来,但是全为1的那种情况也是不存在的,根据这个表,怎么根据cnt判断空满显而易见,或者是将指针扩一位,根据最高位不同,剩余位相同来判断满,所有位都相同来判断空

同步FIFO中almost_full信号

这个问题在地平线面试中又被问过,怎么利用almost_full信号来判断空满,假设almost_full信号在FIFO差一个深度就满的时候拉高。

//增加almost信号判断满
always @(posedge clk or negedge rstn) begin
    if(!rstn)
    full <= 0;
    else if(al_full && wr_en && !rd_en)
    full <= 1;
    //else if(al_full && rd_en && !wr_en)
    else if(al_full && rd_en)
    full <= 0;
end

always@(posedge clk or negedge rstn)begin
    if(!rstn)
    empty <= 0;
    else if(al_empty && rd_en && !wr_en)
    empty <= 1;
    //else if(al_empty && wr_en && !rd_en)
    else if(al_empty && wr_en)
    empty <= 0;
end

异步fifo

异步FIFO就是读写操作是在两个时钟下进行的,即读时钟与写时钟。内部的写逻辑和读逻辑的交互需要异步处理,异步FIFO常用于跨时钟域的交互

异步FIFO结构

这是论文中给出来的fifo电路图,论文:Simulation and Synthesis Techniques for Asynchronous FIFO Design

它可以抽象为下面的图:

异步FIFO主要有五部分组成:

  • FIFO memory:双端口ram
  • 写控制端:用于判断是否可以写入数据,也包括写指针的变化与转换格雷码
  • 读控制端:用于判断是否可以读取数据,也包括读指针的变化与转换格雷码
  • 时钟同步器:用于读写地址的跨时钟域传输,以判断空满,这里传输的地址的格雷码

空满判断

空满判断与同步FIFO的类似:

当读写指针相等的时候,表示FIFO为空,这种情况发生在复位操作的时候,或者读指针读取了FIFO中的最后一个数据,他追上了写指针。

当读写指针再次相等的时候,就是写指针转了一圈回来追上了读指针,表示FIFO为满。

 所以我们将指针扩展一位作为折回标志位,当写指针转了一圈回来追上了读指针的时候,折回标志位为一,其他位相等,如果加上折回标志位的所有位都相等,那么FIFO为空。但是在异步FIFO中判断空满是用格雷码判断的,那么转换为格雷码怎么判断空满呢?

最高位不同,次高位不同,剩余位相同为满,所有位相同为空

异步FIFO设计

integer i;
always@(posedge wclk or negedge rst_n)
if(!rst_n)begin
    for(i=0;i<ram_depth;i=i+1)
    ram_fifo[i] <= 0;
end
else if(wr && (!full))
ram_fifo[wr_addr] <= din;

always @(posedge rclk or negedge rst_n) begin
    if(!rst_n)
    dout <= 0;
    else if(rd && (!empty))
    dout <= ram_fifo[rd_addr];  
end

always @(posedge wclk or negedge rst_n) begin
    if(!rst_n)
    wr_addr_ptr <= 0;
    else if(wr &&(!full))
    wr_addr_ptr <= wr_addr_ptr + 1;
end

always @(posedge rclk or negedge rst_n) begin
    if(!rst_n)
    rd_addr_ptr <= 0;
    else if(rd &&(!empty))
    rd_addr_ptr <= rd_addr_ptr + 1;
end

 以上这段代码描述的是FIFO的读写和地址的增减,相当于图中的写控制端和读控制端

assign wr_addr = wr_addr_ptr[data_depth-1:0];
assign rd_addr = rd_addr_ptr[data_depth-1:0];

assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

这段描述的是扩展一位最高位为折回标志位,以及将扩展后的地址转为格雷码来进行跨时钟域传输

always@(posedge wclk or negedge rst_n) begin
    if(!rst_n)
    wr_addr_gray_rdy <= 0;
    else
    wr_addr_gray_rdy <= wr_addr_gray;
end

always@(posedge rclk or negedge rst_n) begin
    if(!rst_n)
    rd_addr_gray_rdy <= 0;
    else 
    rd_addr_gray_rdy <= rd_addr_gray;
end

在跨时钟域前不能有组合逻辑,不然可能引入毛刺,这段代码就是将转换为格雷码的地址进行打拍,防止采到毛刺

always@(posedge wclk or negedge rst_n)begin
    if(!rst_n)begin
    rd_addr_gray1 <=0;
    rd_addr_gray2 <=0;
    end
    else begin
    rd_addr_gray1 <= rd_addr_gray_rdy;
    rd_addr_gray2 <= rd_addr_gray1;
    end
end

always@(posedge rclk or negedge rst_n)begin
    if(!rst_n)begin
    wr_addr_gray1 <=0;
    wr_addr_gray2 <=0;
    end
    else begin
    wr_addr_gray1 <= wr_addr_gray_rdy;
    wr_addr_gray2 <= wr_addr_gray1;
    end
end

将格雷码进行跨时钟域传输,相当于图中的时钟同步模块

assign empty = (wr_addr_gray2 == rd_addr_gray)? 1'b1:1'b0;
assign full = (wr_addr_gray[data_depth:data_depth-1'b1] != rd_addr_gray2[data_depth:data_depth-1'b1]) && (wr_addr_gray[data_depth-1:0] == rd_addr_gray2[data_depth-1:0]);

 跨时钟域后的格雷码来进行空满判断,比如,读指针转换为格雷码后及逆行跨时钟域传输,然后再与写指针的格雷码进行空满判断

注意事项

为什么用格雷码?出错怎么办?

使用格雷码的根本原因就是就算跨时钟域的时候,出现了亚稳态,整个fifo的功能也不会出现错误。比如用二进制的waddr来进行跨时钟域,比如waddr从7到8的变化(0111->1000),如果此时raddr此时处于6,说明要读raddr=6的这个数据。写指针跨时钟域的时候,寄存器会随机的取到中间状态的一个值,所以在rq2_wptr处得到的结果可能是2,这就相当于,我明明写到了8,但是跨时钟域完,你告诉读时钟域下,你写到了2,此时raddr是6,相当于fifo写只写到了2,读却读到了6。这样会使fifo的功能错误。如果使用格雷码,waddr从7到8的变化(0100->1100),中间就涉及到了一个值的变化,就算出现亚稳态,不是1100就是0100,是1100的话,正好功能正确,如果是0100的话,不影响fifo的读,最多可能出现fifo的假空判断,不会出现fifo功能的错误。

假空假满

假空假满根本原因是因为打两拍的原因,比如写指针打两拍到了读时钟域,此时写指针已经大于了同步的写指针,如果此时判断为空的话,实际上,写指针在同步的时间里已经存了值进去,这样就是假空。假满同理,但是这个对fifo功能没有影响。比如要此时要判断满,二进制下,读指针为000,写指针为100。都指针同步的是000的值,同步过程中可能会变为001,但是此时判断为了满,这就相当于FIFO的深度本来要为4,但是在3的时候就判断为了满,这是一种保守设计,FIFO功能不会出错。

读写频率差距很大,会出问题嘛

如果写的频率很高,相对于读时钟是一个快时钟,那么如果读时钟域读了一个数,写时钟域写了三个。写地址变化了两三次,这样是否会引起多bit跨时钟域,从而导致FIFO功能的错误?

比如一个同步到读时钟域的写地址是011,写时钟相较于读时钟快很多,在同步的过程中,写地址连续变化了011-010-110,也就是读时钟域刚采到同步 过来的011,就要去采样新的地址110,这第0位和第2位都发生了变化,这样会出问题嘛?

 答案:不会。

因为格雷码的变化是有顺序的,是按照011-010-110的顺序变化,就算变化的再快,采样的要么是010(同步失败),要么是110(同步成功),不会出现其他的值,FIFO的功能也是正确的。如果是二进制直接同步,就有可能发生010-011-100,这样在011-100的过程中所有位都发生变化,如果同步失败得到的结果就是一个随机的二进制数,会导致FIFO功能错误。需要明确一个关键点:

多bit变化的含义是:写时钟域的写指针是相对于前一个写时钟域的写指针的变化,而不是相对于写时钟域的写指针相对于读时钟域的写指针的变化

为什么工程上的不用手撕的异步FIFO?

1.自己写的FIFO代码质量可能不高,会出现一些问题

2.需要对格雷码做一些时序约束来保证FIFO的功能正确:在DC中约束从格雷码寄存器的时钟端口---->到3级同步器的输入端口的最大延时(max_delay)。不然可能会出现两个错误:

  1. 格雷码各bit位延时不一致---导致同步器采样的地址不符合gray规律,afifo功能异常
  2. 格雷码到同步器的延时有好多个周期----异步afifo性能下降

情况1:格雷码各bit位延时不一致

格雷码各bit位延时不一致,导致fifo功能异常

假设3bit的gray码各比特位延时不一致,比如gray[1]延时比gray[0]多一个采样周期,比如gray[2]延时比gray[1]多一个采样周期,如图所示同步器syn_3x采样端的数据入口处的波形。

虽然源端格雷码是符合要求的,但是由于格雷码延时不一致,导致采样端采样的格雷码不符合要求,如图所示:采样后的格雷码由001跳转到了010又由011跳转到了111,这会造成溢出和读空信号有效,afifo功能异常。

情况2:格雷码到同步器的延时有好多个周期

格雷码到同步器的延时越长,流水间隔越大,fifo性能越差。

假如afifo深度为16,写地址waddr_gray码到同步器的延时为8个周期,加上同步器3个周期,写数据侧写入数据后,至少需要11个read_clk后读数据侧empty信号无效,也就是说至少11个read_clk后读侧才能读数据。而如果写地址waddr_gray码到同步器的延时为1个周期,则写数据侧写入数据后,只需要4个周期,读侧就能读数据了

这里复制粘贴了一篇博客,它写的很好

相关文章:https://blog.csdn.net/icxiaoge/article/details/88925743

FIFO的深度必须是2^n吗?

一般而言异步FIFO的深度必须是2^n,因为要用格雷码转换,比如深度为4,格雷码是00-01-11-10。当10-00时候还是一位的变化,如果不是2^n的话就会有很多位变化。

如果深度就是要非2^n怎么办?

那么 格雷码的变化范围就要重新确定一下,用深度为6的FIFO举例子,重新确定格雷码变化的范围是001-011-010-110-111-101。这样从101-001还是一位变化。

读写位宽的不同的FIFO

原理:根据读写计数器累加来实现数据的拼接

相关链接:FIFO读写侧位数不同的处理_fifo读写位宽不一致-CSDN博客

如果写数据是16bit,读数据是8bit,FIFO的位宽最好设置为16bit,然后再读端加计数器来判断读地址的增。因为如果FIFO的位宽不是16bit的话,是8bit的话,格雷码需要变化两次,这样格雷码就失效了,还需要对其进行进一步的处理

另一种情况,FIFO的数据输入是96位,输出是192位,FIFO的宽度是24,所以,写一个数据占用4个深度,读一个数据释放8个深度 ,这时候是否读写就不能用empty来判断了,需要有个enough_data的信号,这个信号的判断:(1)wptr与rptr高位相同,只需waddr >= raddr + 8;(2)wptr与rptr高位不同时,rptr >= waddr + 8。注意:wptr是waddr扩展最高位后的数据

FIFO深度的计算

FIFO深度的计算在这篇文章中已经说的特别详细了,FIFO深度计算 - 知乎 (zhihu.com)

在这里主要说下考虑到两级同步后,FIFO的深度计算会变大还是变小?

答案是变大

就像这篇文章中说的那样,正常来说FIFO的计算是这么算的,它是假设读时钟和写时钟同时开始读写。如果考虑两级同步打拍,fifo 的深度会更大一点,会多几个深度,以上面这个例子为例,写一个数据是12.5ns,读一个数据是20ns,在写完第一个数据的时候开始读,要经过40ns,此时fifo里面有个四个数据了,剩余burst的长度为116,此时读写同时进行,按照上述的思路算后的最小深度为44,加上原来的4个是48,这样比不考虑两级dff的深度大了一点点。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值