异步FIFO

个人导航网站:yun916831.github.io

 

第1章 传递多个异步信号

下文将“异步FIFO”简称为“ FIFO”

将多个信号从一个时钟域同步到另一个时钟域,并确保所有的信号都同步到新时钟域中的同一时钟周期这是一个关键问题。FIFO在设计中用于将多位数据从一个时钟域安全地传递到另一个时钟域。通过一个时钟域中的控制信号将数据存入FIFO缓存中,并通过来自第二时钟域的控制信号将数据从同一FIFO缓存的另一个端口中读取并删除。

FIFO设计的难点在于生成FIFO指针以及FIFO上的满和空状态确定。

1.1 同步FIFO指针

对于同步FIFO设计(在同一时钟域中执行FIFO读写操作),一种实现方式是对FIFO的写入和读取次数进行计数,递增(以FIFO写入但不读取),递减(在FIFO读取但无写入时)或保持(无写入和读或同时进行写入和读取操作)FIFO计数值。当FIFO计数器达到预定的最大值时,FIFO为满,而当FIFO计数器为零时,FIFO为空。

不幸的是,对于异步FIFO设计,不能使用增减FIFO填充计数器,因为将需要两个不同的异步时钟来控制计数器。异步FIFO设计的满状态和空状态,必须通过比较写指针和读指针确定。

1.2 异步FIFO指针

写指针总是指向下一个要写的字;因此,在复位时,两个指针都设置为零,这也恰好是下一个要写入的FIFO字位置。

在FIFO写入操作中,将数据写入指针所指向的位置,然后将写指针递增以指向要写入的下一个位置。同样,读取指针始终指向要读取的当前FIFO字。再次复位时,两个指针均复位为零,FIFO为空,而读指针指向无效数据(因为FIFO为空且声明了空标志)。

一旦第一个数据被写入FIFO,写入指针就会递增,并且在寻址第一个FIFO存储字内容的读指针会立即将第一个有效数据驱动到FIFO数据输出端口,由接收端读取。

读指针始终指向要读取的下一个FIFO字,意味着接收端不必使用两个时钟周期来读取数据。如果接收端在读取FIFO数据之前必须先递增读指针,则接收端必须先用一个周期等FIFO输出数据字,再用一个周期将数据字捕获到接收端,这回浪费一倍的时间。

当读写指针相等时,FIFO为空。当两个指针在复位操作期间都复位为零时,或者在从FIFO读取了最后一个字的情况下,读指针赶上了写指针,就会发生这种情况。

当指针再次相等时,也就是说,当写指针跑了一圈并追上读指针时,FIFO为满。那么问题来了,当指针相等时,FIFO到底为空还是满?

区分是满还是空可以为每个指针添加一个额外的位。当写指针增加到最终FIFO地址之后,写指针将使未使用的MSB标志位递增,同时将其余位置回零,如下图所示(FIFO使用了带MSB标志位的指针)。读取指针也是如此。如果两个指针的MSB不同,则意味着写指针比读指针多跑了一圈。如果两个指针的MSB相同,则意味着两个指针的圈数一致相同。

Figure 1 - FIFO full and empty conditions

使用n位指针(其中n-1是访问整个FIFO缓存所需的地址位数),当两个指针(包括MSB)相等时,FIFO为空。当两个指针(MSB除外)相等时,FIFO已满。

1.3 二进制FIFO指针注意事项

将二进制计数值从一个时钟域同步到另一个时钟域存在一些问题,因为n位计数器的每个位都可以同时更改(例如,二进制数7-> 8的值为0111-> 1000,所有位都更改了)。解决该问题的一种方法是使用握手机制将周期二进制计数值采样并保存在保持寄存器中,然后将同步请求信号传递到新的时钟域。识别请求信号后,接收时钟域将同步的应答信号发送回发送时钟域。在从接收时钟域接收到应答信号之前,不得更改采样指针。使用此技术,可以将具有多个更改位的计数值安全地传输到新的时钟域。收到应答信号后,发送时钟域有权清除就绪信号并重新采样二进制计数值。

