分频器
在数字系统的设计中经常会碰到需要使用多个时钟的情况。时钟信号的产生通常具有两种方法,一种是使用PLL(Phase Locked Loop,锁相环),可生成倍频、分频信号;另一种则是使用硬件描述语言构建一个分频电路。
分频器的设计通常分为以下三类:偶数分频器、奇数分频器、及小数分频器。
偶数分频器
D触发器实现偶分频,占空比50%
D触发器实现2分频的电路图如下所示:
通过将D触发器2分频电路级联,可实现输入时钟的2N倍分频,其中N为D触发器2分频电路级联的个数。(注:不管输入时钟的占空比是多少,输出占空比均为50%。)
RTL实现代码
module Even_Freq_Div_N(clk, rst_n, div_clk);
/********************参数定义********************/
/*********************IO 说明********************/
input clk;
input rst_n;
output reg div_clk;
/***************** 内部信号声明 *****************/
/*******************功能定义*********************/
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
div_clk <= 1'b0;
else
div_clk <= ~div_clk;
end
endmodule
testbench 代码
module tb_Even_Freq_Div_N();
/*测试模块输入、输出变量定义*/
//输入
reg clk;
reg rst_n;
//输出
wire div_clk;
/*待测试模块调用*/
Even_Freq_Div_N Even_Freq_Div_N_test
(
.clk (clk ),
.rst_n (rst_n ),
.div_clk (div_clk )
);
/*激励向量定义*/
always #(10) clk = ~clk;
//初始化输入信号
initial begin
clk = 1'b0;
rst_n <= 1'b0;
#20
rst_n <=1'b1;
end
/*显示格式定义*/
endmodule
功能仿真
计数器实现偶分频,输出占空比任意(细度取决于源时钟频率和分频数)
若要实现N分频(N为偶数)占空比为50%,只需在计数器计数到N-1(重新从0开始计数),在小于N/2-1时输出低电平,输出N/2-1时输出高电平。而要改变占空比的,则调整比较器比较的值。(尽量不要在一个module内用作后级时钟输入,但可以用于其他module的时钟输入)
6分频RTL代码实现
module Even_Freq_Div_N(clk, rst_n, div_clk);
/********************参数定义********************/
parameter Div_N = 6; //分频数,N为偶数
parameter width = $clog2(Div_N);
parameter Duty = Div_N/2-1;//用于调整占空比
/*********************IO 说明********************/
input clk;
input rst_n;
output reg div_clk;
/***************** 内部信号声明 *****************/
reg [width-1:0] cnt;
/*******************功能定义*********************/
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= {width{1'b0}};
else if(cnt == (Div_N-1))
cnt <= {width{1'b0}};
else
cnt <= cnt + 1'd1;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
div_clk <= 1'b0;
else if(cnt > Duty)
div_clk <= 1'b1;
else
div_clk <= 1'b0;
end
endmodule
功能仿真:
计数器实现偶分频,输出分频标志
若要实现N分频,只需在计数器计数到N-1(重新从0开始计数)时,输出分频标志即可。
6分频,输出分频标志RTL代码实现
module Even_Freq_Div_N(clk, rst_n, div_flag);
/********************参数定义********************/
parameter Div_N = 6; //分频数,N为偶数
parameter width = $clog2(Div_N);
/*********************IO 说明********************/
input clk;
input rst_n;
output reg div_flag;
/***************** 内部信号声明 *****************/
reg [width-1:0] cnt;
/*******************功能定义*********************/
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= {width{1'b0}};
else if(cnt == (Div_N-1))
cnt <= {width{1'b0}};
else
cnt <= cnt + 1'd1;
end
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
div_flag <= 1'b0;
else if(cnt == (Div_N-1))
div_flag <= 1'b1;
else
div_flag <= 1'b0;
end
endmodule
功能仿真:
总结
采用输出占空比任意的方法得到的分频信号不能直接用作同一个module内后级模块时钟信号,即
always@(posedege div_clk or negedge rst_n )
begin
if(!rst_n)
A <= 4'b0;
else
A <= A+1'b1;
else
end
这样的用法衍生的潜在问题在低速系统内不易察觉,但在高速系统中很容易出现问题。而采用输出分频标志的方法,虽然会多使用一些寄存器,但是可以和所在系统时钟下产生的信号都保持几乎相同的时钟关系。在同一个module内后级使用方式如下:
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
A <= 4'b0;
else if(clk == 1'b1)
A <= A+1'b1;
else
A <= A;
end
奇分频
计数器实现奇分频,占空比非50%
若要实现N分频(N为奇数),只需将计数器在待分频时钟上升沿触发下循环计数,从0计数到(N-1)后计数器清零。当计数小于(N-1)/2时输出高电平,其他情况下输出低电平。(尽量避免将分频后时钟用作module内的后级时钟输入,但可以用于其他module的时钟输入)。7分频的Verilog代码如下:
奇分频占空比非50%RTL代码
module Odd_Freq_Div_N(clk, rst_n, div_clk);
/********************参数定义********************/
parameter DIV_NUM = 3'd7;//分频数
parameter width = $clog2(DIV_NUM);
parameter Duty = 3'd4; //用于调整占空比
/*********************IO 说明********************/
input clk;
input rst_n;
output reg div_clk;
/***************** 内部信号声明 *****************/
reg [width-1:0] cnt; //计数寄存器
/*******************功能定义*********************/
//上升沿计数
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= {width{1'b0}};
else if(cnt == (DIV_NUM-1))
cnt <= {width{1'b0}};
else
cnt <= cnt + 1'b1;
end
//上升沿计数器生成分频时钟
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
div_clk <= 1'b0;
else if(cnt < Duty)
div_clk <= 1'b1;
else
div_clk <= 1'b0;
end
endmodule
功能仿真:
计数器实现奇分频,占空比50%
用两个计数器,一个由输入时钟上升沿触发,一个由输入时钟下降沿触发,最后将两个计数器的输出相或,即可得到占空比为50%的方波波形。(尽量避免将分频后时钟用作module内的后级时钟输入,但可以用于其他module的时钟输入)
奇分频占空比50%RTL代码
module Odd_Freq_Div_N(clk, rst_n, div_clk);
/********************参数定义********************/
parameter DIV_NUM = 3'd7;
parameter width = $clog2(DIV_NUM);
/*********************IO 说明********************/
input clk;
input rst_n;
output div_clk;
/***************** 内部信号声明 *****************/
reg [width-1:0] cnt_p;//上升沿计数寄存器
reg [width-1:0] cnt_n;//下降沿计数寄存器
reg clk_p;
reg clk_n;
/*******************功能定义*********************/
//上升沿计数
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_p <= {width{1'b0}};
else if(cnt_p == (DIV_NUM-1))
cnt_p <= {width{1'b0}};
else
cnt_p <= cnt_p + 1'b1;
end
//下降沿计数
always@(negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n <= {width{1'b0}};
else if(cnt_n == (DIV_NUM-1))
cnt_n <= {width{1'b0}};
else
cnt_n <= cnt_n + 1'b1;
end
//上升沿计数器生成分频时钟
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p <= 1'b0;
else if(cnt_p < (DIV_NUM-1)/2)
clk_p <= 1'b1;
else
clk_p <= 1'b0;
end
//下降沿计数器生成分频时钟
always@(negedge clk or negedge rst_n)
begin
if(!rst_n)
clk_n <= 1'b0;
else if(cnt_n < (DIV_NUM-1)/2)
clk_n <= 1'b1;
else
clk_n <= 1'b0;
end
assign div_clk = clk_p | clk_n;
endmodule
功能仿真:
计数器实现奇分频,输出分频标志
若要实现N分频,只需在计数器计数到N-1(重新从0开始计数)时,输出分频标志即可。
7分频,输出分频标志RTL代码
module Odd_Freq_Div_N(clk, rst_n, div_flag);
/********************参数定义********************/
parameter DIV_NUM = 3'd7;//分频数
parameter width = $clog2(DIV_NUM);
/*********************IO 说明********************/
input clk;
input rst_n;
output reg div_flag;
/***************** 内部信号声明 *****************/
reg [width-1:0] cnt; //计数寄存器
/*******************功能定义*********************/
//上升沿计数
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
cnt <= {width{1'b0}};
else if(cnt == (DIV_NUM-1))
cnt <= {width{1'b0}};
else
cnt <= cnt + 1'b1;
end
//产生分频标志
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
div_flag <= 1'b0;
else if(cnt == (DIV_NUM-1))
div_flag <= 1'b1;
else
div_flag <= 1'b0;
end
endmodule
功能仿真
利用基本逻辑单元实现奇分频,占空比50%
以实现5分频,占空比50%为例
思路:先使用触发器构成序列生成器,输出00011循环脉冲,实现占空比非50%的5分频,然后用负沿触发器打一拍,再相或。由于00011循环共五个状态,故需3个D触发器。通过列状态表、画卡诺图,得到由三个D触发器及逻辑门构成的00011序列生成器,后接负沿触发器打一拍并将其输出与序列生成器的输出相或,即得到占空比为50%的5分频电路
- 确定序列发生器使用D触发器的个数:
先用2级。则移位状态00-00-01-…,有重复,不可以。
用3级。移位状态000-001-011-110-100-000,没有重复的回到原始状态,可以。故最少需要3级D触发器。 - 通过列状态表、得到由三个D触发器及逻辑门构成的00011序列生成器
由状态表达式可得逻辑表达式:
D
=
Q
1
+
Q
2
‾
D=\overline{Q_1+Q_2}
D=Q1+Q2
3.序列生成器后接负沿触发器打一拍并将其输出与序列生成器的输出相或,电路结构如下
5分频RTL代码
module Odd_Freq_Div_5(clk_in, rst_n, clk_out);
/********************参数定义********************/
/*********************IO 说明********************/
input clk_in;
input rst_n;
output clk_out;
/***************** 内部信号声明 *****************/
reg Q0;
reg Q1;
reg Q2;
reg Q3;
wire D;
/*******************功能定义*********************/
assign D = ~(Q1|Q2);
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
begin
Q0 <= 1'b0;
Q1 <= 1'b0;
Q2 <= 1'b0;
end
else
begin
Q0 <= D;
Q1 <= Q0;
Q2 <= Q1;
end
end
always@(negedge clk_in or negedge rst_n)
begin
if(!rst_n)
Q3<= 1'b0;
else
Q3<= Q2;
end
assign clk_out = Q3 | Q2;
endmodule
功能仿真
半整数分频
方法一
半整数分频器的设计思想:计数器是通过上升沿触发,故在计数为N-1时对计数触发进行翻转,时钟的下降沿变为上升沿,因此计数值为0,所以每产生n+0.5分频时钟的周期,触发时钟都要翻转一次。如图所示,采用异或门和2分频模块设计脉冲扣除电路,脉冲扣除是输入频率和2分频输出相异或的结果。
6.5分频,输出分频标志RTL代码
module Freq_Div6_5(clk_in, rst_n, clk_out);
/********************参数定义********************/
parameter CNT_NUM = 7;
parameter WIDTH = $clog2(CNT_NUM);
/*********************IO 说明********************/
input clk_in;
input rst_n;
output reg clk_out;
/***************** 内部信号声明 *****************/
reg [WIDTH-1:0] cnt; //计数寄存器
reg clk1;
wire clk2;
/*******************功能定义*********************/
//异或
assign clk2 = clk1 ^ clk_in;
//D触发器实现2分频
always@(posedge clk_out or negedge rst_n)
begin
if(!rst_n)
clk1 <= 1'b0;
else
clk1 <= ~clk1;
end
//上升沿计数
always@(posedge clk2 or negedge rst_n)
begin
if(!rst_n)
cnt <= {WIDTH{1'b0}};
else if(cnt == (CNT_NUM-1))
cnt <= {WIDTH{1'b0}};
else
cnt <= cnt + 1'b1;
end
//产生分频标志
always@(posedge clk2 or negedge rst_n)
begin
if(!rst_n)
clk_out <= 1'b0;
else if(cnt == (CNT_NUM-1))
clk_out <= 1'b1;
else
clk_out <= 1'b0;
end
endmodule
功能仿真:
个人觉得这种方法在高速时钟系统不太好,受时钟偏斜和抖动影响,会使分频后的时钟和源时钟相位有所改变,而用分频后的时钟用作module内之后的always的敏感条件,容易出现时钟和数据不同步的现象。个人认为采用PLL分频或以下方法的方法比较好。
方法二
设K+0.5=N,N为要实现的分频。首先设置了两个计数器cnt_p和cnt_n,分别在时钟上升沿和下降沿改变,同时也设置了两个时钟信号clk_p,clk_n,同样也分别在上升沿和下降沿改变。clk_p信号先持续N+0.5个周期高电平,持续N-0.5个周期低电平,clk_n先持续N-0.5个周期低电平,持续N+0.5个周期高电平,然后将clk_p与clk_n相与,就能得到一个N分频标志的时钟。
5.5分频,RTL实现代码
module Freq_Div5_5(clk_in, rst_n, clk_out);
/********************参数定义********************/
parameter Div_N = 5; //5+0.5分频
parameter WIDTH = $clog2(Div_N*2);
/*********************IO 说明********************/
input clk_in;
input rst_n;
output clk_out;
/***************** 内部信号声明 *****************/
reg [WIDTH-1:0] cnt_p;
reg [WIDTH-1:0] cnt_n;
reg clk_n;
reg clk_p;
/*******************功能定义*********************/
//cnt_p计数
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
cnt_p <= {(WIDTH){1'b0}};
else if(cnt_p == Div_N*2)
cnt_p <= {(WIDTH){1'b0}};
else
cnt_p <= cnt_p + 1'b1;
end
//cnt_n计数
always@(negedge clk_in or negedge rst_n)
begin
if(!rst_n)
cnt_n <= {(WIDTH){1'b0}};
else if(cnt_n == Div_N*2)
cnt_n <= {(WIDTH){1'b0}};
else
cnt_n <= cnt_n + 1'b1;
end
//clk_p
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
clk_p <= 1'b0;
else if(cnt_p <= Div_N)
clk_p <= 1'b1;
else
clk_p <= 1'b0;
end
always@(negedge clk_in or negedge rst_n)
begin
if(!rst_n)
clk_n <= 1'b0;
else if(cnt_n < Div_N)
clk_n <= 1'b0;
else
clk_n <= 1'b1;
end
assign clk_out = clk_n & clk_p;
endmodule
功能仿真
小数分频
假设输出clk_out是输入时钟clk_in的N分频,首先要将分频系数N转换为分数形式4.75
→
\rightarrow
→
19
4
\frac{19}{4}
419,3.4
→
\rightarrow
→
34
10
\frac{34}{10}
1034,例如8.7可以化为
87
10
\frac{87}{10}
1087,这意味着87个clk_in周期内可以输出10个clk_out周期就可以实现分频。
然后采用若干种(一般是两种)整数分频在87个原周期clk_in内产生10个新时钟周期clk_out。整数分频的分频系数有很多种选择,但要尽可能接近,提高clk_out的均匀度。一般推荐在小数分频系数N的附近选取。因为8<N<9,所以两个整数分频系数是8和9。接着要计算87个clk_out周期分别有多少个是8分频和9分频的。设每10个clk_out中有x个8分频输出和y个9分频输出,则可列出如下方程:
{
x
+
y
=
10
8
x
+
9
y
=
87
\begin{cases} x+y=10\\ 8x+9y=87\\ \end{cases}
{x+y=108x+9y=87
可得x=3,y=7。也就是3个8分频和7个9分频一组,循环输出,就等效于8.7分频。 最后安排组内8分频和9分频的位置。如下图。
8.7分频,RTL实现代码
module Odd_Freq_Div_N(clk_in, rst_n, clk_out);
/********************参数定义********************/
parameter M_N = 8'd87;
parameter c89 = 8'd24; // 8/9时钟切换点
parameter div_e = 5'd8; //偶数周期
parameter div_o = 5'd9; //奇数周期
/*********************IO 说明********************/
input clk_in;
input rst_n;
output reg clk_out;
/***************** 内部信号声明 *****************/
reg [3:0] clk_cnt;
reg [6:0] cyc_cnt;
/*******************功能定义*********************/
//原周期计数
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
cyc_cnt <= 7'b0;
else if(cyc_cnt == M_N-1)
cyc_cnt <= 7'b0;
else
cyc_cnt <= cyc_cnt + 1'b1;
end
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
clk_cnt <= 4'b0;
else if(cyc_cnt < c89)
begin
if (clk_cnt == div_e-1)
clk_cnt <= 4'b0;
else
clk_cnt <= clk_cnt + 1'b1;
end
else
begin
if (clk_cnt == div_o-1)
clk_cnt <= 4'b0;
else
clk_cnt <= clk_cnt + 1'b1;
end
end
always@(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
clk_out <= 1'b0;
else if(cyc_cnt < c89 && clk_cnt == div_e-1)
clk_out <= 1'b1;
else if(cyc_cnt >= c89 && clk_cnt == div_o-1)
clk_out <= 1'b1;
else
clk_out <= 1'b0;
end
endmodule
功能仿真
参考资料
- 牛客网任意小数分频
- 常用电路设计——“分频电路”
- 数字系统设计与Verlilog HDL(第7版),王金明