时钟切换逻辑避免产生glitch的原理
先关闭当前时钟,再打开目标时钟。而不管关闭还是使能,都必须保证当前时钟或目标时钟的使能信号的跳变都分别在时钟为低电平期间进行的,防止产生时钟glitch。
这样在时钟切换时就必然要经历4个阶段:
1)选择信号改变、2)在clk1为低时停掉clk1的选择 、3)在clk2为低时打开clk2的选择端、3)正常工作,完成切换。
无缝切换需要解决两个问题:
一是异步切换信号的跨时钟域同步问题,这里需要使用《Verilog基本电路设计之一》里的同步电路原理消除亚稳态;
二是同步好了的切换信号与时钟信号如何做逻辑,才能实现无毛刺。
第2步恐怕很多人未必能考虑到,做ASIC工程师就是要不断反复考虑自己的设计,不到大批量用上1、2年,你恐怕都要时刻的反省。这很重要,也很累。
module clk_switch (
rst_n,
clka,
clkb,
sel_clkb,
clk_o
);
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_dly3), .Q(clka_g) .TE(1'b0) );
clk_gate_xxx clk_gate_b ( .CP(clkb), .EN(sel_clkb_dly3), .Q(clkb_g) .TE(1'b0) );
//assign clka_g = clka & sel_clka_dly3 ;
//assign clkb_g = clkb & sel_clkb_dly3 ;
assign clk_o = clka_g | clkb_g ;
endmodule</span>
上面是我认为比较合理的无缝切换电路,其他切换方式跟这个会有些许出入,但基本大同小异原理是一样的。
有几点说明:
抛开注释掉的电路不看,由于part5部分直接调用库里的clock gating cell,使得整个切换电路全部只需要用到时钟上升沿,无需额外定义反向时钟,精简了DC综合的时钟约束;直接调用gating cell的 另一个好处是,前后端工具会自动检查gating cell的CP信号与EN信号的setup/hold时间,使得gating后的Q时钟输出无毛刺尖峰。TE端可以根据实际需要接上scan测试模式信号。如果使用part5部分的gating cell实现,前面的part1,2,3,4全部替换成注释掉的反相时钟也是没有问题。
part2和part4部分,具体需要多少级DFF,甚至完全不要也是可以的,这就回到了《Verilog基本电路设计之一》里讨论的到底多少级DFF消除亚稳态才算合理的问题。时钟频率很低可能无所谓,如果时钟频率达到GHz,这部分建议至少保留三级DFF,因为三级DFF延时也仅仅只有3ns的时间裕度。没必要为了省这么几个DFF降低电路可靠性,在复杂IP以及大型SOC系统中,你会发现多几十个DFF,面积上可以忽略,系统可靠性和稳定性才是首要的。
如果part5部分希望使用注释掉的两行“与”逻辑实现时钟gating,此时part1与part3使用正相或者反相时钟都可以,但是必须把part2和part4部分改为注释掉的反相时钟实现,目的是初步从RTL设计上避免“与”逻辑的毛刺,同时还需要后端配合,因为很多后端工具对时钟“与”逻辑的clock gating check未必会检查。用clk下降沿拍出的en信号,再跟clk做与逻辑得到的门控时钟,在RTL仿真阶段看到的一定不会有毛刺,但是布线完成后,如果clk相对en后移,那与逻辑得到的门控时钟就有毛刺了。这就是用与逻辑做门控的缺点,由于后端工具可能不会去检查这个与门的时序关系而导致出错。但直接调用库里的gating cell,工具天然就会去检查这个时序,免去人工确认的后顾之忧。
!
参考2
下面是收集的几种无毛刺的时钟切换电路。
http://www.cnblogs.com/xinlukk/p/6923061.html
1. openMSP430 ipcore中的时钟切换电路 (来源:OpenCore)
`timescale 1ns/10ps
module clock_mux (
// OUTPUTs
//=========
output clk_out, // Clock output
// INPUTs
//=========
input clk_in0, // Clock input 0
input clk_in1, // Clock input 1
input reset, // Reset
input select_in // Clock selection
);
//----------------------------------------
// Regs declare
//----------------------------------------
reg dff0a,dff0b;
reg dff1a,dff1b;
wire clk_in0_inv = ~clk_in0;
wire clk_in1_inv = ~clk_in1;
//----------------------------------------
// clk_in0 path
//----------------------------------------
// negedge of clk_in0
always @(posedge clk_in0_inv or posedge reset)
if(reset) dff0a <= 1'b1;
else dff0a <= !select_in & !dff1b;
always @(posedge clk_in0 or posedge reset)
if(reset) dff0b <= 1'b1;
else dff0b <= dff0a;
wire clk_in0_gate = ~(~clk_in0 & dff0b);
//----------------------------------------
// clk_in1 path
//----------------------------------------
// negedge of clk_in1
always @(posedge clk_in1_inv or posedge reset)
if(reset) dff1a <= 1'b0;
else dff1a <= select_in & !dff0b;
always @(posedge clk_in1 or posedge reset)
if(reset) dff1b <= 1'b0;
else dff1b <= dff1a;
wire clk_in1_gate = ~(~clk_in1 & dff1b);
//-----------------------
// clock mux out
//-----------------------
assign clk_out = clk_in0_gate & clk_in1_gate;
endmodule
- 《verilog编程艺术》中的时钟切换电路
`timescale 1ns/1ns
module clk_switch
(
input clk_a,
input clk_b,
input select,
output out_clk
);
reg q1,q2,q3,q4;
wire or_one,or_two,or_three,or_four;
always @(posedge clk_a)
if(clk_a==1'b1) begin
q1 <= q4;
q3 <= or_one;
end
always @(posedge clk_b)
if(clk_b==1'b1) begin
q2 <= q3;
q4 <= or_two;
end
assign or_one = (!q1) | (select == 1'b1);
assign or_two = (!q2) | (select == 1'b0);
assign or_three = (q3) | (clk_a);
assign or_four = (q4) | (clk_b);
assign out_clk = or_three & or_four;
endmodule
module clk_switch_tb;
reg clk_a,clk_b;
reg select;
wire out_clk;
clk_switch clk_switch(clk_a,clk_b,select,out_clk);
initial begin
clk_a = 0;
forever #50 clk_a = !clk_a;
end
initial begin
clk_b = 0;
forever #10 clk_b = !clk_b;
end
initial begin
select = 0;
#1000;
select = 1;
#1000;
select = 0;
#1000;
select = 1;
#1000;
$finish;
end
endmodule