verilog奇偶、小数分频

转载自时钟分频电路

时钟分频(clock divide)

每个时钟就是一个在0和1之间周期变化的信号。那么我们在不同的场合可能需要不同的频率的时钟,很多时候我们可以在一个已有时钟的基础上对它进行分频操作,从而得出一个另外更低频率的时钟。我们把这样的电路叫做时钟分频电路,即clock divider。

偶数分频

在这里插入图片描述
相信这个大家都知道该怎么做,我们把时序图画出来,即使你之前完全没有听说过clock divider,看着时序图也可以很快想出来。2分频的另外一个意思是,在clk_out的1个周期内,clk_in经过了2个周期(注意这个说法,之后我们还会用到,便于你理解)。在每次clk_in的上升沿来临的时候,clk_out翻转一下。所以电路很简单,只需要一个Flop加一个反相器即可。如果flop本身自带Q反端,那连反相器都可以省下来,如下图所示:
在这里插入图片描述
那么有了2分频,那如果需要一个4分频电路,怎么办呢?很简单,给上面的clk_out再接一个2分频电路呗,只要你一直2分下去,你就可以得到4分频,8分频,16分频,32分频。。。这种办法当然可以,但是里面有个隐含的缺陷,我们之后再说。

那么现在假设需求变一下,我们要求你输出一个6分频的电路,也就是在clk_out的1个周期内,clk_in经过了6个周期,那么你没有办法利用上面2分频级联的办法得到。那么要怎么做呢?更general的要求是,如果要求你做一个任意偶数的分频,要如何做呢?

我们还是先以4分频为例,看一下时序图在这里插入图片描述

可以看出,要求是clk_div4要保持2个clk_src周期为高,2个clk_src周期为低。自然而然,我们需要个计数器counter,counter 从0开始每个clk_src的上升沿到来的时候加1,加到3后返回0,一直这样循环下去。然后我们clk_div4就可以这样产生:当counter值为0,1的时候,clk_div4为0, 当counter值为2,3的时候,clk_div4为1。

这个可以推广到任意偶数倍2m的分频(假设m为自然数),做法就是利用一个counter从0到2m-1不停计数,当counter值为0-m-1的时候,输出0,当counter值为m到2m-1的时候,输出1。RTL code老李就不写了,留给大家自己做练习(注意,看到上面那样说0到m-1,千万不要用小于等于<=或者大于等于>=这类操作,最多用一个相等判断操作就行了)。

所以任意偶数分频代码为




module divide#(
		parameter NUM = 16)	
		(
		input clk_in,
		input rst,
		output reg clk_out
		);
		
		reg [NUM-1:0] cnt
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				cnt<= 'd0;
			end
			else if(cnt==(NUM-1)) begin
				cnt <= 0;
			end
			else begin
				cnt <= cnt + 1'b1;
		end
		
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				clk_out <= 0;
			end
			else if(cnt == (NUM-1)/2)begin
				clk_out <= 1'b1;
			end
			else if(cnt == NUM -1 )begin
				clk_out <= 1'b0;
			end
			else begin
				clk_out <= clk_out
			end
		end
				
					
endmodule

因为考虑到参量化,但是分频数不一定为2的幂次方,因此计数器的位宽设置的过大,不知道还有没有优化的方法。

奇数分频

于是问题就来了,能不能做一个奇数倍分频的分频器呢?最小的奇数倍分频是3分频。这里我们要引入一个占空比(duty cycle)的概念。我们知道时钟是一段时间为1,一段时间为0,我们把为1的这段时间占整个周期时间的比叫做占空比。以我们上面看到的clk_src为例,为1和为0的时间是相等的,也就是各占整个周期的一半,所以我们说占空比是50%。假设我们要做一个3分频,意思是clk_out的一个周期内,clk_src经过了3个周期。如果还要求50%占空比的话,那么clk_out为1和为0持续的时间是clk_src的1.5个周期,如下图clk_div3所示:

在这里插入图片描述
那么我们还能直接用之前偶数分频的时候用counter计数来做吗?可以,但是需要一点技巧。如果我们只用上升沿触发的flop,那么我们只能得到上图中clk_div3_pos的波形。而要想在下降沿发生变化,我们只能用一个下降沿触发的flop,如上图的clk_div3_neg的波形。然后我们将两个再OR起来,就可以得到一个占空比50%的3分频了。
其实之前任意偶数分频的代码也可以实现奇数分频,只是占空比不为50%

