FPGA-跨时钟域问题的解决

FPGA 跨时钟域解决问题

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

文章目录

  • 前言
  • 解决跨时钟域问题的原理
    • 两级采样法
    • 握手法
    • 异步FIFO法


前言

如果控制两个时钟域的信号没有关联,例如它们分别是由两个不同频率的晶振产生的,那么它们的相位可能经过上亿个周期才能对齐,甚至可能永远可对不起1,这种情况下,肯定会出现采样到不稳定态的问题。

解决跨时钟域问题的原理

既然跨时钟域的问题称为隐患已经成为必然,所以我们需要用恰当的方法来确定当我们跨时钟域取信号的时候,当前时钟的有效沿不会采样到另一个时钟域的不稳定态,这样可以保证FPGA设计的各个时域之间能够进行正常的通信,从而保证了FPGA设计整体设计行为的正确性。

两级采样法

当一个高频率时钟域需要使用一个非它时钟域内的电平信号时,强烈建议采用两级采样法来确保设计行为的正确性和一致性,若**当前时钟域频率小于另一个时钟域频率,则另一个时钟域必须保证其输出信号在一个大于当前时钟域周期的时间内保持不变,否则信号将会被漏采样,**两级采样法就是异步信号同步化的一个基本方法。
为什么要对非本时钟域用本时钟域的信号进行采样呢?
这是因为,如果一个信号的更新频率与当前时钟域内的信号更新频率不同步,那么和当前时钟域内的信号做任何运算,由可能导致结果出现毛刺或者不满足下级寄存器的建立或保持时间,更难的是,所有和它联系的寄存器的时序分析都会特别困难,因此为了确保新引入的这个非本时钟域的信号不会对本时钟域造成毁灭性打击,必须进行同步化处理,方法就是使用当前时钟域的时钟对其进行采样。
采样一次,已经完成了非本时钟域信号的同步操作,为什么还要采样两次呢?从逻辑来看,两级采样法就像移位寄存器一样,不能改变信号的逻辑值,还会增加信号传递进来的延迟,但是这不是画蛇添足,这是非常有必要的,首先,进行第一级采样的那个寄存器,其建立时间和保持时间是否能够满足?显然在某些情况下,建立与保持时间并不会得到满足,因为它和当前时钟域信号的变化并不同步,因此总会有这个问题,例如,当asyn signal从0变化到1,如果在这前后时间内,clk共经历了3个上升沿,那么unsafe signal的输出可能是001,也可能是011,其中第二次采样由于建立或保持时间不满足,所以无法确定其是0还是1,不过值得欣慰的是,无论是001,还是011,至少正确的捕捉到了原始信号的变化,也许你会觉得,异步逻辑本身就不可能再时间上被精确地捕捉到,既然可以正确的捕捉到信号的变化,这不就够了吗,从逻辑上是够了,从驱动能力上远远不够,一般FPGA内部的工作电压是1.5V,理想情况下,逻辑1对应电压1.5V,逻辑0对应电压0V,但是事实上,逻辑1不会精确地是1.5V,逻辑0也不会是精确的0V,其实公认的0.5V以上就可以判定为逻辑1,反之可以判断为逻辑0,所以数字信号比模拟信号更容易抗干扰。所以,假设FPGA内部有两个触发器的输出都是逻辑1,那么他俩的物理电压是相等的嘛,答案是不一定,为什么触发器能够正确地输出结果的前提是输入信号要满足建立、保持时间?原因其实很简单,就是要给数字电路以充足的时间来进行充电或者放电操作,从而让其输出的而逻辑1更接近于1.5V,逻辑0更接近于0V,那么它就更容易在规定时间内对其后级触发器进行充分的充放电控制,从而使后级触发器的输出更加强壮,那么现在,我们过来审视unsafe signal的物理电压,由于输出unsafe signal的触发器,很可能出现建立或保持时间不满足的情况,因此在这种情况下,充放电操作都不充分,输出逻辑1或者逻辑0的物理电压 都不太好,例如,如果unsafe signal为逻辑1,那么其物理电压很可能是0.6V,如果当前时钟域中有很多地方都用到了unsafe signal,那么0.6V电压的扇出能力显然会比较差,因此传递到后后续各个用到unsafe signal的地方,物理电压可能变为0.4V、0.5V,0.55V等等,那么这时unsafe signal就会被不同的触发器认成不同的逻辑电平,于是错误就诞生了,为了避免这种情况的发生,我们对unsafe signal再次进行采样,由于unsafe signal和safe signal是同步的,因此对于输出safe signal的触发器来说,建立时间已经远远超出了其建立时间的要求,因此,即使0.6V的电压对其充电比较慢,但是由于充电时间足够,充电电流有保障,可以让safe signal达到一个比较健壮的物理电压,例如1.4V,接下来,将safe signal连接到各个需要它的地方,其扇出能力就不会有任何问题了。

