大家好,我是数字小熊饼干,一个练习时长两年半的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) beginif (!rst_n) begindiv_cnt <= {DIV_WIDTH{1'b0}}; end else if (div_cnt_full | div_cnt_zero) begin div_cnt <= {DIV_WIDTH{1'b0}};end else begindiv_cnt <= div_cnt + 1'b1;end endalways @(posedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int <= 1'b1;end else if (div_cnt_zero) beginclk_div_int <= 1'b1;end else if (div_cnt_full | div_cnt_half) begin clk_div_int <= ~clk_div;endendwire 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) beginif (!rst_n) begindiv_cnt <= {DIV_WIDTH{1'b0}}; end else if (div_cnt_full) begin div_cnt <= {DIV_WIDTH{1'b0}};end else begindiv_cnt <= div_cnt + 1'b1;end endalways @(posedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int0 <= 1'b1;end else if (div_cnt_full) begin clk_div_int0 <= 1'b0;end else if (div_cnt_half) beginclk_div_int0 <= 1'b1;endendalways @(negedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int1 <= 1'b1;end else if (div_cnt_full) begin clk_div_int1 <= 1'b0;end else if (div_cnt_half) beginclk_div_int1 <= 1'b1;endendand2_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) beginif (!rst_n) begindiv_cnt <= {DIV_WIDTH{1'b0}}; end else if (div_cnt_full) begin div_cnt <= {DIV_WIDTH{1'b0}};end else begindiv_cnt <= div_cnt + 1'b1;end endalways @(posedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int0 <= 1'b1;end else if (div_cnt_full) begin clk_div_int0 <= 1'b0;end else if (div_cnt_half) beginclk_div_int0 <= 1'b1;endendalways @(negedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int1 <= 1'b1;end else if (div_cnt_full) begin clk_div_int1 <= 1'b0;end else if (div_cnt_half) beginclk_div_int1 <= 1'b1;endend//换成或逻辑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) beginif (!rst_n) begindiv_cnt <= {DIV_WIDTH{1'b0}}; end else if (div_cnt_full | div_cnt_zero) begin div_cnt <= {DIV_WIDTH{1'b0}};end else begindiv_cnt <= div_cnt + 1'b1;end endalways @(posedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int0 <= 1'b0;end else if ((div_cnt==1'b0) || (div_cnt==(div_cfg/2+1))) beginclk_div_int0 <= 1'b1;end else beginclk_div_int0 <= 1'b0;endendalways @(negedge clk_src or negedge rst_n) beginif (!rst_n) beginclk_div_int1 <= 1'b0;end else if ((div_cnt==1'b1) || (div_cnt==(div_cfg/2+1))) beginclk_div_int1 <= 1'b1;end else beginclk_div_int1 <= 1'b0;endendor2_wrp or2_inst (.a (clk_div_int1),.b (clk_div_int2),.q (clk_div));endmodule
四、总结
本篇文章我们对一些常见的时钟分频器,比如偶数分频器、计数分频器、半整数分频器进行了介绍。我们可以基于计数器、上升沿敏感的触发器和下降沿敏感的触发器来产生我们不同分频比的时钟。
如果你喜欢这篇文章的话,请关注我的公众号-熊熊的ic车间,里面还有ic设计和ic验证的学习资料和书籍等着你呢~欢迎您的关注!