提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


大家好,我是老王,一鼓作气,让我们把异步FIFO的约束说完。


前言

上一篇中,我们提到,异步FIFO的关键异步路径有三类,包括下面图中的两类,和一个图上没有画出来的异步reset。

图1. 异步FIFO关键异步路径示意图
图1. 异步FIFO关键异步路径示意图

上一篇文章我们仔细讲解了如何使用set_data_check约束异步路径,以及对于上面图中的第一类格雷码地址路径,应该如何约束;这篇文章,我们来讲讲另外两类。

第二类路径:FIFO读数据

为什么要约束?

首先,让我们先确立一个共识,FIFO的rdata是一条异步路径;
很简单,其起点的DFF是写时钟域,通过读时钟域下的读地址从众多DFF中选择出来送出,送到终点是读时钟域的DFF中去,当然是一条异步路径。

(对于使用双端口SRAM的实现方式而言,同样是类似的逻辑,只是部分选择逻辑做到了SRAM内部,本文以DFF为例,如果您使用的是SRAM,请继续看,相信您会发现SRAM同样存在需要约束的问题)

确立了共识之后,就是下一个问题,这条路径为什么要约束?
一定会有人认为,虽然是异步路径,但是我已经通过格雷码敲2T后选出数据,这时候写进去的数据早已稳定

实则不然,让我们设想下面这种场景:

1. 格雷码写地址的异步路径非常短,几乎秒到;
2. 上面提到的data array到read出来第一级DFF的异步路径非常长,超过3T;

那么,就会发生当写地址通过2T的sync之后,读侧发现要读,选出一笔数据送出去后,外面的DFF在下一个read clock边沿看到的数据,还是前一T或者跳变中的亚稳态。
为了说明这种情况,我们把这部分关键电路放大
图2. 读数据异步路径示意图
图2. 读数据异步路径示意图

对于图上的ABCD四个点,我们将上面设想的场景对应的波形图画出来,就是下面这样:

图3.读数据异步路径问题场景波形图
图3.读数据异步路径问题场景波形图

我们可以看到,当上面途中,A到B的路径如果因为没有约束而走的太长,而写地址又以一种最快的可能到达读时钟域, 就有可能在读侧根据读地址去选择出当前的数据时,这个数据还处于不稳定的状态,即最下面一行D:rdata第一个读出来的X。

对于后面的可能,如果其他data[1]/[2]/…和0一样非常慢,则有可能向上面一样,每个数据都读错,甚至有可能像上面图中那样,D点DFF采样时,刚好是C传播到D点的跳变时刻,那么,就有可能因为亚稳态,而变成任意数值。

明白了问题之后,下一步就是要约束什么了。可以发现,如果二者非常快,则上面图中的数据在读出时已经稳定;如果二者都非常慢,则读侧通过比对地址发现非空时,数据也已到达;因此,我们需要约束的是写地址和读数据这两个异步路径延迟之间的差值。

即“(写数据到达rdata DFF D pin的时间),要早于(写地址的异步路径延迟+同步器带来的最大延迟)

我们以Trdata2D表示rdata到D pin的延迟;TwptrAsync表示写地址的异步路径延迟;TwptrSync表示写地址经过2T同步器带来的最大延迟;将上面的理解写作公式,就是:

T_rdata2D<(T_wptrAsync+T_wptrSync)

稍微调整一下,也就是:

T_rdata2D- T_wptrAsync<T_wptrSync

而这里的2T同步器的延迟是可以计算出来的,我们可以用时钟的T数表示。要注意,这里通过的2T同步器,延迟最大可能约为3T,而非2T。(如有疑问,欢迎提出;后面有机会我们也会详细说明)

因此,上面的公式最后可以写成:

T_rdata2D- T_wptrAsync<3T_rd

这样,就把上面我们要实现的需求,转换成了对两个异步路径延迟的数值约束。

怎么约束?

确定了要约束的内容之后,怎么约束就变得简单了。

基于上一篇文章的内容,我们已经知道如何使用set_data_check来卡两条路径之间的抖动(skew),让我们快速回顾一下:
图4.set_data_check约束效果示意图
图4.set_data_check约束效果示意图

当我们设定这样一条SDC约束:

set_data_check -from $bit0 -to $bit1 -setup $setup_value -hold $hold_value 

