本文主要是介绍CDC(Clock Domain Crossing )跨时钟域问题的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
CDC(Clock Domain Crossing )跨时钟域问题的处理
目录
CDC(Clock Domain Crossing )跨时钟域问题的处理
综述
两级寄存器做同步器
MTBF-mean time before failure-同步故障间隔时间
同步信号的来源
单bit信号的同步
”三边沿“要求
快采慢
慢采快
多bit信号的同步
D_MUX同步方案
握手
异步FIFO
综述
本小节主要对CDC问题进行讨论总结,多时钟设计一定会带来亚稳态问题,亚稳态问题简单来说是由于异步时钟之间的时钟采样边沿关系无法确定,导致在建立时间保持时间内采样数据,导致的下一级寄存器的输出无法预知,从而导致整个电路的状态无法预知的问题。在多时钟设计中,无法避免亚稳态,但可以抵消亚稳态的不利影响。下图是一个简单的示例:其中bclk采样数据adat时,数据正在改变,导致寄存器输出bdata1为0或者1。显然在设计中是致命性的错误。
需要使用同步器对异步信号进行同步处理。从而避免不确定的电平在寄存器间传播。
存在两种场景,对于同步器的设计原则。第一种是允许错过在时钟域之间传递的样本。另一种则是必须对时钟域之间传递的每个信号进行采样。在这两种情况下,CDC 信号都需要某种形式的同步到接收时钟域。
两级寄存器做同步器
第一个触发器将异步输入信号采样到新的时钟域并等待一个完整的时钟周期以允许第一级输出信号上的任何亚稳态衰减,然后第一级信号由同一时钟采样到一个第二级触发器,其预期目标是第二级信号现在是一个稳定且有效的信号,已同步并准备好在新时钟域内分发。下图是文字描述的图示
理论上,当信号被计时进入第二级时,第一级信号仍然足够亚稳态以使第二级输出信号也进入亚稳态。同步故障间隔时间 (MTBF) 概率的计算是多个变量的函数,包括用于生成输入信号和为同步触发器提供时钟的时钟频率。
MTBF-mean time before failure-同步故障间隔时间
对于大多数应用来说,计算任何跨越 CDC 边界的信号的平均故障前时间 (MTBF) 非常重要。从这个意义上说,故障意味着传递到同步触发器的信号,在第一级同步器触发器上进入亚稳态,并且在一个周期后当它被采样到第二级同步器触发器时继续亚稳态。由于信号在一个时钟周期后没有稳定到已知值,因此在采样并传递到接收时钟域时信号仍然可能是亚稳态的,从而导致相应逻辑的潜在故障。
在计算 MTBF 数字时,较大的数字优于较小的数字。较大的 MTBF 数字表示潜在故障之间的间隔时间较长,而较小的 MTBF 数字表示亚稳态可能经常发生,同样会导致设计中的故障。在不重复公式和分析的情况下,应该指出直接影响同步器电路 MTBF 的两个最重要的因素是采样时钟频率(信号被采样到接收时钟域的速度)和数据更改频率(跨越 CDC 边界的数据更改速度有多快)。
从上面的部分方程可以看出,在更高速度的设计中,或者当采样数据更频繁地变化时,故障发生的频率更高。对于更高速的设计,当设计人员不满意做两级同步的时候可以使用三个寄存器做同步器。
同步信号的来源
常常有这样的问题,同步信号更优的选择是不是应该是寄存器输出的信号?答案是肯定的,需要同步的信号若是从组合逻辑输出,则会有不同的路径延时,这种组合必定会增加数据变化频率,可能会产生小的振荡glitch数据突发,从而增加在变化时可以采样的边沿的数量,相应地增加对变化数据进行采样和生成亚稳态信号的可能性。如下图adat所示。
使用寄存器输出,可以有效地降低了 MTBF 方程中的数据变化频率,从而增加了计算故障之间的时间。如下图所示。aclk 逻辑在被传递到 bclk 域之前在 adat 触发器上建立和设置。 adat 触发器过滤掉触发器输入 (a) 上的组合逻辑的震荡,并将干净的信号传递给 bclk 逻辑。
总结而言,需要同步的信号必须是寄存器输出,也就是说发送时钟域的寄存器输出直接连到接收时钟域的寄存器输入。
接下来是同步的问题,我们如何进行同步,这里分为两种,单bit以及多bit的同步问题。
单bit信号的同步
首先介绍danbit信号的同步方法与原则,这里我们讨论的都是确保同步数据不会丢掉的情况。
”三边沿“要求
首先需要介绍一个原则,需要同步的数据值必须在至少三个目标时钟边沿保持稳定。
快采慢
根据三边沿要求,如果较快时钟域的频率是较慢时钟域的频率(或更多)的 1.5 倍(或更多),则将较慢的控制信号同步到较快的时钟域通常不是问题,因为较快的时钟信号将对较慢的 CDC 信号进行一次或多次采样。也就是说一定会采集到。
慢采快
当慢时钟采集快时钟的时候,就会发生未采集到数据的情况。如果 CDC 信号仅在一个快速时钟周期内产生脉冲,则 CDC 信号可能会在较慢时钟的上升沿之间变为高电平和低电平,并且不会被捕获到较慢时钟域中,如下图所示
可以看到,其中发送时钟域向接收时钟域发送一个脉冲,该脉冲比接收时钟频率的周期略宽。在大多数情况下,信号将被采样并通过,但 CDC 脉冲变化太接近接收时钟域的两个上升时钟沿并因此违反第一个时钟沿的建立时间的可能性很小但确实存在并且违反了第二个时钟沿的保持时间而不形成预期的脉冲。也是没有采集到信号。需要使用同步器对信号进行采样,解决此问题的一个潜在解决方案是将 CDC 信号置位一段时间,该时间超过最小脉冲宽度是接收时钟频率周期的 1.5 倍。
有两种方式来保证将数据同步过去,开环与闭环方案。
开环解决方案 - 使用同步器采样信号
首先来看开环方案,就是保证需要同步的最小脉冲宽度是采样时钟周期的 1.5 倍。当时钟频率固定且正确分析的时候,可以使用该方案。
- 优点:开环解决方案是通过 CDC 边界传递信号的最快方式,不需要确认接收到的信号。
- 缺点:与开环解决方案相关的最大潜在问题是其他工程师可能会将解决方案误认为是通用解决方案,或者设计要求可能会发生变化,并且工程师可能无法重新分析原始开环解决方案。
闭环解决方案 - 使用同步器采样信号
该问题的第二个潜在解决方案是发送启用控制信号,将其同步到新的时钟域,然后将同步信号通过另一个同步器传递回发送时钟域作为确认信号。
- 优点:同步反馈信号是一种非常安全的技术,可以确认第一个控制信号已被识别并采样到新的时钟域。
- 缺点:在允许控制信号改变之前,在两个方向上同步控制信号可能存在相当大的延迟。同时需要消耗过多的寄存器。
多bit信号的同步
给出几个使用的方案,如下所示:
- d_mux同步器
- 握手
- 异步FIFO,异步双口RAM,格雷码(异步FIFO中使用了后面这两种方式)
还有一些不常用的以及使用的改进版本也会在该部分展示
D_MUX同步方案
本质上对于D_MUX的同步方案是使用1bit信号的成功同步来保证多bit数据的正确性。首先来看基本的D_MUX同步电路示意图。将vld信号同步,在vld稳定的时候,data也一定已经稳定了。如下如所示的电路。
对应的代码如下所示:
/*=============================================================================
#
# Author: mengguodong Email: 823300630@qq.com
#
# Personal Website: guodongblog.com
#
# Last modified: 2022-09-28 22:13
#
# Filename: d_mux.v
#
# Description:
#
=============================================================================*/
module D_MUX#(parameter DATA_W = 4)(input wire i_tx_clk ,input wire i_tx_rst_n ,input wire i_rx_clk ,input wire i_rx_rst_n ,input wire i_tx_vld ,input wire [DATA_W -1: 0] i_tx_data ,output wire o_rx_vld ,output reg [DATA_W -1: 0] o_rx_data
);
//*************************************************\
//define parameter and intennal singles
//*************************************************/
reg tx_vld ;
reg [DATA_W -1: 0] tx_data ;
reg rx_vld_ff ;
reg rx_vld_ff2 ;
//*************************************************\
//main code
//*************************************************/
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)begin rx_vld_ff <= 1'b0;rx_vld_ff2 <= 1'b0;endelse begin rx_vld_ff <= i_tx_vld;rx_vld_ff2 <= rx_vld_ff;end
end
assign o_rx_vld = rx_vld_ff2;
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)o_rx_data[DATA_W-1:0] <= {DATA_W{1'b0}};else if(rx_vld_ff2 == 1'b1)o_rx_data[DATA_W-1:0] <= i_tx_data[DATA_W-1:0];
end
endmodule
优点:需要同步的时间较少,在Rx域3个Cycle就可以接收到有效的数据信号。电路较为简单,实现所需要的寄存器少。
缺点:1,需要同步的数据必须带valid信号。2,需要对两个域的时钟频率有一定的要求,需要满足valid信号必须在接受域保证有三个沿有效,同时需要发送端的数据保持稳定。3,在接收域的没有有效的vld信号输出。
为了保证在输入输出均为一个时钟周期,并且不受时钟频率的影响,保证输入输出时序状态一致。时序图,代码实现,电路图如下所示。这里的步骤尽可能是先想时序,然后想根据时序想电路,后来出代码~
/*=============================================================================
#
# Author: mengguodong Email: 823300630@qq.com
#
# Personal Website: guodongblog.com
#
# Last modified: 2022-09-28 22:13
#
# Filename: d_mux.v
#
# Description:
#
=============================================================================*/
module SYN_2FF(input wire i_clk ,input wire i_rst_n ,input wire i_sig ,output wire o_sig_syn
);
reg ff0;
reg ff1;
always @ (posedge i_clk or negedge i_rst_n) begin if(i_rst_n == 1'b0)begin ff0 <= 1'b0;ff1 <= 1'b0;endelse begin ff0 <= i_sig;ff1 <= ff0;end
end
assign o_sig_syn = ff1;
endmodulemodule D_MUX#(parameter DATA_W = 4,DATA_VLD_EN = 1)(input wire i_tx_clk ,input wire i_tx_rst_n ,input wire i_rx_clk ,input wire i_rx_rst_n ,input wire [DATA_VLD_EN -1: 0] i_tx_vld ,input wire [DATA_W -1: 0] i_tx_data ,output reg [DATA_VLD_EN -1: 0] o_rx_vld ,output reg [DATA_W -1: 0] o_rx_data
);
//*************************************************\
//define parameter and intennal singles
//*************************************************/
reg [DATA_VLD_EN -1: 0] tx_vld ;
reg [DATA_W -1: 0] tx_data ;
reg rx2tx_flag ;
wire rx2tx_flag_sync ;
reg rx2tx_flag_sync_ff ;
wire sync_edge ;
//*************************************************\
//main code
//*************************************************/
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)tx_data[DATA_W -1: 0] <= {DATA_W{1'b0}};else if(i_tx_vld == 1'b1)tx_data[DATA_W -1: 0] <= i_tx_data;
end
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)tx_vld[DATA_VLD_EN -1: 0] <= {DATA_VLD_EN{1'b0}};else if(i_tx_vld == 1'b1)tx_vld[DATA_VLD_EN -1: 0] <= i_tx_vld[DATA_VLD_EN -1: 0];
end
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)rx2tx_flag <= 1'b0;else if(i_tx_vld == 1'b1)rx2tx_flag <= i_tx_vld;
endSYN_2FF U_SYN_2FF_0(.i_clk (i_rx_clk ),.i_rst_n (i_rx_rst_n ),.i_sig (rx2tx_flag ),.o_sig_syn (rx2tx_flag_sync )
);
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)rx2tx_flag_sync_ff <= 1'b0;elserx2tx_flag_sync_ff <= rx2tx_flag_sync;
endassign sync_edge = rx2tx_flag_sync_ff != rx2tx_flag_sync;
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)o_rx_data[DATA_W-1:0] <= {DATA_W{1'b0}};else if(sync_edge == 1'b1)o_rx_data[DATA_W-1:0] <= tx_data[DATA_W-1:0];
end
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)o_rx_vld[DATA_VLD_EN -1: 0] <= {DATA_VLD_EN{1'b0}}; else if(sync_edge == 1'b1)o_rx_vld[DATA_VLD_EN -1: 0] <= tx_vld[DATA_VLD_EN -1: 0];else o_rx_vld[DATA_VLD_EN -1: 0] <= {DATA_VLD_EN{1'b0}};
end
endmodule
优点:不需要考虑接收发送域的时钟关系,可以稳定的将数据进行同步,及其安全,将数据使能进行简单的扩展,可以同时对多个vld进行同步(注意此时需要注意应用场景,这里本质上是将vld也当作数据来处理的)。
缺点:需要寄存器较多,同步时间较长。tx域需要1个Cycle,rx域需要3个Cycle。使用寄存器较多。
只要原理正确,可以使用其他的实现方式。注意,D_MUX的基本原理就是通过对使能信号的同步,确认该同步信号已经稳定,来保证多bit数据的稳定。
握手
握手的特点是:应答机制。通过应答机制来保证接受域已经锁存到信号。同时告知发送域可以进行下一次数据发送。
相比较于DMUX的同步方式,握手的优点在于可以知道接受域是否接受到数据,同时可以设计在预期的时间内,接收到应答信号。相同之处在于本质上,还是通过单bit打拍来进行的同步。对应的时序图如下所示:
首先是发起一个脉冲发起请求,同时将数据进行锁存,拉起一个请求的电平信号,将此电平信号在接受域打两拍,然后根据r_ack信号与打了两拍之后的信号进行逻辑,取边沿,将数据锁存到接受域。然后将ack电平信号打两拍,同步到发送域,同样将同步后的信号采沿,拉请求的电平信号。对此种情况,在发送域需要一个Cycle拉起请求信号,接受域需要3-4Cycle(考虑margin),将数据同步到接受域。相应信号需要3-4Cycle将相应信号进行同步。
对应的代码如下所示:
/*=============================================================================
#
# Author: mengguodong Email: 823300630@qq.com
#
# Personal Website: guodongblog.com
#
# Last modified: 2022-10-02 17:44
#
# Filename: shake_hands.v
#
# Description:
#
=============================================================================*/
module SYN_2FF(input wire i_clk ,input wire i_rst_n ,input wire i_sig ,output wire o_sig_syn
);
reg ff0;
reg ff1;
always @ (posedge i_clk or negedge i_rst_n) begin if(i_rst_n == 1'b0)begin ff0 <= 1'b0;ff1 <= 1'b0;endelse begin ff0 <= i_sig;ff1 <= ff0;end
end
assign o_sig_syn = ff1;
endmodulemodule SHAKE_HANDS #(parameter DATA_W = 4)(input wire i_tx_clk ,input wire i_tx_rst_n ,input wire i_rx_clk ,input wire i_rx_rst_n ,input wire i_tx_vld ,input wire [DATA_W -1: 0] i_tx_data ,output reg [DATA_W -1: 0] o_rx_data ,output reg o_req ,output reg o_rx_ack );
//*************************************************\
//define parameter and intennal singles
//*************************************************/
reg [DATA_W -1: 0] tx_data ;
wire req_sync ;
wire req_sync_edge ;
wire ack_sync_edge ;
wire rx_ack_sync ;
reg rx_ack_sync_ff ;//*************************************************\
//main code
//*************************************************/
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)tx_data[DATA_W -1: 0] <= {DATA_W{1'b0}};else if(i_tx_vld == 1'b1)tx_data[DATA_W -1: 0] <= i_tx_data[DATA_W -1: 0];
end
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)o_req <= 1'b0;else if(ack_sync_edge == 1'b1)o_req <= 1'b0;else if(i_tx_vld == 1'b1)o_req <= 1'b1;
endSYN_2FF U_SYN_2FF_0(.i_clk (i_rx_clk ),.i_rst_n (i_rx_rst_n ),.i_sig (o_req ),.o_sig_syn (req_sync ));always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)o_rx_ack <= 1'b0;else o_rx_ack <= req_sync;
end
assign req_sync_edge = (o_rx_ack == 1'b0) && (req_sync == 1'b1);
always @ (posedge i_rx_clk or negedge i_rx_rst_n) begin if(i_rx_rst_n == 1'b0)o_rx_data[DATA_W -1: 0] <= {DATA_W{1'b0}};else if(req_sync_edge == 1'b1)o_rx_data[DATA_W -1: 0] <= tx_data[DATA_W -1: 0];
end
SYN_2FF U_SYN_2FF_1(.i_clk (i_tx_clk ),.i_rst_n (i_tx_rst_n ),.i_sig (o_rx_ack ),.o_sig_syn (rx_ack_sync ));
always @ (posedge i_tx_clk or negedge i_tx_rst_n) begin if(i_tx_rst_n == 1'b0)rx_ack_sync_ff <= 1'b0;else rx_ack_sync_ff <= rx_ack_sync;
end
assign ack_sync_edge = (rx_ack_sync_ff == 1'b0) && (rx_ack_sync == 1'b0);endmodule
改进方案如下:
- 为了防止请求电平信号一直拉低,可以使用计数器来计算其高电平的时间,若是达到预期的同步时间,可以进行异常上报或是其他响应,同时将其拉低。
- 同样根据需求,选择是否需要将响应信号同步过去。可以再次基础上进行修改。原则同样是使用同步后的单bit信号请求信号的同步是否成功来锁存数据。
异步FIFO
对于异步FIFO的处理方式在自己动手写一个异步FIFO部分进行详述
这篇关于CDC(Clock Domain Crossing )跨时钟域问题的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!