ic基础|时钟篇02:关于时钟分频器的二三事

大家好,我是数字小熊饼干,一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结,并通过汇总成文章的形式进行输出,相信无论你是在职的还是已经还准备入行,看过之后都会有有一些收获,如果看完后喜欢的话就请关注我吧~谢谢~

本期内容我们围绕时钟分频来展开,在实际项目中,我们在从锁相环(pll)中得到了主时钟后,通常会对其进行分频处理,以得到许多频率更低的时钟。这一方面是由于时钟在芯片中不断翻转,因此会带来许多的动态功耗,另一方面是芯片中不同的模块对时钟的频率的要求不同,因此通过对时钟分频,并以更低的时钟驱动电路,我们可以在保证芯片各个模块正常工作的前提下,降低影响芯片的功耗,并且在不同的工作条件下使用不同的分频比,使得芯片保持稳定的性能。

一、偶数分频

首先,我们来聊聊最为基础的偶数分频,通常来说,我们可以直接使用计数器对时钟进行分频,verilog代码如下所示:

module even_divider
#(
    parameter DIV_WIDTH = 8    
)
(
    input  wire                 clk_src,
    input  wire                 rst_n  ,
    input  wire [DIV_WIDTH-1:0] div_cfg,
    output reg                  clk_div
);


    reg [DIV_WIDTH-1:0] div_cnt;
    wire div_cnt_full; //计数满
    wire div_cnt_half; //计数到一半
    wire div_cnt_zero;
    reg clk_div_int;


    assign div_cnt_full = (div_cnt==div_cfg);
    assign div_cnt_half = (div_cnt=={1'b0,div_cfg[DIV_WIDTH-1:1]});
    assign div_cnt_zero = ~(|div_cfg);


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            div_cnt <= {DIV_WIDTH{1'b0}};        
        end else if (div_cnt_full | div_cnt_zero) begin 
            div_cnt <= {DIV_WIDTH{1'b0}};
        end else begin
            div_cnt <= div_cnt + 1'b1;
        end        
    end


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int <= 1'b1;
        end else if (div_cnt_zero) begin
            clk_div_int <= 1'b1;
        end else if (div_cnt_full | div_cnt_half) begin        
            clk_div_int <= ~clk_div;
        end
    end


    wire clk_sel = div_cnt_zero; //若是不分频,则选择源时钟,否则选择分频后的时钟
    clk_mux2 clk_mux2_inst(
        .in0(clk_div_int),
        .in1(clk_src),
        .s  (clk_sel),
        .q  (clk_div)
    
    );

endmodule

以上是产生50%偶数分频的verilog代码,简单解释一些:

通过div_cfg选择需要的分频比,例如:

当分频比为0时,视为不分频,通过clk_mux2选择源时钟进行输出。

当为1时,则为2分频,3时为4分频,5时为6分频,以此类推。

使用计数器div_cnt进行计数,当计数达到配置值的一半或者全部时,分频时钟clk_div翻转。

2分频时的仿真结果如下所示:
在这里插入图片描述
可见,和我们设计目标一致,当div_cnt为1时,输出2分频时钟。

二、奇数分频

奇数分频相对于偶数分频要稍微复杂一些,如果我们不需要生成50%占空比的时钟,我们可以直接通过计数来产生所需的时钟。

如果要得到具有50%占空比的时钟,我们不能直接使用计数器生成时钟。我们可以基于时钟双边沿特性,首先以期望输出频率生成两个占空比不为50%的时钟(分别由时钟的上升沿和下降沿产生),它们的高低电平只差一个周期,然后再利用或操作或者与操作对生成的两个时钟进行处理,产生具有50%占空比的奇数分频时钟,具体操作如下所示:

module odd_divider
#(
    parameter DIV_WIDTH = 8    
)
(
    input  wire                 clk_src,
    input  wire                 rst_n  ,
    input  wire [DIV_WIDTH-1:0] div_cfg,
    output wire                 clk_div
);


    reg [DIV_WIDTH-1:0] div_cnt;
    wire div_cnt_full;
    wire div_cnt_half;
    wire div_cnt_zero;
    reg clk_div_int0;
    reg clk_div_int1;


    assign div_cnt_full = (div_cnt==(div_cfg-1'b1));
    assign div_cnt_half = (div_cnt==({1'b0,div_cfg[DIV_WIDTH-1:1]}-1'b1));


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            div_cnt <= {DIV_WIDTH{1'b0}};        
        end else if (div_cnt_full) begin 
            div_cnt <= {DIV_WIDTH{1'b0}};
        end else begin
            div_cnt <= div_cnt + 1'b1;
        end        
    end


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int0 <= 1'b1;
        end else if (div_cnt_full) begin        
            clk_div_int0 <= 1'b0;
        end else if (div_cnt_half) begin
            clk_div_int0 <= 1'b1;
        end
    end


    always @(negedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int1 <= 1'b1;
        end else if (div_cnt_full) begin        
            clk_div_int1 <= 1'b0;
        end else if (div_cnt_half) begin
            clk_div_int1 <= 1'b1;
        end
     end
    
    and2_wrp and2_inst (
      .a (clk_div_int1),
      .b (clk_div_int2),
      .q (clk_div)
    );

    
endmodule

仿真如下所示:
在这里插入图片描述
现在我们结合上图来解释产生奇数分频的原理:

首先,我们的目标是产生3分频时钟,因此我们基于源时钟的上升沿和下降沿产生了两个占空比不为50%的时钟——clk_div_int0和clk_div_int1。

由于clk_div_int0和clk_div_int1有两个源时钟周期为高电平,有一个源时钟周期为低电平,且它们彼此之间只错开了半个周期,因此我们可以利用与操作,来产生具有50%占空比的3分频时钟。其他奇数分频的时钟也是同理产生。



同理,我们可以基于或操作来产生3分频时钟,和上面不同的是,我们只需要生成相对于源时钟有一个时钟周期为高电平,有两个时钟周期为低电平的两个时钟即可。代码和仿真图如下所示:

module odd_divider
#(
    parameter DIV_WIDTH = 8    
)
(
    input  wire                 clk_src,
    input  wire                 rst_n  ,
    input  wire [DIV_WIDTH-1:0] div_cfg,
    output wire                  clk_div
);


    reg [DIV_WIDTH-1:0] div_cnt;
    wire div_cnt_full;
    wire div_cnt_half;
    wire div_cnt_zero;
    reg clk_div_int0;
    reg clk_div_int1;


    assign div_cnt_full = (div_cnt==(div_cfg-1'b1));
    //对下面的div_cnt_half 进行更改,使得生成的时钟晚一拍变为高电平
    assign div_cnt_half = (div_cnt==({1'b0,div_cfg[DIV_WIDTH-1:1]}));



    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            div_cnt <= {DIV_WIDTH{1'b0}};        
        end else if (div_cnt_full) begin 
            div_cnt <= {DIV_WIDTH{1'b0}};
        end else begin
            div_cnt <= div_cnt + 1'b1;
        end        
    end


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int0 <= 1'b1;
        end else if (div_cnt_full) begin        
            clk_div_int0 <= 1'b0;
        end else if (div_cnt_half) begin
            clk_div_int0 <= 1'b1;
        end
    end


    always @(negedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int1 <= 1'b1;
        end else if (div_cnt_full) begin        
            clk_div_int1 <= 1'b0;
        end else if (div_cnt_half) begin
            clk_div_int1 <= 1'b1;
        end
     end
     
     //换成或逻辑
    or2_wrp or2_inst (
      .a (clk_div_int1),
      .b (clk_div_int2),
      .q (clk_div)
    );


endmodule

在这里插入图片描述

三、半整数分频

我们同样可以基于双边沿时钟来产生半整数分频,但是这种方式无法产生具有50%占空比的时钟,下面让我们举例来说明该如何产生2.5分频的时钟:

首先,我们利用计数器,其计数器循环计数到 5。

对源时钟的上升沿敏感的寄存器生成时钟clk_div_int0:当计数器计数到0或3时,使其变为高电平,其余时刻为低电平。

对源时钟的下降沿敏感的寄存器生成时钟clk_div_int1:我们当计数器计数到1或3时,使其变为高电平,其余时刻为低电平。

我们再对clk_div_int0和clk_div_int1进行或操作,就可以得到一个2.5分频的时钟了。

上述过程的仿真图如下所示:
在这里插入图片描述

其对应的verilog如下所示:

module even_divider
#(
    parameter DIV_WIDTH = 8    
)
(
    input  wire                 clk_src,
    input  wire                 rst_n  ,
    input  wire [DIV_WIDTH-1:0] div_cfg,
    output wire                 clk_div
);


    reg [DIV_WIDTH-1:0] div_cnt;
    wire div_cnt_full;
    wire div_cnt_half;
    wire div_cnt_zero;
    reg clk_div_int0;
    reg clk_div_int1;


    assign div_cnt_full = (div_cnt==(div_cfg-1'b1));
    assign div_cnt_half = (div_cnt==({1'b0,div_cfg[DIV_WIDTH-1:1]}));



    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            div_cnt <= {DIV_WIDTH{1'b0}};        
        end else if (div_cnt_full | div_cnt_zero) begin 
            div_cnt <= {DIV_WIDTH{1'b0}};
        end else begin
            div_cnt <= div_cnt + 1'b1;
        end        
    end


    always @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int0 <= 1'b0;
        end else if ((div_cnt==1'b0) || (div_cnt==(div_cfg/2+1))) begin
            clk_div_int0 <= 1'b1;
        end else begin
            clk_div_int0 <= 1'b0;
        end
    end


    always @(negedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            clk_div_int1 <= 1'b0;
        end else if ((div_cnt==1'b1) || (div_cnt==(div_cfg/2+1))) begin
            clk_div_int1 <= 1'b1;
        end else begin
            clk_div_int1 <= 1'b0;
        end
     end

   or2_wrp or2_inst (
      .a (clk_div_int1),
      .b (clk_div_int2),
      .q (clk_div)
    );





endmodule

四、总结

本篇文章我们对一些常见的时钟分频器,比如偶数分频器、计数分频器、半整数分频器进行了介绍。我们可以基于计数器、上升沿敏感的触发器和下降沿敏感的触发器来产生我们不同分频比的时钟。

如果你喜欢这篇文章的话,请关注我的公众号-熊熊的ic车间,里面还有ic设计和ic验证的学习资料和书籍等着你呢~欢迎您的关注!
在这里插入图片描述

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字ic小熊饼干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值