下面代码为占空比为50%的任意奇数分频代码

module divide#(
		parameter NUM = 7)	
		(
		input clk_in,
		input rst,
		output  clk_out
		);
		
		reg [NUM-1:0] cnt
		reg clk_out1,clk_out2;
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				cnt<= 'd0;
			end
			else if(cnt==(NUM-1)) begin
				cnt <= 0;
			end
			else begin
				cnt <= cnt + 1'b1;
		end
		
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				clk_out1 <= 0;
			end
			else if(cnt == (NUM-1)/2)begin
				clk_out1 <= 1'b1;
			end
			else if(cnt == NUM -1 )begin
				clk_out1 <= 1'b0;
			end
			else begin
				clk_out1 <= clk_out
			end
		end
		always@(negedge clk_in or posedge rst)begin
			if(rst)begin
				clk_out2 <= 0;
			end
			else if(cnt == (NUM-1)/2)begin
				clk_out2 <= 1'b1;
			end
			else if(cnt == NUM -1 )begin
				clk_out2 <= 1'b0;
			end
			else begin
				clk_out2 <= clk_out
			end
		end
		assign clk_out = clk_out1 | clk_out2;		
					
endmodule

讲到这里,我们可以说掌握了最基本的分频器做法,应付大多数公司的笔试题够了,但是其实分频器里面还有一些更加深入的知识点,下面才是真正的干货内容。

首先,我们上面提到的分频器都是基于flop的,也就是说输出clock是直接来自于flop的Q(偶数分频)或者来自于两个flop的Q之后再加一个OR门(奇数分频)。这样做分频的优缺点是什么呢?优点很明显,就是50%的占空比。但是由此带来的一些问题:

1.因为驱动clk_out的flop是由clk_src作为时钟的,那么这个flop的Q端变化相比于clk_src有一个必不可少的clk-to-q延时
在这里插入图片描述
这个clk-to-q延时根据不同的工艺,值会不同。这个clk-to-q delay在做时钟树综合的时候是要考虑进去的。特别是如果你还期望clk_src和clk_out是同步的时钟,沿要对齐的话,在做clock tree的时候要给clk_src的tree加一些buffer来弥补这个clk-to-q。而如果你是用了好几个分频器级联产生更低频率,那么每一级的分频器都会贡献一个clk-to-q延时,那么你需要平衡时钟的时候就需要插入更多的buffer,这部分buffer又占面积,又耗功耗,甚至可能导致时钟无法平衡。所以这是需要大家在设计的时候考虑进去的。

  1. 奇数分频需要在两个flop之后再加一个组合逻辑门,这个组合逻辑门又会增加clk的delay,同时设计的时候要非常小心,否则可能出现毛刺

回顾一下我们的设计,对于绝大多数的flop,我们只需要用到时钟的上升沿触发,而并不会用到下降沿,甚至有的工艺库里面就不提供下降沿触发的flop。大家如果不信,可以看看你的设计,是不是几乎都是always_ff @(posedge clk)。在这种情况下,只有上升沿和时钟频率有关系,什么时候来下降沿不重要!如果你能保证你的模块当中不存在下降沿触发的flop,那么50%的占空比不是必须的。这种情况下我们可以利用clock gater 来帮我们实现分频。

关于clock gating的基本介绍,大家可以看我之前的一篇推送。Clock Gating 技术解析 , 对于一个clock gater来说,都有一个时钟的使能端enable,当enable为1的时候,clock gater 是通的,会放过一个clk_src的pulse。所以,利用clock gater来分频的思路其实也很简单,如果要N分频,就是在N个clk_src周期内,只要使得clock gater使能1个周期即可。以3分频为例,时序图如下
在这里插入图片描述我们依然需要一个counter,从0计数到N-1, 当counter值等于N-1(其实等于0到N-1的任意一个值都行)的时候,enable clock gater,enable只持续一个周期,这样就会产生一个pulse,pulse的宽度为clk_src的半周期。电路图也非常简单,如下图所示
在这里插入图片描述
那么基于clock gater的分频器有什么好处呢?首先解决的问题就是上面flop based分频器clk-to-q延时大的问题。我们知道,clock gater里面是有一个latch,而latch是在下降沿被enable的,**所以一个clock gater传递那个pusle的延时只是里面的那个AND门,这个delay通常要比clk-to-q要小很多。**在时钟树上有一个clock gater对于时钟树综合(CTS)通常不是什么问题。