使用此技术,将定期采样二进制计数器值,并且并非所有二进制计数器值都可以传递到新的时钟域。问题是,我们是否需要关注二进制计数器可能继续递增和溢出的情况?还是在采样的计数器值之间使FIFO下溢?答案是否定的。

当写指针追上同步和采样的读指针时,FIFO满。同步和采样的读取指针可能无法反映实际读取指针的当前值,但写入指针将不会尝试计数超过同步读取指针值的值。因此不会发生溢出。

当读指针赶上同步采样的写指针时,FIFO空。同步和采样的写指针也可能无法反映实际写指针的当前值,但读指针将不会尝试计数超过同步写指针值的值。因此不会发生下溢[8]。

1.4 FIFO测试的问题

在RTL仿真中,如果设计中包含二进制计数FIFO指针,则所有FIFO指针位将同时变化,没有机会观察到同步和比较问题。在没有反标延迟的门级仿真中,如果门上升沿和下降沿信号的门延迟不同,观察到问题的机会将会很小,即使能够观察到,也必须需要具有正确的时序能够触发这种情况。数据在时钟上升沿之前和之后变化,对于更高速度的设计,上升沿信号和下降沿信号之间的延迟差减小,发现问题的可能性也会减小。找到实际的FIFO设计问题对于带有反标延迟的门级设计来说是更加容易一些,但是即使进行这种类型的仿真,也很难做到发现问题,而且随着信号传播延迟的减少,观察到设计问题的几率也会降低。

如果要解决这一问题,就必须认识到存在潜在的FIFO设计问题,并且从一开始就进行正确地设计。

我有时用于测试FIFO设计的行为模型是FIFO模型,它易于编码,对于行为测试来说是准确的,但是如果用作RTL综合模型,则很难调试。只建议将此FIFO模型用于FIFO测试平台。该模型可准确地确定何时应设置FIFO满和空状态位,并可用于确定应存储在工作FIFO中的数据值。再重复一遍,此FIFO模型不能安全地进行综合!

module beh_fifo (rdata,

                 wfull,

                 rempty,

                 wdata,

                 winc,

                 wclk,

                 wrst_n,

                 rinc,

                 rclk,

                 rrst_n);

    parameter DSIZE = 8;

    parameter ASIZE = 4;

    output [DSIZE-1:0] rdata;

    output wfull;

    output rempty;

    input [DSIZE-1:0] wdata;

    input winc, wclk, wrst_n;

    input rinc, rclk, rrst_n;

   

    reg [ASIZE:0] wptr, wrptr1, wrptr2, wrptr3;

    reg [ASIZE:0] rptr, rwptr1, rwptr2, rwptr3;

   

    parameter MEMDEPTH = 1<<ASIZE; // 移位后-1得到1111

   

    reg [DSIZE-1:0] ex_mem [0:MEMDEPTH-1];

   

    // 写入行为

    always @(posedge wclk or negedge wrst_n)

        if (!wrst_n) wptr <= 0;

        else if (winc && !wfull) begin

        ex_mem[wptr[ASIZE-1:0]] <= wdata;

        wptr                    <= wptr+1;

    end

 

    // 将Read Pointer打3排同步到Write时钟域

    always @(posedge wclk or negedge wrst_n)

        if (!wrst_n) {wrptr3,wrptr2,wrptr1} <= 0;

        else {wrptr3,wrptr2,wrptr1}  <= {wrptr2,wrptr1,rptr};

 

    // 读出行为

    always @(posedge rclk or negedge rrst_n)

        if (!rrst_n) rptr              <= 0;

        else if (rinc && !rempty) rptr <= rptr+1;

 

    // 将Write Pointer打3排同步到Read时钟域

    always @(posedge rclk or negedge rrst_n)

        if (!rrst_n) {rwptr3,rwptr2,rwptr1} <= 0;

        else {rwptr3,rwptr2,rwptr1} <= {rwptr2,rwptr1,wptr};

   

    assign rdata  = ex_mem[rptr[ASIZE-1:0]];

    assign rempty = (rptr == rwptr3);

    assign wfull  = ((wptr[ASIZE-1:0] == wrptr3[ASIZE-1:0])

                    && (wptr[ASIZE] !  = wrptr3[ASIZE]));

endmodule

 

