Verilog学习心得之一-----时钟无缝切换

 

2018年05月09日 16:04:36 poirot12 阅读数:1620

本文讨论了时钟切换的两种基本情况以及两种基本电路结构,讨论了一些问题:

下图是一个时钟选择的简单实现以及时序图,使用AND-OR多路复用逻辑,其中SELECT信号为时钟选择信号,如图中所示,直接切换会产生毛刺(glitch)

时钟切换分为两种情况:(1)CLK0与CLK1为相关时钟源,即CLK0与CLK1成整数倍关系;(2)CLK0与CLK1之间没有关系;

(1)CLK0与CLK1为相关时钟源

(2)CLK0与CLK1为无关时钟源

时钟切换源代码:

`timescale 1ns/1ns
module clk_syn_tst (
                rst_n,
                clka, 
                clkb, 
                sel_clkb, 
                clk_o
                   );
input rst_n;
input clka;
input clkb;
input sel_clkb;

output clk_o;

reg    sel_clka_d0;
reg    sel_clka_d1;
reg    sel_clka_dly1;
reg    sel_clka_dly2 ;
reg    sel_clka_dly3;
reg    sel_clkb_d0;
reg    sel_clkb_d1;
reg    sel_clkb_dly1;
reg    sel_clkb_dly2;
reg    sel_clkb_dly3;

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 @ (negedge 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'b1;
   sel_clkb_d1 <= 1'b1;
   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 @ (negedge clkb or negedge rst_n)
begin
   if (!rst_n) 
   begin
      sel_clkb_dly1 <= 1'b1;
      sel_clkb_dly2 <= 1'b1;
      sel_clkb_dly3 <= 1'b1;
   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
时钟切换测试代码:

`timescale 1ns/1ns
module clk_syn_tb;

parameter DLY=1;

reg rst_n;
reg clka;
reg clkb;
reg sel_clkb;

wire clk_o;
clk_syn_tst test1(
                       //input
                       .rst_n           (rst_n), //system reset
                       .clka            (clka), //clock A
                       .clkb            (clkb), //clock B
                       .sel_clkb        (sel_clkb),//pulse input from clka
                       // output 
                       .clk_o           (clk_o)//pulse output in clkb
                  );
initial
begin
rst_n=0;
clka=0;
clkb=0;
sel_clkb=0;
#20 rst_n =1;
#500 sel_clkb=1;
#500 sel_clkb=0;
end

always #5 clka<=~clka;

always #20 clkb<=~clkb;

//always @(posedge clka or negedge rst_n)
//begin
//if(rst_n==1'b0)
//puls_a_in<=0;
//else
//puls_a_in<=# DLY $random %2;
//end

endmodule

本文代码部分中clka即为CLK0,clkb即为CLK1,sel_clkb即为SELECT,时钟切换的结构有些不同,

   begin
   sel_clkb_d0 <= sel_clkb & (~sel_clka_dly3) ;
   sel_clkb_d1 <= sel_clkb_d0 ;
   end

代码相当于将(sel_clkb)&(~sel_clka_dly3)之后,再打两拍在反馈回路上增加两个DFF,结合sel_clkb_d1之后经过了三拍延迟,

begin
sel_clkb_dly1 <= sel_clkb_d1;
sel_clkb_dly2 <= sel_clkb_dly1 ;
sel_clkb_dly3 <= sel_clkb_dly2 ;
end

故由sel_clkb由0变为1时,clk_o在经过5个clkb下降沿之后,才由clka切换至clkb。同时在这里需要考虑两个问题:

(1)为什么part4中,需要在下降沿(negedge)对sel_clkb_d1进行采样,假设采用上升沿(posedge)对sel_clkb_d1进行采样,结果又会怎样:

//always @ (posedge clka_n or negedge rst_n)
always @ (negedge 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

采用下降沿对sel_clkb_d1进行采样:

采用上升沿对sel_clkb_d1进行采样:

圆圈中的毛刺是由于在sel_clkb由0变为1时,需要5个clka时钟输出clk_o才关闭,但是由于是在clka上升沿进行采样,之后与clka进行&操作,&操作在clk上升沿之后,故进行与操作会产生毛刺。

(2)在对寄存器进行赋值时,为什么复位初始值都设置为0,这里面出于什么考虑:

// part2
//always @ (posedge clka_n or negedge rst_n)
always @ (negedge 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

复位初始值设置为0:

此时sel_clka_dly3与sel_clkb_dly3都为0,即在复位时将clk_o锁住,输出为0,在rst_n被拉起后,经过5个clka,clka的使能信号sel_clkb(0)才被采到。

假设与clkb相关的寄存器复位状态都设置为1,与clka相关的寄存器复位状态都设置为0,则时序图为:

此时相当于clk_o由时钟clkb向clka进行了切换,因此在复位的时候进行了依次切换。

至于代码中part5部分,关于clock gating cell 的说明可以参考:

https://blog.csdn.net/github_33678609/article/details/54575442

本文参考EETimes文章,连接https://www.eetimes.com/document.asp?doc_id=1202359;

猜你喜欢

转载自blog.csdn.net/wangyanchao151/article/details/88533950