1.亚稳态
在进行跨时钟域信号处理之前,我们首先要了解为什么要进行跨时钟与处理,这就需要我们了解触发器的建立时间、保持时间以及亚稳态的传播
建立时间:触发器在时钟上升沿到来之前,其数据输入端的数据必须保持不变的最小时间。保持时间:触发器在时钟上升沿到来之后,其数据输入端的数据必须保持不变的最小时间。触发器内部数据的形成是需要一定的时间的,如果不满足建立和保持时间,触发器将进入亚稳态,进入亚稳态后触发器的输出将不稳定,在0和1之间变化,这时需要经过一个恢复时间,其输出才能稳定,但稳定后的值并不一定是你的输入值。这就是为什么要用两级触发器来同步异步输入信号。这样做可以防止由于异步输入信号对于本级时钟可能不满足建立保持时间而使本级触发器产生的亚稳态传播到后面逻辑中,导致亚稳态的传播。
2.单比特信号跨时钟域信号处理
2.1信号从clk_b到clk_a:从慢到快 :慢时钟域的控制信号比较长一定能被慢时钟域检测到。如下图clk_b传输到clk_a的信号pulse_b。解决这种问题采用的是触发器打两拍,而且是在快时钟域打两拍,才可以获得正确的不产生亚稳态的信号。(如果慢时钟域的控制信号是组合逻辑信号,需要在慢时钟域打入触发器再输出到快时钟域,快时钟域再打两拍,因为组合逻辑容易产生毛刺,直接输入到快时钟域会增加亚稳态的概率)。
verilog代码:
module tclk(
input clk_a,
input rst_n,
input pulse_b,
output pulse_a
);
reg pulse_r1;
reg pulse_r2;
reg pulse_r3;
wire pulse_a_pose;
wire pulse_a_nege;
[email protected](posedge clk_a or negedge rst_n)
begin
if(!rst_n)
begin
pulse_r1<=1'b0;
pulse_r2<=1'b0;
pulse_r3<=1'b0;
end
else
begin
pulse_r1<=pulse_b;
pulse_r2<=pulse_r1;
pulse_r3<=pulse_r2;
end
end
assign pulse_a_pose=pulse_r2&( ~pulse_r3);//pulse_b上升沿检测,是指上升沿保持一个clk_a周期
assign pulse_a_nege=(~pulse_r2)& pulse_r3;//pulse_b下降沿检测
assign pulse_a=pulse_r2;
endmodule
vivado RTL视图:
2.2信号从clk_a到clk_b:从快到慢:
2.1中的信号足够长,我们可以理解为电平信号。当信号从快时钟域到慢时钟域时,我们可以看到,信号pulse_a的宽度足够宽才能被clk_b采集到才可以保证系统正常工作。很明显,pulse_a不够长,我们称之为脉冲信号,那么对于脉冲信号pulse_a采取怎样的处理方法呢?可以用一个展宽信号来替代pulse_a实现垮时钟域的握手。其主要原理就是先把脉冲信号在clka下展宽,变成电平信号,再向clkb传递,当确认clkb已经“看见”信号同步过去之后,再清掉clka下的电平信号。
左边为电平信号 右边为脉冲信号。
电平信号可理解为慢时钟域到快时钟域,因为肯定能采到。
rtl代码:
module tclk(
input clk_a,
input clk_b,
input rst_n,
input pulse_a,
output pulse_b_out,
output lev_b_out
);
reg signal_a;
reg signal_b;
reg signal_b_a1;
reg signal_b_a2;
reg pulse_r1;
reg pulse_r2;
[email protected](posedge clk_a or negedge rst_n)
begin
if(!rst_n)
signal_a <= 1'b0;
else if(pulse_a)
signal_a <= 1'b1;///检测到快时钟域的信号,产生一个展宽标志
else if(signal_b_a2)
signal_a <= 1'b1;
end
//将脉冲信号作用到clkb
[email protected](posedge clk_b or negedge rst_n)
begin
if(!rst_n)
signal_b<= 1'b0;
else
signal_b<=signal_a;
end
//signal_b打两拍
[email protected](posedge clk_b or negedge rst_n)
begin
if(!rst_n)
begin
pulse_r1<= 1'b0;
pulse_r2<=pulse_r1;
end
else
begin
pulse_r1<= signal_b;
pulse_r2<=pulse_r1;
end
end
//产生反馈拉低signal_a的信号signal_b1_a2用来反馈回clka时钟域,清除clka下的脉冲展宽信号。这里不能直接使用signal_b_b1,因为signal_b_b1是clkb时钟域下的信号,所以要把它同步回clka时钟域后再使用。
always @ (posedge clk_a or negedge rst_n)
begin
if (rst_n == 1'b0) begin
signal_b_a1 <= 1'b0 ;
signal_b_a2 <= 1'b0 ;
end
else begin
signal_b_a1 <= signal_b ;
signal_b_a2 <= signal_b_a1 ;
end
end
//输出电平信号与脉冲信号
assign pulse_b_out=pulse_r1&(~pulse_r2);
assign lev_b_out=pulse_r1;
/// _r1是打过两拍的,基本可用。_b2是打过三拍的,当然更加保险。 这样写的意思是说:如果你的设计不需要用到同步过去的_puls信号,而且认为打两拍已经足够,_r2这个寄存器就可以节省掉不需要了。
endmodule
单bit信号跨时钟域先学习到这,多bit的信号同步会使用双口ram或者异步FIFO完成,下次学习。