奇偶分频的Verilog实现(占空比50%,面试必备)
分频器,常用于将输入的某个时钟频率的波形进行分频。分频大概可以分为两类: 奇数倍分频和 偶数倍分频再细分又可以将占空比作为一个考虑的因素,粗浅的分为 占空比为50% 和 占空比不是50% 这两类。接下来细讲这四种分频的实现。
1 偶数倍分频(占空比为50%)
由于偶数倍分频很容易实现占空比为50%的偶数倍分频,因此不讨论占空比不为50%的偶数倍分频。
1.1用计数器来实现偶数倍的分频。
比如要实现一个分频数为DIV_NUM的偶数倍分频器,则我们将计数器从0计数到DIV_NUM/2-1,判断计数到该设定值后,让clk_div翻转一次,然后将计数器清零继续计数,如此循环,即可实现一个时钟周期相较于原时钟DIV_NUM倍的时钟。(分频即是将原时钟频率分为更慢的频率,倍频则相反)
代码如下:
module div_even #(
paramter DIV_NUM = 20
)(
input clk,
input rst_n,
output reg div_clk
);
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 'b0;
else if(cnt != DIV_NUM/2 - 1)
cnt <= cnt + 1'b1;
else
cnt <= 'b0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
div_ clk <= 'b0;
else if(cnt == DIV_NUM/2 - 1)
div_clk <= ~div_clk;
else
div_clk <= div_clk;
end
endmodule
1.2用触发器来实现偶数倍分频
触发器的Q端经过反向器送入D端,即可实现一个简单的2分频
若要实现
2
n
2^{n}
2n分频,可以通过级联2分频的模块,得到如下所示电路:
但是该方法只能实现
2
n
2^{n}
2n分频,即2、4、8、16等分频,并不能包含诸如10分频,20分频的情况。
因此还有一个通用的方法,通过移位寄存器来实现偶数倍分频。
若要实现DIV_NUM的分频,则需要DIV_NUM/2个寄存器。具体实现电路图如下所示:
虽然该方案比较通用,但该方案在分频数很大的时候,需要很多的寄存器,比较浪费资源,因此要酌情使用。(分频数小的时候可以使用触发器,分频数大的时候可以考虑用计数器)
2 奇数倍分频(占空比不为50%)
2.1 用模n计数器来实现奇数倍分频
所谓模n计数器,就是用计数器来实现一个模值为n的计数,从0计数到n-1,然后清零继续计数到n-1,然后清零,如此循环。将计数器的最高位作为分频信号输出,即可得到n分频(该方法奇偶分频都可以使用,但是不能保证50%的占空比)
例如:我们要实现一个11分频,我们设计一个模值为10的计数器,从0开始计数,计数到10后,清零,继续计数到10,清零,如此反复,然后我们将该计数器的最高位的值直接作为分频器的输出,即可得到11分频。
二进制计数值(十进制值) | 最高位值 |
---|---|
0000(0) | 0 |
0001 (1) | 0 |
0010 (2) | 0 |
0011 (3) | 0 |
0100 (4) | 0 |
0101 (5) | 0 |
0110 (6) | 0 |
0111 (7) | 0 |
1000 (8) | 1 |
1001 (9) | 1 |
1010 (10) | 1 |
代码如下:
module div #(
parameter N = 11
)(
input clk,
input rst_n,
output clk_div
);
reg [3:0] div_cnt ;//分频计数器
always @(posedge clk or negedge rst_n)begin
if (rst_n == 1'b0 )
div_cnt <= 0 ;
else if(div_cnt == N-1)//从0计数到N-1,然后返回到0,即N分频
div_cnt <= 0;
else
div_cnt <= div_cnt + 1'b1 ;
end
assign clk_div = div_cnt[3] ;
endmodule
原时钟周期10ns,经过11分频后,可以看到分频后的时钟周期为110ns。
因此我们得到一个通用的结论:一个最大计数长度为N(从0计数到N-1)的计数器,其最高位的输出,是输入频率的N分频。(奇偶分频通用,不能保证占空比)
3 奇数倍分频(占空比为50%)
3.1 用待分频时钟的上升沿和下降沿和改进的模n计数器,再或
如果要实现占空比为50%的奇数倍分频,可以通过待分频时钟的上升沿和下降沿触发分别计数(不是简单的计数),然后将上升沿和下降沿产生的时钟进行相或运算,即可得到占空比为50%的时钟。
具体的计数规则总结如下:
正沿和负沿分别都计数,计数到(N-1)/2时,翻转,然后继续计数,计数到N-1时,再翻转。然后计数器清零,继续计数,如此循环。(其实跟模n计数器一样,不过穿插了一个中间步骤:计数到(n-1)/2时,要翻转一次)
具体verilog实现代码为:
module div_odd#(
parameter DIV_NUM = 11 //分频数
)(
input clk,
input rst_n,
output clk_out
);
reg clk_p,clk_n;
reg [7:0] cnt_n,cnt_p;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
clk_p <= 1'b0;
else if(cnt_p == (DIV_NUM-1)/2) //穿插了一个中间步骤:计数到(n-1)/2时,要翻转一次
clk_p <= ~clk_p;
else if(cnt_p == DIV_NUM -1)
clk_p <= ~clk_p;
end
always@(negedge clk or negedge rst_n)begin
if(!rst_n)
clk_n <= 1'b0;
else if(cnt_n == (DIV_NUM-1)/2)//穿插了一个中间步骤:计数到(n-1)/2时,要翻转一次
clk_n <= ~clk_n;
else if(cnt_n == DIV_NUM -1)
clk_n <= ~clk_n;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_p <= 'b0;
else if(cnt_p == DIV_NUM-1)//计数器计数到n-1时,进行清零
cnt_p <= 'b0;
else
cnt_p <= cnt_p + 1'b1;
end
always@(negedge clk or negedge rst_n)begin
if(!rst_n)
cnt_n <= 'b0;
else if(cnt_n == DIV_NUM-1)//计数器计数到n-1时,进行清零
cnt_n <= 'b0;
else
cnt_n <= cnt_n + 1'b1;
end
assign clk_out = clk_p | clk_n; //将上沿和下沿得到的两个波形进行相或运算
endmodule
tb如下:
module div_tb();
parameter PERIOD = 10;
reg clk;
reg rst_n;
initial begin
clk = 0;
rst_n = 0;
#10 rst_n =1;
end
always #(PERIOD/2) clk = ~clk;
wire div_clk;
div_odd #(
.DIV_NUM(11)
)inst1(
.clk(clk),
.rst_n(rst_n),
.clk_out(div_clk)
);
endmodule
还是进行11分频,得到的波形如下:
可以看到分频后时钟周期为110ns。
至于某些情形是:将上升沿和下降沿产生的时钟进行相与运算
其实这个方法和或的方法是一样,也是分别采样上升沿和下降沿,然后在等于n-1和(n-1)/2时翻转,得到两个信号。那为什么这个是与,而刚才的是或呢?主要是复位信号到来后,clk_p和clk_n的复位值为1,而不是0。
3.2 上沿和下沿错开计数,再异或
在讨论错开计数之前,我们先了解一下不错开计数会产生什么影响。(答案是:达不到占空比为50%)
用待分频时钟的上升沿和下降沿分别进行模DIV_NUM的计数,计数到DIV_NUM-1时,翻转。
最后再将两者的波形进行异或。
具体代码实现如下:
module div_odd #(
parameter DIV_NUM = 11
)(
input clk,
input rst_n,
output clk_out
);
reg [3:0] div_cnt1;
reg div_clk1;
always@(posedge clk or negedge rst_n)begin //上升沿
if(!rst_n)begin
div_cnt1 <= 'b0;
div_clk1 <= 'b0;
end
else if(div_cnt1 != DIV_NUM - 1)//模DIV_NUM计数器
div_cnt1 <= div_cnt1 + 1;
else begin
div_cnt1 <= 0;
div_clk1 <= ~div_clk1;//当计数值达到DIV_NUM-1时,信号进行翻转
end
end
reg [3:0] div_cnt2;
reg div_clk2;
always@(negedge clk or negedge rst_n)begin //下降沿
if(!rst_n)begin
div_cnt2 <= 'b0;
div_clk2 <= 'b0;
end
else if(div_cnt2 != DIV_NUM - 1 )//模DIV_NUM计数器
div_cnt2 <= div_cnt2 + 1;
else begin
div_cnt2 <= 0;
div_clk2 <= ~div_clk2;//当计数值达到DIV_NUM-1时,信号进行翻转
end
end
assign div_clk = div_clk1 ^ div_clk2;//两个波形进行异或
endmodule
还是以11分频为例,可以发现,两个模DIV_NUM计数器实现了奇分频,但是占空比不为50%
怎样才能在该方法的基础上得到占空比为50%呢?
其实只需要让上升沿计数的计数器与下降沿计数的计数器错开(DIV_NUM-1)/2拍即可实现。即让div_clk1与div_clk2错开(DIV_NUM-1)/2拍。我们知道错开拍子可以通过寄存器直接对div_clk1或者div_clk2打(DIV_NUM-1)/2拍,以让两个信号错开(DIV_NUM-1)/2拍,但是如果分频数越高,这样就会需要更多寄存器。因此不推荐使用。
我推荐的方式是让两个计数器错开计数,比如让div_cnt1复位一结束刚开始从0计数到DIV_NUM+(DIV_NUM-1)/2-1,然后再回到(DIV_NUM-1)/2,之后便一直从(DIV_NUM-1)/2计数到DIV_NUM-1+(DIV_NUM-1)/2循环。同时让div_clk1每次当div_cnt1等于DIV_NUM-1+(DIV_NUM-1)/2时进行翻转。这就做到了div_clk1与div_clk2错开(DIV_NUM-1)/2拍,从而使得最后得到的分频时钟占空比为50%。
代码:
module div_odd #(
parameter DIV_NUM = 11
)(
input clk,
input rst_n,
output clk_out
);
reg [3:0] div_cnt1;
reg div_clk1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
div_cnt1 <= 'b0;
div_clk1 <= 'b0;
end
else if(div_cnt1 != DIV_NUM - 1 + (DIV_NUM-1)/2) //多加了一个 (DIV_NUM-1)/2
div_cnt1 <= div_cnt1 + 1;
else begin
div_cnt1 <= (DIV_NUM-1)/2; //复位的值为 (DIV_NUM-1)/2
div_clk1 <= ~div_clk1;
end
end
reg [3:0] div_cnt2;
reg div_clk2;
always@(negedge clk or negedge rst_n)begin
if(!rst_n)begin
div_cnt2 <= 'b0;
div_clk2 <= 'b0;
end
else if(div_cnt2 != DIV_NUM - 1 )
div_cnt2 <= div_cnt2 + 1;
else begin
div_cnt2 <= 0;
div_clk2 <= ~div_clk2;
end
end
assign div_clk = div_clk1 ^ div_clk2;
endmodule
仿真波形如下(11分频示例),分频输出时钟为div_clk:
总结
如果想要直接进行分频,不要求占空比的话,直接上模n计数器即可。
如果想要进行占空比50%的偶数倍分频,直接用计数器计数到DIV_NUM/2-1,然后信号翻转即可。
如果想要进行占空比50%的奇数倍分频,有两个方法可供选择,不过我还是推荐用3.1的方法。两个沿的计数器计数到 (DIV_NUM-1) / 2和 DIV_NUM-1 时,信号分别翻转一次,最后将两个信号进行或操作即可。
注:部分方法参考公众号:小鱼学IC