其中:

setup_value(简写为Ts)是让到 b i t 1 这个 p i n 的路径延迟和到 bit1这个pin的路径延迟和到 bit1这个pin的路径延迟和到bit0这个pin的路径延迟差值可以在[-∞:-Ts)之间的范围内变化,即:

T_tobit1-T_tobit0> -T_s

或者是:

T_tobit0-T_tobit1< T_s

hold value(简写为Th)是让到 b i t 1 这个 p i n 的路径延迟和到 bit1这个pin的路径延迟和到 bit1这个pin的路径延迟和到bit0这个pin的路径延迟差值可以在(-T+Th:+∞]之间波动,即:

T_tobit1-T_tobit0> -T+T_h

或者是:

T_tobit0-T_tobit1< T-T_h

二者结合就可以让到 b i t 1 这个 p i n 和到 bit1这个pin和到 bit1这个pin和到bit0这个pin的两条路径,延迟差值固定在(-T+Th:-Ts)这个区间;

而我们上面的要求,只有单个方向的约束,另一侧是开区间,因此setup和hold我们只需要设定一个即可。

T_rdata2D- T_wptrAsync<3T_rd

如果是setup,则是

set_data_check -from $wptr_2tsync0_input_D_pin -to $rdata2DFF_D_pin -setup -3*RD_CK_PERIOD

如果是hold,则是

set_data_check -from $wptr_2tsync0_input_D_pin -to $rdata2DFF_D_pin -hold 4*RD_CK_PERIOD

第三类路径:reset

一般会有两个reset,读时钟域的reset,最少要接在读时钟域影响的逻辑区域,写时钟域的reset,最少接在写时钟域影响的逻辑区域。

或许,还会有人知道要对完全异步的reset做异步触发同步释放,但这也不是我们今天要讨论的问题,如果您还不了解这是什么,没有关系,这不是本文的重点,我们后续有机会再新开文章说明。

但这些都远远不够,存在一个关键的问题,如果不解决,可能会导致数据丢失、多传、错传等等千奇百怪的问题。

让我们开始分析。

有什么问题?

让我们设想下面这几种场景:

1. 对于写reset只接在写时钟域,读reset只接在读时钟域的异步FIFO,写的一侧,在读的一侧完全不知情的情况下,因为一些特殊原因(可能是软件原因,需要操作了寄存器控制的reset)拉了reset;这时会发生什么?
2. 对于两侧reset有互相拉(即任意一侧reset拉了之后,都会reset整个异步FIFO)的异步FIFO,发生上述同样的场景;

这时会发生什么?

让我们先来看第一种,一步一步推一下:
**

1. 假如当前异步FIFO共4层,且已经工作了一段时间,写地址和读地址都停留在1,即二进制3’b101(已经发生过一次回绕);读空信号empty为高
2. 写reset时,写指针从1变为0;读指针没变;此时看到的写地址二进制为3’b000;读地址二进制为3’b101;
3. 二者地址比对,读侧可以得到的结论为:当前写侧又写了三笔数据进来,导致指针从1跳到2跳到3又跳到了0。
4. 读空信号empty从1变0,读侧开始对外发送数据。
5. 显然,这里的数据是错误的。

**
也就是下面的波形:
图5. reset(读写独立)问题示意图
图5. reset(读写独立)问题示意图

其实这里面还买了一个小坑,不知道各位有没有发现;没发现也没关系,看完接下来第二个场景的分析再回头看这边,就会发现问题在哪里。

我们接着来分析第二种场景:
**

1. 和上面一样,假如当前异步FIFO共4层,且已经工作了一段时间,写地址和读地址都停留在1,即二进制3’b101(已经发生过一次回绕);读空信号empty为高
2. 写reset时,写指针从1变为0;读指针在收到写reset后同样从1变为0;两侧同时reset后,似乎看到的地址都为0,不会有上面的问题。但真的是这样吗?
3. 让我们回顾上一篇文章中提到的一个概念:异步路径延迟在完全没有约束的情况下,是有可能为任意数值的,也就是说,写reset拉到读一侧这条路,可能延迟非常大;即写一侧被reset后,读侧可能很久之后才被reset。
4. 如果这个时间超过了读时钟域同步写时钟最快2T的延迟,则读时钟域同样会发生上面第一种场景的错误送出数据的场景。
5. 但造成送出错误数据的原因还有一个:这个例子中,reset前的写地址二进制码是3’b101,对应的格雷码是3’b111;拉reset后会瞬间从111跳到000,同时发生3bit的跳变,违背了格雷码每T只变1bit的要求,如果3bit的跳变同时发生在读时钟的采样边沿,则可能因为亚稳态,导致读侧看到的写指针变为3bit的任意值。

