典型情况下SoC要对设计中各种组件提供许多与相位相关的时钟。将主时钟以2^n进行分割来产生同步偶数分频时钟。然而有时候也会需要要求按奇数甚至小数进行分频。在这些情况下,如果没有更高频的主时钟,则无法得到同步分频时钟。
a.直接用触发器实现(N=2、N=4)
常用双 D-FF 或双 JK-FF 器件来构成,下图的分频电路输出占空比均为 50%,可用 D-FF,也可用 JK-FF 来组成,用 JK-FF 构成分频电路容易实现并行式同步工作,因而适合于较高频的应用场合。而 FF 中的引脚 R、S( P )等引脚如果不使用,则必须按其功能要求连接到非有效电平的电源或地线上。
补充:用加法器,计数器实现
b.分频系数较大可以用计数器实现
偶数分频器的实现较为简单,要想得到分频系数为N的频率输出,通过设定一个计数器,当加到N/2-1时计数器清零,或者clkout翻转,以此循环,即可实现偶数倍分频。
//6分频器
module divider(
clk,
rst_n,
clk_div
);
input clk;
input rst_n;
output clk_div;
reg clk_div;
reg [3:0] cnt;
parameter NUM_DIV = 6;
always @(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
cnt <= 4'd0;
clk_div <= 1'b0;
end
else if(cnt < NUM_DIV / 2 - 1) begin
cnt <= cnt + 1'b1;
clk_div <= clk_div;
end
else begin
cnt <= 4'd0;
clk_div <= ~clk_div;
end
end
endmodule
实现的思想和偶数分频器相似,按一定的规律节奏对分频后的时钟进行翻转即可。
实现一:直接计数翻转
// 占空比2/5 五分频
module clk_div(
clk,
rst_n,
clk_div
);
input clk;
input rst_n;
output reg clk_div;
reg [2:0] cnt;
always@(posedge clk or negedge rst_n)
begin
if(!rst_n) begin
cnt <=0;
clk_div <=0;
end
else if( cnt < 2) begin //0、1、2 clk_div=0
clk_div <=0;
cnt <= cnt +1;
end
else if( cnt < 4 ) begin //3、4 clk_div = 1
cnt <= cnt +1;
clk_div<=1;
end
else begin
cnt <=0;
clk_div <= 0;
end
end
endmodule
// 3/7占空比 7分频电路
module div_moore(
clk,
rst_n,
clk_div
);
input clk;
input rst_n;
output clk_div;
reg [2:0] curr_state;
reg [2:0] next_state;
parameter S0 = 3'b000,
S1 = 3'b001,
S2 = 3'b010,
S3 = 3'b011,
S4 = 3'b111,
S5 = 3'b101,
S6 = 3'b100;
always@ (posedge clk or negedge rst_n)
begin
if (!rst_n)
curr_state <= S0 ;
else
curr_state <= next_state;
end
always @(*) begin
case (curr_state)
S0 : next_state = S1;
S1 : next_state = S2;
S2 : next_state = S3;
S3 : next_state = S4;
S4 : next_state = S5;
S5 : next_state = S6;
S6 : next_state = S0;
endcase
end
assign clk_div = ((curr_state == S4)||(curr_state == S5)||(curr_state == S6)) ? 1'b1 : 1'b0 ;
endmodule
方法一:
采用计数器 cnt1 进行计数,在时钟上升沿进行加 1 操作,计数器的值为 0、1 时,输出时钟信号 clk_div 为高电平;计数器的值为2、3、4 时,输出时钟信号 clk_div 为低电平,计数到 5 时清零,从头开始计数。我们可以得到占空比为 40% 的波形 clk_div1。
采用计数器 cnt2进行计数,在时钟下降沿进行加 1 操作,计数器的值为 0、1 时,输出时钟信号 clk_div 为高电平;计数器的值为2、3、4 时,输出时钟信号 clk_div 为低电平,计数到 5 时清零,从头开始计数。我们可以得到占空比为 40% 的波形 clk_div2。
clk_div1 和clk_div2 的上升沿到来时间相差半个输入周期,所以将这两个信号进行或操作,即可得到占空比为 50% 的5分频时钟
// 50%占空比 5分频器
module divider(
clk,
rst_n,
clk_div
);
input clk;
input rst_n;
output clk_div;
reg clk_div;
parameter NUM_DIV = 5;
reg[2:0] cnt1;
reg[2:0] cnt2;
reg clk_div1, clk_div2;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt1 <= 0;
else if(cnt1 < NUM_DIV - 1)
cnt1 <= cnt1 + 1'b1;
else
cnt1 <= 0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
clk_div1 <= 1'b1;
else if(cnt1 < NUM_DIV / 2)
clk_div1 <= 1'b1;
else
clk_div1 <= 1'b0;
always @(negedge clk or negedge rst_n)
if(!rst_n)
cnt2 <= 0;
else if(cnt2 < NUM_DIV - 1)
cnt2 <= cnt2 + 1'b1;
else
cnt2 <= 0;
always @(negedge clk or negedge rst_n)
if(!rst_n)
clk_div2 <= 1'b1;
else if(cnt2 < NUM_DIV / 2)
clk_div2 <= 1'b1;
else
clk_div2 <= 1'b0;
assign clk_div = clk_div1 | clk_div2;
endmodule
仿真波形:
方法二:
来自《硬件架构的艺术》
产生具有50%占空比的奇数分频时钟最简单的方式是以期望输出频率的一半生成两个正交的相位时钟。然后通过异或得到输出频率。
由于存在90°相位差,每次异或输入只有一端变化,有效消除了输出波形上的毛刺。
3分频例子:
步骤1:创建0到(N-1)的计数器,3分频 N=3;
步骤2:使用2各触发器按以下方式产生使能信号,
tff1_en:TFF1在计数值为0时使能。
tff2_en:TFF2在计数值为(N+1)/2时使能(3分频这里等于2)
步骤3:产生以下信号
div1:TFF1的输出由输入时钟上升沿触发;
div2:TFF2的输出由输入时钟下降沿触发;
步骤4:div1和div2异或得到输出时钟。
相应电路图:
系统中的一些模块可能根据应用需求(需要模式切换,比如低功耗),需要对输入时钟进行切换。
在模块运行时进行时钟切换的一种最简单的方法就是使用Mux(或者直接使用逻辑门搭建的时钟选择电路)。但使用这种方法会造成glitch,导致系统运行出错。
通过在每个时钟源的选择路径中插入一个负边沿触发的D触发器,可以确保时钟在高电平时,输出保持不变。 通过这种反馈的方式使得在时钟切换时需要等待当前时钟取消选择,从而避免毛刺的产生。
时序图显示了SELECT信号从0到1的跃迁如何首先在CLK0的下降沿停止CLK0到输出的传播,然后在随后的CLK1的下降沿开始CLK1到输出的传播。
该电路中有三个时序路径需要特别考虑:SELECT控制信号到两个负边沿触发触发器任意一个,DFF0的输出到DFF1的输入,DFF1输出的DFF0输入。 如果这三个路径中的任何一条信号在目标触发器时钟的捕获沿发生改变,则该寄存器的输出可能变为亚稳态。因此,需要将两个触发器的捕获沿和SELECT信号的发射沿设置为彼此分开,这可以通过时序约束实现,因为两个时钟之间的时序关系是已知的。
always @(negedge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel1 <= 0;
else sel1 <= !sel0 & select;
always @(negedge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel0 <= 0;
else sel0 <= !sel1 & !select;
assign outclk = (sel1 & clk1) | (sel0 & clk0);
case1避免时钟开关输出出现毛刺要求两个时钟源互为倍数,以便用户可以避免信号与任一时钟域异步。 在该实现中,不可以处理异步信号。
这当两个时钟源完全不相关时,异步行为的源可以是SELECT信号,也可以是从一个时钟域到另一时钟域的反馈。
如上图所示,通过为每个时钟源增加一个额外的上升沿触发触发器级来防止亚稳态。 每个选择路径中的上升沿触发触发器,与现有的下降沿触发触发器一起,防止出现亚稳态,(亚稳态可能是由异步SELECT信号或从一个时钟域到另一个时钟域的异步反馈引起的)
第一级通过锁存数据来帮助稳定数据,然后将其传递到下一级。
always @(posdge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel1_tp <= 0;
else sel1_tp <= !sel0 & select;
always @(negedge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel1 <= 0;
else sel1 <= sel1_tp;
always @(posdge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel0_tp <= 0;
else sel0_tp <= !sel1 & !select;
always @(negedge clk1 or negedge rst_n)
if(rst_n == 1'b0) sel0 <= 0;
else sel0 <= sel0_tp;
assign outclk = (sel1 & clk1) | (sel0 & clk0);
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
参考:
https://blog.csdn.net/qq_41883755/article/details/81535222
https://mp.weixin.qq.com/s/ziJVAokVjuH4q-pUMDcTbQ
https://www.cnblogs.com/zhangxianhe/p/11083208.html
https://www.cnblogs.com/Dinging006/p/9452313.html
Techniques to make clock switching glitch free
《verilog编程艺术》
《硬件架构的艺术》