握手法

举个例子:小明饿了回家吃饭,妈妈不在家,于是妈妈给他留了张便签,给你留了饭,小明找到了饭的位置,并且美美的饱餐了一顿,这就是握手法的精髓,小明与妈妈完全处于两个不同的空间,但是事情却进展的非常顺利,如果处于信息传递的两个区域,就可以同样各取所需,握手法没有固定的形式,为大家介绍一种比较方便的方法实现数据传递的握手。
如果时钟域A要向时钟域B传递数据,可以采用以下步骤
对于时钟域A
step1:先将要传递的数据用一个寄存器dataA寄存,然后保持该寄存器的输出不变。
step2:等待至少一个时钟周期后,将寄存器AOK设置为逻辑1,并保持至少相当于一个B时域周期的时间长度。
step3:释放AOK为逻辑0
step4:等待至少一个周期后,如果有新的值需要传递,则更新dataA后重复step2、3、4.
对于时钟域B:
如果AOK为1,则将dataA的输出用本时钟域的dataB寄存器寄存。
到此为止,时钟域A就完成了向时钟域B内可靠传递数据的工作,对于时钟域A的step2,为什么AOK的逻辑1要保持至少相当于一个B时钟域周期的时间长度呢?就是为了让B时钟域内的时钟肯定能采样到一次AOK为逻辑1的情况。那么为什么dataA要从AOK为1的前一个周期保持不变到AOK为0后的下一个周期呢?这就是要保证当B时钟域检测到AOK为1的时刻,那么dataA的输出是稳定的,不存在不定态。

异步FIFO法