在上面的行为模型中,使用二进制计数指针,一个Verilog数组来表示FIFO缓存,同一模块中的多个异步时钟以及未注册的输出。该模型不适合综合!

模块中的两个Always模块(带有连接的Always模块),以行为模型的方式表示实际RTL FIFO设计中所需的同步。它们对于通过FIFO进行的数据传输测试并不重要,但对于在FIFO模型中正确生成的满标志和空标志的测试至关重要。行为模型中所需的同步阶段的确切数量取决于FIFO设计。该模型可用于帮助测试本文所述的FIFO设计。

第2章  格雷码计数器(风格1)

2.1 格雷码模式

出于后面的原因,我们希望同时设计n位格雷码计数器和(n-1)位格雷码计数器。分别设计两个计数器很容易,但是设计一个通用的n位格雷码计数器然后修改第二个MSB以形成一个共享LSB的(n-1)位格雷码计数器也很容易。这将被称为“对偶n-bit位格雷码计数器”。

为了更好地理解将n位格雷码转换为(n-1)位格雷码的问题,请考虑设计像图2中的双4位和3位格雷码计数器。

如上图所示,最常见的格雷码是一种反射码,其中除MSB以外的任何列中的位均关于序列中点对称。这意味着4位格雷码的后半部分是MSB反转的前半部分的镜像。

要将4位转换为3位格雷码,我们不希望4位序列的后半部分的LSB是前半部分的LSB的镜像,而是希望将4位序列的后半部分的LSB镜像。下半部分重复上半部分的4位LSB序列。

经过仔细检查,很明显将4位格雷码后半部分的第二个MSB取反将在4位序列的三个LSB中产生所需的3位格雷码序列。唯一的另一个问题是带有额外MSB的3位格雷码不再是真正的格雷码,因为当序列从7(格雷码0100)变为8(格雷码1000),然后又从15(格雷码1100)变为0(格雷码0000),两位在变化,而不是一位。真正的格雷码只会在计数之间改变一位。

2.2 格雷码计数器基础

关于格雷码,任何两个相邻数之间的编码距离仅为1(从一个格雷计数到下一个格雷计数,只能改变一位)。其次,最有用的格雷码计数器的深度必须是2的幂。可以使格雷码计数器计数偶数个序列,但是这些序列之间的转换通常不像标准格雷码那样简单。另外请注意,由于没有奇数长度的格雷码序列,因此无法制作23深度的格雷码。这意味着本文描述的技术用于制作2^n深度的FIFO。

图3是风格1的对偶n-bit位格雷码计数器的框图。风格1的格雷码计数器假定寄存器位的输出是格雷码值本身(ptr,wptr或rptr)。然后,将格雷码输出传递到格雷码—二进制转换程序(bin),将其传递给条件二进制值增量器,以生成下一个二进制计数值(bnext),并将其传递给二进制—格雷码转换器,生成下一个格雷码计数值(gnext),该值传递到寄存器输入。

2.3 对偶n-bit格雷码计数器

对偶n-bit格雷码计数器是一个格雷码计数器,它生成n位格雷码序列和一个(n-1)位格雷码序列。

通过对n位格雷码的两个MSB进行异或运算,即可生成(n-1)位格雷码的MSB,从而简单地生成(n-1)位格雷码。它与n位格雷码计数器的(n-2)个LSB组合在一起,形成(n-1)位格雷码计数器。

2.4 其他格雷码计数器注意事项

1.如图3所示,应该对二进制值增量器进行“不满”或“不空”检查,以确保FIFO指针在FIFO满或FIFO空的情况下不会递增,最终导致FIFO缓存上溢或下溢。

2.如果能够保证在FIFO满状态时不向FIFO发送数据,则可以通过从FIFO写指针中删除满状态检查逻辑(full-testing logic)来简化FIFO设计。

3.FIFO指针本身并不能保护FIFO缓存不被覆盖,但是可以将其他条件逻辑添加到FIFO缓存中,以确保在FIFO满状态下不能激活write_enable信号。

4.可以将额外的“粘性”状态位ovf(上溢)或unf(下溢)添加到指针设计中,以指示在满状态时发生了另外的FIFO写操作,或者在空状态时发生了另外的FIFO读操作,而标志位错误情况只能在重置期间清除。

