Verilog基本电路设计之二(时钟无缝切换)
普通切换,就是不关心切出的时钟是否存在毛刺,这种方式电路成本小。如果时钟切换时,使用此时钟的模块电路处于非工作状态,或者模块内电路被全局复位信号reset住的,即使切出毛刺也不会导致DFF误触发,这样的模块可以选择用此种切换方式。
写法很简单 assign clk_o = sel_clkb ? clkb : clka ,当sel_clkb为1时选择clkb,否则选择clka。不过在实际设计中,建议直接调用库里的MUX单元并set_dont_touch,不要采用这里的assign写法,因为这种写法最后综合得到的可能不是MUX而是复杂组合逻辑,给前后端流程的时钟约束和分析带来不便。
无缝切换,就是切换时无毛刺时钟平稳过渡。在时钟切换中,只要出现比clka或者clkb频率更高的窄脉冲,不论是窄的高电平还是窄的低电平,都叫时钟毛刺。工作在切换后时钟clk_o下的电路模块,综合约束是在max{clka,clkb}频率下的,也就是说设计最后signoff的时候,只保证电路可以稳定工作的最高频率是max{clka,clkb},如果切换中出现更高频的时钟毛刺,电路可能出现无法预知的结果而出错。无缝切换,一般用在处于工作状态的模块需要调频或者切换时钟源,比如内部系统总线,CPU等。你刚用手机打完游戏后马上关屏听音乐,这两种场景中,CPU在满足性能前提下为了控制功耗,其工作频率会动态地从很高调至较低,此时就可能是在CPU一直处于工作状态下,通过无缝切换时钟源头实现的。
在无缝切换电路中,切换信号sel_clkb可以是任意时钟域下的信号,包括但不限于clka或者clkb域,但是sel_clkb必须是一个DFF输出信号;clka与clkb的频率大小相位关系可以任意。无缝切换需要解决两个问题,一是异步切换信号的跨时钟域同步问题,这里需要使用《Verilog基本电路设计之一》里的同步电路原理消除亚稳态;二是同步好了的切换信号与时钟信号如何做逻辑,才能实现无毛刺。
下面写出无缝切换电路的主体部分,忽略了内部信号的定义声明等。
module clk_switch (
rst_n , //
clka , //
clkb , //
sel_clkb , //
clk_o //
);
//assign clka_n = ~clka;
//assign clkb_n = ~clkb;
// part1
//always @ (posedge clka_n or negedge rst_n)
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n) begin
sel_clka_d0 <= 1'b0;
sel_clka_d1 <= 1'b0;
end
else begin
sel_clka_d0 <= (~sel_clkb) & (~sel_clkb_dly3) ;
sel_clka_d1 <= sel_clka_d0 ;
end
end
// part2
//always @ (posedge clka_n or negedge rst_n)
always @ (posedge clka or negedge rst_n)
begin
if (!rst_n) begin
sel_clka_dly1 <= 1'b0;
sel_clka_dly2 <= 1'b0;
sel_clka_dly3 <= 1'b0;
end
else begin
sel_clka_dly1 <= sel_clka_d1;
sel_clka_dly2 <= sel_clka_dly1 ;
sel_clka_dly3 <= sel_clka_dly2 ;
end
end
// part3
//always @ (posedge clkb_n or negedge rst_n)
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n) begin
sel_clkb_d0 <= 1'b0;
sel_clkb_d1 <= 1'b0;
end
else begin
sel_clkb_d0 <= sel_clkb & (~sel_clka_dly3) ;
sel_clkb_d1 <= sel_clkb_d0 ;
end
end
// part4
//always @ (posedge clkb_n or negedge rst_n)
always @ (posedge clkb or negedge rst_n)
begin
if (!rst_n) begin
sel_clkb_dly1 <= 1'b0;
sel_clkb_dly2 <= 1'b0;
sel_clkb_dly3 <= 1'b0;
end
else begin
sel_clkb_dly1 <= sel_clkb_d1 ;
sel_clkb_dly2 <= sel_clkb_dly1 ;
sel_clkb_dly3 <= sel_clkb_dly2 ;
end
end
// part5
clk_gate_xxx clk_gate_a ( .CP(clka), .EN(sel_clka_dly1), .Q(clka_g) .TE(1'b0) );
clk_gate_xxx clk_gate_b ( .CP(clkb), .EN(sel_clkb_dly1), .Q(clkb_g) .TE(1'b0) );
//assign clka_g = clka & sel_clka_dly1 ;
//assign clkb_g = clkb & sel_clkb_dly1 ;
assign clk_o = clka_g | clkb_g ;
endmodule
上面是我认为比较合理的无缝切换电路,其他切换方式跟这个会有些许出入,但基本大同小异原理是一样的。有几点说明:
1、抛开注释掉的电路不看,由于part5部分直接调用库里的clock gating cell,使得整个切换电路全部只需要用到时钟上升沿,无需额外定义反向时钟,精简了DC综合的时钟约束;直接调用gating cell的 另一个好处是,前后端工具会自动检查gating cell的CP信号与EN信号的setup/hold时间,使得gating后的Q时钟输出无毛刺尖峰。TE端可以根据实际需要接上scan测试模式信号。如果使用part5部分的gating cell实现,前面的part1,2,3,4全部替换成注释掉的反相时钟也是没有问题。
2、part2和part4部分,具体需要多少级DFF,甚至完全不要也是可以的,这就回到了《Verilog基本电路设计之一》里讨论的到底多少级DFF消除亚稳态才算合理的问题。时钟频率很低可能无所谓,如果时钟频率达到GHz,这部分建议至少保留三级DFF,因为三级DFF延时也仅仅只有3ns的时间裕度。没必要为了省这么几个DFF降低电路可靠性,在复杂IP以及大型SOC系统中,你会发现多几十个DFF,面积上可以忽略,系统可靠性和稳定性才是首要的。
3、如果part5部分希望使用注释掉的两行“与”逻辑实现时钟gating,此时part1与part3使用正相或者反相时钟都可以,但是必须把part2和part4部分改为注释掉的反相时钟实现,目的是初步从RTL设计上避免“与”逻辑的毛刺,同时还需要后端配合,因为很多后端工具对时钟“与”逻辑的clock gating check未必会检查。用clk下降沿拍出的en信号,再跟clk做与逻辑得到的门控时钟,在RTL仿真阶段看到的一定不会有毛刺,但是布线完成后,如果clk相对en后移,那与逻辑得到的门控时钟就有毛刺了。这就是用与逻辑做门控的缺点,由于后端工具可能不会去检查这个与门的时序关系而导致出错。但直接调用库里的gating cell,工具天然就会去检查这个时序,免去人工确认的后顾之忧。
最后,请大家仔细看看sel_clka_d0 <= (~sel_clkb) & (~sel_clkb_dly3) 和sel_clkb_d0 <= sel_clkb & (~sel_clka_dly3) 这两处逻辑,按理说,sel_clkb跟sel_clka_dly3以及sel_clkb_dly3之间相互都是异步的,而按照异步信号同步处理原则,两个不同时钟域下的信号是不允许直接做组合逻辑的,为什么这里可以这样使用?
写得很好!
对于最后那个问题,我的理解是:因为后级是对这个与门的输出进行同步,所以是允许的。
而且这里必须进行这样的与操作,保证在一个时钟输出关闭后才开启另一个时钟输出,否则在时钟频率相差较大时,两路时钟可能都输出了。
good
关于最后的问题:因为(~sel_clkb_dly3)或者 (~sel_clka_dly3)最后都是会保持为1的,也就是说他们的0/1变换仅发生时钟切换点上,而这个切换点又经过目标时钟的同步(以及处理亚稳态的过程),所以最后的选通信号不会有问题
写的不错
学习了。
问一个问题,part1和part3中的组合逻辑 (~sel_clkb) & (~sel_clkb_dly3)和sel_clkb & (~sel_clka_dly3) 已经做同步处理了,为啥还有经过part2和part4。
这个问题请看后面的第2点注解,连同最后面抛出的那个问题一起思考,理解清楚了,你就可以根据不同的时钟应用条件对于part2以及part4部分游刃有余地做灵活增减处理。
就是说是也是可以完全不要的,理由就是我说的已经在part1和part3做过同步了。
嗯,你从时钟切换中需要先关clk1再开clk2角度,回答了要做这个与逻辑的原因。我问的侧重点在于,为什么这里能够把异步时钟域的组合逻辑信号后再往另一个时钟域去同步,在上一篇帖子里特别指出过,被两级同步过去的异步信号输入,必须是另一个时钟域的DFF信号,不能是组合逻辑信号。对于c=a&b的逻辑,如果信号a来自clk1域,信号b来自clk2域,信号c又将在clk3域被使用,从常规设计规则里是不允许的,原因在于组合逻辑会产生毛刺,更何况这里是两个异步时钟域下的信号在做与逻辑,而毛刺信号却可能被另一个异步时钟clk3抓到。
你楼下那位兄弟基本回答了同步这个组合逻辑信号过去不会存在问题的原因,重点就是在于设计中保证a和b信号不会同时变化,得到的c不会有毛刺,我后面再详细回复。
好东西啊 好东西啊
正解!多谢你的探讨回复,能考虑到问题这一层面的,说明你也是深究过问题本质的。
对此问题,那我就来做个总结吧。
回到part1和part3中的两处组合逻辑sel_clka_d0 <= (~sel_clkb) & (~sel_clkb_dly3) 和sel_clkb_d0 <= sel_clkb & (~sel_clka_dly3) ,等价抽象为c=a&b的逻辑,其中a来自clk1域,b来自clk2域,信号c将会在clk3域被同步。当a,b两个信号同时变化时,得到的c会存在毛刺,毛刺可能被clk3抓到而导致错误,避免错误的唯一办法:保证a和b不要同时跳变!
看我们的设计中,sel_clka_dly3和sel_clkb_dly3通过part2和part4电路delay,实际上就是错开了和sel_clkb的跳变沿,part2和part4部分用的DFF越多,它们与sel_clkb的变化沿相隔的越远而越安全,大家可以仿真模拟看看波形。也许有人说去掉part2和part4部分,通过前面的part1和part3两级同步电路,已经可以让它们之间的跳变沿相差两个cycle了。如果你考虑1GHz高频时钟切换就不会这么想了,两个cycle的差异仅仅是2个ns,而且再去做c=a&b这个与逻辑输入端时,由于a与b的异步时序关系以及信号传递差异,此时它们之间的跳变沿相差是无法保证还有两个ns的。因此,对于高频时钟切换,只能通过尽量增加part2和part4的级数,提高绝对跳变沿的延时,从而留给c=a&b这个与逻辑更多的时间裕量。
所以,part2和part4部分的作用:1、如果part5用与逻辑做gating,保留part2和part4的反相沿设计初步避免时钟gating毛刺;2、高频时钟切换中,增加级数降低part1和part3同步的亚稳态概率;3,高频时钟切换中,增加级数降低part1和part3中“与”逻辑毛刺风险。清楚了原因,就可以根据自己的实际情况对part2和part4做取舍。
我想把glitch free clock switch简单化下,如果给两bit的选择信号,2'b01表示选clka, 2'b10表示选clkb, 2‘b00和2'b11表示全部gating, 是不是对clockswitch 的要求非常简单,就是2'b01变 2'b10的时候需要保证一定gap来gating 两个clock, 反之亦然。
那问题来了,如何由1bit的select信号转换出上面写的两bits select 信号, 并保证一定的gap.
always@(posedge clka or negedge rstn) begin
if(~rstn)
clka_d1 <= 1'b1;
clka_d2 <= 1'b1;
end else begin
clka_d1 <= clka_sel;
clka_d2 <= clka_d1;
end
end
always@(posedge clkb or negedge rstn) begin
if(~rstn)
clkb_d1 <= 1'b0;
clkb_d2 <= 1'b0;
end else begin
clkb_d1 <= clkb_sel;
clkb_d2 <= clkb_d1;
end
end
wire clka_sel = select & ~clkb_d2 ; //better to use standard cell with don't touch
wire clkb_sel = ~select & ~clka_d2 ; //better to use standard cell with don't touch
wire clka_out = clka_d2 & clka; //better to use standard cell with don't touch
wire clkb_out = clkb_d2 & clkb; //better to use standard cell with don't touch
wire clk_out = clka_out | clkb_out;
clka_sel和clkb_sel就是两bit的信号,
你的意思是说,你要求用一个两bit的select【1:0】信号,实现2'b01选clka, 2'b10选clkb, 2‘b00和2'b11不输出时钟,是这样吗?灵活变通一下嘛,主体切换电路直接调用我的模块,外围信号再简单处理一下先得到sel_clkb,最后输出时钟做个门控关断即可。
always@(posedge clkx or negedge rst_n)
begin
if (~rst_n)
sel_clkb <= 1'b0;
else
sel_clkb <= (select==2'b10);
end
clk_switch clk_switch (
.rst_n (rst_n ) , //
.clka (clka ), //
.clkb (clkb ), //
.sel_clkb (sel_clkb), //
.clk_o (clk_tmp ) //
);
always@(posedge clkx or negedge rst_n)
begin
if (~rst_n)
clk_en <= 1'b0;
else
clk_en <= (^select);
end
clk_gate_xxx clk_gate_o ( .CP(clk_tmp ), .EN(clk_en ), .Q(clk_o) .TE(1'b0) );
clk_o就是最后你需要的时钟了。select是哪个时钟域的信号,上面的两个always中的clkx就用哪个时钟。如果想使用与门代替上面的clk_gate_xxx,用clk_tmp下降沿把clk_en抓两拍后再跟clk_tmp做与逻辑得到最后的clk_o
不是,我的意思是说, 设计1bit控制的glitch free clock switch, 可以转换角度,设计一个两bit的控制,核心问题转变成如何产生2'b01-N*2'B00-2'b10这样的pattern. 保证没有glitch,只需要select信号和各自的时钟对齐,且10/01之间的gap足够。
你仔细看看我的那段code,也是由单bit select产生sela和selb, 但我的思路和你略有不同。
思考问题的方式差异而已,你没发现你写出的code跟我的其实本质是一样的吗,是我写的电路简化掉part2和part4部分后的版本。而对于part2和part4能否随意简化,前面已经有过讨论了。另外对于你的:wire clka_out = clka_d2 & clka; //better to use standard cell with don't touch
wire clkb_out = clkb_d2 & clkb; //better to use standard cell with don't touch
这里clka_d2和clkb_d2必须要用下降沿产生,否则有毛刺,其中原因帖子里也分析过了。
多谢小编肯定,另外我有一个问题就是小编的时钟切换电路中间有一段时间是没有clk输出的,如果后面delay选择信号越多拍,那么这个gap会越大,是吗?
果然,再仔细看下,实质是一样的, 保证一定的gap,其实也是通过增加pile来实现。
不过你说的会有毛刺,这个不是必然的,为什么建议采用standard cell在手动搭,就是考虑到这个问题,当clka和sela有固定的相位关系是没有毛刺的。
是的,在切换过程中,会有一段时间没有时钟输出。增加的DFF级数越多,切换过程花更长的时间来完成,也就是带来gap段更大,这是增加DFF级数的弊端。无缝切换,你是希望切换过程更安全,还是追求切换快速完成,这就是矛盾。所以大家才需要根据自己的实际应用条件,对part2和part4部分进行灵活增减处理。
小编分析的很透彻,尤其是最后一个问题的解释很精彩,之前一直没考虑到,学习了!
突然之间觉得这里的信息量好大,学到了!
找工作刚好用得着
多谢小编分享,受益匪浅。
好好看看!
花了波形图,才看懂了大部分,真心不错!
期待小编的其他分享,我已经做成WORD,留下来好好学习了。谢谢,祝小编您健康幸福!
复位完后,若选择信号一直为1或0,同步完的dly3信号是不是每经过延时时间就在0/1间变一次,为0的这段时间就没时钟信号输出?
多谢分享~~受益匪浅~~~
great ! This is a classic circuit, a deep analysis
学习了,感谢小编。
还望继续多发这种贴子啊,我会追踪学习的。
