信号从快时钟域传输到慢时钟域来时,需要根据信号的特点来进行同步处理。对于单 bit 信号,一般可按电平信号和脉冲信号来区分。
电平信号同步
同步逻辑设计中,电平信号是指长时间保持不变的信号。保持不变的时间限定是相对于慢时钟而言的。只要快时钟的信号保持高电平或低电平的时间足够长,以至于能被慢时钟在满足时序约束的条件下采集到,就可以认为该信号为电平信号。
既然电平信号能够被安全的采集到,所以从快时钟域到慢时钟域的电平信号也采用延迟打拍的方法做同步。
脉冲信号同步
同步逻辑设计中,脉冲信号是指从快时钟域输出的有效宽度小于慢时钟周期的信号。如果慢时钟域直接去采集这种窄脉冲信号,有可能会漏掉。
假如这种脉冲信号脉宽都是一致的,在知道两个时钟频率比的情况下,可以采用"快时钟域脉宽扩展+慢时钟域延迟打拍"的方法进行同步。
如果有时窄脉冲信号又表现出电平信号的特点,即有时信号的有效宽度大于慢时钟周期而能被慢时钟采集到,那么对此类信号再进行脉冲扩展显然是不经济的。此时,可通过"握手传输"的方法进行同步。
假设脉冲信号的高电平期间为有效信号期间,其基本原理如下。
- (1) 快时钟域对脉冲信号进行检测,检测为高电平时输出高电平信号 pulse_fast_r。或者快时钟域输出高电平信号时,不要急于将信号拉低,先保持输出信号为高电平状态。
- (2) 慢时钟域对快时钟域的信号 pulse_fast_r 进行延迟打拍采样。因为此时的脉冲信号被快时钟域保持拉高状态,延迟打拍肯定会采集到该信号。
- (3) 慢时钟域确认采样得到高电平信号 pulse_fast2s_r 后,再反馈给快时钟域。
- (4) 快时钟域对反馈信号 pulse_fast2s_r 进行延迟打拍采样。如果检测到反馈信号为高电平,证明慢时钟域已经接收到有效的高电平信号。如果此时快时钟域自身逻辑不再要求脉冲信号为高电平状态,拉低快时钟域的脉冲信号即可。
此方法实质是通过相互握手的方式对窄脉冲信号进行脉宽扩展。
利用握手信号进行同步处理的 Verilog 模型描述如下。
实例
//同步模块工作时钟大约为 25MHz 的模块
//异步数据对来自工作时钟为 100MHz 的模块
module pulse_syn_fast2s
#( 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
assign pulse_slow = pulse_fast2s_r[1] ;
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]) ;
endmodule
//同步模块工作时钟大约为 25MHz 的模块
//异步数据对来自工作时钟为 100MHz 的模块
module pulse_syn_fast2s
#( 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
assign pulse_slow = pulse_fast2s_r[1] ;
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]) ;
endmodule
testbench 描述如下。
实例
`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
pulse_syn_fast2s 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
`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
pulse_syn_fast2s 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
仿真结果如下,由图可知:
- (1) 快时钟域单个窄脉冲信号被慢时钟域采集到,但是同步后的信号延迟较长,脉冲宽度较大。信号延迟是因为延迟打拍和反馈清零决定的,无法避免。脉宽过大问题,可以通过延迟打拍进行边沿检测的方法处理。
- (2) 两个紧临的窄脉冲信号同步后的信号脉宽与单个窄脉冲同步后的信号脉宽没有差异,也就是说,同步电路漏掉了第二个窄脉冲的检测。这也属于握手传输处理同步问题的特点,当快时钟域的脉冲信号变化速率过快时,该方法不能分辨相邻的脉冲。
- (3) 当多个宽窄脉冲信号相邻较近时,虽然该同步方法不能分辨多个脉冲信号,但同步后的信号脉宽可能会相对大一些。
多位宽数据同步
当多位宽数据进行同步时,如果该数据各 bit 位都可以看作电平信号,即相对一段时间内各 bit 位数据均可以保持不变以至于能被慢时钟采集到,可以消耗一些触发器资源对多位宽数据进行简单的延迟打拍同步。
但如果数据变化速率过快,就不能再使用延迟打拍采样的方法。因为此时数据各 bit 位不再是电平信号,变化的时间也参差不齐,用异步时钟进行打拍采样,可能会采集到因路径延迟不同而导致的错误数据。
解决此类异步问题的常用方法是采用异步 FIFO (First In First Out)。具体请参考下一节:《4.4 FIFO 设计》。