数字设计--门控时钟与时钟切换
门控时钟(ICG)
使用门控时钟的原因
芯片功耗组成中,大部分是由时钟树消耗掉的。因为这些时钟树在系统中具有最高的切换频率,而且有很多时钟buffer,并且为了最小化时钟延时,它们通常具有很高的驱动强度。此外,即使输入和输出保持不变,接收时钟的触发器也会消耗一定的功耗。而且这些功耗主要是动态功耗。
那么减少时钟网络的功耗消耗,最直接的办法就是如果不需要时钟的时候,就把时钟关掉。这种方法就是大家熟悉的门控时钟:clock gating。
门控时钟的结构
与门门控
实现门控时钟最直接的方法,即不需要时钟的时候关掉时钟,就是与操作,我们只需要把enable使能信号和CLK时钟进行与操作就可以了,电路图如下:
这种直接将控制EN信号和时钟CLK进行与操作完成门控的方式,可以完成EN为0时,时钟被关掉。但是同时带来另外一个很大的问题:毛刺
如上图所示,EN是不受控制的,随时可能跳变,这样纯组合输出GCLK就完全可能会有毛刺产生。时钟信号上产生毛刺是很危险的。虽然采用这种门控方式最直接,但在实际中很少采用。
所以我们需要改进电路,为了使门控时钟不产生毛刺,我们必须对EN信号进行处理,使其在CLK的高低电平期间保持不变,或者说EN的变化就是以CLK为基准的。
1 很自然的我们会想到触发器,只要把EN用CLK寄存一下,那么输出就是以CLK为基准的;
2 其实还有一种办法是锁存器,把EN用锁存器锁存的输出,也是以CLK为基准的。
锁存器门控
时序
可以看到,当CLK为低电平时,锁存器输出EN,当CLK升为高电平时,锁存器锁存输入的EN。因为后面与门只有在CLK为高电平时才可能输出高,所以只有在CLK为高的时候,GCLK才可能会输出高,并且在CLK高电平时,锁存器输出不变,这样就能消除EN带来的毛刺。
这是因为D锁存器是电平触发,在clk=1时,数据通过D锁存器流到了Q;在Clk=0时,Q保持原来的值不变。
虽然达到了我们消除毛刺的目的,但是这个电路还有两个缺点:
-
如果在电路中,锁存器与与门相隔很远,到达锁存器的时钟与到达与门的时钟有较大的延迟差别(也有可能锁存器输出先到,也有可能时钟先到),则仍会出现毛刺。
-
如果在电路中,时钟使能信号距离锁存器很近,可能会不满足锁存器的建立时间,会造成锁存器输出出现亚稳态。
如下图分析所示:
上述的右上图中,B点的时钟比A时钟迟到,并且Skew>delay,这种情况下,产生了毛刺。为了消除毛刺,要控制Clock Skew,使它满足Skew<Latch delay(也就是锁存器的clk-q的延时)。
上述的右下图中,B点的时钟比A时钟早到,并且\(|Skew| > EN_{setup_{(D->Q)}}\),这种情况下,也产生了毛刺。为了消除毛刺,要控制Clock Skew,使它满足\(|Skew|<EN_{setup_{(D->Q)}}\)。
寄存器门控
用寄存器来寄存 EN 信号再与上 CLK 得到GCLK
由于DFF输出会delay一个周期,所以除非CLKB上升沿提前CLKA很多,快半个周期,才会出现毛刺,而这种情况一般很难发生。CLKB也可能比CLKA晚到,这种情况是不会出现毛刺的。
当然,如果第一个D触发器不能满足setup时间,还是有可能产生亚稳态。
门控寄存器存在时钟翻转,可能产生功耗,但是他控制着后面的时钟通断,当用不到时钟时,后面的寄存器(或寄存器组)将没有时钟,降低了功耗。
门控时钟的选择
那么到底采用哪一种门控时钟的结构呢?是锁存结构还是寄存结构呢?通过分析,我们大概会选择寄存器结构的门控时钟,这种结构比锁存器结构的问题要少,只需要满足寄存器的建立时间就不会出现问题。
那么实际中是这样么?答案恰恰相反,SOC芯片设计中使用最多的却是锁存结构的门控时钟。
原因是:在实际的SOC芯片中,要使用大量的门控时钟单元。所以通常会把门控时钟做出一个标准单元,有工艺厂商提供。那么锁存器结构中线延时带来的问题就不存在了,因为是做成一个单元,线延时是可控和不变的。而且也可以通过挑选锁存器和增加延时,总是能满足锁存器的建立时间,这样通过工艺厂预先把门控时钟做出标准单元,这些问题都解决了。
那么用寄存器结构也可以达到这种效果,为什么不用寄存器结构呢?那是因为面积!一个DFF是由两个D锁存器组成的,采用D锁存器组成门控时钟单元,可以节省一个锁存器的面积。当大量的门控时钟插入到SOC芯片中时,这个节省的面积就相当可观了。
所以,我们在工艺库中看到的标准门控时钟单元就是锁存结构了:
当然,这里说的是SOC芯片中使用的标准库单元。如果是FPGA或者用RTL实现,个人认为还是用寄存器门控加上setup约束来实现比较稳妥。
RTL中的门控时钟
通常情况下,时钟树由大量的缓冲器和反相器组成,时钟信号为设计中翻转率最高的信号,时钟树的功耗可能高达整个设计功耗40%。
加入门控时钟电路后,由于减少了时钟树的翻转,节省了翻转功耗。同时,由于减少了寄存器时钟引脚的翻转行为,寄存器的内部功耗也减少了。采用门控时钟,可以非常有效地降低设计的功耗,一般情况下能够节省20%~60%的功耗。
那么RTL中怎么才能实现门控时钟呢?答案是不用实现。现在的综合工具比如DC会自动插入门控时钟。如下图所示:
这里有两点需要注意:
1. 插入门控时钟单元后,上面电路中的MUX就不需要了,如果数据D是多bit的(一般都是如此),插入CG后的面积可能反而会减少;
2. 如果D是单bit信号,节省的功耗就比较少,但是如果D是一个32bit的信号,那么插入CG后节省的功耗就比较多了。
这里的决定因素就是D的位宽了,如果D的位宽很小,那么可能插入的CG面积比原来的MUX大很多,而且节省的功耗又很少,这样得不偿失。只有D位宽超过了一定的bit数后,插入CG的收益就比较大。
那么这个临界值是多少呢?不同的工艺可能不一样,但是DC给的默认值是3.
也就是说,如果D的位宽超过了3bit,那么DC就会默认插入CG,这样综合考虑就会有收益。
门控时钟的RTL描述
虽然现在综合工具可以自动插入门控时钟,但是如果编码风格不好,也不能达到自动插入CG的目的。比较下面两种RTL写法:
左边的RTL代码能够成功的综合成自动插入CG的电路;
右边的RTL不能综合成插入CG的电路;
右边电路在d_valid为低时,d_out也会一直变化,其实没有真正的数据有效的指示信号(d_out设置为0,也认为这个输出在变化),所以综合不出来插入CG的电路。
需要注意的是,有的前端设计人员,为了仿真的时候看的比较清楚,很容易会写成右边的代码,这样不仅不能在综合的时候自动插入CG来减少功耗;而且增加了d_out的翻转率,进一步增加了功耗。
在不用的时候把数据设成0并不能减少功耗,保持数据不变化才能减少toggle,降低功耗!
如果在第一种编码风格中再加一句else信号保持不变,DC仍然能自动插入clock gating。
时钟切换
随着各种应用场景的限制,芯片在运行时往往需要在不同的应用下切换不同的时钟源,例如低功耗和高性能模式就分别需要低频率和高频率的时钟。两个时钟源有可能是同源且同步的,也有可能是不相关的。
直接时钟切换
可以通过选择逻辑直接对时钟进行切换。
assign clk_out = sel_clk ? clk1 : clk2;
这种方法的问题在于会产生毛刺,如果在clk1高电平时进行时钟切换就有可能导致毛刺的出现。
clk1为高电平,在切换时clk2为低电平,切换时输出时钟就会出现一段低电平,之后是clk2的高电平,输出出现了毛刺。
glitch free时钟切换电路
为了避免毛刺可以通过glitch free时钟切换电路进行时钟的切换。
在时钟选择信号和时钟输出之间插入由clk1和clk0下降沿驱动的寄存器。一开始两个寄存器初始化输出为0,如果select为1,Q1在clk1下降沿到来时输出为1,Q1N输出为0,在clk0下降沿到来时,Q0输出为0,Q0N输出为1,这时相当于选择clk1输出。当select由1变为0时,在这一时刻,Q1N输出仍为0,Q0N输出仍为1,DFF1输入D1为0,DFF0输入D0为0,当clk1下降沿到来时,DFF1输出Q1为0,Q1N为1,这时DFF0输入D0为1,当clk0下降沿到来时,DFF0输出Q0为1,Q0N为0,这时完成了对clk0的选择输出。
触发器下降沿触发的原因在于如果是在高电平时做切换,很容易造成高电平被切掉一段变成一个毛刺,下降沿触发可以保证时钟在低电平时进行切换,这时保证了时钟输出没有毛刺。
两个寄存器一开始要初始化为0,如果一开始初始化为1,这时相当于两个时钟都被选通,如果复位的时间比较长,则一直输出两个时钟相或的结果,这肯定不是我们想看到的。一开始初始化为0,保证这时输出时钟为0。
module test_clk_switch(
input clk0,
input clk1,
input rst_n,
input select,
output clk_out
);
reg clk1_out;
reg clk0_out;
always @(negedge clk0 or negedge rst_n)
if(!rst_n)
clk0_out <= 1'b0;
else
clk0_out <= ~select & ~clk1_out;
always @(negedge clk1 or negedge rst_n)
if(!rst_n)
clk1_out <= 1'b0;
else
clk1_out <= select & ~clk0_out;
assign clk_out = (clk1_out & clk1) | (clk0_out & clk0);
endmodule
testbench
module test_clk_switch_sim(
);
reg clk0;
reg clk1;
reg select;
reg rst_n;
wire clk_out;
initial
begin
#0 clk0 = 1'b0;
forever #5 clk0 = ~clk0;
end
initial
begin
#0 clk1 = 1'b0;
forever #18 clk1 = ~clk1;
end
initial
begin
#0 select = 1'b0;
#42 select = 1'b1;
#200 select = 1'b0;
end
initial
begin
#0 rst_n = 1'b1;
#1 rst_n = 1'b0;
#5 rst_n = 1'b1;
end
test_clk_switch test_1(
.clk0(clk0),
.clk1(clk1),
.rst_n(rst_n),
.select(select),
.clk_out(clk_out)
);
endmodule
仿真结果
可以看到
当select变化时,先经过原时钟的下降沿再经过要切换时钟的下降沿之后输出要切换的时钟。因为中间经过了原时钟的下降沿后保证了原时钟的一个脉冲,再经过要切换时钟的下降沿保证了两个时钟的高电平不会有重叠的部分,保证了不会产生毛刺。
这个电路概念上是对的,但有明显的瑕疵,如果 Select 相对于 clk1, clk0是完全异步的,电路中的两个触发器在Select变化时会有亚稳态的可能性。解决这个问题的方法很简单,把图中的单个寄存器换成两级寄存器组成的同步器就可以了。
对于每个时钟,两个寄存器,第一个寄存器时钟的上升沿触发,第二个寄存器时钟的下降沿触发。
clk1和clk0的第一个寄存器上升沿有效,用于削弱亚稳态出现的可能性,第二个寄存器下降沿有效用于时钟的切换。
module test_clk_switch_1(
input clk0,
input clk1,
input rst_n,
input select,
output clk_out
);
reg clk1_out_1,clk1_out_2;
reg clk0_out_1,clk0_out_2;
always @(posedge clk1 or negedge rst_n)
if(!rst_n)
clk1_out_1 <= 1'b0;
else
clk1_out_1 <= ~select & ~clk0_out_2;
always @(negedge clk1 or negedge rst_n)
if(!rst_n)
clk1_out_2 <= 1'b0;
else
clk1_out_2 <= clk1_out_1;
always @(posedge clk0 or negedge rst_n)
if(!rst_n)
clk0_out_1 <= 1'b0;
else
clk0_out_1 <= select & ~clk1_out_2;
always @(negedge clk0 or negedge rst_n)
if(!rst_n)
clk0_out_2 <= 1'b0;
else
clk0_out_2 <= clk0_out_1;
assign clk_out = (clk1 & clk1_out_2) | (clk0 & clk0_out_2);
endmodule
testbench
module test_clk_switch_sim(
);
reg clk0;
reg clk1;
reg select;
reg rst_n;
wire clk_out;
initial
begin
#0 clk0 = 1'b0;
forever #5 clk0 = ~clk0;
end
initial
begin
#0 clk1 = 1'b0;
forever #18 clk1 = ~clk1;
end
initial
begin
#0 select = 1'b0;
#42 select = 1'b1;
#200 select = 1'b0;
end
initial
begin
#0 rst_n = 1'b1;
#1 rst_n = 1'b0;
#5 rst_n = 1'b1;
end
test_clk_switch_1 test_1(
.clk0(clk0),
.clk1(clk1),
.rst_n(rst_n),
.select(select),
.clk_out(clk_out)
);
endmodule
仿真结果
可以看到
当select变化时,先经过原时钟的一个上升沿和下降沿再经过要切换时钟的一个上升沿和一个下降沿之后输出要切换的时钟。
如果觉得用一个寄存器来减少亚稳态出现的概率有点不够用,可以在一开始打两拍,即再插入一个上升沿驱动的寄存器。
如上图,对于每个时钟,有三个寄存器,前两个寄存器由上升沿驱动,用来处理亚稳态,第三个寄存器用来时钟切换。
我们前面提到了门控时钟(ICG),那能不能将最后时钟输出前的与门替换为ICG呢?当然可以
但是换成ICG后如果时钟的初始值时高电平,电路可能会出问题,因为ICG内部存在一个低电平时钟触发的锁存器,当一开始时钟的电平为高电平时,锁存器这时没有初始数据输出,所以输出信号为高阻态x。为了避免上述ICG未初始化引起的问题,使用ICG搭时钟切换电路时,系统设计中要考虑加一个时钟初始为0的要求。
上面几种电路都只能在时钟正常跑起来的情况下完成切换。如果当前选中的时钟停了,选择信号无法通过由当前时钟驱动的两级DFF,也就无法实现切换了。
时钟停止属于系统级错误,需要在系统设计找到解决方案,这里就不赘述了。
参考
(3条消息) 18.门控时钟_薄荷茶哈哈哈的博客-CSDN博客_门控时钟
(3条消息) Verilog设计(一):时钟门控_风中少年01的博客-CSDN博客_门控时钟
芯片设计进阶之路——门控时钟 - 知乎 (zhihu.com)
(3条消息) 数字SOC设计之低功耗设计入门(五)——RTL级低功耗设计(续)_简单同学的博客-CSDN博客
ICG
为什么ICG Cell中使用锁存器(Latch),而不使用触发器(Flip Flop)? - 知乎 (zhihu.com)
(4条消息) Clock Gating cell 与 Integrated Clock Gating cell(ICG)_zhenhuagege的博客-CSDN博客_cell clock gating
ICG的结构是怎么样的,工作原理是什么啊?和使用MUX相比有什么优点?[已解决] - 后端讨论区 - EETOP 创芯网论坛 (原名:电子顶级开发网) -
ICG与low power设计 - 知乎 (zhihu.com)
IMPL20. ICG应用浅析 - 知乎 (zhihu.com)
数字IC设计面试--阿里平头哥--门控时钟(ICG) (aisoutu.com)
(4条消息) Integrated Clock Gating (ICG)_kevindas的博客-CSDN博客_芯片icg
时钟切换
Glitch Free时钟切换技术 - 知乎 (zhihu.com)
(4条消息) 用Verilog实现时钟切换电路_wangn1633的博客-CSDN博客_verilog时钟切换
(4条消息) Verilog学习心得之一-----时钟无缝切换_poirot12的博客-CSDN博客_verilog时钟切换
时钟切换(1) - jake的日志 - EETOP 创芯网论坛 (原名:电子顶级开发网) -
时钟切换 (2) - jake的日志 - EETOP 创芯网论坛 (原名:电子顶级开发网) -
FPGA中多时钟切换(无毛刺):通过代码的方式 - 小翁同学 - 博客园 (cnblogs.com)
(4条消息) 时钟分频与时钟切换_Zokion的博客-CSDN博客_soc设计
5.4 Verilog 时钟切换 | 菜鸟教程 (runoob.com)