参考这篇文章:数字电路时钟无毛刺切换
因为直接用组合逻辑判断去切换时钟,会产生毛刺,如下图所示。
产生该毛刺的原因: sel没有在时钟跳边沿改变。因为我们一般是用clk上升沿采样数据,所以我们sel选择信号最好是在clk下降沿改变,可以让切换的时候,不会错过上升沿。
我理解的整体思路: 将en信号先经过打3拍(异步时钟要打三拍),然后经过一个ICG(integrate clock Gating)
ICG框图
作用:让sel同步于时钟并且在下降沿改变。
设计代码
module clock_switch(
input s_rst_n ,
input s_clk_1 ,
input s_clk_2 ,
input s_clk_3 ,
input [1:0] i_clk_sel ,//00: clk1, 01: clk2, 10/11: clk3
output o_clk
);
//========================================================================\
// =========== Define Parameter and Internal signals ===========
//========================================================================/
wire clk1_sel ;
reg clk1_sel_syn1 ;
reg clk1_sel_syn2 ;
reg clk1_sel_syn3 ;
wire clk2_sel ;
reg clk2_sel_syn1 ;
reg clk2_sel_syn2 ;
reg clk2_sel_syn3 ;
wire clk3_sel ;
reg clk3_sel_syn1 ;
reg clk3_sel_syn2 ;
reg clk3_sel_syn3 ;
reg clk1_sel_real ;
reg clk2_sel_real ;
reg clk3_sel_real ;
assign clk1_sel = (i_clk_sel == 2'b00)? 1'b1: 1'b0;
assign clk2_sel = (i_clk_sel == 2'b01)? 1'b1: 1'b0;
assign clk3_sel = (i_clk_sel == 2'b10 || i_clk_sel == 2'b11)? 1'b1: 1'b0;
always @(posedge s_clk_1 or negedge s_rst_n) begin
if(s_rst_n == 1'b0) begin
clk1_sel_syn1 <= 'd0 ;
clk1_sel_syn2 <= 'd0 ;
clk1_sel_syn3 <= 'd0 ;
end
else begin
clk1_sel_syn1 <= clk1_sel & (~clk2_sel_syn3) & (~clk3_sel_syn3) ;
clk1_sel_syn2 <= clk1_sel_syn1 ;
clk1_sel_syn3 <= clk1_sel_syn2 ;
end
end
always @(posedge s_clk_2 or negedge s_rst_n) begin
if(s_rst_n == 1'b0) begin
clk2_sel_syn1 <= 'd0 ;
clk2_sel_syn2 <= 'd0 ;
clk2_sel_syn3 <= 'd0 ;
end
else begin
clk2_sel_syn1 <= clk2_sel & (~clk1_sel_syn3) & (~clk3_sel_syn3) ;
clk2_sel_syn2 <= clk2_sel_syn1 ;
clk2_sel_syn3 <= clk2_sel_syn2 ;
end
end
always @(posedge s_clk_3 or negedge s_rst_n) begin
if(s_rst_n == 1'b0) begin
clk3_sel_syn1 <= 'd0 ;
clk3_sel_syn2 <= 'd0 ;
clk3_sel_syn3 <= 'd0 ;
end
else begin
clk3_sel_syn1 <= clk3_sel & (~clk1_sel_syn3) & (~clk2_sel_syn3) ;
clk3_sel_syn2 <= clk3_sel_syn1 ;
clk3_sel_syn3 <= clk3_sel_syn2 ;
end
end
always @(negedge s_clk_1 or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
clk1_sel_real <= 'd0;
else
clk1_sel_real <= clk1_sel_syn3;
// clk1_sel_real <= clk1_sel_syn2;
end
always @(negedge s_clk_2 or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
clk2_sel_real <= 'd0;
else
clk2_sel_real <= clk2_sel_syn3;
//clk2_sel_real <= clk2_sel_syn2;
end
always @(negedge s_clk_3 or negedge s_rst_n) begin
if(s_rst_n == 1'b0)
clk3_sel_real <= 'd0;
else
clk3_sel_real <= clk3_sel_syn3;
//clk3_sel_real <= clk3_sel_syn2;
end
//验证了,只打两拍确实有问题
assign o_clk = (clk1_sel_real & s_clk_1) | (clk2_sel_real & s_clk_2) | (clk3_sel_real & s_clk_3);
endmodule
testbench 代码
`timescale 1ns/1ns
module clock_switch_tb();
reg s_rst_n ;
reg s_clk_1 ;
reg s_clk_2 ;
reg s_clk_3 ;
reg [1:0] i_clk_sel ;
wire o_clk ;
clock_switch clock_switch_inst(
.s_rst_n (s_rst_n ),
.s_clk_1 (s_clk_1 ),
.s_clk_2 (s_clk_2 ),
.s_clk_3 (s_clk_3 ),
.i_clk_sel (i_clk_sel ),
.o_clk (o_clk )
);
always #5 s_clk_1 = ~s_clk_1;
always #10 s_clk_2 = ~s_clk_2;
always #20 s_clk_3 = ~s_clk_3;
initial begin
s_rst_n = 0 ;
s_clk_1 = 0 ;
s_clk_2 = 0 ;
s_clk_3 = 0 ;
i_clk_sel = 0 ;
#20;
s_rst_n = 1;
#200;
i_clk_sel = 1;
#200;
i_clk_sel = 2;
#200;
i_clk_sel = 3;
#200;
$finish;
end
//generate fsdb file
initial begin
$fsdbDumpfile("clock_switch_tb.fsdb");
$fsdbDumpvars(0,clock_switch_tb);
end
endmodule
仿真波形
一般无毛刺切换,切换始终的时候,都会需要几个clock的缓冲。