跨时钟域信号的处理

参考文献:

https://www.cnblogs.com/rouwawa/p/7501319.html

https://www.cnblogs.com/lyc-seu/p/12441366.html

 

1. 时钟域

        假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么这个设计只有一个时钟域。假如设计有两个输入时钟,一个时钟给接口1使用,另一给接口2使用,那么这个设计中有两个时钟域。

2. 亚稳态

        原因:

        触发器的建立时间保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),就会产生时序违规,即亚稳态。此时触发器输出端Q在有效时钟沿之后比较长的一段时间处于不确定的状态(Q端毛刺振荡固定的某一电压值),这段时间称为决断时间(resolution time)。经过resolution time之后Q端将稳定到0或1上,但是究竟是0还是1,这是随机的,与输入没有必然的关系。

        表现:

        由于输出在稳定下来之前可能是毛刺、振荡、固定的某一电压值,因此亚稳态除了导致逻辑误判之外,输出0~1之间的中间电压值还会使下一级产生亚稳态(即导致亚稳态的传播)。

在这里插入图片描述

        触发器进入亚稳态的时间用参数 MTBF(mean time between failures,平均无故障时间)描述,MTBF与触发器的时钟频率密切相关,甚至受到时钟频率的影响极大,但亚稳态发生的概率与时钟的频率无关,只是MTBF与触发器的时钟频率有密切联系。 MTBF即触发器采样失败的时间间隔,其公式如下:

        例如对于一个系统来说,可能触发器的时钟频率为20MHZ时,系统MTBF为5年,但是当触发器时钟为40MHZ时,可能MTBF只剩下一分钟了。例如假设触发器的时钟频率为10MHZ,而输入数据频率为3KHZ,其MTBF如下图所计算的:

        在这里插入图片描述

MTBF的提高 :

       提高MTBF有利于减少系统在信号传输过程因亚稳态而带来的风险。MTBF取决于电路寄存器的工艺及电路的Tmet(Tr,在不引起同步失败的前提下亚稳态可持续的最长时间)。
       提高MTBF的第一种办法:1)提高工艺,例如从180nm的工艺提高到90nm的工艺,更快的供电电压跟更快的晶体管可以使亚稳态信号更快的稳定下来。晶体管的速度提升通常来说也是提高了亚稳态的平均无故障时间。2)提高MTBF的第二种办法:采用3-5的同步链装置,来提高电路的Tmet/Tr,根据系统应用的MTBF需求甚至可以采用更高更多的同步链。例如:如果给定器件和运行条件下的Tmet是50ps,则在Tmet上提高200ps则提高MTBF的e^4倍,即50多倍,然而如果提高400ps则累乘e8是3000多倍。为了增加Tmet可以增加同步链的寄存器个数,因为寄存器到寄存器之间的时间余量都会加到Tmet中去,一般来说,在面对跨时域数据交汇的电路,为了更好的亚稳态保护,一般会采用两级同步链电路。

 

在这里插入图片描述

 

3.一般解决信号亚稳态有三种方法:

1 )相位控制
相位控制技术可以在一个时钟频率是另外一个时钟的数倍,并且其中一个时钟可以由FPGA 内部PLL 或者DLL 控制时使用。

2)多级寄存器(打两拍),处理单bit数据的跨时钟域问题
一般针对单bit控制信号跨越两个异步时钟域传输,可以采用多级寄存器,俗称多打拍。同步电路中的第一拍后也许会产生亚稳态,但是信号有机会在其被第二级寄存以及被其它逻辑看到之前稳定下来。常用的就是对单bit信号打两拍,这也是最简单、最常见的处理方式。

3)异步FIFO缓存(双口RAM)
一般用于跨时钟域传输数据,写端和读端分别对应两个时钟域,由空/满信号控制着读写过程,实现数据的跨域传输。

4.多级寄存器处理

        一般而言单bit信号就是我们所用到的脉冲信号或者电平信号。假设A和B是两个时钟域,各自的频率是clk_a和clk_b,clk_a的频率高于clk_b(同频相位差稳定的,不在讨论范围内),那么单bit信号传输分为两种情况。

4.1 信号从B到A(慢到快)

        在时钟域B下的脉冲信号pulse_b在时钟域A看来,是一个很宽的“电平”信号会,保持多个clk_a的时钟周期,所以一定能被clk_a采到。经验设计采集过程必须寄存两拍。第一拍将输入信号同步化,同步化后的输出可能带来建立/保持时间的冲突,产生亚稳态。需要再寄存一拍,减少亚稳态带来的影响。一般来说两级是最基本要求,如果是高频率设计,则需要增加寄存级数来大幅降低系统的不稳定性。

        特别强调的是,此时pulse_b必须是clk_b下的寄存器信号,如果pulse_b是clk_b下的组合逻辑信号,一定要先在clk_b先用D触发器(DFF)抓一拍,再使用两级DFF向clk_a传递。这是因为clk_b下的组合逻辑信号会有毛刺,在clk_b下使用时会由setup/hold时间保证毛刺不会被clk_b采到,但由于异步相位不确定,组合逻辑的毛刺却极有可能被clk_a采到。代码设计如下:

always @ (posedge clk_a or negedge rst_n)
	begin
		if (rst_n == 1'b0) 
			begin
			   pules_a_r1 <= 1'b0;
			   pules_a_r2 <= 1'b0;
			   pules_a_r3 <= 1'b0;
			end
		else 
			begin									//打3拍
			   pules_a_r1 <= pulse_b;
			   pules_a_r2 <= pules_a_r1;
			   pules_a_r3 <= pules_a_r2;
			end
	end

assign pulse_a_pos	= pules_a_r2 & (~pules_a_r3);	//上升沿检测
assign pulse_a_neg	= pules_a_r3 & (~pules_a_r2);	//下降沿检测
assign pulse_a		= pules_a_r2;

4.2 信号从A到B(快到慢)

       如果单bit信号从时钟域A到时钟域B,那么存在两种不同的情况,传输脉冲信号pulse_a或传输电平信号level_a。只有电平信号level_a的宽度能被clk_b采集到才可以保证系统正常工作。那么对于脉冲信号pulse_a采取怎样的处理方法呢?可以用一个展宽信号来替代pulse_a实现垮时钟域的握手

     主要原理就是先把脉冲信号在clk_a下展宽,变成电平信号signal_a,再向clk_b传递,当确认clk_b已经“看见”信号同步过去之后,再清掉signal_a。代码通用框架如下

module Sync_Pulse (
                  clk_a, 		
                  clk_b,   
				  rst_n, 			
                  pulse_a_in, 	
                 
                  pulse_b_out, 	
                  b_out	
                  );
/****************************************************/

	input				clk_a;
	input				clk_b;
	input				rst_n;
	input				pulse_a;
	
	output				pulse_b_out;
	output				b_out;		
	
/****************************************************/	

	reg					signal_a;
	reg					signal_b;
	reg					signal_b_r1;
	reg					signal_b_r2;
	reg					signal_b_a1;
	reg					signal_b_a2;
	
/****************************************************/
	//在时钟域clk_a下,生成展宽信号signal_a
	always @ (posedge clk_a or negedge rst_n)
		begin
			if (rst_n == 1'b0)
				signal_a <= 1'b0;
			else if (pulse_a_in)			//检测到到输入信号pulse_a_in被拉高,则拉高signal_a
				signal_a <= 1'b1;
			else if (signal_b_a2)			//检测到signal_b1_a2被拉高,则拉低signal_a
				signal_a <= 1'b0;
			else;
		end
	
	//在时钟域clk_b下,采集signal_a,生成signal_b
	always @ (posedge clk_b or negedge rst_n)
		begin
			if (rst_n == 1'b0)
				signal_b <= 1'b0;
			else
				signal_b <= signal_a;
		end
	//多级触发器处理
	always @ (posedge clk_b or negedge rst_n)
		begin
			if (rst_n == 1'b0) 
				begin
					signal_b_r1 <= 1'b0;
					signal_b_r2 <= 1'b0;
				end
			else 
				begin
					signal_b_r1 <= signal_b;		//对signal_b打两拍
					signal_b_r2 <= signal_b_r1;
				end
		end
	//在时钟域clk_a下,采集signal_b_r1,用于反馈来拉低展宽信号signal_a
	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_r1;		//对signal_b_r1打两拍,因为同样涉及到跨时钟域	
					signal_b_a2 <= signal_b_a1;
				end
		end

	assign	pulse_b_out =	signal_b_r1 & (~signal_b_r2);
	assign	b_out		=	signal_b_r1;

endmodule

       这样一来,实际上clk_a下的脉冲信号“作用”到了clk_b时钟域下,它对于clk_a与clk_b的时钟频率关系没有任何限制,快到慢,慢到快就都没问题了。

总而言之,在设计中可以简单的牢记以下五条原则:
1. 再全局时钟的跳变沿最可靠。
2. 来自异步时钟域的输入需要寄存一次以同步化,再寄存一次以减少亚稳态带来的影响。
3. 不需要用到跳变沿的来自同一时钟域的输入,没有必要对信号进行寄存。
4. 需要用到跳变沿的来自同一时钟域的输入,寄存一次即可。
5. 需要用到跳变沿的来自不同时钟域的输入,需要用到3个触发器,前两个用以同步,第3个触发器的输出和第2个的输出经过逻辑门来判断跳变沿。

4.3 设计分区同步器模块

        在顶层为设计分区是一个好的设计实践行为,这样任何功能模块外面都包含一个独立的同步器模块。这样有利于在划分模块的基础上实现所谓的理想时钟域情况(即整个设计模块只有一个时钟),如下图所示:

对设计进行分区有很多理由。首先,对每个独立的功能模块进行时序分析变得简易,因为模块都是完全的同步设计。其次,整个同步模块中的时序例外也很容易得到定义。再次,底层模块的同步器加时序例外在代入到设计顶层时,大大降低了由于人为失误造成的疏漏。所以,同步寄存器应该在功能模块外单独分区。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值