更大的好处是分频器的级联,我们可以把分频器设计成带有输入enable和输出enable的模块,如下图所示
在这里插入图片描述
这样做的好处在于clk_src到clk_div4依然只有一个clock gater的delay,其实不管你级联多少个,只要利用enable来级联,只有最后一级的clock divider会引入一个clock gater延时,而不是累计的,而且每一级的输出分频clock 都各自对clk_src只有一个clock gater delay,也就是说它们彼此之间几乎是已经balance的,这对时钟树综合大有帮助。还有一个好处就是clock gater的输出可以保证没有glitch。

好,那么我们再进阶继续问下去,我们前面讨论的都是整数倍的分频,也就是2,3,4…这样分频下去,那么如果我想从一个3MHz的时钟得到一个2MHz的时钟,即1.5倍分频,要怎么做呢?

首先我们来看1.5倍分频长什么样,时序图如下
在这里插入图片描述
那么大家思考一下,只用数字电路里面基本的这些逻辑门和flop,我们能够做到上面的时序吗?

答案是做不到,注意红圈标出来的地方,clk_div1p5要发生变化,但是clk_src那一时刻既没有上升沿,也没有下降沿,而我们数字电路是基于时钟沿触发的,在没有时钟沿的地方我们就无能为力了(如果我们非要达到50%的占空比,需要借助锁相环PLL的帮助了)。

那么如果我们把条件放宽一些,如果不要求50%的占空比,我们可以做到吗?答案是可以,1.5倍分频,换句话说,就是3个clk_src周期内,有2个clk_div1p5周期,如果基于clock gater做,无非就是在3个clk_src周期里,让clock gater enable 2个周期,放过去2个pulse就行了。时序图如下

在这里插入图片描述
看起来很简单吧,但是这里面有几个需要注意的点:

1.如果上面clk_src是3MHz, 我们其实并没有做出严格意义上的2MHz 的clock。注意上面图中前两个上升沿,它们之间的时间间隔是1个clk_src周期,而不是1.5个clk_src周期!所以在STA分析的时候,clk_div1p5的cycle还是要按照3MHz来。

2.我们虽然可以看到似乎整体上是3个clk_src周期里,有两个clk_div1p5的2个pulse,但是并不是任意3个连续的clk_src周期里,你都能看到2个pulse,比如红圈里面的3个周期,你其实只能看到1个clk_div1p5的pulse。这在整数分频里是不存在这个问题的。

上面两个注意的点就会引出我们接下来要思考的问题,假设这次以2.5倍分频为例,即5个周期里产生2个pulse,那么我们要在哪两个周期里面产生pulse呢?有下面几种方式
在这里插入图片描述
分别对应

clk_1: counter在4和0的时候enable clock gater

clk_2: counter在4和1的时候enable clock gater

clk_3: counter在4和2的时候enable clock gater

clk_4: counter在4和3的时候enable clock gater

这几种方式都实现了5个周期里产生2个pulse,那么请问大家,以上哪一个你觉得对时钟来说更好呢?

clk_2和clk_3要比另外两个好。为什么呢?因为pulse的间隔比较均匀,clk_1和clk_4中最近的两个pulse其实是挨着的,那么STA里面虽然进行了2.5分频,但是你还是要按照分频前的周期来进行约束。而clk_2和clk_3,它们最近的两个pulse至少间隔了2个周期,这样你可以按照clk_src周期的两倍来进行约束,timing要容易close一些。

所以对于非整数的分频,我们其实要找这么一种解决办法,就是要让输出时钟的pulse间隔尽可能均匀,这才是时钟分频器里最难的考点。

我们知道,任何有理数都可以表示为一个分数,分子分母都为整数。那么我们把问题扩展为:设计一个M/N的分频器,M, N互质,要求是在N个clk_src周期内产生M个pulse,且这M个pulse尽可能均匀分布。为什么要求互质?因为不互质你就可以约分为互质的M/N。

