本文首发自:FPGA逻辑设计回顾(8)单比特信号的CDC处理方式之Toggle同步器[1]本文作为本系列CDC的最后一篇吧,作为前几篇有关CDC处理的文章的补充,本文所要介绍的同步器适用场景是:单比特信号的同步处理,且可以用于快时钟到慢时钟的跨时钟域同步。切换同步器,英文名:Toggle synchronizer,无论怎么翻译吧,它的含义就是将快时钟域内的单比特脉冲同步至慢时钟域,这像是一个切换过程,给出原理图:
Toggle Synchronizer
这种方式与本系列的另一篇文章应用场景相似,但更简单一些!
本文不仅讲这一个问题,同时本文还将对文章:FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题[2]中讲到的反馈展宽的同步器进行补充说明,因为有小伙伴问我这个问题,快时钟同步的单脉冲信号同步到慢时钟后如何也保持一个时钟周期呢?
还有一个问题,就是如何使用脉冲展宽同步器对低电平脉冲进行展宽处理呢?
好了,前言里作为一个引入,就不说这么多了,我们进入正文:
首先说明的是,这个脉冲反馈展宽同步器是我自己给的名字,因为它的展宽方式是一种握手反馈的方式,但是握手反馈,又与握手同步有点相似,因此为了不误会,单独给了一个名字。
好了,回到前言中的第一个问题,有个小伙伴问我,如何让快时钟域中的单脉冲信号同步到慢时钟域后,还保持一个周期?对于这个问题,我知道你肯定是个新手,然后看了我的这篇文章,问了这样的问题。不过嘛,这总是正常的,因为大家看文章都是随缘,看到哪里是哪里,不能说看你的一整个系列。
这个问题很简单,就是对同步到慢时钟域后的脉冲进行上升沿检测就可以得到一个时钟的高脉冲。我之所以强调高脉冲,是因为我们的同步是对高脉冲的同步,我们设定的场景是高脉冲是我们需要的脉冲。如果我们需要同步低电平脉冲,那么这个同步展宽的方式就要另当别论了,需要简单的修改下。
既然说到了这个问题,我们就把这个问题当作我们今天要补充的第二个问题:如何使用脉冲反馈展宽同步器同步低电平脉冲?
嗯,第一个问题还没有给出详细的可以直接使用的代码,这里给出(如果我是一个老师,可能不是一个好老师吧,什么都给学生了,他们自己会动手吗?好在我不是老师,你们看我的文章也可能就是主动解决疑惑,这样就不需要藏着掖着了,达到这个目的就行了。):
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: fast2slow_CDC
//
module fast2slow_CDC(
input wire clk1 ,
input wire clk2 ,
input wire rst ,
input wire pulse_clk1 ,
output wire pulse_syn_clk2
);
reg pulse_wide_clk2 ;
reg reg1_pulse_wide_clk2 ;
reg reg1_pulse_wide_clk1 ;
reg reg2_pulse_wide_clk1 ;
//生成脉冲展宽信号
reg pulse_wide_clk1 ;
// always@(posedge clk1 or posedge rst) begin
// if(rst) begin
// pulse_wide_clk1 <= 1'b0 ;
// end
// else if(pulse_clk1) begin
// pulse_wide_clk1 <= 1'b1 ;
// end
// else if(reg2_pulse_wide_clk1) begin
// pulse_wide_clk1 <= 1'b0 ;
// end
// else begin
// pulse_wide_clk1 <= pulse_wide_clk1 ;
// end
// end
always@(posedge clk1 or posedge rst) begin
if(rst) begin
pulse_wide_clk1 <= 1'b0 ;
end
else if(pulse_clk1) begin
pulse_wide_clk1 <= 1'b1 ;
end
else begin
if(reg2_pulse_wide_clk1) begin
pulse_wide_clk1 <= 1'b0 ;
end
else begin
pulse_wide_clk1 <= pulse_wide_clk1 ;
end
end
end
//在目的时钟域内采样展宽后的信号
always@(posedge clk2 or posedge rst) begin
if(rst) begin
pulse_wide_clk2 <= 1'b0 ;
reg1_pulse_wide_clk2 <= 1'b0 ;
end
else begin
pulse_wide_clk2 <= pulse_wide_clk1 ;
reg1_pulse_wide_clk2 <= pulse_wide_clk2 ;
end
end
//在源时钟域内同步目的时钟域内的展宽信号,以生成反馈信号
always@(posedge clk1 or posedge rst) begin
if(rst) begin
reg1_pulse_wide_clk1 <= 1'b0 ;
reg2_pulse_wide_clk1 <= 1'b0 ;
end
else begin
reg1_pulse_wide_clk1 <= reg1_pulse_wide_clk2 ;
reg2_pulse_wide_clk1 <= reg1_pulse_wide_clk1 ;
end
end
//目的时钟域将展宽后的信号变成1个脉冲的信号,取目的时钟域展宽后的上上升沿实现
reg reg2_pulse_wide_clk2; //对reg1_pulse_wide_clk2同步一拍
reg pos_pulse_wide_clk2;
always@(posedge clk2 or posedge rst) begin
if(rst) begin
reg2_pulse_wide_clk2 <= 1'd0;
pos_pulse_wide_clk2 <= 1'd0;
end
else begin
reg2_pulse_wide_clk2 <= reg2_pulse_wide_clk1;
pos_pulse_wide_clk2 <= ~reg2_pulse_wide_clk2 && reg1_pulse_wide_clk2;
end
end
//将时钟域clk2中的单脉冲信号作为输出;
assign pulse_syn_clk2 = pos_pulse_wide_clk2;
endmodule
RTL原理图:
RTL原理图
图中方框内部就是上升沿检测的实现,最后一个触发器是对上升沿检测结果打了一拍,对时序还是有点好处的。
仿真平台和以前的一样:
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: cdc_tb
//
module cdc_tb(
);
reg clk1 ;
reg clk2 ;
reg rst ;
reg pulse_clk1 ;
wire pulse_syn_clk2 ;
initial begin
clk1 = 0;
forever
#3 clk1 = ~clk1;
end
initial begin
clk2 = 0;
forever
#5.5 clk2 = ~clk2;
end
initial begin
rst = 1;
pulse_clk1 = 0;
#15
rst = 0;
#15
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
#3 //距离太近
#59.5 //距离足够远
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
end
fast2slow_CDC u_fast2slow_CDC(
.clk1 ( clk1 ),
.clk2 ( clk2 ),
.rst ( rst ),
.pulse_clk1 ( pulse_clk1 ),
.pulse_syn_clk2 ( pulse_syn_clk2 )
);
endmodule
仿真波形:
仿真波形
可见,实现了预期结果。内部信号我就不拉出来了,有兴趣的自己拉出来看看实现!
现在我们进入第二个补充问题,即如何实现对低电平脉冲的展宽同步处理?
先给一个仿真截图:也就是需要这个效果:
低电平脉冲展宽处理
我们如何了解了高电平脉冲的展宽处理方式之后,低电平脉冲的展宽处理也就很简单了,一句话,展宽目标改为低电平脉冲就好了,下面给出RTL代码:
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: fast2slow_CDC_LL
//
module fast2slow_CDC_LL(
input wire clk1 ,
input wire clk2 ,
input wire rst ,
input wire pulse_clk1 ,
output wire pulse_syn_clk2
);
reg pulse_wide_clk2 ;
reg reg1_pulse_wide_clk2 ;
reg reg1_pulse_wide_clk1 ;
reg reg2_pulse_wide_clk1 ;
//生成脉冲展宽信号
reg pulse_wide_clk1 ;
// always@(posedge clk1 or posedge rst) begin
// if(rst) begin
// pulse_wide_clk1 <= 1'b0 ;
// end
// else if(pulse_clk1) begin
// pulse_wide_clk1 <= 1'b1 ;
// end
// else if(reg2_pulse_wide_clk1) begin
// pulse_wide_clk1 <= 1'b0 ;
// end
// else begin
// pulse_wide_clk1 <= pulse_wide_clk1 ;
// end
// end
always@(posedge clk1 or posedge rst) begin
if(rst) begin
pulse_wide_clk1 <= 1'b1 ;
end
else if(~pulse_clk1) begin
pulse_wide_clk1 <= 1'b0 ;
end
else begin
if(~reg2_pulse_wide_clk1) begin
pulse_wide_clk1 <= 1'b1 ;
end
else begin
pulse_wide_clk1 <= pulse_wide_clk1 ;
end
end
end
//在目的时钟域内采样展宽后的信号
always@(posedge clk2 or posedge rst) begin
if(rst) begin
pulse_wide_clk2 <= 1'b1 ;
reg1_pulse_wide_clk2 <= 1'b1 ;
end
else begin
pulse_wide_clk2 <= pulse_wide_clk1 ;
reg1_pulse_wide_clk2 <= pulse_wide_clk2 ;
end
end
//在源时钟域内同步目的时钟域内的展宽信号,以生成反馈信号
always@(posedge clk1 or posedge rst) begin
if(rst) begin
reg1_pulse_wide_clk1 <= 1'b1 ;
reg2_pulse_wide_clk1 <= 1'b1 ;
end
else begin
reg1_pulse_wide_clk1 <= reg1_pulse_wide_clk2 ;
reg2_pulse_wide_clk1 <= reg1_pulse_wide_clk1 ;
end
end
//目的时钟域将展宽后的信号变成1个脉冲的信号,取目的时钟域展宽后的上上升沿实现
reg reg2_pulse_wide_clk2; //对reg1_pulse_wide_clk2同步一拍
reg neg_pulse_wide_clk2;
always@(posedge clk2 or posedge rst) begin
if(rst) begin
reg2_pulse_wide_clk2 <= 1'd1;
neg_pulse_wide_clk2 <= 1'd0;
end
else begin
reg2_pulse_wide_clk2 <= reg2_pulse_wide_clk1;
neg_pulse_wide_clk2 <= ~reg1_pulse_wide_clk2 && reg2_pulse_wide_clk2;
end
end
//将时钟域clk2中的单脉冲信号作为输出;
assign pulse_syn_clk2 = ~neg_pulse_wide_clk2;
endmodule
我在高电平展宽电路的设计基础上改了下,就是上述的RTL代码,下面给出仿真平台:
`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: cdc_tb
//
module cdc_tb(
);
reg clk1 ;
reg clk2 ;
reg rst ;
reg pulse_clk1 ;
wire pulse_syn_clk2 ;
initial begin
clk1 = 0;
forever
#3 clk1 = ~clk1;
end
initial begin
clk2 = 0;
forever
#5.5 clk2 = ~clk2;
end
initial begin
rst = 1;
pulse_clk1 = 1;
#15
rst = 0;
#15
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
#3 //距离太近
#59.5 //距离足够远
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
end
fast2slow_CDC_LL u_fast2slow_CDC_LL(
.clk1 ( clk1 ),
.clk2 ( clk2 ),
.rst ( rst ),
.pulse_clk1 ( pulse_clk1 ),
.pulse_syn_clk2 ( pulse_syn_clk2 )
);
endmodule
也仅仅是微调而已!
仿真波形前面也已经给出:
Toggle同步器用于将源时钟域产生的脉冲同步到目的时钟域。脉冲不能直接使用2 FF同步器进行同步。当使用2个FF同步器从快时钟域同步到慢时钟域时,脉冲可以被跳过,这可能会导致脉冲检测的损失,因此依赖于它的后续电路可能无法正常工作。
其实现原理图:
Toggle 同步器
时序图:
Toggle同步器的时序图
如何理解呢?
首先就是对源时钟域的脉冲进行处理,处理方式是将脉冲信号作为一个MUX选择器的选择信号,如果为1,选择同步源触发器的反信号作为输出,如果为0,选择同步源触发器输出信号本身作为输出;源触发器的输出经过目的时钟域两级同步,取上升沿即可实现高电平脉冲的同步。
module toggle_synchronizer(
input wire clk1,
input wire clk2,
input wire rst,
input wire pulse_clk1,
output reg pulse_syn_clk2
);
reg toggle_pulse_clk1;
always@(posedge clk1 or posedge rst) begin
if(rst) begin
toggle_pulse_clk1 <= 1'd0;
end
else if(pulse_clk1) begin
toggle_pulse_clk1 <= ~toggle_pulse_clk1;
end
else begin
toggle_pulse_clk1 <= toggle_pulse_clk1;
end
end
reg toggle_pulse_clk2, toggle_pulse_clk2_d1, toggle_pulse_clk2_d2;
always@(posedge clk2 or posedge rst) begin
if(rst) begin
toggle_pulse_clk2 <= 1'd0;
toggle_pulse_clk2_d1 <= 1'd0;
toggle_pulse_clk2_d2 <= 1'd0;
pulse_syn_clk2 <= 1'd0;
end
else begin
toggle_pulse_clk2 <= toggle_pulse_clk1;
toggle_pulse_clk2_d1 <= toggle_pulse_clk2;
toggle_pulse_clk2_d2 <= toggle_pulse_clk2_d1;
pulse_syn_clk2 <= ~toggle_pulse_clk2_d2 && toggle_pulse_clk2_d1;
end
end
endmodule
这种方式我不想说太多,因为局限性太强,只适用于只有一个脉冲的情况,
为什么呢?给出仿真平台来看看:
module cdc_tb(
);
reg clk1 ;
reg clk2 ;
reg rst ;
reg pulse_clk1 ;
wire pulse_syn_clk2 ;
initial begin
clk1 = 0;
forever
#3 clk1 = ~clk1;
end
initial begin
clk2 = 0;
forever
#5.5 clk2 = ~clk2;
end
initial begin
rst = 1;
pulse_clk1 = 0;
#15
rst = 0;
#15
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
#3 //距离太近
#79.5 //距离足够远
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
end
toggle_synchronizer u_toggle_synchronizer(
.clk1 ( clk1 ),
.clk2 ( clk2 ),
.rst ( rst ),
.pulse_clk1 ( pulse_clk1 ),
.pulse_syn_clk2 ( pulse_syn_clk2 )
);
endmodule
仿真波形如下:
toggle 同步器的波形图
可见,第二个脉冲被吞掉了!
如果仅有一个脉冲呢?
toggle同步器的波形图
看起来就很完美,所以这是一个适用性的问题!
[1]
FPGA逻辑设计回顾(8)单比特信号的CDC处理方式之Toggle同步器: https://www.ebaina.com/articles/140000005491
[2]
FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题: https://www.ebaina.com/articles/140000005331