一、什么是跨时钟域?
- 1、同步设计:在同步设计中,整个设计都是使用同一个时钟源,所有时钟的频率和相位都可以事先预知
- 2、异步设计:在设计中有两个或以上的时钟, 且时钟之间是同频不同相或不同频的关系,异步时序设计的关键就是把数据或控制信号正确地进行跨时钟域传输。
- 3、跨时钟域(CDC,Clock Domain Crossing):数据从一个时钟域传输到另一个时钟域
- 4、跨时钟域是如何产生的呢?
现在的芯片(比如SOC,片上系统)集成度和复杂度越来越高,通常一颗芯片上会有许多不同的信号工作在不同的时钟频率下。比如SOC芯片中的CPU通常会工作在一个频率上,总线信号(比如DRAM BUS)会工作在另一个时钟频率下,而普通的信号又会工作在另外的时钟频率下。这3个不同时钟频率下工作的信号往往需要相互沟通和传递信号。不同时钟域下的信号传递就涉及到跨时钟域信号处理。
二、跨时钟域传输的问题?
- 跨时钟域问题的本质是:亚稳态,根据传输的数据大小,分为单比特亚稳态、多比特亚稳态
组合逻辑竞争冒险、时序逻辑亚稳态
2、1 亚稳态(单bit:两级D触发器(双DFF))
亚稳态:数据无法在规定的时间段内达到一个稳定的状态
亚稳态的发生原因:
- 1、数据:数据传输中不满足D触发器的
建立时间Tsu
和保持时间Th
要求- 2、复位:复位过程中复位信号的释放相对于有效时钟沿的
恢复时间(recovery time)
和移除时间(removal time)
不满足
亚稳态
主要发生在异步信号采集、跨时钟域信号传输以及异步复位电路等常用设计中
亚稳态输出不确定,但是会传给后一级触发器,这会导致后级电路出错,所以亚稳态危害很大
- 1、单bit信号的跨时钟域传输:用两级D触发器做同步处理
- 2、常见错误:时钟域A的组合逻辑信号(即时钟域A发出的信号先经过组合逻辑再传输到时钟域B)直接敲两级DFF同步到时钟域B
- 解决方案:时钟域A的组合逻辑信号必须先经过一级DFF,等数据稳定后,再传输到B时钟域
- 解决方案:时钟域A的组合逻辑信号必须先经过一级DFF,等数据稳定后,再传输到B时钟域
2、2 数据收敛(多bit亚稳态)(格雷码编码、握手协议、异步FIFO、DMUX)
- 1、数据收敛:如何确保一组相关联的同步信号在经过不同的路径之后可以在某一个相同的时钟周期正确地到达另一个时钟域。
- 2、对于多bit信号跨时钟域传输时,虽然可以对每一个信号使用Double FF来进行信号同步,但是信号的准确性和关联性却会出现问题,这就是数据的收敛问题,也就是多bit信号的亚稳态。
- 3、比如有两个相关联的信号X,Y要从时钟域clk_A向时钟域clk_B传播,这里分别对两个信号进行两级DFF同步处理,最终同步信号就可以满足后续电路的时序要求,但是可能会出现如下的情况:
- 4、多bit信号的跨时钟域传输:格雷码编码、握手协议、FIFO
2、3 多路扇出:(先同步后扇出)
-
1、有些情况下,一个信号在跨越时钟域之后又分为了多个分支。比如:
一个使能的控制信号分别使能后续的多个模块
- ①、同一个信号源经过不同的路径跨越时钟域之后,多路扇出的值不一定相同
- ①、同一个信号源经过不同的路径跨越时钟域之后,多路扇出的值不一定相同
-
2、解决方法:将信号同步之后再多路扇出(即先在时钟域B过两级DFF同步,再将信号扇出,不要在各自的分支上同步)
2、4 数据丢失(延长输入数据信号):类似脉冲展宽
- 1、输入端信号不能保持足够的时间使得接收端不能采样到数据,导致数据丢失
- 2、解决方案:延长输入信号
2、5 异步复位(同步释放)
- 1、在亚稳态一节中,我们讲到:异步复位信号在释放时不满足
恢复recovery时间
和移除removal时间
要求,会导致亚稳态- 异步复位信号引起的触发器亚稳态并不是在复位的时候出现的,而是在复位信号释放时出现的
- 2、虽然异步信号没有固定的时钟,但我们还是把它看作一个CDC问题
- 3、解决方法:在异步复位信号释放时对其进行同步处理(即设计要求中,常讲的:“复位信号——异步复位同步释放)
- ①、异步复位信号之所以会导致触发器出现亚稳态,原因在于:复位信号释放时不能够与触发器的时钟保持同步
- ②、因此可以通过对复位信号同步处理来解决
//Synchronized Asynchronous Reset
//异步复位、同步释放:只适用于没有PLL的系统复位信号的设置
module sync_async_reset(clock,reset_n,rst_n);
input clock, reset_n;
output rst_n;
reg rst_nr1;//打一拍
reg rst_nr2;//打两拍
always @(posedge clock or negedge reset_n) begin
if(!reset_n) begin
rst_nr1 <= 1'b0;
rst_nr2 <= 1'b0; //异步复位(一旦复位信号有效,就拉低)
end
else begin
rst_nr1 <= 1'b1;
rst_nr2 <= rst_nr1; //同步释放(打两拍,再拉高(释放))
end
end
assign rst_n = rst_nr2; //新的系统复位信号rst_n
//信号rst_n作为新的系统复位信号,后续可以用来直接“异步复位”
endmodule // sync_async_reset
三、跨时钟域传输问题的解决方法?
- 1、跨时钟域信号传输分为:单比特信号、多比特信号
- 单比特脉冲信号跨时钟域传输又分为:慢时钟域到快时钟域、快时钟域到慢时钟域
3、1 单比特信号
- 1、如果是电平信号:可以用两级D触发器同步来实现跨时钟域传输
- 2、如果是脉冲信号(数据稳定一个时钟周期):
- ①、慢到快:先用两级D触发器实现同步,再用边沿检测电路得到脉冲信号
- ②、快到慢:先将脉冲信号展宽,再同步到慢时钟域,最后用边沿检测电路得到脉冲信号
3、1、1 单比特脉冲信号(慢时钟域到快时钟域):两级D触发器同步处理
- 1、单比特电平信号:
//单比特电平信号
module single_cdc(
input clk1,
input clk2,
input rst_n,
input signal_in,
output signal_out
);
reg signal_out_r; //打一拍
reg signal_out_rr; //打两拍
always @(posedge clk2 or negedge rst_n) begin
if(!rst_n) begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
end
else if(signal_in == 1'b1) begin
signal_out_r <= signal_in;
signal_out_rr <= signal_out_r;
end
else begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
end
end
assign signal_out = signal_out_rr;
endmodule
- 2、单比特脉冲信号:慢到快
//单比特脉冲信号:慢到快
module single_cdc(
input clk1,
input clk2,
input rst_n,
input signal_in,
output reg signal_out
);
reg signal_out_r; //打一拍
reg signal_out_rr; //打两拍
reg signal_out_rrr; //边沿检测电路
always @(posedge clk2 or negedge rst_n) begin
if(!rst_n) begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
end
else if(signal_in == 1'b1) begin
signal_out_r <= signal_in;
signal_out_rr <= signal_out_r;
signal_out_rrr <= signal_out_rr;
end
else begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
signal_out_rrr <= 1'b0;
end
end
//组合逻辑(与逻辑)输出脉冲
assign signal_out = signal_out_rr && !signal_out_rrr;
endmodule
- 3、为啥是两级D触发器呢(一级或三级行不行)?
这里有一个平均失效间隔时间MTBF(Mean Time Between Failure)的考虑。 MTBF即触发器采样失败的时间间隔,MTBF时间越长,出现亚稳态的概率就越小,但是也不能完全避免亚稳态。过一级DFF,相当于乘了一个MTBF,也就是说,每过一级DFF,MTBF就会变大,亚稳态概率就会变小。
- 各参数含义:
- 如果在时钟频率 和数据变化率固定的情况下,要增大MTBF,设计者要做的只能是增大Tmet的值
- 为了增加Tmet:可以增加同步链的寄存器个数,因为寄存器到寄存器之间的时间余量会累加到Tmet上
- 有文献给出的数据:对于一个采样频率为200Mhz的系统,如果不做同步MTBF是2.5us,一级DFF同步的MTBF大概是23年,两级DFF同步的大约MTBF大概是640年,MTBF越长出错的概率越小。所以一级看上去不太稳,二级差不多够用了,至于三级可能会影响到系统的性能,而且增加面积,所以看上去没什么必要。
3、1、2 单比特脉冲信号(快时钟域到慢时钟域):脉冲展宽
-
1、从慢时钟域到快时钟域的单比特数据传输,用两级D触发器同步的方法就可以解决; 但从快时钟域到慢时钟域呢?用两级DFF同步的方法已经无法满足了(慢时钟域仍然无法采集到信号),此时我们就引入了脉冲展宽信号,把快时钟域的信号多稳定一段时间,等到慢时钟域采到了,再拉低,如此便能保证数据的跨时钟域传输。
-
2、具体实现:
- ①、快时钟域,当采样到输入信号为高时,拉高脉冲展宽信号;
- 脉冲展宽信号也可以理解为一个比clk_slow慢的多的时钟,那么从慢时钟域(脉冲展宽信号)到快时钟域(clk_slow)的信号只要过两级DFF,就一定能被快时钟域采样到
- ②、脉冲展宽信号在慢时钟域过两级DFF,确保慢时钟域采样到脉冲展宽信号,随后将信息返回到快时钟域==(慢到快:信号在快时钟域过两级D触发器)==
- ③、快时钟域采样到慢时钟域返回的高电平(表示慢时钟域已经成功采样到输入信号),将脉冲展宽信号拉低;慢时钟域采样到脉冲展宽信号为低,则拉低慢时钟域信号
- ④、慢时钟域用边沿检测电路得到脉冲信号
- ①、快时钟域,当采样到输入信号为高时,拉高脉冲展宽信号;
-
3、在快时钟域的一个单脉冲信号
signal_in
,当快时钟域时钟采集到该信号为1
时拉高singal_a
,以展宽该脉冲,方便慢时钟域采样;展宽后的脉冲通过慢时钟域进行采集(过两级DFF)得到singal_b_r
。紧接着利用singal_b_r
进行两个操作:①、拉低快时钟域的展宽信号singal_a
,表示慢时钟域已经采集到该脉冲;②、再次打拍后进行边沿检测,在慢时钟域输出该单脉冲。 -
4、代码:
module led(
input clk_fast,
input clk_slow,
input rst_n,
input signal_in,
output signal_out
);
//快时钟域脉冲展宽
reg signal_a;
always @(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
signal_a <= 1'b0;
else if(signal_in == 1'b1)//拉高
signal_a <= signal_in;
else if(signal_a_rr == 1'b1)//拉低
signal_a <= 1'b0;
//其它情况,保持上一时刻的值,这里可以省略
end
//慢时钟域采集脉冲展宽信号
//信号同步不用加判断条件
reg signal_b; //打一拍
reg signal_b_r; //打两拍
always @(posedge clk_slow or negedge rst_n) begin
if(!rst_n) begin
signal_b <= 1'b0;
signal_b_r <= 1'b0;
end
else begin
signal_b <= signal_a;
signal_b_r <= signal_b;
end
end
//快时钟域采集慢时钟域返回信息:signal_b_r
reg signal_a_r; //打一拍
reg signal_a_rr; //打两拍
always @(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
{signal_a_rr,signal_a_r} <= {2{1'b0}};
else
{signal_a_rr,signal_a_r} <= {signal_a_r,signal_b_r};//左边给左边,右边给右边
end
//慢时钟域边沿检测,得到脉冲信号
//与逻辑检测上升沿:signal_b_r为1,且signal_b_rr为0
reg signal_b_rr; //上升沿检测,将signal_b_r打一拍
always @(posedge clk_slow or negedge rst_n) begin
if(!rst_n)
signal_b_rr <= 1'b0;
else
signal_b_rr <= signal_b_r;
end
assign signal_out = signal_b_r && (!signal_b_rr);
endmodule
- 5、TestBench:
`timescale 1ns/1ns
module tb_led();
reg rst_n;
reg clk_fast;
reg clk_slow;
reg pulse_in;
wire pulse_out;
led u1
(
.rst_n (rst_n ),
.clk_fast (clk_fast ),
.clk_slow (clk_slow ),
.signal_in (pulse_in ),
.signal_out(pulse_out)
);
initial
begin
clk_fast = 1;
clk_slow = 0;
rst_n = 0;
pulse_in = 0;
repeat(5)@(posedge clk_slow);
rst_n = 1;
repeat(5)@(posedge clk_slow);
pulse_gen();
end
task pulse_gen;
begin
pulse_in <= 1;
@(posedge clk_slow);
pulse_in <= 0;
end
endtask
always #10 clk_slow = ~clk_slow;
always #5 clk_fast = ~clk_fast;
endmodule
3、2 多比特信号
3、2、1 格雷码+双DFF(异步FIFO)
常用于异步FIFO中读写地址的跨时钟域传递!
- 1、多比特信号为啥要转换成格雷码再同步,不直接同步呢?
- ①、异步FIFO采用格雷码的最大原因是:即使发生了亚稳态,也不会发生错误的读写操作(即在写满的情况下,仍写数据;读空的情况下,仍读数据)
- ②、注意!!!可以在写满、读空之前判断成写满、读空,但是,不能在已经写满、读空之后没有及时判断成写满、读空
- ③、那格雷码是怎么办到的呢?
-
不同信号路径延时不同,导致它们无法被在同一个时钟沿被采样,信号打两拍之后可能出现不同状态:
- 假如是二进制码:地址从
0111变成1000
,直接打拍,如果发生亚稳态,则可能变成1111、1001、1100
等中间态(因为每个比特都会发生改变),这些中间态,很有可能造成已经写满、读空的状态下没有及时判断成写满、读空,发生数据的覆盖或者重复读出; - 而格雷码相邻状态只有1bit不同,二进制的
0111变成1000
,转换成格雷码是从0100变成1100(注意只有最高位发生了改变)
,如果发生亚稳态,也只会变成0111
(因为每次只改变一位信号:本例中是最高位发生变化(可以变成0或1)),这只会导致在写满、读空之前判断成写满、读空,而不会造成该空满而未报空满的情况。 - 具体来讲,假如是读地址同步到写时钟域(判断写满),读指针
从0100变成1100
,如果写时钟域采到的是0100
,则1100
是实际的读指针,0100
是同步到写时钟域的读指针,数据安全写入的标准是:同步到写时钟域的读指针应该小于等于实际的读指针(这样当写指针追了一圈,又追上读指针时,写满标志才是安全的),而此时0100
显然比1100
要小,因此数据写入是安全的; - 假如是写地址同步到读时钟域(判断读空),写指针
从0100变成1100
,如果读时钟域采到的是0100
,则1100
是实际的写指针,0100
是同步到读时钟域的写指针,数据安全读出的标准是:同步到读时钟域的写指针应该小于等于实际的写指针(这样当读指针追上写指针时,读空标志才是安全的),而此时0100
显然比1100
要小,因此数据读出是安全的。
- 假如是二进制码:地址从
-
不同信号路径延时不同,导致它们无法被在同一个时钟沿被采样,信号打两拍之后可能出现不同状态:
- 2、格雷码应用时一定要保证:首尾数据也只有1bit不同,也就是数据个数必须是
2^n
,否则就不能用格雷码加双DFF的方式跨时钟域传输- ①、例如:0到3的4个格雷码:
0000、0001、0011、0010
,则首0000
和尾0010
之间,只有1bit不同;
0到2的3个格雷码:0000、0001、0011
,则首0000
和尾0011
之间,有2bit不同,因此不能用双DFF方式 - 对于那些无法用格雷码编码的多bit信号,可使用握手协议来处理跨时钟域的问题
- ①、例如:0到3的4个格雷码:
- 3、二进制码转格雷码一般是通过组合电路实现的,由于组合电路不同的路径延时不同,因此转换后的格雷码必须在过两级DFF之前,先过一级DFF,保证输出稳定的数据
3、2、2 握手协议
-
1、握手协议将多比特数据的传输问题转换成单个信号的跨时钟域问题(只对请求信号REQ和应答信号ACK进行同步);
-
2、握手协议可以只对请求信号REQ和应答信号ACK进行同步,在请求信号REQ有效期间,发送端的数据保持不变,因此握手协议可以满足并行数据传输安全的需要
假设发送端:clk_a;接收端:clk_b
- ①、类似于AXI的vaild和ready信号,发送端先将多比特数据驱动到总线上(先将数据准备好),然后发送请求信号REQ `;
- ②、接收端识别到REQ有效,接收这组数据(这里只需要将REQ同步到接收端时钟域)
两个clk_b同步
; - ③、接收完毕之后,接收端返回一个应答信号ACK
1个clk_b拉高应答ACK
; - ④、发送端识别到ACK有效,则将REQ拉低(这里只需要将ACK同步到发送端时钟域)
两个clk_a同步+1个clk_a拉低REQ
;
-
3、握手机制相关信号:文章来源:https://www.toymoban.com/news/detail-564497.html
- ①、时钟、复位:clk_a、clk_b、rst_n
- ②、a_en,data_in(这两者作为输入,可以当成一种协议,检测到a_en有效(下降沿)的时候,输入数据就更新,更新数据一直持续到a_en的下一个下降沿)
- ③、b_en:检测到a时钟域发出的请求信号的上升沿,则b时钟域可以接收数据
-
代码:文章来源地址https://www.toymoban.com/news/detail-564497.html
module led(
input clk_a,
input clk_b,
input rst_n,
input a_en,
input [3:0] data_in,
output b_en,
output reg [3:0] data_out
);
//a_en下降沿检测(与逻辑)
reg a_en_d1;
wire a_en_neg;
always @(posedge clk_a or negedge rst_n) begin
if(!rst_n)
{a_en_neg,a_en_d1} <= {2{1'b0}};
else
a_en_d1 <= a_en;
end
assign a_en_neg = a_en_d1 && !a_en;
//a时钟域发出请求信号
reg req_a;
//ack信号打两拍同步到a时钟域
reg ack_a_r;
reg ack_a_rr;
always @(posedge clk_a or negedge rst_n) begin
if(!rst_n)
req_a <= 1'b0;
else if(a_en_neg)
req_a <= 1'b1;
else if(ack_a_rr)//拉低
req_a <= 1'b0;
//其它情况,保持上一时刻的值,这里可以省略
end
//请求信号打两拍同步到b时钟域
reg req_b_r;
reg req_b_rr;
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n)
{req_b_rr,req_b_r} <= {2{1'b0}};
else
{req_b_rr,req_b_r} <= {req_b_r,req_a};
end
//检测到a时钟域发出的请求信号的上升沿,则b时钟域可以接收数据
assign b_en = req_b_r && !req_b_rr;
//b时钟域接收数据
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n)
data_out <= 'b0;
else if(b_en)
data_out <= data_in;
end
//b时钟域接收完数据,发送ack信号,打两拍同步到a时钟域
always @(posedge clk_a or negedge rst_n) begin
if(!rst_n)
{ack_a_rr,ack_a_r} <= {2{1'b0}};
else
{ack_a_rr,ack_a_r} <= {ack_a_r , req_b_rr};
end
endmodule
3、2、3 DMUX(D触发器加二选一选择器)数据使能选通设计
- 1、通过一个使能信号来判断data信号是否已经稳定,当使能信号有效的时候说明data处于稳定状态,在这种情况下终点寄存器才对信号进行采样,可以保证没有setup/hold违例,相当于把多比特信号的CDC问题转换成了单比特信号的CDC问题
- ①、
慢时钟域到快时钟域
:只需要将使能信号过两级DFF,同步到接收时钟域 ; - ②、
快时钟域到慢时钟域
:需要将使能信号用脉冲展宽的方法同步到接收时钟域 ; - ②、使能信号接MUX的sel端口,若使能信号有效,则发送端数据选通,传输到接收端
- ③、若使能信号无效,则MUX的另一输入端选通,另一输入端与MUX的输出端(数据接收端)相连,相当于接收端数据保持不变
- 注意!!!MUX的输出端无法直接与输入端相连,需要先将输出数据保存在DFF中,再和输入端相连
- 注意!!!MUX的输出端无法直接与输入端相连,需要先将输出数据保存在DFF中,再和输入端相连
- ①、
- 2、代码:
//DMUX
module led(
input clk_a,
input clk_b,
input rst_n,
input a_en,
input [3:0] data_in,
output reg [3:0] data_out
);
//a时钟域使能信号同步到b时钟域,作为MUX的sel
reg a_en_r;
reg a_en_rr;
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n)
{a_en_rr,a_en_r} <= {2{1'b0}};
else
{a_en_rr,a_en_r} <= {a_en_r,a_en};
end
//二选一MUX
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n)
data_out <= 'b0;
else if(a_en_rr == 1'b1)//如果使能信号有效
data_out <= data_in;
else //如果使能信号无效
data_out <= data_out;
end
endmodule
到了这里,关于【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!