**
但幸在解决方法和上面的问题是一样的,只要这个错误的指针在真的产生valid之前被reset掉就好了。

也就是下面这个波形:
图5. reset(读写合并)问题示意图
图5. reset(读写合并)问题示意图

我们最后总结一下问题:
**

无论是读写reset合并还是读写reset独立,都有可能因为写侧reset导致格雷码违例,从而使得读侧读到错误的有效数据; 区别在于,读写reset独立、且没有其他保护手段的情况下,这种情况救不回来,会彻底错误;而读写reset合并的情况下,只有在写侧传到读侧的异步reset路径太长,格雷码地址先传到读侧,才会产生这个错误,而且之后很快会被reset掉。

**

对于读写reset独立的情况,想解决,只有通过外部硬件或软件的协议层面来保证(如写reset之前先通过其他手段通知读侧,读收到信号后不再关注异步FIFO的有效信号);

而读写reset合并的设计,是有机会通过硬件约束来解决的。因为,这种情况只会在下面的条件下发生,即:

写reset传到读侧的异步路径延迟,大于写格雷码地址传输到读侧异步路径加上同步器延迟和产生empty信号的延迟。

如何约束?

明确了条件之后,约束就变得很简单了;

让我们用公式来表示上面的发生条件:

T_wrst2rd>T_wgptr2rd+T_sync+T_empty

其中,表示同步器延迟的T_sync和之前所说的一样,我们这里取3T_rd;

产生empty的信号取决于各位的设计,这里我们假设为纯组合逻辑产生,延迟约等于0;

因此,这个公式可以变为:

T_wrst2rd>T_wgptr2rd+〖3T〗_rd

更进一步:

T_wrst2rd-T_wgptr2rd>〖3T〗_rd

带入之前的set_data_check约束的等效公式,可以得到,设为以下两种之一即可:
setup :

set_data_check -from $wrst_rd_load_pin -to $wptr_2tsync0_input_D_pin -setup -3*RD_CK_PERIOD

hold :

set_data_check -from $wrst_rd_load_pin -to $wptr_2tsync0_input_D_pin -hold 4*RD_CK_PERIOD

还有什么方法?

除了上述的方法之外,我们在这里再提供一种比较简单的、IP设计者可以靠纯前端逻辑实现而不考虑后端约束的方法,这要通过我们之前说到的协议层面;

这种协议可以是硬件层面,也可以是软件层面的,其整体思想,是通过协议确保,在拉reset之前,读侧对外的empty或者valid信号均已受到保护,可以是外部不再关注异步FIFO送出去的信号,也可以是FIFO内部这两个信号被锁到了一个安全的位置上(即empty为高,valid为低)。

再确认了上述状态之后,才会真正触发reset,这样就可以确保不会发生上面提到的错误数据情况了。

思考题

到此为止,对于reset的分析就结束了;不知道大家有没有注意到,其实上面分析的时候其实一直规避了一些问题,现在我把这个问题列在这里,各位读者可以想想看是否有想到,以及答案是什么。

1. 上述分析均考虑的是写侧reset对读侧的影响,那么反方向会不会有什么问题?
2. 对于使用双口SRAM而非DFF搭建的异步FIFO,是否存在上述问题?如果有,如何解决?

总结

异步FIFO中的关键异步路径到此为止,我们就分析完了;基本都可以通过set_data_check约束来解决,除此之外,reset路径还可以通过协议层面来解决。

不过还是要强调一下,如果您的设计属于低速或FPGA,或者并不在意额外的过约束,是可以通过一些简单的手段实现的,而不一定要通过set_data_check。

最后,欢迎大家来分享一下,你是否遇到过因为没有上述约束而发生的问题?你的IP设计时是否考虑到了上述问题?是否有其他解决方法?

欢迎关注“老王谈谈芯”个人公众号,获取第一手最新资讯~

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