数字IC设计知识点及综合题详解(提前批、秋招必刷基础题)——(四)亚稳态、跨时钟域(CDC)处理方法及其编程仿真代码
一、什么是跨时钟域?影响是什么?1、时钟域假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域。假如设计有两个输入时钟,如图所示,一个时钟给接口1使用,另一给接口2使用,那么我们说这个设计中有两个时钟域。2、单时钟域(同步时钟域)单时钟设计 (更确切地说,也就是同步设计) 如下图所示。在单时钟域中,有单个时钟贯穿整个设计。同多时钟设计相比,这样的设计
目录
二、跨时钟域(CDC)问题分类及对应解决方法汇总(简答、填空、选择题常考题)
2、 信号从快时钟域A到慢时钟域B(低频采高频:脉冲同步器)
1、时钟域
假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域。
假如设计有两个输入时钟,如图所示,一个时钟给接口1使用,另一给接口2使用,那么我们说这个设计中有两个时钟域。

2、单时钟域
单时钟设计 (更确切地说,也就是同步设计) 如下图所示。

在单时钟域中,有单个时钟贯穿整个设计。同多时钟设计相比,这样的设计更易于实现,并且更少产生与亚稳态、建立和保时间违背方面的问题。
3、跨时钟域(多时钟域)
含有多个时钟(如下图所示)的设计是一种挑战!

这些时钟的关系,可能是下列之一,或全部包含:
- 时钟的频率不同;

- 时钟频率相同,但相位不同;

4、时域域分类
同步时钟域:
- 频率相等,相差为0——同频零相位差时钟(单时钟域,不考虑clock skew)
- 频率相等,相差固定——同频恒定相位差时钟(可以理解为单时钟域的一种,但是有clock skew)
- 频率不等,相差可变——非同频可变相位差时钟,又可分为:
- 整数倍时钟(频率成倍数)
- 有理数倍时钟(频率不成倍数)
异步时钟域:
- 高频采低频
- 低频采高频
5、跨时钟域的问题——亚稳态
触发器的建立时间和保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),那么就会产生时序违规。存在这个时序违规是因为建立时间要求和保持时间要求被违反了,此时触发器内部的一个节点(或者要输出到外部的节点)可能会在一个电压范围内浮动,无法稳定在逻辑0或者逻辑1状态。
换句话说,如果数据在上述窗口中被采集,触发器中的晶体管不能可靠地设置为逻辑0或者逻辑1对应的电平上。所以此时的晶体管并未处于饱和区对应的高或者低电平,而是在稳定到一个确定电平之前,徘徊在一个中间电平状态(这个中间电平或许是一个正确值,也许不是)。
如图所示,这就是所谓的亚稳态。