握手法适用于间歇的、不连续的、频率较低的数据传递,可是如果有大量连续数据需要在不同时钟域间传递,握手法就显得力不从心了,此时就需要一种更高效的、更可靠的方法,而异步FIFO法就是这类方法中最为典型的,不过使用了更多的资源。
(1)蓄水池问题
我们可以通过蓄水池问题来理解异步FIFO的思路:蓄水池有一个入水口,还有一个出水口,如果单独打开入水口,可以在X小时之内将水池蓄满,而单独打开出水口,可以在Y小时内将水放完,对于蓄水池来说,X和Y可以为任意值,因此灌水操作与排水操作是不相关的,也就是异步的,那么,对于蓄水池来说,无论是连续灌水连续排水、间歇灌水连续排水、连续灌水间歇排水、间接灌水间接排水等等,只要保证蓄水池里的水不溢出,那么所有从入水口流入水池的水,肯定能够一滴不漏地经由出水口流出,这就是异步FIFO传递数据能够准确无误,一个不漏的原因。
那么要怎么保证蓄水池不溢出呢?条件有两个
第一要保证入水口的平均流入水量WIA等于出水口的平均流出水量WOA,这一进一出必须相等,即WIA=WOA,水池才能达到平衡,从而才能不断地完成从灌水区域到排水区域的一个长期稳定的异步交互,如果平衡打破,那么必然会出现问题,若WIA>WOA,由于流入的水多,流出的水少,那么水池中的水量必然会不断上升,从而水池很快就会被蓄满,然后就会发生溢出,从而就会发生溢出,从而造成数据流失,需要注意的是,不用担心出现WIA 第二,要保证在任意一个时间段T内,总流入的水量WTIS减去总流出的水量WTOS小于水池的蓄水总量,如果说第一条件是从长期上来保证水池不出现溢出的情况,那么第二条就是保证从短期上来保证水池不出现溢出的情况,而要保证水池在短期内不会溢出,就必须合理设计水池的蓄水量,在水池的蓄水量为无限的情况下,水池的水位肯定会随着时间不断地变化,若水位线上升从来没有超过H,那么可以设计一个总蓄水量为H的水池作为灌水操作和排水操作的异步缓冲,并且可以确保水池不会出现短期溢出的情况。
(2)异步FIFO的接口说明
与蓄水池的入水口和出水口一样,操作FIFO需要控制好FIFO的数据写入端和数据读出端即可
写部分接口:
写部分接口中,最重要的是要数写时钟(w_clk)、写使能(w_en)、和写数据(w_data),如果想要完成一次“灌水操作”,就必须在w_clk的有效沿到来的时刻,保证w_en有效,w_data上有稳定地保持有想要写入的FIFO的数据。
当然了,要想成功的完成一次“灌水操作”,有时仅仅依靠w_clk、w_en和w_data三者可能还不够,因为FIFO可能有被灌满的那一刻,所以FIFO输出的满(FULL)信号也是非常重要的,我们可以通过先读取信号的状态,来判断是否可以进行新的一次灌水,有时候我们可能不满足于只“灌水”一次,而想要能够像“刷屏”一样,一样一次连续写入多个数据,那么full信号可能不能满足我们的需求了,于是FIFO还提供了接近满(almost_full)和若干可编程满(prog_full)信号,例如我们希望当FIFO内部的水位到达3/4的时候给出一个警戒信号,此时就可以利用almost_full和prog_full来实现。
更一般地是,我么们可以直接阅读FIFO内部的一个位于写时钟域内的计数器–w_data_count,来获得当前FIFO内的近似水位,从而更加灵活的设计我们想要的数据写入方式。
除了上面介绍的这些端口之外,一些FIFO的IP核还会给出更多可供我们使用的写FIFO信号,不过离了谁都可以,唯独不能缺少w_clk、w_en和w_data这三个接口。
在FIFO的写部分接口中,只需要在意FIFO的满状态。
读部分接口:
读部分接口中,最重要的是要数读时钟(r_clk)、读使能(r_en)、和读数据(r_data),如果想要完成一次“排水操作”,就必须在r_clk的有效沿到来的时刻,保证r_en有效,r_data上有稳定地保持有想要读出的FIFO的数据。
当然了,要想成功的完成一次“排水操作”,有时仅仅依靠r_clk、r_en和r_data三者可能还不够,因为FIFO可能有被排空的那一刻,所以FIFO输出的空(empty)信号也是非常重要的,我们可以通过先读取信号的状态,来判断是否可以进行新的一次排水,有时候我们可能不满足于只“排水”一次,而想要能够像“刷屏”一样,一样一次连续读出多个数据,那么empty信号可能不能满足我们的需求了,于是FIFO还提供了接近空(almost_empty)和若干可编程满(prog_empty)信号,例如我们希望当FIFO内部的水位减小到达1/4的时候给出一个警戒信号,此时就可以利用almost_emptyl和prog_emptyl来实现。
更一般地是,我么们可以直接阅读FIFO内部的一个位于读时钟域内的计数器–r_data_count,来获得当前FIFO内的近似水位,从而更加灵活的设计我们想要的数据读出方式。
除了上面介绍的这些端口之外,一些FIFO的IP核还会给出更多可供我们使用的写FIFO信号,不过离了谁都可以,唯独不能缺少r_clk、r_en和r_data这三个接口。
在FIFO的读部分接口中,只需要在意FIFO的空状态。

你可能感兴趣的:(FPGA之道)