第3章 格雷码计数器(风格2)

从本文的1.2版开始,FIFO设计将使用格雷码计数器风格2,这种风格使用两组寄存器来消除将格雷指针值转换为二进制值的需求。第二组寄存器(二进制寄存器)也可用于直接寻址FIFO存储器,而无需将存储器地址转换为格雷码。但是仍然需要n位格雷码指针将指针同步到相反的时钟域,但是n-1位二进制指针可用于直接寻址内存。如果需要,二进制指针还使运行计算更容易以生成“接近空”和“接近空”的位。

下图是第一种风格的FIFO、为了进行风格1FIFO设计的静态时序分析,该设计已划分为以下六个具有以下功能和时钟域的Verilog模块:

fifo1.v--此为顶层的包装器模块,包括所有时钟域。顶部模块仅用作包装,以实例化设计中使用的所有其他FIFO模块。如果将此FIFO用作较大的ASIC或FPGA设计的一部分,则可能会舍弃此顶级包装程序,以允许将其他FIFO模块分组到各自的时钟域中,以改进综合和静态时序分析。

  • fifomem.v--这是FIFO缓存,可通过写入和读取时钟域进行访问。该缓冲区很可能是实例化的同步双端口RAM。可以修改其他存储器样式以用作FIFO缓冲区。
  • sync_r2w.v--这是一个同步器模块,用于将读指针同步到写时钟域中。wptr_full模块将使用同步的读指针来生成FIFO满状态。该模块仅包含与写时钟同步的触发器。此模块中没有其他逻辑。
  • sync_w2r.v--这是一个同步器模块,用于将写指针同步到读时钟域中。rptr_empty模块将使用同步的写指针来生成FIFO空状态。该模块仅包含与读取时钟同步的触发器。此模块中没有其他逻辑。
  • rptr_empty.v--此模块与读取时钟域完全同步,并包含FIFO读取指针和空标志逻辑。
  • wptr_full.v--此模块与写时钟域完全同步,并且包含FIFO写指针和全标志逻辑。

为了使用此FIFO风格执行FIFO满和FIFO空测试,必须将读取和写入指针传递到相反的时钟域以进行指针比较。

与其他FIFO设计一样,由于两个指针是从两个不同的时钟域生成的,因此需要将这些指针“安全地”传递到相对的时钟域。本文使用的技术是同步格雷码指针,以确保一次只能更改一个指针位。

第4章 处理空满标志

4.1 生成空标志位

如2.2中所述,当读指针和同步写指针相等时,FIFO为空。

空标志位很容易生成。使用指针比寻址FIFO缓存所需的指针大一位。如果两个指针的额外位(指针的MSB)相等,则指针的换行次数相同,并且如果其余读取指针等于写指针,则FIFO为空。

格雷码写指针必须通过sync_w2r模块中的一对同步器寄存器同步到读时钟域中。由于使用格雷码指针一次只更改一位,因此在时钟域之间同步多位转换没有问题。

为了有效地输出空标志位,实际上会将写指针与rgraynext(rptr的下一个格雷码值)进行比较。下面是空标志位检查的代码,来自示例6中的rptr_empty.v:

assign rempty_val = (rgraynext == rq2_wptr);

always @(posedge rclk or negedge rrst_n)

    if (!rrst_n) rempty <= 1'b1;

    else         rempty <= rempty_val;

4.2 生成满标志位

由于需要通过在写时钟域中比较写指针和读指针来生成满标志,因此要在进行指针比较之前将读指针同步到写时钟域中。

而对于Full的判断,考虑这种例子:此时 rptr = 0_100, wptr = 0_100,两个均为7,此时为Empty,而写入一次数据后, wptr = 1_100,如果根据第1部分中提到的,此时最高位不同,其余为相同,则判断为Full,显然这是错误的,因为此时wptr此时指示的地址并不为7,而是为0。