总结:亚稳态来源于建立时间和保持时间违例;单个时钟域中可以通过对关键路径的分析来避免亚稳态,但是多时钟域中,亚稳态不可避免!不过可以减少亚稳态的发生和传播,消除亚稳态的有害影响。
6、亚稳态的量化公式
采用平均故障间隔时间(MTBF:mean-time-between-failure)这个指标来估算从问题出现并导致故障的两个事件间的平均时间。MTBF越高,说明设计的稳定性越好。如果发生了故障,只是说明没有解决亚稳态的问题,并不是系统本身真的出现了问题。
其中,C1和C2代表器件的相关的常数,器件的建立时间和保持时间越小,C2越小,MTBF就越大。所以可以通过更快的触发器,来减少亚稳态发生的概率。是时钟频率,
是异步信号边沿频率。
此外,数据变化频率越低,采样时钟频率越低,MTBF越高
7、解决亚稳态的方法
亚稳态是不可避免的,是器件的固有属性。可以减少亚稳态的发生和传播,避免亚稳态带来的消极影响。
减少亚稳态的方法,通过对MTBF的分析,提出以下几种方法:
(1)使用同步器(多级寄存器):比如常用的2级或者多级FF打拍的方法(最常见的方法)
(2)降低频率:如果能满足功能要求,降低频率能够减少亚稳态的发生。(在高性能要求下并不实用)
(3)避免变化过快或者过于频繁的信号进行跨时钟采样。(在高性能要求下并不实用)
(4)采用更快的触发器:更快的触发器,也可以减少亚稳态的产生
二、跨时钟域(CDC)问题分类及对应解决方法汇总(简答、填空、选择题常考题)
假设数据从clk1传输到clk2,先看单bit数据的传输(电平或者脉冲信号):
1、同步时钟域:
- 频率相等,相差为0——同频零相位差时钟:为相同时钟,可以直接采
- 频率相等,相差固定——同频恒定相位差时钟:需要满足保持/建立时间
- 频率不等,相差可变——非同频可变相位差时钟,又可分为:
- 整数倍时钟(频率成倍数)
- 低频采高频:数据要至少保持目的时钟clk2两个周期;有可能出现亚稳态,还可能出现漏采
- 高频采低频:保证建立/保持时间;有可能出现亚稳态,不可能出现漏采
- 有理数倍时钟(频率不成倍数)
- 低频采高频:数据要至少保持目的时钟clk2两个周期:肯定出现亚稳态
- 高频采低频:数据要至少保持目的时钟clk2两个周期:肯定出现亚稳态
- 整数倍时钟(频率成倍数)
2、异步时钟域:
- 高频采低频
- 电平同步器(双锁存)
- 边沿同步器
- 脉冲同步器
- 低频采高频
- 脉冲同步器
注:
上述几种同步器方法,统称为多级寄存器处理或打拍;关于数据需要保持的时间,不管是快域采慢域,还是慢域采快域,数据都有至少保持慢域的一个时钟周期;否则就会出现以下情况:
再看多bit数据传输(数据总线、地址总线):
- 握手信号方法
- 异步FIFO(使用DPRAM)
3、总结如下:
1、有关系的时钟之间传单bit数据,理论上只需要源数据保持足够长的时间(clk2的两个周期)即可;
2、无关系的时钟之间传单bit数据,必须要使用同步器;
3、不管有无关系的时钟进行单bit传输,脉冲同步器都可以解决这个问题;
4、多bit传输只能使用握手机制或者异步fifo;
5、低频采高频,为防止数据不丢失,应当让源数据变慢,多保持一些周期;高频采低频则不需要,但是高频采低频得到的结果可能带有很多冗余。
三、 多级寄存器处理
- 在全同步设计中,如果信号来自同一时钟域(同步时钟域),各模块的输入不需要使用寄存器来寄存。只要满足建立时间和保持时间的约束,可以保证在时钟上升沿到来时,输入信号已经稳定,可以采样得到正确的值。
- 一般而言单bit信号就是我们所用到的脉冲信号或者电平信号。假设A和B是两个时钟域,各自的频率是clk_a和clk_b,clk_a的频率高于clk_b,那么单bit信号传输又分为两种情况。
1、信号从慢时钟域B到快时钟域A

