全文分析较长,嫌麻烦直接翻到底看结论。

引言

在各种条件下(温度、压力、震动等)电路可能出现偶发性故障,解决难度较大,要减少数字系统中的这种问题,即要保障电路最大程度的可靠性。时序电路中的这种不稳定的状态即称为亚稳态

时序电路中的触发器需要满足建立时间保持时间的要求,才能满足电路稳定工作的时序要求。

建立时间:在有效时钟沿到来之前,输入端信号需要保持稳定不变的最小时间。
保持时间:在有效时钟沿到来之后,输入端信号需要保持稳定不变的最小时间。

如何分析建立时间和保持时间呢?这里得从数电基础来分析一下。下面上D触发器的图。图源数字电子技术基础第5版

d触发器

在CLK上升沿到来之前,CLK=0,G1和G2门的前端会被锁住,Q和Q’保持不变。

d触发器

因为D端的数据变化经过G6和G5后会控制G3和G4的输出,因此在CLK上升沿到来之前,需要保持G5和G6的输出稳定,这样才能保证G3和G4能够正确输出,实现D值传递到Q的效果。因此在CLK上升沿到来之前,D的变化在G5和G6的传递必须完成。所以其建立时间必须tset≥2tpd(tpd为一个与门的信号延迟时间)。

因为CLK端的数据变化经过G4后会控制G6的输出,因此D的变化不能在G4变化的时候进行,在CLK上升沿到来之后,D必须保持不变直到G4的输出完成。所以其保持时间必须thold≥tpd。其电平转换如下。

d触发器跳转

d触发器跳转

以上只是举个例子,实际触发器电路可能不完全等同于此。另外实际情况下各个门电路延迟也并不相同,并且电路本身延迟也不完全一致。总而言之,触发器的建立和保持时间取决于生产工艺,由工艺厂商决定。

由上述分析可知,若时序不能满足建立时间和保持时间要求,输出端值会呈现未知状态,电路进入亚稳态。

跨时钟域主要问题

最好的解决亚稳态的方法就是使用同步设计,同步设计可以通过STA(静态时序分析)来解决时序问题。但实际上绝大部分的ASIC设计是由多个异步的时钟驱动的,因此多时钟的设计里就需要对跨时钟域的信号、数据进行特殊操作来保障电路的稳定性。

跨时钟域(Clock Domain Crossing,CDC)问题主要有以下几种:

  1. 亚稳态

    确保亚稳态对系统不产生错误影响

  2. 数据丢失

    确保信号由快速时钟域向慢速时钟域传播时能被正确捕捉。

  3. 数据收敛及多路扇出

    确保一组相关联的同步信号在经过不同的路径之后可以在某一个相同的时钟周期正确的到达另一个时钟域。确保在一个信号在跨时钟域出现多路扇出时,后续逻辑得到相同的值。

  4. 异步复位

    确保异步复位释放时不导致电路出现亚稳态。

下面分别介绍各个问题及其实际解决方法。

单bit-亚稳态

亚稳态

如上图,当信号adat由A时钟域进入B时钟域时,由于信号adat的时钟域和B的时钟域存在频率差和相位差,信号adat可能会在B时钟沿跳变时发生变化(从而不能满足建立时间和保持时间要求),从而出现亚稳态。此时bdat1值可能为1,也可能为0,并且可能到下一次B时钟的上升沿到来时这个不稳定状态仍然没有恢复,这样进入亚稳态的信号bdat1就会传播到下一级电路去。

亚稳态

解决这类问题的办法是使用同步器,也就是常说的打两拍(Double Flip-Flops),即对进入时钟域B的信号经过两级触发器的同步后再使用该信号。

亚稳态

由上图,打了两拍之后,对于进入亚稳态的bdat1来说,在下一个时钟沿到来的时候,他的不稳定值(0或1)会被第二级触发器捕捉到,从而bdat2会有一个稳定的输出(0或1),从而避免了亚稳态的进一步传播。注意,此方法只能避免亚稳态传播,并不保证出现亚稳态时后续电路的结果正确。就像上图中如果bdat1稳定值为0,则bdat2就会继续保持为0,从而两种情况下的输出结果完全不同,可能会影响后端电路。

那是不是打两拍后数据就不存在亚稳态问题了呢?并不是。因为实际情况下bdat1的亚稳态持续时间是有可能持续到第二个时钟沿到来时刻的,即第二个图里的样子。亚稳态的持续时间与触发器的恢复能力有关。