考虑上图所示的深度为8的FIFO。在这个例子中,使用3位格雷码指针寻址存储器,并添加了一个额外的位来判断满和空状态。如果允许FIFO填充前七个位置(0-6),然后如果通过回读相同的七个字来清空FIFO,则两个指针将相等并指向地址Gray-7(FIFO为空)。在下一次写操作中,写指针将使4位格雷码指针递增,使MSB在和读指针不一样,但其余部分和读指针一致,判定FIFO满状态。这是错误的!不仅FIFO未满,而且3个LSB均未更改,这意味着寻址的缓存位置会覆盖最后写入的FIFO缓存。这就是为什么使用第4.0节的对偶n-bit位格雷码计数器的原因之一。

通过将rptr同步到wclk域中,进行比较,然后需要满足以下三个条件才能判定为满状态:

(1)wptr和rptr的MSB不相等(因为wptr必须比rptr多跑一圈)。

(2)wptr和rptr的次MSB不相等。

(3)所有其他wptr和同步rptr位必须相等。

为了有效地判定满标志,实际上需要把同步读指针与wgnext(wptr的下一个格雷码)进行比较。如下所示,这是从示例7的wptr_full.v代码中提取的代码块

assign wfull_val = ((wgnext[ADDRSIZE]    !=wq2_rptr[ADDRSIZE] ) &&

                    (wgnext[ADDRSIZE-1]  !=wq2_rptr[ADDRSIZE-1]) &&

                    (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));

always @(posedge wclk or negedge wrst_n)

    if (!wrst_n) wfull <= 1'b0;

    else         wfull <= wfull_val;

 

上述代码中的assign可以进一步简化为

assign wfull_val = (wgraynext==

                    {~wq2_rptr[ADDRSIZE:ADDRSIZE-1],

                    wq2_rptr[ADDRSIZE-2:0]});

 

4.3 不同的时钟速度

由于异步FIFO是从两个不同的时钟域提供时钟,因此显然时钟运行在不同的速度。将较快的时钟同步到较慢的时钟域时,由于较快的时钟将在较慢的时钟沿之间半周期地递增两次,因此会跳过一些计数值。

这引发了对以下两个问题的讨论:

第一个问题。快时钟是慢时钟的两倍,那么快域地址变化两次,慢时钟域采样一次,前后采样值变化了两次,会产生多位同步的问题吗?

不会。快时钟域第一次改变一位,比如从A到B,慢时钟域没有采样,当快时钟域改变第二次B到C之后,慢时钟域才采样,虽然这期间快时钟域的地址从A到C变了两次,但是慢时钟域只看到第二次B到C,只跳变了一位,因此不会产生多位同步问题。

第二个问题。快时钟域是否会引起full+1的情况—写溢出,或者empty+1—读溢出?

在本文的设计下,答案是不。当写指针追上同步的读指针并且在写时钟域中检测到FIFO满状态时,FIFO充满。如果wclk域快于rclk域,则写指针最终将追上同步的读指针,FIFO达到满状态,wfull置1,并且FIFO将停止写操作,直到同步的读指针前进才会继续。也就是说写指针不能超过wclk域中的同步读指针。

对空标志的检查也是类似的,当读取指针追上同步写入指针并且在读取时钟域中检测到FIFO空状态时,FIFO变空。如果rclk域快于wclk域,则读指针最终将追上同步写指针,FIFO将为空,rempty位将置位,并且FIFO会暂停读取,直到同步写指针前进才会继续。读指针不能超过rclk域中的同步写指针。

4.4 虚空与虚满

由于在写时钟生成FIFO-full状态,且当写指针赶上同步读指针时发生成FIFO-full,因此满检测是“准确的”和即时的。删除“满”状态是悲观是因为“满”比较是用同步读指针完成的。当读指针增加时,FIFO不再满,但是直到两个wclk边沿将更新后的rptr同步到wclk域中,整个生成逻辑才会检测到这种变化。这并不是一个问题,因为这意味着数据发送硬件被“阻止”了,或者FIFO标志位仍然是满的,有两个额外的wclk周期延迟。因此,能够保证FIFO不会溢出。向数据发送方发出信号,让它不要发送更多的数据,腾出两个额外的wclk周期,给FIFO留出时间来接收更多的数据。

