参考:
目录
为什么要进行跨时钟域处理?
避免亚稳态
亚稳态:数据无法在规定时间内达到一个可确认的状态。
产生亚稳态现象的原因:数据传输不满足触发器的建立时间和保持时间。
建立时间(Tsu):时钟上升沿之前数据必须稳定的最短时间。
保持时间(Th):时钟上升沿之后数据必须稳定的最短时间。
触发器进入亚稳态后,输出端在有效时钟沿后会经历一个较长的不确定状态。这段时间内输出端可能会出现毛刺、振荡或某一固定电压值等无用的输出电平,而不会稳定在输入数据的期望值上,这段时间称为决断时间。在决断时间后输出端的状态将会稳定在0或1,但此时具体是哪一个值却与输入端无关。
触发器进入亚稳态后,无法预测会输出何种电平,也无法预测何时会输出某个正确的电平。
信号从一个时钟域传递到另一个异步时钟域时,由于多个时钟域中频率和相位不同,在数据采样时,有可能违背建立时间或保持时间,从而产生亚稳态问题,而且可能在整个周期内都保持在亚稳态。把这个不稳定的信号采样后,会传递给后级电路,会导致整个电路功能的错误。
保证数据完整性
-
防止数据丢失:在跨时钟域传输数据时,如果处理不当,可能会导致数据丢失。例如,当快时钟域的时钟频率远高于慢时钟域时,快时钟域的数据可能在慢时钟域的有效采样窗口内多次变化,从而导致接收端无法准确捕获数据。
-
确保数据同步:跨时钟域处理还可以确保不同时钟域之间的数据同步。在分布式系统或复杂电路中,不同部分可能由不同的时钟源驱动,导致数据在传输过程中存在时间差。通过跨时钟域处理,可以消除这种时间差,确保数据在接收端能够正确同步。
跨时钟域处理的方法
单比特信号
慢时钟域到快时钟域
两级触发器同步法
用两或三级触发器对异步信号缓存,主要针对单Bit信号。
(1)单bit电平信号
//单比特电平信号
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)单bit脉冲信号
//单比特脉冲信号
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
快时钟域到慢时钟域
两级触发器同步法
(1)单bit电平信号
只要快时钟域的信号保持高电平或低电平的时间足够长,以至于能被慢时钟在满足时序约束的条件下采集到,就可以认为该信号为电平信号,从快时钟域到慢时钟域的电平信号也采用两级触发器同步的方法。
脉宽扩展法
(2)单bit脉冲信号
这里的脉冲信号是指从快时钟域输出的有效宽度小于慢时钟周期的信号。如果慢时钟域直接去采集这种窄脉冲信号,有可能会漏掉。脉宽扩展法是通过相互握手的方式对窄脉冲信号进行脉宽扩展。
脉宽扩展法的基本原理:
(1) 快时钟域对脉冲信号进行检测,检测为高电平时输出高电平信号 pulse_fast_r。或者快时钟域输出高电平信号时,不要急于将信号拉低,先保持输出信号为高电平状态。
(2) 慢时钟域对快时钟域的信号 pulse_fast_r 进行延迟打拍采样。因为此时的脉冲信号被快时钟域保持拉高状态,延迟打拍肯定会采集到该信号。
(3) 慢时钟域确认采样得到高电平信号 pulse_fast2s_r 后,再反馈给快时钟域。
(4) 快时钟域对反馈信号 pulse_fast2s_r 进行延迟打拍采样。如果检测到反馈信号为高电平,证明慢时钟域已经接收到有效的高电平信号。如果此时快时钟域自身逻辑不再要求脉冲信号为高电平状态,拉低快时钟域的脉冲信号即可。
//同步模块工作时钟大约为 25MHz 的模块
//异步数据对来自工作时钟为 100MHz 的模块
module test
#( parameter PULSE_INIT = 1'b0
)
(
input rstn,
input clk_fast,
input pulse_fast,
input clk_slow,
output pulse_slow);
wire clear_n ;
reg pulse_fast_r ;
/**************** fast clk ***************/
//(1) 快时钟域检测到脉冲信号时,不急于将脉冲信号拉低
always@(posedge clk_fast or negedge rstn) begin
if (!rstn)
pulse_fast_r <= PULSE_INIT ;
else if (!clear_n)
pulse_fast_r <= 1'b0 ;
else if (pulse_fast)
pulse_fast_r <= 1'b1 ;
end
reg [1:0] pulse_fast2s_r ;
/************ slow clk *************/
//(2) 慢时钟域对信号进行延迟打拍采样
always@(posedge clk_slow or negedge rstn) begin
if (!rstn)
pulse_fast2s_r <= 3'b0 ;
else
pulse_fast2s_r <= {pulse_fast2s_r[0], pulse_fast_r} ;
end
reg [1:0] pulse_slow2f_r ;
/********* feedback for slow clk to fast clk *******/
//(3) 对反馈信号进行延迟打拍采样
always@(posedge clk_fast or negedge rstn) begin
if (!rstn)
pulse_slow2f_r <= 1'b0 ;
else
pulse_slow2f_r <= {pulse_slow2f_r[0], pulse_slow} ;
end
//控制快时钟域脉冲信号拉低
assign clear_n = ~(!pulse_fast && pulse_slow2f_r[1]) ;
reg pulse_fast2s_r_r;
/************ slow clk *************/
//(4) 得到脉冲信号
always@(posedge clk_slow or negedge rstn)begin
if(!rstn)begin
pulse_fast2s_r_r<='d0;
end
else begin
pulse_fast2s_r_r<=pulse_fast2s_r[1];
end
end
assign pulse_slow = (!pulse_fast2s_r_r)& pulse_fast2s_r[1];
endmodule
`timescale 1ns/1ps
module test ;
reg clk_100mhz, clk_25mhz ;
reg rstn ;
initial begin
clk_100mhz = 0 ;
clk_25mhz = 0 ;
rstn = 0 ;
#11 rstn = 1 ;
end
always #(10/2) clk_100mhz = ~clk_100mhz ;
always #(45/2) clk_25mhz = ~clk_25mhz ;
reg [7:0] cnt ;
reg pulse_sig ;
always @(posedge clk_100mhz or negedge rstn) begin
if (!rstn) begin
cnt <= 'b0 ;
end
else begin
cnt <= cnt + 1'b1 ;
end
end
//窄脉冲生成部分
always @(posedge clk_100mhz or negedge rstn) begin
if (!rstn) begin
pulse_sig <= 1'b0 ;
end
else if (cnt == 5 ||
cnt == 40 || cnt == 42 ||
cnt >= 75 && cnt <= 81 || cnt == 85 || cnt == 87 )
begin
pulse_sig <= 1'b1 ;
end
else begin
pulse_sig <= 1'b0 ;
end
end
test u_fast2s_pulse(
.rstn (rstn),
.clk_fast (clk_100mhz),
.pulse_fast (pulse_sig),
.clk_slow (clk_25mhz),
.pulse_slow ());
initial begin
forever begin
#100;
if ($time >= 10000) $finish ;
end
end
endmodule // test
可见,当快时钟域的脉冲信号变化速率过快时,该方法不能分辨相邻的脉冲。
多比特信号
慢时钟域到快时钟域
数据使能选通法(DMUX)
基本思想是保证信号被安全采集的时刻,通过使能信号把多bitCDC问题转化为单bitCDC问题。
当使能信号周期变化时,可以对使能信号进行二级打拍缓存并检测上升沿来确定采样时刻。
//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
延迟计数法
当使能信号一直有效或无使能信号时,可以在快时钟域检测慢时钟的边沿。如果两个时钟频率相差较大,在检测到慢时钟上升沿后,可以通过计数定位到慢时钟周期中间时刻采样。
//clk1:999kHz clk2:100MHz
//4级缓存:3级用于打拍同步,一级用于边沿检测
reg [3:0] edge_r ;
always @(posedge clk2 or negedge rstn) begin
if (!rstn) edge_r <= 3'b0 ;
else edge_r <= {edge_r[2:0], clk1} ;
end
wire edge_pos = edge_r[2] && !edge_r[3] ;
//延迟计数器,检测到慢时钟上升沿时开始计数
reg [5:0] cnt ;
always @(posedge clk2 or negedge rstn) begin
if (!rstn) cnt <= 6'h3f ;
else if (edge_pos && din_en)
cnt <= 6'h0 ;
else if (cnt != 6'h3f) cnt <= cnt + 1'b1 ;
end
//数据同步
reg [31:0] dout_r ;
reg dout_en_r ;
always @(posedge clk2 or negedge rstn) begin
if (!rstn)
dout_r <= 'b0 ;
else if (din_en && cnt == 47) //大约在慢时钟周期中间时刻采样
dout_r <= din ;
end
//数据使能信号较数据采样时刻延迟一个周期输出
always @(posedge clk2 or negedge rstn) begin
if (!rstn) dout_en_r <= 1'b0 ;
else if (din_en && cnt==48)
dout_en_r <= 1'b1 ;
else dout_en_r <= 1'b0 ;
end
通用方法
异步FIFO
多比特信号跨时钟域处理的常用方法是使用异步FIFO,有关异步FIFO可参考:
异步FIFO设计
全握手传输法
1.tx模块接收外部输入信号data_in和valid,在valid上升沿到来时输出data_out_t到写数据线上,并且同时发出请求信号req_t告知rx模块。
2.req_t在传递给rx后再经过打两拍处理得到req_dd,此时经过两拍之后数据已经稳定。rx认为tx的数据已经准备好了便接收写数据线上的数据,同时将要返回的数据data_out_r输出,并且返回一个应答信号ack_r给tx,表面rx已接收数据,至此已经完成了半握手。
3.应答信号ack_r传给tx后再经过打两拍处理得到ack_dd,tx检测到应答信号后拉低请求信号req_t。
4.rx在检测到req_dd拉低后,将应答信号ack_r也拉低。
5.tx检测到ack_dd拉低,本次传输过程结束。
发送模块:
module fullhs_tx(
input tclk ,
input treset ,
input [31:0] data_in ,
input valid ,
input ack_t ,
output reg [31:0] data_out_t ,
output reg req_t
);
reg ack_d ;
reg ack_dd ;
reg valid_d ;
wire valid_posflag ; //valid上升沿信号。
//使用valid的上升沿作为判断条件可以防止在一个valid期间多次传输数据。
//上升沿检测。
always@(posedge tclk or negedge treset) begin
if(!treset) begin
valid_d <= 'b0;
end
else begin
valid_d <= valid;
end
end
assign valid_posflag = valid & (~valid_d);
//应答信号同步到发端
always@(posedge tclk or negedge treset) begin
if(!treset) begin
ack_d <= 'b0;
ack_dd <= 'b0;
end
else begin
ack_d <= ack_t;
ack_dd <= ack_d;
end
end
always@(posedge tclk or negedge treset) begin
if(!treset) begin
req_t <= 1'b0;
data_out_t <= 'b0;
end
else if(ack_dd == 1) begin //收到返回的ack后将req置零。
req_t <= 1'b0;
end
else if(valid_posflag == 1) begin //数据有效时拉高请求信号
req_t <= 1'b1 ;
data_out_t <= data_in;
end
end
endmodule
接收模块:
module fullhs_rx(
input rclk ,
input rreset ,
input [31:0] data_in_r ,
input req_r ,
output reg ack_r ,
output reg [31:0] data_out_r
);
reg req_d ;
reg req_dd ;
wire rx_en;
assign rx_en=(!req_dd)&req_d;
//将req信号同步到收端
always@(posedge rclk or negedge rreset) begin
if(!rreset) begin
req_d <= 'b0;
req_dd <= 'b0;
end
else begin
req_d <= req_r;
req_dd <= req_d;
end
end
always@(posedge rclk or negedge rreset) begin
if(!rreset) begin
ack_r <= 'b0;
data_out_r <= 'b0;
end
else if(rx_en) begin
ack_r <= 1'b1;
data_out_r <= data_in_r ;
end
else if(req_dd == 0) begin //req信号拉低后,拉低ack信号,结束一次通信。
ack_r <= 1'b0 ;
end
end
endmodule
顶层:
module fullhs_top(
input tclk,
input rclk,
input [31:0] data_in,
input valid ,
input reset,
output [31:0] data_out
);
wire [31:0] txdata;
wire req;
wire ack;
fullhs_tx fullhs_tx_u(
.tclk ( tclk ),
.treset ( reset ),
.ack_t ( ack ),
.data_out_t( txdata ),
. req_t ( req ),
.data_in (data_in ),
.valid (valid )
);
fullhs_rx fullhs_rx_u(
.rclk ( rclk ),
.rreset ( reset ),
.data_in_r ( txdata ),
.req_r ( req ),
.ack_r ( ack ),
.data_out_r ( data_out )
);
endmodule
仿真:
`timescale 1ns / 1ps
module tb_fullhs( );
reg tclk;
reg rclk;
reg [31:0] data_in;
reg valid;
reg reset;
initial begin
tclk = 0;
rclk = 0;
reset = 0;
data_in = 0;
valid = 0;
#200 ;
reset = 1;
#300 ;
data_in = 32'hf0f0f0f0;
valid = 1;
#200 ;
valid = 0;
#300 ;
data_in = 32'hffff0000;
valid = 1;
#200 ;
valid = 0;
# 300;
data_in = 32'hff00ff00;
valid =1;
end
always # 5 tclk = ~tclk;
always # 10 rclk = ~rclk;
fullhs_top fullhs_top_u(
.tclk (tclk ),
.rclk (rclk ),
.data_in(data_in),
.valid(valid),
.reset(reset )
);
endmodule
打拍通用原则
1.输入来自同步时钟域,需要跳变沿:寄存1次
2.输入来自异步时钟域、不需要跳变沿:寄存2次
3.输入来自异步时钟域,需要跳变沿:寄存3次