因此在跨两个时钟域的时候是否出现亚稳态情况其实是一个概率问题,可以想得到的是,这个出现概率与两个时钟的频率(频率越高数据越有可能撞上触发器边沿)、触发器的恢复能力等参数有关。概率分析的部分可以见参考资料[5]以及参考资料[6] 跨时钟域同步,为什么两级寄存器结构能够降低亚稳态?的分析,其主要分析参数为MTBF(Mean Time Between Failures,平均故障间隔时间)。其结论是:在一般情况下(典型参数见上述资料),对于绝大多数设计来说,打两拍可以将亚稳态控制在可以接受的范围内

同步器代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module synchronizer
(
input clkb ,
input rst ,
input adat ,
output wire bdat2
);
reg[1:0] bdat ;

always@(posedge clkb or posedge rst)begin
if(rst==1'b1)begin
bdat <= 0;
end
else begin
bdat <= {bdat[0],adat};
end
end

assign bdat2 = bdat[1];

endmodule

为有效避免跨时钟域带来的亚稳态问题,规范的设计要求

  1. 每个独立模块只使用一个时钟;

  2. 不同时钟域的模块间如果需要信号交互,必须使用同步器进行同步。;

  3. 对系统进行STA(静态时序分析),MIN-MAX Timing Analysis

亚稳态

单bit-数据丢失

如果数据从快的时钟域进入慢的时钟域,则可能会因为快时钟域的信号持续时间过短导致慢时钟域的时钟无法捕获到该信号从而导致数据丢失。

亚稳态

还是上一节的那个电路,按照上图所示时序,在B时钟域的两次上升沿之间,adat持续了一个A时钟域的周期,但是B时钟域无法捕获到该信号,导致数据无法在B时钟域传递下去。解决这种问题最直观的想法就是延长adat信号至少一个bclk的周期,使得bclk能够捕获到他,如下图。

亚稳态

那是不是只要延时就行呢?并不是。注意到因为adat的持续周期大于bclk的周期,这种方法bclk端可能采集到1次adat信号,也可能采集到2次,因此bdat的持续时间是不可控的。所以一般来说还有一种方法,即通过控制信号进行同步反馈

亚稳态

信号传递如上图所示,adat信号先置高,但暂时不拉低,什么时候拉低呢?等到B时钟域的反馈信号回来之后再拉低。由于跨时钟域,因此在adat1进入B时钟域和反馈信号bdat2进入A时钟域的时候,均打两拍处理亚稳态问题。知道在A时钟域收到反馈信号abdat2后,adat再拉低,bdat2会在之后跟随拉低。这是一种比较保险的办法,但进行同步需要很长的延迟时间

亚稳态

代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module synchronizer_aclk
(
input aclk ,
input rst ,
input bdat2 ,
input a ,
output reg adat ,
output reg adat1
);

reg abdat1 ;
reg abdat2 ;

always@(posedge aclk or posedge rst)begin
if(rst==1'b1)begin
adat <= 0;
adat1 <= 0;
end
else begin
adat1 <= adat;
if(a==1'b1)begin
adat <= 1;
end
else if(abdat2==1'b1)begin
adat <= 1'b0;
end
else begin
adat <= adat;
end
end
end

always@(posedge aclk or posedge rst)begin
if(rst==1'b1)begin
abdat1 <= 0;
abdat2 <= 0;
end
else begin
abdat1 <= bdat2;
abdat2 <= abdat1;
end
end

endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module synchronizer_bclk
(
input bclk ,
input rst ,
input adat1 ,
output reg bdat2
);

reg bdat1 ;

always@(posedge bclk or posedge rst)begin
if(rst==1'b1)begin
bdat1 <= 0;
bdat2 <= 0;
end
else begin
bdat1 <= adat1;
bdat2 <= bdat1;
end
end

endmodule

多bit-数据收敛和多路扇出

数据收敛问题情况较多,主要原因都是因为跨时钟域的一组相关联的信号在跨时钟域时因为传播的延时路径不同的误差导致进入另一个时钟域时出现的误码问题。下面分多种情况讨论一下并介绍解决方法。

数据收敛-2bit同时变化的信号

如下图,A时钟域的触发器A需要a_load和a_en两个信号同时为1才能触发adata加载到abus,而这两个信号b_load和b_en均来自另一个时钟域B。

亚稳态

当b_load和b_en跨时钟域传递时存在小的误差延时,而这个误差延迟正好被aclk捕捉到,这样会最终导致a_en比a_load信号晚一拍出来。从而在A时钟域,这两个信号不会同时出现,因此adata也就不会被加载到abus端。

解决这个问题的办法就是很自然就是跨时钟域只传递一个使能即可,传递过来同步之后再进行信号分配,如下图所示。

亚稳态

数据收敛-2bit有相位差的信号

如下图,A时钟域的触发器A需要两拍aen1和aen2分别加载数据a1到a2,然后a2到a3,而这两个信号ben1和ben2均来自另一个时钟域B。

亚稳态

当ben1和ben2跨时钟域传递时存在小的误差延时,而在A时钟域,这个误差延迟正好被aclk捕捉到,这两个信号aen1和aen2就会相差一整个时钟周期,最终导致a2端能加载而到a3却不能。

解决这个问题的办法,同样很自然就是跨时钟域只传递一个使能,传递过来同步之后再对信号打拍,如下图所示。

亚稳态

多路扇出-2bit编码信号

如下图,A时钟域的2-4译码器需要对adec[1]和adec[0]编码信号进行解码,而这两个信号的来源bdec[1]和bdec[0]均来自另一个时钟域B。

亚稳态

当ben[1]和ben[2]跨时钟域传递时存在小的误差延时,而在A时钟域,这个误差延迟正好被aclk捕捉到,这两个信号aen[1]和aen[2]就会相差一整个时钟周期,最终导致译码结果会有一个周期异常。

解决这个问题的办法,就不能跨时钟域只用一个使能信号了。因为两位信号为编码(数据)信号而不再是简单的控制信号,逻辑功能已经不同。而其解决办法其实有两种:

  1. 给A和B跨时钟域添加一个单独的控制使能信号来控制编码信号传输

    即保证在A时钟域,控制信号只在a_dec[1]和a_dec[0]稳定的情况下有效,这样就需要几个条件:

    1. 控制信号在b时钟域要在编码信号b_dec[1]和b_dec[0]变化开始后一个clkb后再变化

    2. 控制信号在b时钟域要在编码信号b_dec[1]和b_dec[0]变化结束前一个clkb先结束

    3. 控制信号的持续时间必须大于一整个clka周期

      这样其实就需要对两个时钟频率即相位控制进行比较清晰的分析才能实现。

    亚稳态

  2. 在B时钟域先进行2-4编码(即转换为独热码),然后在A时钟域通过状态机过滤掉异常的编码值(即不是独热码的值)

    亚稳态

    当然跟前一种方法一样,独热码的信号持续时间必须大于一整个clka周期,以便被clka捕捉到。

多bit信号-综合问题

其实之前讨论的三种情况都可以总结为相同的思路。

  1. 找到问题点,即数据发生变化且被另一个时钟域捕获的瞬间

  2. 将多bit跨时钟域信号想办法转换为单bit跨时钟域信号,然后通过门控、编解码等方法解决问题

但是这种解决办法可能对信号的持续时间,还有触发时间等有不同的要求,并且不同的问题需要进行不同的具体分析。那有没有通用的方法可以传递多位的信号呢?还真有。下面介绍两种常用方法。

握手协议

总体思路其实与之前的思路一致,将多bit数据通过单bit控制信号来传递。

  1. 在需要传递一个data的时候,发送端时钟域A先将data放到总线上,然后输出一个data_valid信号;

  2. data_valid信号跨时钟域并经过处理后传递到接收端时钟域B;

  3. 接收端时钟域B采集到这个data_valid信号之后,从总线上拿走data,再反馈acknowledge信号给原时钟域A;

  4. 反馈的acknowledge信号跨时钟域并经过处理后传递到发送端时钟域A;

  5. 发送端时钟域A再继续发送下一个数据。

总体结构为一次发送,一次返回,结构图如下。

亚稳态

这个结构也可以有其他变种:

  1. 一次发送,两次返回

    为了更保险,在每次直到接收端发现data_valid拉低之后,再发送一个ready信号,而发送端时钟域A接受到ready之后再继续发送下一个数据。即acknowledge用于拉低data_valid信号,然后再用控制信号ready来表示收到data_valid拉低,并开始下一次数据发送。结构图如下。

    亚稳态

  2. 两次发送,两次返回
    最保险的办法,就是发送端时钟域A发送data_valid信号,接收端返回acknowledge信号,发送端发送cancel_acknowledge信号,接收端返回cancel_finish信号。通过两次握手形成完整的同步机制。结构图如下。

    亚稳态

    这几种结构进行数据交互的延迟时间依次增大,但是遇到跨时钟域问题的概率却依次降低,实际设计时应在评估后结合具体实例选取不同的方法。

异步fifo

使用异步双口RAM,或异步FIFO,是一种非常方便的处理跨时钟域多bit数据传递的方法。发送端只需要根据自己时钟域A不断的把数据放进FIFO,接收端再根据自己时钟域B不断取数据即可,当然设计这种FIFO其实有一定难度。异步FIFO设计的主要难点就在于FIFO空信号(EMPTY)和FIFO满信号(FULL)的操作。为什么呢?因为空满信号是通过发送端的写地址和接收端的读地址进行对比来产生的。

举个例子,假设我们设计一个深度为16的FIFO,其地址从0~15,那么FIFO满的时候就是写地址增加到15的时候,FIFO空的时候就是读地址减少到0的时候。但是地址的传递是多bit跨时钟域的,存在跨时钟域的问题,即可能多bit数据传递因为传播的延时路径不同的误差存在误码可能。如果我们地址使用一般的二进制计数,假设我写地址在7的之后写入一个数据,写地址加1,即地址会从7(0111)跳到8(1000)(在发送端时钟域A),但是由于各bit信号的传递存在很小的误差延时,导致其跳转的中间状态可能被接收端时钟域B捕获到,形成异常的地址(这种情况类似于两bit关联-两个编码信号的情况),而地址异常跳0的瞬间被接收端时钟域B捕获到就会产生错误的FIFO空信号(EMPTY)。同理,由于地址跨时钟域传递的问题可能导致发送端时钟域接收到地址异常跳15的瞬间,从而产生错误的FIFO满信号(FULL)。

亚稳态

那么如何处理这种地址跨时钟域传递的问题呢?其实思路还是跟最开始一致,即我们将多bit跨时钟域信号想办法转换为单bit跨时钟域信号,然后通过一些普通方法来解决问题。在两bit关联-两个编码信号那一节中使用独热码编码来进行多bit转单bit信号,而对于地址信号这种每次只加一或减一的连续信号,可以使用格雷码来进行编码。由于格雷码相邻两个码值只有1个bit不同,因此即使出现异常,最多也就保持上一个正确的值而已。

亚稳态

根据不同的设计,可能会用到格雷码的编解码器。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module gray2bin #(parameter WIDTH = 4)
(
input [WIDTH-1:0] gray ,
output reg[WIDTH-1:0] bin
);

integer i;

always@(gray)begin
for (i=0;i<WIDTH;i=i+1)
bin[i] = ^(gray>>i);
end

endmodul
1
2
3
4
5
6
7
8
9
module bin2gray #(parameter WIDTH = 4)
(
input [WIDTH-1:0] bin ,
output wire[WIDTH-1:0] gray
);

assign gray = (bin>>1)^(bin);

endmodule

格雷码计数器的代码就不贴了,就是在普通二进制计数器后加上格雷码编码。

因此。异步FIFO的设计架构如下。主要是几个部分:

  1. 发送端的FIFO控制器,负责将数据放入FIFO,并在接收FIFO满标志时不再放数据
  2. 接收端的FIFO控制器,负责从FIFO取出数据,并在接收FIFO空标志时不再取数据
  3. FIFO满标志控制器以及FIFO空标志控制器,负责进行地址的格雷码转换并输出标志
  4. 同步器,负责同步收发两端发送给对方的地址信号

亚稳态

给自己留个作业,以后有时间了自己写一个异步FIFO。

1
module fifo_async

单bit-异步复位

数字系统的上电复位信号都是异步的,复位信号的亚稳态都是在复位信号释放的时候出现的。对于异步复位信号,引入了标准的恢复时间(Recovery Time)和移除时间(Removal Time)的概念。本质上还是建立时间和保持时间的概念。而解决异步复位问题,一般就是一句口诀:异步复位,同步释放。即在进行异步复位释放的时候,先对复位信号打两拍,同步处理之后的信号再作为系统的复位信号使用。

结语

  1. 单bit亚稳态问题一般解决方法:打两拍。如果系统频率很高或者有其他特殊约束,也可能打3拍,要分析MTBF;
  2. 数据丢失的问题一般解决方法:延宽信号或进行握手;
  3. 多bit数据传输的一般解决方法:握手协议(2次,3次或4次),或使用异步FIFO(格雷码转换地址信号);
  4. 异步复位问题的解决方法:异步复位,同步释放。

同时,在过程中作者验证了另外几个小问题,限于篇幅不做分享,列在下方与读者一共同思考。

  1. 常说的竞争和冒险与亚稳态问题有什么关系?
  2. 格雷码编码是如何保证相邻码之间只有一个bit有变化的?

本文部分的内容完全来自参考资料[1],大家有兴趣可以去看看英文原版,写的非常好。

参考资料

[1] Synthesis and Scripting Techniques for Designing Multi-Asynchronous Clock Designs

[2] 揭秘《跨时钟域处理》三大方法

[3] 万年不翻的数字电子技术基础(第5版)

[4] 芯片设计中跨时钟域(CDC)5大问题与解决方法

[5] William J. Dally and John W. Poulton, Digital Systems Engineering, Cambridge University Press, 1998, pp. 469-470.

[6] 跨时钟域同步,为什么两级寄存器结构能够降低亚稳态?