单bit信号跨时钟域(2)

握手概念的引入

上篇文章中,我们基本了解了单bit信号跨时钟域的三种同步器,在最后一种脉冲同步器中,实现了要求较为苛刻的从慢时钟域到快时钟域的跨越,并输出了与新时钟周期等宽的脉冲信号。但在功能仿真中可以看到,要跨越时钟域的两个单bit信号,存在一个最小间隔的要求,即输入脉冲的最小间隔必须等于两个新时钟的时钟周期。而且,上述三种同步器的设计中,同步器的控制传递都是单向的,仅从原时钟域到目的时钟域,后者并没有状态反馈,这样,在输入脉冲间隔不满足要求的情况之下,信号丢失且没有指示,这必然是设计者不想看到的情况。

要解决以上问题,就需要引入握手机制,保证每个脉冲都同步成功,同步成功后再进行下一个脉冲的同步。全握手(full-handshake)的基本原理:双方电路在声明或终止各自的握手信号信号前都要等待对方的相应。完整的同步过程(A→B)可有以下4个步骤:

  1. 请求信号的产生:当同步器处于空闲状态时,在输入脉冲到来时,A声明它的请求信号sync_reg;
  2. 请求信号的跨越与应答信号的产生:sync_reg信号需要跨时钟域同步到B,与此同时,B产生同步脉冲,并产生应答信号sync_ack,此时,已经给出了要输出的脉冲
  3. 应答信号的跨越与请求信号的清除:sync_ack信号跨时钟域同步到A,与此同时,A清除之前产生的sync_reg;
  4. 应答信号的清除:在sync_reg信号清除之后,B清除sync_ack信号。此时,一次信号的跨时钟域完成,等待同步下一个脉冲。

给出上述表达的示意图:
在这里插入图片描述
Mike Stein给出了全握手方式所消耗的延时。根据经验估算法,信号跨越一个时钟域需要花两个时钟周期的实现,信号在跨域多个时钟域前被电路寄存,因而,上述方式的延时构成为<5TA + 6TB>。由此也可以知道,全握手方式需要消耗很多的时钟周期。

给出握手方式的设计及测试代码:

//脉冲同步器
module pulse_syc_handshake(
    input wire din,
	input wire clk_A, 			
	input wire rst_n_A,			
	 			
	input reg clk_B,			
	input reg rst_n_B, 		
						
	output wire sync_idle,		//给出同步器是否空闲的信号
	output reg sync_fail, 		//同步失败:位于原时钟域
	
	output wire dout 			
);	
	
    reg src_sync_req;			//原时钟产生同步请求
	reg req_state_dly1;
	reg req_state_dly2;			//同步器的输出
	reg req_state_dly3;			//目的时钟延后信号,以保证脉冲输出
	
	reg dst_sync_ack;			//目的时钟产生应答
	reg ack_state_dly1;
	reg ack_state_dly2;			//同步器的输出
	wire src_sync_ack;			//原时钟接收应答

	
	//同步器空闲状态的判断:原时钟下:请求和应答信号同时无效
	assign sync_idle = ~(src_sync_req | src_sync_ack );
	
	//同步失败的判断
	always @(posedge clk_A or negedge rst_n_A)
	begin
		if(rst_n_A == 1'b0) 
			sync_fail <= 1'b0;
		else if(din & (~sync_idle))		//源时钟脉冲到来,此时同步器不空闲,给出同步失败
			sync_fail <= 1'b1;
		else
			sync_fail <= 1'b0;
	end
	
	//原时钟产生请求信号,请求信号的产生相当于将脉冲转化为了电平
	always @(posedge clk_A or negedge rst_n_A)
	begin
		if(rst_n_A == 1'b0)
			src_sync_req <= 1'b0;
		else if(din & sync_idle)			//源时钟脉冲到来,且源时钟空闲,传递请求。同时完成了脉冲转电平
			src_sync_req <= 1'b1;
		else if(src_sync_ack)						//检测到应答以后,清除请求
			src_sync_req <= 1'b0;
	end
	
	//同步原时钟请求信号到目的时钟,利用请求信号跨时钟域
	always @(posedge clk_B or negedge rst_n_B)
	begin
		if(rst_n_B == 1'b0)
		begin
			req_state_dly1 <= 1'b0;
			req_state_dly2 <= 1'b0;
			req_state_dly3 <= 1'b0;
		end
		else
		begin
			req_state_dly1 <= src_sync_req;
			req_state_dly2 <= req_state_dly1;		//打两拍结束
			req_state_dly3 <= req_state_dly2;		//再外接一个寄存器,以保证脉冲输出
		end
	end
	
	//上升沿检测,产生输出脉冲
	assign dout = (~req_state_dly3) & req_state_dly2;		//完成输出脉冲
	
	//目的时钟产生应答信号
	always @(posedge clk_B or negedge rst_n_B)
	begin
		if(rst_n_B == 1'b0)
			dst_sync_ack <= 1'b0;
		else if (req_state_dly2)		//同步高电平已到达
			dst_sync_ack <= 1'b1;
		else
			dst_sync_ack <= 1'b0;
	end
	
	//同步目的时钟产生的应答信号到原时钟
	always @(posedge clk_A or negedge rst_n_A)
	begin
		if(rst_n_A == 1'b0)
		begin
			ack_state_dly1 <= 1'b0;
			ack_state_dly2 <= 1'b0;
		end
		else
		begin
			ack_state_dly1 <= dst_sync_ack;
			ack_state_dly2 <= ack_state_dly1;			
		end
	end
	assign src_sync_ack = ack_state_dly2;

	endmodule
	

module pulse_syc_handshake_tb;
	
	reg clk_A;
	reg rst_n_A;
	reg din;
	
	wire sync_idle;
	wire sync_fail;
	
	reg clk_B;
	reg rst_n_B;
	wire dout;
	
	always #10 clk_A = ~clk_A;
	
	always #40 clk_B = ~clk_B;
	
	initial
		fork
			clk_A 	= 1'b1;
			din 	= 1'b0;
			clk_B 	= 1'b0;
			#10 rst_n_A = 1'b0;
			#20 rst_n_B = 1'b0;
			#60 rst_n_A = 1'b1;
			#70 rst_n_B = 1'b1;
			
			#300 din = 1'b0;		
			#320 din = 1'b1;
			#340 din = 1'b0;
											//脉冲间隔大于等于5Ta+6Tb			
			#920 din = 1'b1;
			#940 din = 1'b0;		
											//脉冲间隔小于5Ta+6Tb
			#1400 din = 1'b1;
			#1420 din = 1'b0;
			
		join
	
	pulse_syc_handshake u1(.clk_A(clk_A),
						   .rst_n_A(rst_n_A),
						   .din(din),
						   .sync_idle(sync_idle),
						   .sync_fail(sync_fail),
						   .clk_B(clk_B),
						   .rst_n_B(rst_n_B),
						   .dout(dout));
	
	endmodule	

给出Modelsim的仿真波形:
在这里插入图片描述
从波形图中可以看出,当在输入脉冲到来时,同步器空闲信号(sync_idle)无效,表示此时同步器正被使用;

在一定时钟周期之后,同步器完成了信号的同步,输出了脉冲信号(dout)。但此时,同步器仍未进入空闲,这是因为B时钟域的应答信号正在向A时钟域跨越。在应答信号到达A时钟域之后,请求信号被无效,同时,在B时钟域检测到无效的请求信号以后,应答信号也被无效,最终两个信号都无效的情况下,同步器进入空闲状态,等待下一个脉冲的输入

当同步器被使用的情况下,输入脉冲到来时,会同步失败信号会被直接拉高,以表示此时无法正常完成信号的同步。

重点考虑完成这样的同步所花费的时钟周期,在不考虑输入脉冲在A时钟域寄存的前提之下:

  1. A时钟域的请求信号跨越至B时钟域,所消耗的时间为(TB,2TB](此时,请求信号顺利跨越到B时钟域);
  2. B时钟域检测到同步来的请求信号,消耗时间为TB(此时,时序已稳定,消耗时间可视为常数);
  3. B时钟域产生的应答信号,跨时钟域到A时钟域,所消耗的时间为(TA, 2TA];
  4. A时钟域检测到同步的应答信号,将请求信号置为无效,消耗时间为TA;
  5. 无效的请求信号仍要同步到B时钟域,所消耗的时间为(TB,2TB];
  6. 请求信号无效,应答信号随之拉低,消耗时间为TB;
  7. 无效的应答信号需要再次跨越到A时钟域,所消耗的时间为(TA, 2TA];

通过上述推导,可得最坏情况下所需要的时钟周期为(5TA+6TB)才能完成整个操作,同步器才能再次被置为空闲。**这种稳定性很好的全握手方式,需要消耗巨大的时钟周期(原先的脉冲同步器仅消耗2TB)。**因而又有很多稳定性和速度折衷的方法,比如部分握手,通信双方的电路都不等对方的相应就终止各自的信号,并继续执行握手命令序列,在本文中就暂不详述了,之后详细了解再做整理哈。

参考文章

  1. Stein, Mike . “跨越鸿沟: 同步世界中的异步信号.” 电子设计技术 11(7)(2004): 76-76.
  2. 路科验证笔试题目整理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值