异步bus交互(一)— 两级DFF同步器

跨时钟域处理 & 亚稳态处理

1.问题产生

现在的芯片(比如SOC,片上系统)集成度和复杂度越来越高,通常一颗芯片上会有许多不同的信号工作在不同的时钟频率下。比如SOC芯片中的CPU通常会工作在一个频率上,总线信号(比如DRAM BUS)会工作在另一个时钟频率下,而普通的信号又会工作在另外的时钟频率下。这3个不同时钟频率下工作的信号往往需要相互沟通和传递信号。
异步bus交互(一)— 两级DFF同步器

不同时钟域下的信号传递就涉及到跨时钟域信号处理,因为相互之间的频率、相位不一样,如果不做处理或者处理不当,如下图所示的时钟域CLK_A的数据信号A可能无法满足时钟域CLK_B的setup/hold时间,可能导致:

1.数据丢失,无法采到预期中的信号;

2.亚稳态的产生。
异步bus交互(一)— 两级DFF同步器

.
.

2.概述

常见的跨时钟域信号处理方法都有哪些呢?有如下的三种:

(1)两级DFF同步器
(2)握手协议
(3)异步FIFO

.
.

3.两级DFF同步器

单比特信号处理通常采用两级DFF串联进行同步
单比特信号处理通常采用两级DFF串联进行同步
单比特信号处理通常采用两级DFF串联进行同步
(!!!!!重要的事说三遍!!!!!)
最简单的单比特信号处理通常采用两级DFF串联进行同步,如下图所示:
异步bus交互(一)— 两级DFF同步器
从时钟域A(CLKA)传过来的信号a_in, 直接用时钟域B CLKB采用很容易产生亚稳态,用两级DFF 敲过后再使用就可以把亚稳态概率降到一个合理的值。

错误1 :时钟域A的组合逻辑信号直接敲两级DFF同步到时钟域B
如下图(1)所示虽然时钟域A的逻辑信号c0 传输到时钟域B的时候,也用了两级DFF 同步器,但我们知道组合逻辑电路各个输入信号的不一致性以及组合逻辑内部路径的延时时间不一样,运算后的信号存在毛刺如图(2),而我们又无法预先知道CLKB 的上升沿何时会到来,CLKB 采样到的信号就无法预知,这显然不是我们想要的结果。
异步bus交互(一)— 两级DFF同步器
因此,要想CLKB 能采到稳定的信号,时钟域A的信号必须是经过CLKA 敲过,在一个时钟周期内是稳定的信号,如图(3)所示:
异步bus交互(一)— 两级DFF同步器
补充说明建立时间和保持时间,建立时间(Tsu:set up time)是指在时钟沿到来之前数据从不稳定到稳定所需的时间,如果建立的时间不满足要求那么数据将不能在这个时钟上升沿被稳定的打入触发器;保持时间(Th:hold time)是指数据稳定后保持的时间,如果保持时间不满足要求那么数据同样也不能被稳定的打入触发器。
.
.

4.两级DFF同步器代码示例demo

通常在慢时钟域到块时钟域的单bit信号,我们直接打两拍即可进行传输。但是在快时钟到慢时钟进行传输的时候,如果传输的是脉冲信号,那么直接打拍传输的话就很有可能会导致一些脉冲信号没法采集到,从而丢失数据。

为解决上面单bit脉冲信号从快时钟到慢时钟传输的问题,我们可以:1.将脉冲信号在快时钟下进行扩宽。当慢时钟采集到信号后再进行复位。 2.将扩宽后的信号同步至慢时钟域。 3.在慢时钟域进行打两拍操作,去除亚稳态以及防止数据丢失,同时在慢时钟域进行输出。 4.将打拍后的数据在快时钟下进行反馈,直接在快时钟下打两拍反馈,生成扩宽信号的复位信号。

RTL代码:

module clock_h_or_l(
	input 	rst_n,
	input	clka,   //快时钟
	input	clkb,   //慢时钟
	input	pulse_ina,
	output	pulse_outb,
	output	signal_outb
);

	reg 	signal_a;
	reg		signal_b;
	reg		[1:0] 	signal_a_r;
	reg		[1:0]	signal_b_r;
	

//在clka时钟下将signal_a展宽	
always @(posedge clka or negedge rst_n) begin
	if(!rst_n)
		signal_a <= 1'b0;
	else if(pulse_ina == 1'b1)
		signal_a <= 1'b1;
	else if(signal_a_r[1] == 1'b1)
		signal_a <= 1'b0;
	else
		signal_a <= signal_a;
end 	

//在clkb下同步signal_a
always @(posedge clkb or negedge rst_n) begin
	if(!rst_n)
		signal_b <= 1'b0;
	else
		signal_b <= signal_a;
end

//在clkb下打两拍signal_b. 生成脉冲和输出信号
always @(posedge clkb or negedge rst_n) begin
	if(!rst_n)
		signal_b_r <= 2'b00;
	else
		signal_b_r <= {signal_b_r[0], signal_b};

end
assign pulse_outb = ~signal_b_r[1] & signal_b_r[0];
assign signal_outb = signal_b_r[1];

//在clka下采集signal_b_r[1],生成signal_a_r,用于反馈拉低signal_a
always @(posedge clka or negedge rst_n) begin
	if(!rst_n)
		signal_a_r <= 2'b00;
	else
		signal_a_r <= {signal_a_r[0], signal_b_r[1]};
end

endmodule

testbench的代码

module clock_h_or_l_tb();

	reg 	rst_n;
	reg 	clka, clkb;
	reg		pulse_ina;
	wire	pulse_outb;
	wire 	signal_outb;
	
	
initial begin
	rst_n = 1'b0;
	clka = 1'b1;
	clkb = 1'b1;
	pulse_ina = 1'b0;
	#20
	rst_n = 1'b1;
	#50
	pulse_ina = 1'b1;
	#10
	pulse_ina = 1'b0;
	#2000
	pulse_ina = 1'b1;
	#50
	pulse_ina = 1'b0;
	#5000
	$stop;
end 

always #5 clka = ~clka;
always #23 clkb = ~clkb;



clock_h_or_l t1(
	.rst_n(rst_n),
	.clka(clka),
	.clkb(clkb),
	.pulse_ina(pulse_ina),
	.pulse_outb(pulse_outb),
	.signal_outb(signal_outb)
);


endmodule
上一篇:5.1.2 静态坐标变换


下一篇:前端vue正则表达式验证手机号