回到上面的例子,我们设计一个2.5倍分频器,即M=2,N=5, 如何做呢?思路是counter不能再在每个时钟沿加1了,而是要按照一个巧妙的方式进行加,并循环。如下图所示
在这里插入图片描述
这里counter加的办法是,每次加2,如果counter值+2后大于等于5,那么就减掉5,之后再每次加2。这样counter值就是上面的0,2,4,1,3进行循环。在什么时候enable clock gater呢,就是在每次要减5的那个周期,就是在counter等于4和3的周期enable,这样我们就可以得到相对均匀的clock。

推广一下,对于M/N分频,假设counter初始值为0,counter的变化规则为

如果counter当前值加M大于等于N,则下个周期counter值为当前值+M-N

否则,counter值加M。

在当counter当前值加M大于等于N的周期enable clock,输出pulse。

大家可以自己验算一下,比如以2/7为例,看是否达到了均匀pulse的效果。

任意占空比为50的整数分频

如果让实现任意整数分频该怎么办呢,这里我们可以将偶数分频和奇数分频结合一下,然后做一个切换,就可以轻松的实现了,

module divide#(
		parameter NUM = 7)	
		(
		input clk_in,
		input rst,
		output  clk_out
		);
		
		reg [NUM-1:0] cnt
		reg clk_out1,clk_out2;
		wire clk_odd,clk_even;
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				cnt<= 'd0;
			end
			else if(cnt==(NUM-1)) begin
				cnt <= 0;
			end
			else begin
				cnt <= cnt + 1'b1;
		end
		
		always@(posedge clk_in or posedge rst)begin
			if(rst)begin
				clk_out1 <= 0;
			end
			else if(cnt == (NUM-1)/2)begin
				clk_out1 <= 1'b1;
			end
			else if(cnt == NUM -1 )begin
				clk_out1 <= 1'b0;
			end
			else begin
				clk_out1 <= clk_out
			end
		end
		always@(negedge clk_in or posedge rst)begin
			if(rst)begin
				clk_out2 <= 0;
			end
			else if(cnt == (NUM-1)/2)begin
				clk_out2 <= 1'b1;
			end
			else if(cnt == NUM -1 )begin
				clk_out2 <= 1'b0;
			end
			else begin
				clk_out2 <= clk_out
			end
		end
		assign clk_odd= clk_out1 | clk_out2;		
		assign clk_even = clk_out1;
		assign clk_out = (NUM%2 == 0)?clk_even:clk_odd;
					
endmodule

上述分频得出的clk_out输出信号就是我们想要的分频后的信号,很多同学就直接把这个信号当作新的低频时钟来使用,并实现了自己想要的功能。大家肯定会觉得能够实现功能就一切OK了,而往往忽略了一些存在的隐患。
如果你对FPGA的了解多一些,就会理解其实这是不严谨的做法。这种做法所衍生的潜在问题在低速系统中不易察觉,而在高速系统中就很容易出现。
为什么会这样呢?因为我们通过这种方式分频得到的时钟虽然表面上是对系统时钟进行了分频产生了一个新的低频时钟,但实际上和真正的时钟信号还是有很大区别的。在FPGA中,凡是时钟信号都要连接到全局时钟网络上,全局时钟网络也称为全局时钟树,是FPGA厂商专为时钟路径而特殊设计的,它能够使时钟信号到达每个寄存器的时间都尽可能相同,以保证更低的时钟偏斜(Skew)和抖动(Jitter)。而我们用这种分频的方式产生的clk_out信号并没有连接到全局时钟网络上,但sclk则是由外部晶振直接通过管脚连接到了FPGA的专用时钟管脚上,自然就会连接到全局时钟网络上,所以在sclk时钟工作下的信号要比在clk_out时钟工作下的信号更容易在高速系统中保持稳定。
既然发现了问题那我们该怎么办呢?这时可以使用我们的flag标志信号。我们以为偶数6分频为例,产生一个用于标记6分频的clk_flag标志信号,这样每两clk_flag脉冲之间的频率就是对sclk时钟信号的6分频,相当于把clk_out的上升沿信号变成了clk_flag的脉冲电平信号,为后级模块实现相同的降频效果,也能让系统更加稳定。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值