在时钟域B下的脉冲信号pulse_b在时钟域A看来,是一个很宽的“电平”信号,保持多个clk_a的时钟周期,所以一定能被clk_a采到。
经验设计采集过程必须寄存两拍。
- 第一拍将输入信号同步化,同步化后的输出可能带来建立/保持时间的冲突,产生亚稳态。
- 需要再寄存一拍,减少亚稳态带来的影响。
- 一般来说两级是最基本要求,如果是高频率设计,则需要增加寄存级数来大幅降低系统的不稳定性。
- 也就是说采用多级触发器来采样来自异步时钟域的信号,级数越多,同步过来的信号越稳定。
特别需要强调的是,此时pulse_b必须是clk_b下的寄存器信号,如果pulse_b是clk_b下的组合逻辑信号,一定要先在clk_b先用D触发器(DFF)抓一拍,再使用两级DFF向clk_a传递。
- 这是因为clk_b下的组合逻辑信号会有毛刺,在clk_b下使用时会由setup/hold时间保证毛刺不会被clk_b采到,但由于异步相位不确定,组合逻辑的毛刺却极有可能被clk_a采到。
一般代码设计如下:
always @ (posedge clk_a or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
pules_a_r1 <= 1'b0;
pules_a_r2 <= 1'b0;
end
else
begin //打2拍
pules_a_r1 <= pulse_b;
pules_a_r2 <= pules_a_r1;
end
end
assign pulse_a = pules_a_r2;
2、 信号从快时钟域A到慢时钟域B(低频采高频:脉冲同步器)
如果单bit信号从时钟域A到时钟域B,那么存在两种不同的情况:
传输脉冲信号pulse_a或传输电平信号level_a。

如果是电平信号,需要保证level_a的宽度足够,至少维持一个clk_b的周期才能保证快的A域信号可以在慢的B域的时钟上升沿采集到;那么对于脉冲信号,宽度又没有那么够,又如何能保证它被采集到呢?
主要原理就是先把脉冲信号在clk_a下展宽,变成电平信号signal_a,再向clk_b传递,当确认clk_b已经“看见”信号同步过去之后,再清掉signal_a。
代码通用框架如下:
module Sync_Pulse(
clk_a,//从时钟域A
clk_b,//到时钟域B
rst_n,//复位
pulse_a_in,//脉冲信号输入
pulse_b_out,//慢域B的采集到的脉冲信号
b_out//慢域B的输出
);
input clk_a;
input clk_b;
input rst_n;
input pulse_a_in;output pulse_b_out;
output b_out;
reg signal_a;//脉冲信号变成电平信号
reg signal_b;//在B域对电平信号signal_a采样
reg signal_b_r1;//在B域对signal_b打一拍的信号
reg signal_b_r2;//在B域对signal_b打2拍的信号
reg signal_b_a1;//在A域对signal_b_r1打1拍的信号
reg signal_b_a2;//在A域对signal_b_r1打2拍的信号//在时钟域clk_a下,生成展宽信号signal_a
always @(posedge clk_a or negedge rst_n)
begin
if (rst_n == 1'b0)
signal_a <= 1'b0;
else if (pulse_a_in) //检测到到输入信号pulse_a_in被拉高,则拉高signal_a
signal_a <= 1'b1;
else if (signal_b_a2) //检测到signal_b1_a2被拉高,则拉低signal_a
signal_a <= 1'b0;
else;
end//在时钟域clk_b下,采集signal_a,生成signal_b
always @(posedge clk_b or negedge rst_n)
begin
if (rst_n == 1'b0)
signal_b <= 1'b0;
else
signal_b <= signal_a;
end
//多级触发器处理
always @ (posedge clk_b or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
signal_b_r1 <= 1'b0;
signal_b_r2 <= 1'b0;
end
else
begin
signal_b_r1 <= signal_b; //对signal_b打两拍
signal_b_r2 <= signal_b_r1;
end
end
//在时钟域clk_a下,采集signal_b_r1,用于反馈来拉低展宽信号signal_a
always @ (posedge clk_a or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
signal_b_a1 <= 1'b0;
signal_b_a2 <= 1'b0;
end
else
begin
signal_b_a1 <= signal_b_r1; //对signal_b_r1打两拍,因为同样涉及到跨时钟域
signal_b_a2 <= signal_b_a1;
end
endassign pulse_b_out = signal_b_r1 & (~signal_b_r2);//脉冲信号
assign b_out = signal_b_r1;//在B域对signal_b打一拍的信号endmodule
上述是对脉冲信号的采样,过程会比较复杂:
①在快域A中生成宽电平信号signal_a;
②再在慢域B中对signal_a进行采样和打两拍;
③根据打拍信号,采用pulse_b_out = signal_b_r1 & (~signal_b_r2);这样的赋值方式,得到脉冲信号(脉宽被拓展了)
而电平信号持续时间比较长(默认比慢域的一个周期还长),可以保证慢域能够采集到,那正常寄存两拍就行了。
仿真代码:
`timescale 1ns/100ps
module tb_Sync_Pulse();
reg clk_a;//T=6,快域
reg clk_b;//T=30,慢域
reg rst_n;
reg pulse_a_in;
wire pulse_b_out;
wire b_out;
Sync_Pulse my_Sync_Pulse(
clk_a,//从快域
clk_b,//到慢域
rst_n,
pulse_a_in,//脉冲信号
pulse_b_out,//慢域B的采集到的脉冲信号
b_out,//慢域B的输出
signal_a,//脉冲信号变成电平信号
signal_b,//在B域对电平信号signal_a采样
signal_b_r1,//在B域对signal_b打一拍的信号
signal_b_r2,//在B域对signal_b打2拍的信号
signal_b_a1,//在A域对signal_b_r1打1拍的信号
signal_b_a2
);
initial begin
clk_a = 0;
clk_b = 0;
pulse_a_in = 0;
rst_n = 0;
#2;
rst_n = 1;
#2;
pulse_a_in = 1;
#7;//比快域宽
pulse_a_in = 0;
#10000;
$stop;
end
always #3 clk_a = ~clk_a;
always #15 clk_b = ~clk_b;
endmodule

从慢域到快域采集脉冲时,相较于快域到慢域或采集电平信号的要求更为严格,因为脉冲同步器的应用不仅限于慢域到快域,快域到慢域当然也同样适用!使用同样的代码,只不过改变目的时钟和源时钟的频率来试一试:
`timescale 1ns/100ps
module tb_Sync_Pulse();
reg clk_a;//源时钟
reg clk_b;//目的时钟
reg rst_n;
reg pulse_a_in;
wire pulse_b_out;
wire b_out;
Sync_Pulse my_Sync_Pulse(
clk_a,//从快域
clk_b,//到慢域
rst_n,
pulse_a_in,//脉冲信号
pulse_b_out,//慢域B的采集到的脉冲信号
b_out,//慢域B的输出
signal_a,//脉冲信号变成电平信号
signal_b,//在B域对电平信号signal_a采样
signal_b_r1,//在B域对signal_b打一拍的信号
signal_b_r2,//在B域对signal_b打2拍的信号
signal_b_a1,//在A域对signal_b_r1打1拍的信号
signal_b_a2
);
initial begin
clk_a = 0;
clk_b = 0;
pulse_a_in = 0;
rst_n = 0;
#2;
rst_n = 1;
#2;
pulse_a_in = 1;
#30;//保持慢域的一个时钟周期
pulse_a_in = 0;
#10000;
$stop;
end
always #15 clk_a = ~clk_a;
always #3 clk_b = ~clk_b;
endmodule

激励代码只是调换了两个时钟域的周期,不过要注意慢域下的脉冲信号宽度大于等于慢域时钟的一个周期。
小结:
对于电平信号(持续时间较长),快域采慢域或慢域采快域,使用电平同步器就够了——即在目的时钟打两拍;
对于脉冲信号(脉宽等于源时钟周期),使用脉冲同步器可以很好地解决两个域互传数据的问题。
四、 握手信号方法
使用握手信号是最古老的在不同域之间转输数据的方式。
1、握手信号工作过程
下图是由两个时钟域分割成的两个单独的系统。
使用握手信号"xreq"和"yack","系统X"将数据发送给"系统Y"。
下面是使用握手信号传输数据的例子:
- 1)发送器"系统 X"将数据放到数据总线上并发出"xreq"(请求)信号,表示有效数据已经发到接收器"系统 Y"的数据总线上。
- 2)把"xreq"信号同步到接收器的时钟域"yclk" 上。
- 3)接收器在识别"xreq"同步的信号"yreq2"后,锁存数据总线上的信号。
- 4)接收器发出确认信号"yack",表示其已经接受了数据。
- 5)接收器发出的"yack"信号同步到发送时钟"xclk"上。
- 6)发送器在识别同步的"xack2"信号后,将下一个数据放到数据总线上。
握手信号序列的时序如下图所示。

从上图可以看出,安全地将一个数据从发送器传输到接收器需要5个时钟周期(X:发送req,1个;Y:接受并同步req,2个、再发送ack,1个;X:接受ack,1个。共5个)。
2、握手信号的要求
数据应该在发送时钟域内稳定至少两个时钟上升沿。
请求信号"xreq"的宽度应该超过两个上升沿时钟,否则从高速时钟域向低速时钟域传递可能无法捕捉到该信号。
3、握手信号的缺点
跨时钟域传输单个数据的延迟比使用 FIFO传输相同的数据要大得多。
五、异步FIFO
同步(单时钟)、异步(双时钟)FIFO的Verilog HDL实现(含Testbench仿真代码)_Cheeky_man的博客-CSDN博客
参考资料
FPGA基础学习(3) -- 跨时钟域处理方法 - 肉娃娃 - 博客园 (cnblogs.com)
同步电路和异步电路的区别_小白来拓荒-CSDN博客_同步电路和异步电路
跨时钟域之全面解析_day day learn的博客-CSDN博客_跨时钟域
《硬件架构的艺术》
单bit跨时钟域代码demo_qq_43222996的博客-CSDN博客
更多推荐



所有评论(0)