文章目录
- 前言
- 一、什么是跨时钟域问题
- 二、单bit跨时钟域(快到慢)
- 三、单bit跨时钟域(慢到快)
- 四、单bit跨时钟域(任意时钟域之间)
- 五、多bit跨时钟域
前言
跨时钟域问题是FPGA以及IC设计中最常见的话题,也几乎是最重要的问题。
一、什么是跨时钟域问题
参考:https://bbs.huaweicloud.com/blogs/283007
只要FPGA设计中的所有资源不全属于一个时钟域,那么就可能存在跨时钟域问题,因为异步逻辑其实也可以看做一种特殊的跨时钟域问题。发生跨时钟域问题的必要条件是不同时钟域之间存在信息交互,如果一个FPGA设计中存在多个时钟域的话,这几乎是无法避免的,否则各个时钟域互不相关,那么就相当于把原设计分解为多个独立的小设计,这样的话FPGA设计的功能就无法通过协作来扩展。
那么当两个不同时钟域之间进行信息交互的时候,到底会存在什么问题呢?
首先介绍亚稳态:
触发器的建立时间和保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),那么就会产生时序违规。存在这个时序违规是因为建立时间要求和保持时间要求被违反了,此时触发器内部的一个节点(或者要输出到外部的节点)可能会在一个电压范围内浮动,无法稳定在逻辑0或者逻辑1状态。换句话说,如果数据在上述窗口中被采集,触发器中的晶体管不能可靠地设置为逻辑0或者逻辑1对应的电平上。所以此时的晶体管并未处于饱和区对应的高或者低电平,而是在稳定到一个确定电平之前,徘徊在一个中间电平状态(这个中间电平或许是一个正确值,也许不是)。
那么当两个不同时钟域之间进行信息交互的时候,遇到的情况:
- 情况一:如果控制两个时钟域的时钟信号有非常高的关联性,例如一个PLL同频不同相的两个输出,又或者干脆就是一个时钟信号的上升沿或下降沿。那么很幸运,此时时序分析仍能够确保隐患的消除,最多再多指定一些简单的信息,例如时钟信号的占空比。
- 情况二:如果控制两个时钟域的时钟信号有关联,但是不同频,例如一个PLL不同频的两个输出,那么,如果这两个频率之间没有较大的公约数的话,那么可能需要成百上千个时钟周期才能让它们的相位重新对齐。而在此期间,一个时钟的边沿可以以一个很小的相位步进差划过另一个时钟周期的所有相位,所以几乎可以肯定,一定会有某些边沿采样到了另一个时钟域内数据的不稳定状态。
- 情况三:如果控制两个时钟域的时钟信号没有关联,例如它们分别是由两个不同频率的晶振产生的,那么它们的相位可能要经过上亿个周期后才能对齐,甚至永远不能对齐,因为不同晶振有着不同的频率偏移和抖动参数。这种情况下,也肯定会出现采样到不稳定态的问题。
针对情况二,还可以通过时序分析加功能仿真确认的方法,虽然比较复杂,但仍能够进行确认是否能消除隐患,但是对于情况三,时序分析已经无能为力了,因为天知道两个晶振起振的相位是多少呢。事实上,除非两个时钟的相关性真的非常高,否则隐患几乎必被激发,那么为了让FPGA设计仍能够有正常的行为,我们必须采取措施!
二、单bit跨时钟域(快到慢)
方法:脉冲展宽和跨时钟域:脉冲展宽,将快时钟域脉冲信号拓展几个时钟周期,至少俩倍于慢时钟域周期。
module single_sync_fast2slow#(
parameter P_CLK_NUM = 1 //慢时钟与快时钟之间的比值
)(
input i_clk_a ,
input i_rst_a ,
input i_single_a ,
input i_clk_b ,
input i_rst_b ,
output o_single_b
);
/*--------clk_a--------*/
reg r_single_a ;
reg [7:0] r_cnt_a ;
always @(posedge i_clk_a or posedge i_rst_a) begin
if(i_rst_a)
r_single_a <= 'd0;
else if(r_cnt_a == 2 * P_CLK_NUM)
r_single_a <= 'd0;
else if(i_single_a)
r_single_a <= 1;
else
r_single_a <= r_single_a;
end
always @(posedge i_clk_a or posedge i_rst_a) begin
if(i_rst_a)
r_cnt_a <= 'd0;
else if(r_cnt_a == 2 * P_CLK_NUM)
r_cnt_a <= 'd0;
else if(i_single_a || r_cnt_a > 0)
r_cnt_a <= r_cnt_a + 1;
else
r_cnt_a <= r_cnt_a;
end
/*--------clk_b--------*/
reg r_single_b1,r_single_b2;
assign o_single_b = r_single_b2;
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_b)begin
r_single_b1 <= 'd0;
r_single_b2 <= 'd0;
end
else begin
r_single_b1 <= r_single_a;
r_single_b2 <= r_single_b1;
end
end
endmodule
P_CLK_NUM参数表示快慢时钟周期之间的比值,要想百分百被慢时钟域正确采集,至少要在快时钟域内将脉冲宽度拓展至慢时钟周期俩倍,因此脉冲拓宽拓宽计数器需要满足:
r_cnt_a == 2 * P_CLK_NUM
仿真波形图:
三、单bit跨时钟域(慢到快)
方法:打俩拍:
如果快时钟域频率大于俩倍慢时钟域频率 f1 > 2 * f2 ,那么打俩拍后快时钟域频率肯定是可以正确采到慢时钟域信号,但如果不满足此条件,打俩拍也可以大大降低亚稳态,保险起见也可以先拓展脉冲宽度,然后再采集。
四、单bit跨时钟域(任意时钟域之间)
方法:握手协议:
具体过程:
当时钟域A产生一个脉冲后,紧接着拉高一个传输信号trans_a,直到收到B时钟域的响应信号然后拉低,那么B时钟域必然是可以采集到信号trans_a的,因为它一直为高,一旦检测到trans_a,则拉高r_single_b1,检测r_single_b1的上升沿,即可在B时钟域下也产生一个脉冲,随后产生一个响应信号r_ack_b,r_ack_b在时钟域B下的持续周期分俩种情况讨论:
- 倘若时钟域A快于时钟域B,那么r_ack_b在时钟域B当中维持俩个时钟周期即可保证时钟域A收到响应信号
- 倘若时钟域A慢于时钟域B,那么需要保证r_ack_b被拓宽至至少俩倍钟域A的时钟周期宽度,在代码里我将其拓宽为俩倍然后加1.
如下:
localparam P_CNT_END_B = P_CLK_FRQ_A >= P_CLK_FRQ_B ? 2 : ((P_CLK_FRQ_B/P_CLK_FRQ_A) * 2 + 1);
完整代码:
module single_sync_module#(
parameter P_CLK_FRQ_A = 50_000_000,
parameter P_CLK_FRQ_B = 50_000_000
)(
input i_clk_a ,
input i_rst_a ,
input i_single_a ,
input i_clk_b ,
input i_rst_b ,
output o_single_b
);
localparam P_CNT_END_B = P_CLK_FRQ_A >= P_CLK_FRQ_B ? 2 : ((P_CLK_FRQ_B/P_CLK_FRQ_A) * 2 + 1);
/*--------clk_a--------*/
reg r_trans_a ;
reg r_ack_a1 ;
reg r_ack_a2 ;
/*--------clk_b--------*/
reg r_single_b ;
reg r_single_b1 ;
reg r_single_b2 ;
reg r_ack_b ;
reg [7:0] r_cnt_b ;
wire w_single_b_pos ;
assign o_single_b = r_single_b ;
assign w_single_b_pos = r_single_b1 & !r_single_b2;
/*--------clk_a--------*/
always @(posedge i_clk_a or posedge i_rst_a) begin
if(i_rst_a)
r_trans_a <= 'd0;
else if(r_ack_a2)
r_trans_a <= 'd0;
else if(i_single_a)
r_trans_a <= 1;
else
r_trans_a <= r_trans_a;
end
always @(posedge i_clk_a or posedge i_rst_a) begin
if(i_rst_a)begin
r_ack_a1 <= 'd0;
r_ack_a2 <= 'd0;
end
else begin
r_ack_a1 <= r_ack_b;
r_ack_a2 <= r_ack_a1;
end
end
/*--------clk_b--------*/
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_b)begin
r_single_b1 <= 'd0;
r_single_b2 <= 'd0;
end
else if(r_trans_a)begin
r_single_b1 <= 'd1;
r_single_b2 <= r_single_b1;
end
else begin
r_single_b1 <= 'd0;
r_single_b2 <= 'd0;
end
end
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_a)
r_single_b <= 'd0;
else if(w_single_b_pos)
r_single_b <= 1;
else
r_single_b <= 'd0;
end
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_a)
r_ack_b <= 'd0;
else if(r_cnt_b == P_CNT_END_B - 1)
r_ack_b <= 'd0;
else if(w_single_b_pos)
r_ack_b <= 1;
else
r_ack_b <= r_ack_b;
end
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_a)
r_cnt_b <= 'd0;
else if(r_cnt_b == P_CNT_END_B - 1)
r_cnt_b <= 'd0;
else if(r_ack_b)
r_cnt_b <= r_cnt_b + 1;
else
r_cnt_b <= 'd0;
end
endmodule
波形图,先看A慢B快的情况:
A频率为50Mhz,B频率为200Mhz,所以B的响应信号r_ack_b持续了9个时钟周期,其实这样子做的主要目的就是为了保证慢时钟域A一定存在一个上升沿可以在满足建立时间和保持时间的窗口内采集到响应信号。
波形图,A快B慢的情况:
A频率为200Mhz,B频率为50Mhz,所以B的响应信号r_ack_b持续了2个时钟周期,这足以保证时钟域A采集响应信号,甚至一个时钟周期都够了,当然这主要是为了确保r_ack_b宽度至少大于俩倍的时钟域A周期。
以上单bit跨时钟处理方法主要是适用于不连续触发的信号,如果连续触发,由于同步一次需要消耗很多时钟周期,会大量漏采数据,不过一般单bit数据主要是控制信号。
如若碰到了连续触发的情况,则需要采用异步RAM和异步FIFO了
五、多bit跨时钟域
- 异步FIFO
- 双端口RAM
- 握手,大概就是把每个bit进行一次握手处理,开销较大
- 合并信号