跨时钟域(Clock Domain Crossing,CDC)是指设计中存在着两个或两个以上异步时钟域,跨时钟域设计问题目前是逻辑设计者经常面临的问题,解决这类问题的方法被称为CDC技术,即跨时钟域技术。
在跨时钟域边界传递信号时,可能出现两种情况,确定属于哪种情况对设计非常重要:
(1).允许在时钟域之间传递时丢失数据。
(2).必须对在时钟域之间传递的每个信号进行采样。
第一种情况:有时不需要对每个值进行采样,但采样值必须准确。一个例子是标准异步FIFO设计中使用的格雷码计数器集。在适当设计的异步FIFO模型中,同步格雷码计数器不需要捕获来自相反时钟域的每个合法值,但至关重要的是,采样值必须准确,以识别何时发生满和空情况。
第二种情况:在允许对CDC信号进行更改之前,必须正确识别或识别并确认CDC信号。
在这两种情况下,CDC信号将需要某种形式的同步进入接收时钟域。
异步信号的跨时钟域同步问题。一般分为单bit的控制信号同步,以及多bit的数据信号同步。多bit的信号同步会使用保持寄存器和握手或异步FIFO完成,而单bit的信号同步,又是时钟无缝切换电路以及异步FIFO电路的设计基础,这里先介绍单bit信号同步处理。
最常见的同步器就是使用两级寄存器,即使用寄存器打两拍的方式进行同步,也就是所谓的电平同步器。如下图所示
在电平同步器中,跨时钟域的信号在接收域中要保持高电平或低电平两个时钟周期以上。同步之后的信号是电平的形式,而该电平所维持的时钟周期个数是其在跨时钟域期间被上升沿检测到的次数。每次同步完,输入信号要恢复到无效状态。所以,如果是从快到慢,信号很有可能被滤除。适用于慢时钟域向快时钟域。
两级触发器可防止亚稳态传播的原理:假设第一级触发器的输入不满足其建立保持时间,它在第一个脉冲沿到来后输出的数据就为亚稳态,那么在下一个脉冲沿到来之前,其输出的亚稳态数据在一段恢复时间后必须稳定下来,而且稳定的数据必须满足第二级触发器的建立时间,如果都满足了,在下一个脉冲沿到来时,第二级触发器将不会出现亚稳态,因为其输入端的数据满足其建立保持时间。
更确切地说,输入脉冲宽度必须大于同步时钟周期与第一级触发器所需的保持时间之和。最保险的脉冲宽度是两倍同步时钟周期。 所以,这样的同步电路对于从较慢的时钟域来的异步信号进入较快的时钟域比较有效,对于进入一个较慢的时钟域,则没有作用。
当然,仍然有可能级联的第二个寄存器输出还会表现为非稳定状态,但是这种双寄存同步器已经可以解决大部分这类亚稳态问题。总的来讲,就是一级概率很大,三级改善不大。如果再加上第三级寄存器,由于第二级寄存器对于亚稳态的处理已经起到了很大的改善作用,第三级寄存器在很大程度上可以说只是对于第二级寄存器的延拍,所以意义是不大的。
在设计这种同步器的时候应当注意遵循以下原则:
1.级联的寄存器必须使用同一个采样时钟。
2.发送端时钟域寄存器输出和接收端异步时钟域级联寄存器输入之间不能有任何其他组合逻辑。
3.同步器中级联的寄存器中除了最后一个寄存器外所有的寄存器只能有一个扇出,即其只能驱动下一级寄存器的输人。
电平同步器代码
module Level_Sync(Clk_fast, Clk_slow,Rst_n, Data_in,Data_out);
/********************参数定义********************/
/*********************IO 说明********************/
input wire Clk_slow ;//发送域时钟
input wire Clk_fast ;//接收域时钟
input wire Rst_n ; //异步复位
input wire Data_in; //发送域数据输入
output reg Data_out; //接收域数据输出
/********************** 内部信号声明 **********************/
reg Data_in_Q;
reg Data_out_Q;
/*************************功能定义*************************/
always@(posedge Clk_slow or negedge Rst_n)
begin
if(!Rst_n)
Data_in_Q <= 1'b0;
else
Data_in_Q <= Data_in;
end
always@(posedge Clk_fast or negedge Rst_n)
begin
if(!Rst_n)
begin
Data_out_Q <= 1'b0;
Data_out <= 1'b0;
end
else
begin
Data_out_Q <= Data_in_Q;
Data_out <= Data_out_Q;
end
end
endmodule
边沿检测同步器在电平同步器的输出端增加了一个触发器和逻辑门,如下图所示。
新增触发器的输出经反相后和电平同步器的输出进行与操作。这一电路会检测同步器输入的上升沿,产生一个与时钟周期等宽、高电平有效的脉冲。如果将与门的两个输入端交换使用,就可以构成一个检测输入信号下降沿的同步器。将与门改为与非门可以构建一个产生低电平有效脉冲的电路。
边沿同步器与电平同步器类似,即输入脉冲的宽度必须大于同步时钟周期与第一个同步触发器所需保持时间之和。最保险的脉冲宽度是同步器时钟周期的两倍。如果输入是一个单时钟宽度脉冲进入一个较慢的时钟域,则这种同步器没有作用,在这种情况下,就要采用脉冲同步器。
边沿同步器代码
/* ======================== *\
* Filename : Edge_Sync.v
* Author : 你我山巅自相逢
* Description : 边沿同步器
* Called by :
* Target Device: EP4CE10F17C8
* Tool versions: Quartus Prime 19.1
* Create Date : 2022/5/3
* Revision : 1.0
* Email :
* Company :
* Copyright(c) 2022,
\* ========================= */
module Edge_Sync(Clk_fast, Clk_slow,Rst_n, Data_in,Data_out);
/********************参数定义********************/
/*********************IO 说明********************/
input wire Clk_slow ;//发送域时钟
input wire Clk_fast ;//接收域时钟
input wire Rst_n ; //异步复位
input wire Data_in; //发送域数据输入
output wire Data_out; //边沿输出
/********************** 内部信号声明 **********************/
reg Data_in_Q;
reg Data_out_Q1;
reg Data_out_Q2;
reg Data_out_Q3;
/*************************功能定义*************************/
always@(posedge Clk_slow or negedge Rst_n)
begin
if(!Rst_n)
Data_in_Q <= 1'b0;
else
Data_in_Q <= Data_in;
end
always@(posedge Clk_fast or negedge Rst_n)
begin
if(!Rst_n)
begin
Data_out_Q1 <= 1'b0;
Data_out_Q2 <= 1'b0;
Data_out_Q3 <= 1'b0;
end
else
begin
Data_out_Q1 <= Data_in_Q;
Data_out_Q2 <= Data_out_Q1;
Data_out_Q3 <= Data_out_Q2;
end
end
assign Data_out = ~Data_out_Q3 & Data_out_Q2; //检测上升沿
// assign Data_out = Data_out_Q3 & ~Data_out_Q2; //检测下升沿
// assign Data_out = Data_out_Q3 ^ Data_out_Q2; //检测下升沿和下降沿
endmodule
脉冲同步器的基本功能是从某个时钟域取出一个单时钟宽度脉冲,然后在新的时钟域中建立另一个单时钟宽度的脉冲。如下图所示:
脉冲同步器也有一个限制,即输入脉冲之间的最小间隔必须等于两个同步器时钟周期。如果输入脉冲相互过近,则新时钟域中的输出脉冲也紧密相邻,结果是输出脉冲宽度比一个时钟周期宽。
当输入脉冲时钟周期大于两个同步器时钟周期时,这个问题更加严重。这种情况下,如果输入脉冲相邻太近,则同步器就不能检测到每个脉冲。
以牛客网问题为例: 从A时钟域提取一个单时钟周期宽度脉冲,然后在新的时钟域B建立另一个单时钟宽度的脉冲。A时钟域的频率是B时钟域的10倍;A时钟域脉冲之间的间隔很大,无需考虑脉冲间隔太小的问题。电路的接口如下图所示。data_in是脉冲输入信号,data_out是新的脉冲信号;clk_fast是A时钟域时钟信号,clk_slow是B时钟域时钟信号;rst_n是异步复位信号。
代码:
module Pulse_Sync(Clk_fast, Clk_slow,Rst_n, Data_in,Data_out);
/********************参数定义********************/
/*********************IO 说明********************/
input wire Clk_slow ;//接收域时钟
input wire Clk_fast ;//发送域时钟
input wire Rst_n ; //异步复位
input wire Data_in; //发送域数据输入
output wire Data_out; //边沿输出
/********************** 内部信号声明 **********************/
reg Data_in_Q;
reg Data_out_Q1;
reg Data_out_Q2;
reg Data_out_Q3;
/*************************功能定义*************************/
/*脉冲信号转电平信号*/
always@(posedge Clk_fast or negedge Rst_n)
begin
if(!Rst_n)
Data_in_Q <= 1'b0;
else
Data_in_Q <= Data_in? ~Data_in_Q:Data_in_Q;
end
/*电平信号打两拍再转为脉冲信号*/
always@(posedge Clk_slow or negedge Rst_n)
begin
if(!Rst_n)
begin
Data_out_Q1 <= 1'b0;
Data_out_Q2 <= 1'b0;
Data_out_Q3 <= 1'b0;
end
else
begin
Data_out_Q1 <= Data_in_Q;
Data_out_Q2 <= Data_out_Q1;
Data_out_Q3 <= Data_out_Q2;
end
end
assign Data_out = Data_out_Q3 ^ Data_out_Q2;
endmodule
同步器设计推荐的做法:
同步器单独成模块,引入两个独立时钟
其他模块都设计为单一时钟模块,完全同步模块
以时钟域作为信号命名的前缀
静态时序分析的时候,对同步器模块异步输入信号的设定false path:
用通配符(.* 通配符,对相同名字自动匹配,属于systemverilog语法)