CDC(Clock Domain Conversion),单比特跨时钟域传输
- 跨时钟域传输CDC(Clock Domain Conversion)问题,是在日常FPGA设计过程中经常遇到的问题,故针对不同情况,即单比特还是多比特,从慢时钟到快时钟还是快时钟到慢时钟,整理出对应的解决思路。
- 该问题的详解将分为单比特和多比特两个章节进行,本次将整理单比特跨时钟域传输问题。
慢时钟域到快时钟域
- 在慢时钟域内的一个脉冲信号,持续一个时钟周期,将其传输到快时钟域内。这个问题,直接使用一个单比特同步器即可,因为快时钟一定能采样到慢时钟域内的信号,我们用两级寄存器进行同步的目的在于消除亚稳态问题,也就是说如果慢时钟域内的脉冲恰好在快时钟域的亚稳态窗口内,快时钟采样时刻(上升沿)采样得到的信号有可能出现亚稳态,再用触发器寄存一拍,可以大大降低最终输出出现亚稳态的概率。如果最后也需要得到一个周期的脉冲,做一次时钟上升沿检测即可。
设计代码:
//clka为慢时钟、clkb为快时钟
module top(
input clka,
input clkb,
input rst_n,
input pulse_ina, //输入慢时钟bit
output pulse_outb //输出快时钟bit
);
reg pulse_midb_r1;
reg pulse_midb_r2;
always@(posedge clkb or negedge rst_n)begin
if(!rst_n) begin //异步复位,同步置数
pulse_midb_r1 <=0;
pulse_midb_r2 <=0;
end
else begin
pulse_midb_r1 <= pulse_ina;
pulse_midb_r2 <= pulse_midb_r1;
end
end
assign pulse_outb = ~pulse_midb_r2 & pulse_midb_r1;
endmodule
RTL图
- RTL原理图如下图所示。可以看到,使用两级寄存器进行跨时钟域操作,两个寄存器均使用快时钟作为驱动时钟。在上升沿1到来时,数据先使用第一级寄存器进行缓存,此时由于输入数据的驱动时钟为慢时钟与寄存器时钟为快时钟,可能会存在亚稳态现象。在上升沿2到来时,第二级寄存器对第一级寄存器已缓存的数据进行缓存,此时,由于寄存器1与寄存器2均由快时钟驱动,则不会存在亚稳态现象。所以,通过两级寄存器进行
testbench
module top_tb(
);
reg clka = 0;
reg clkb = 0;
reg rst_n = 0;
reg pulse_ina = 0;
wire pulse_outb;
initial begin
forever
#5 clka = ~clka;
end
initial begin
forever
#2 clkb = ~clkb;
end
initial begin
#5 rst_n = 1;
#10
@(posedge clka) pulse_ina = 1;
@(posedge clka) pulse_ina = 0;
end
top top(
.clka(clka),
.clkb(clkb),
.rst_n(rst_n),
.pulse_ina(pulse_ina),
.pulse_outb(pulse_outb)
);
endmodule
仿真波形
- 从波形文件中可以看出,当慢时钟域的信号到达上升沿时,信号并不会被马上采样,而是到了快时钟的上升沿时,信号才被快时钟域的寄存器1采样。通过寄存器1和寄存器2的移位寄存,再加以取反和与运算,从而将慢时钟域的脉冲信号转化为脉冲宽度等于快时钟域周期的快时钟域信号。
快时钟域到慢时钟域
- 对于快时钟域clka的一个脉冲信号转化到慢时钟域clkb的情况,会出现慢时钟采样不到快时钟域内的输入脉冲的情况。所以,需要先将快时钟域内的脉冲信号先进行展宽,拓展到慢时钟能够采样的到为止;再在慢时钟域clkb下使用两级同步寄存器进行同步,最后通过上升沿检测的方式检测同步后的信号,得到慢时钟域clkb下的一个周期脉冲,完成单比特同步。
设计代码
//clka为快时钟、clkb为慢时钟
module top(
input clka,
input clkb,
input rst_n,
input pulse_ina, //输入快时钟bit
output wire pulse_outb //输出慢时钟bit
);
reg signal_a;
reg signal_b;
reg signal_a_r;
reg signal_a_rr;
reg signal_b_r;
reg signal_b_rr;
//在快时钟clka下,生成展宽信号signal_a
always @(posedge clka or negedge rst_n)begin
if (!rst_n)begin
signal_a <= 1'b0;
end
else if (pulse_ina == 1'b1)begin
signal_a <= 1'b1;
end
else if (signal_a_rr == 1'b1)begin
signal_a <= 1'b0;
end
else begin
signal_a <= signal_a;
end
end
//在clkb下同步signal_a
always @(posedge clkb or negedge rst_n)begin
if (!rst_n) begin
signal_b <=1'b0;
end
else begin
signal_b <= signal_a;
end
end
//在clkb下生成脉冲信号和输出信号
always @(posedge clkb or negedge rst_n)begin
if (!rst_n)begin
signal_b_r <= 1'b0;
signal_b_rr <= 1'b0;
end
else begin //打两拍防止暂稳态
signal_b_r <= signal_b;
signal_b_rr <= signal_b_r;
end
end
assign pulse_outb = ~signal_b_rr & signal_b_r;
//在clka下采集signal_b_rr,生成signal_a_rr用于反馈拉低signal_a
always @(posedge clka or negedge rst_n)begin
if (!rst_n)begin
signal_a_r <= 1'b0;
signal_a_rr <= 1'b0;
end
else begin//反馈信号也是跨时钟域,也需要打拍
signal_a_r <= signal_b_rr;
signal_a_rr <= signal_a_r;
end
end
endmodule
RTL图
- RTL原理图如下图所示。可以看到,快时钟域clka的输入信号pulse_ina在快时钟域下经过触发器器进行信号延展,延展后的信号通过两级触发器进行从快时钟域到慢时钟域的打拍操作,通过脉冲生成器输出慢时钟域下的脉冲信号。再使用两级触发器将输出从慢时钟域信号转换到快时钟域下,作为脉冲展宽的反馈信号。
testbench
- 仿真文件内容基本同从慢时钟域到快时钟域的testbench,只需要将clka改为快时钟,clkb改为慢时钟即可,具体文件如下:
module top_tb(
);
reg clka = 0;
reg clkb = 0;
reg rst_n = 0;
reg pulse_ina = 0;
wire pulse_outb ;
initial begin
forever
#2 clka = ~clka;
end
initial begin
forever
#3 clkb = ~clkb;
end
initial begin
#5 rst_n = 1;
#10
@(posedge clka) pulse_ina = 1;
@(posedge clka) pulse_ina = 0;
end
top top(
.clka(clka),
.clkb(clkb),
.rst_n(rst_n),
.pulse_ina(pulse_ina),
.pulse_outb(pulse_outb)
);
endmodule
仿真波形
- 从波形文件中可以看出,当输入快时钟域下clka下的脉冲信号pulse_ina后,将其展宽成信号widen_pulse_ina,并通过两级触发器signal_b_r和signal_b_rr进行跨时钟域打拍,再通过脉冲生成器获得慢时钟域clkb下的脉冲信号pulse_outb。并将signal_b_rr作为反馈信号,经过两级触发器signal_a_r和signal_a_rr进行跨时钟域打拍,作为展宽信号拉低的反馈信号。在signal_a_rr被拉高的下个时钟沿,展宽信号widen_pulse_ina被拉低。
参考文献:
谈谈跨时钟域传输问题(CDC)-Author:李锐博恩