类似地,由于用读时钟生成FIFO-empty状态,并且当读指针赶上同步的写指针时产生FIFO-empty,所以空检测是准确的和及时的。清除“空”状态是悲观的,因为“空”比较是用同步写指针完成的。当写指针增加时,FIFO不再为空,但是直到两个上升的rclk上升沿才能将新的wptr同步到rclk域中,空生成逻辑才会检测到这种变化。这并不是问题,因为这意味着数据接收逻辑被“阻止”了,或者通知FIFO仍然是空的,因为有两个额外的rclk边沿。重要的是确保FIFO不会下溢。向数据接收器发出信号,让它停止从FIFO中读取数据,腾出两个额外的rclk周期,这只是为FIFO填充更多的数据提供了时间。

4.4.1 精确的空和满

注意,如果两个指针同时递增,则设置满标志或空标志可能不太准确。例如,如果写指针赶上了同步的读指针,拉高满标志,但是如果读指针与写指针同时增加,FIFO过早的置为满状态。由于读操作与“写到满”操作同时发生,因此未真正写满,但读指针尚未同步到写时钟域中。因此满标志的生成有点太早,有些悲观。但这不是设计问题。

4.5 多bit异步复位

我们已经非常确定FIFO指针一次仅改变一位。问题是是否存在与异步复位相关的问题,是否会导致多个指针位同时更改?

答案是不。复位表示FIFO也已复位,并且FIFO中没有有效数据。复位生效后,所有同步寄存器,wclk域逻辑(包括已生成的满标志)和rclk域逻辑均同时异步复位。生成的空标志也同时复位。更重要的问题是如何有序去除复位信号。

4.6 几乎已满,几乎已空

某些FIFO设计要求可编程的FIFO满和FIFO空差值,以便当两个指针之间的差小于编程差时,会声明相应的几乎满或几乎为空的位。有些设计会要求在固定差值的条件下,生成几乎满或空的内容。当FIFO指针的MSB关闭时,松散地产生几乎满和空的状态,可以满足某些FIFO的要求。而有些设计可能只需要知道FIFO何时满或不足一半即可。

当wptr赶上同步rptr时FIFO已满,几乎满的情况可以描述为(wptr + 4)赶上同步rptr时的情况。

第5章 对比格雷码指针和二进制指针

如果对指针进行了采样并且在两个时钟域之间使用了握手控制信号来安全地传递采样的二进制计数值,也可以可以使用二进制指针进行FIFO设计。与格雷码指针相比,使用二进制指针的一些优点:

  • 将多位值采样到保持寄存器中并使用同步握手控制信号将多位值传递到新的时钟域中的色痕迹可用于跨时钟域传递任何任意多位值。这种方法可用于传递FIFO指针或任何多位值。
  • 每个同步格雷码指针需要2n个触发器(每个指针位2个)。采样的多位寄存器需要2n + 4个触发器(由于握手机制)。两种指针样式产生亚稳性的可能并没有差异。
  • 采样的多位二进制指针允许任意更改指针。格雷码指针只能递增和递减。
  • 采样的多位二进制指针允许任意FIFO深度;格雷码指针需要2的幂的FIFO深度。如果设计需要至少132个字的FIFO深度,则使用标准格雷码指针将采用256个字的FIFO深度。由于大多数实例化的双端口RAM块深度为2的幂,因此这并不是什么大问题。
  • 使用二进制指针使生成“几乎为空”和“几乎为满”状态位变得容易。

与格雷码指针相比,使用二进制指针的一个小缺点是:

  • 采样并持有二进制FIFO指针,然后在时钟边界上握手会有延迟。
  • 至少从接收时钟域两个时钟沿捕获新采样,从发送时钟域至少两个时钟沿捕获新采样。等待时间通常不是问题,但通常会在判别full和empty时会更加悲观,并且可能需要额外的FIFO深度来补偿所增加的悲观度。由于大多数FIFO通常都是用较大的深度指定的,因此不太可能需要额外的寄存器或更大的双端口FIFO缓存大小。

 

参考:Simulation and Synthesis Techniques for Asynchronous FIFO Design

     https://www.cnblogs.com/icparadigm/p/12823427.html

     https://zhuanlan.zhihu.com/p/472912568

  

posted @ 2023-06-23 19:45  superego_zhang  阅读(99)  评论(0编辑  收藏  举报