分频器是用的最广的一种FPGA电路了,下面介绍怎样使用Verilog进行实现分频功能。
从硬件提供的时钟频率,转化到自己需要的频率,则分频为:(硬件提供频率/自己需要得到的目标频率)。例如:硬件板子提供100MHz的时钟,而自己需要20MHz的时钟,则100MHz/20MHz=5,故需要进行5分频。
一。 2 分频
方法1.不使用计数器进行实现。
频率要变一半,也就是周期要变两倍,也就是本来一个clock的时间。由于2分频器很简单,所以不需要用到计数器就可完成,但更复杂的分频器一定要用到计数器。
- 实现代码:
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div2.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 2
Release : 07/12/2008 1.0
*/
module div2 (
input clk,
input rst_n,
output reg o_clk
);
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
o_clk <= 0;
else
o_clk <= ~o_clk;
end
endmodule
理解图像变化:
原时钟rst_n复位脉冲为0时,输出o_clk为低电平,当rst_n复位脉冲为1时,输入clk脉冲开始起作用,输入脉冲clk的上升沿来到时,输出clk脉冲逐次取反。进而得到2分频时钟。
方法2.使用计数器进行实现。
div2_v2.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div2_v2.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 2
Release : 07/12/2008 1.0
*/
module div2_v2 (
input clk,
input rst_n,
output reg o_clk
);
reg cnt;
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (cnt == 1) // 0 ~ 1
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
o_clk <= 0;
else if (cnt < 1) // 0
o_clk = 0;
else // 1
o_clk = 1;
end
endmodule
理解图像变化:
当然我可以将两个always连在一起,不过好的Verilog coding style建议每个always都短短的,最好一个always只处理一个register,第一个always 只处理reg cnt,第二个处理reg o_clk,这样一目了然,对于可读性来说,人们也比较容易理解。从上面的图像中可以清晰的看出上升沿到来时,cnt和o_clk都会产生相应的反应。
testbench
div2_tb.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div2_tb.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 2 testbench
Release : 07/16/2008 1.0
*/
`timescale 1ns/10ps
module div2_tb;
reg clk;
reg rst_n;
wire o_clk;
div2 u0 (
.clk(clk),
.rst_n(rst_n),
.o_clk(o_clk)
);
initial begin
clk = 1'b1;
rst_n = 1'b1;
end
// 50MHz clk
always #10 clk = ~clk;
endmodule
通过加入testbench,产生预定的激励,输入到div2_v2 ,然后有选择地观察输出波形,并检查这个二分频是否为我们所期望的。
仿真波形如图:
二. 4 分频
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div4.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 4
Release : 07/16/2008 1.0
*/
module div4 (
input clk,
input rst_n,
output reg o_clk
);
reg [1:0] cnt;
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 0;
else if (cnt == 3) // 0 ~ 3
cnt <= 0;
else
cnt <= cnt + 1;
end
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
o_clk <= 0;
else if (cnt < 2) // 0 ~ 1
o_clk = 0;
else // 2 ~ 3
o_clk = 1;
end
endmodule
理解图像变化:
从上面的图像中可以清晰的看出上升沿到来时,cnt和o_clk都会产生相应的反应。
testbench
div4_tb.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div4_tb.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 4 testbench
Release : 07/16/2008 1.0
*/
`timescale 1ns/10ps
module div4_tb;
reg clk;
reg rst_n;
wire o_clk;
div4 u0 (
.clk(clk),
.rst_n(rst_n),
.o_clk(o_clk)
);
initial begin
clk = 1'b1;
rst_n = 1'b1;
end
// 50MHz clk
always #10 clk = ~clk;
endmodule
通过加入testbench,产生预定的激励,输入到div4 ,然后有选择地观察输出波形,并检查这个二分频是否为我们所期望的。
仿真波形如图:
看到这里,你应该会想用parameter打造一个万用(泛型)的偶数的分频器,不过先別急,等我们看了除3的分频器后,最后一起打造一个N分频器。
三. 3 分频
根据前面的经验,2分频器就是每1个clock就0变1、1变0。4分频器就是每2个clock就0变1、1变0,所以3分频器应该是每1.5个clock就0变1、1变0,但问题来了,哪来的1.5个clock?计数不能产生1.5!!
回想一下波形,正源触发与負源触发的时间间隔是不是刚好是0.5个clock?所以我们产生两个clock,一个是posedge clk,一个是negedge clk,最后將两个clock做or,这样就可以产生出0.5个clock了,很巧妙吧!!
div3.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div3.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 3
Release : 07/12/2008 1.0
*/
module div3 (
input clk,
input rst_n,
output o_clk
);
reg [1:0] cnt_p;
reg [1:0] cnt_n;
reg clk_p;
reg clk_n;
assign o_clk = clk_p | clk_n;
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_p <= 0;
else if (cnt_p == 2) // 0 ~ 2
cnt_p <= 0;
else
cnt_p <= cnt_p + 1;
end
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
clk_p <= 1;
else if (cnt_p < 1) // 0
clk_p = 1;
else // 1 2
clk_p = 0;
end
always@(negedge clk or negedge rst_n) begin
if (!rst_n)
cnt_n <= 0;
else if (cnt_n == 2) // 0 ~ 2
cnt_n <= 0;
else
cnt_n <= cnt_n + 1;
end
always@(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1;
else if (cnt_n < 1) // 0
clk_n = 1;
else // 1 2
clk_n = 0;
end
endmodule
testbench
div3_tb.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : div3_tb.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by 3 testbench
Release : 07/12/2008 1.0
*/
`timescale 1ns/10ps
module div3_tb;
reg clk;
reg rst_n;
wire o_clk;
div3 u0 (
.clk(clk),
.rst_n(rst_n),
.o_clk(o_clk)
);
initial begin
clk = 1'b1;
rst_n = 1'b1;
end
// 50MHz clk
always #10 clk = ~clk;
endmodule
因为將clk_p和clk_n做or产生新的clk的方式很太特別,所以特別將clk_p和clk_n也用modelsim展示出來,这样可以明显地看到or后产生的3分频的波形,神奇吧!!
四. n 分频(n为整数,可奇数也可偶数)
divn.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : divn.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by n
Release : 07/16/2008 1.0
*/
module divn (
input clk,
input rst_n,
output o_clk
);
parameter WIDTH = 3;#计数器最大值为:(2^WIDTH)-1。必须符合N=< (2^WIDTH)
parameter N = 6;#决定分频数
reg [WIDTH-1:0] cnt_p;
reg [WIDTH-1:0] cnt_n;
reg clk_p;
reg clk_n;
assign o_clk = (N == 1) ? clk :
(N[0]) ? (clk_p | clk_n) : (clk_p);
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_p <= 0;
else if (cnt_p == (N-1))
cnt_p <= 0;
else
cnt_p <= cnt_p + 1;
end
always@(posedge clk or negedge rst_n) begin
if (!rst_n)
clk_p <= 1;
else if (cnt_p < (N>>1))#N>>1表示N/2,即N值右移1位
clk_p = 1;
else
clk_p = 0;
end
always@(negedge clk or negedge rst_n) begin
if (!rst_n)
cnt_n <= 0;
else if (cnt_n == (N-1))
cnt_n <= 0;
else
cnt_n <= cnt_n + 1;
end
always@(negedge clk or negedge rst_n) begin
if (!rst_n)
clk_n <= 1;
else if (cnt_n < (N>>1))
clk_n = 1;
else
clk_n = 0;
end
endmodule
上面这一小段代码是关键:
若N为1,表示不用分频,直接輸出clk即可,N[0]是Verilog个小技巧,判段N是否为奇数,若为奇数,则clk_p | clk_n,若为偶数,则clk_p即可。这样无论N为奇数或者偶数都没问题。其实C/C++也可以用这个小技巧判断是否为奇数,只要x & 1即可,以后就不要靠x % 2了。
testbench
divn_tb.v / Verilog
/*
(C) OOMusou 2008 http://oomusou.cnblogs.com
Filename : divn_tb.v
Compiler : Quartus II 7.2 SP3 + ModelSim-Altera 6.1g
Description : Demo how to write frequency divider by n testbench
Release : 07/16/2008 1.0
*/
`timescale 1ns/10ps
module divn_tb;
reg clk;
reg rst_n;
wire o_clk;
divn u0 (
.clk(clk),
.rst_n(rst_n),
.o_clk(o_clk)
);
initial begin
clk = 1'b1;
rst_n = 1'b1;
end
// 50MHz clk
always #10 clk = ~clk;
endmodule
5分频波形:
6分频波形:
五. 半整数分频(例如,5.5分频,6.5分频…)
半整数指的是N+0.5分频器设计:先进行模N+1计数,计数到N时输出时钟赋值为1,然后当计数到0时,输出时钟赋值为0,因此保持计数值为N的时间为半个时钟周期即为设计的关键,从中可以发现,计数器是在时钟的上升沿计数,那么我们可以让时钟在计数值为N时,将计数触发时钟翻转,时钟的下降沿变为上升沿,因此计数值为0,所以每产生一个N+0.5分频时钟周期,触发时钟都要翻转一次.
通用代码:
计算5.5分频,故这里的N=5
module ModuloN_Cntr(clk,clk_div,temp1,temp2);//N+0.5
input clk;
output clk_div;
reg[31:0]cnt1=0;
reg[31:0]cnt2=0;
output reg temp1,temp2;
initial begin temp1=0;temp2=1;end //首先进行初始化,temp1=0;temp2=1
parameter N=5; //设定分频系数为N+0.5
always @(posedge clk) //temp1上升沿跳变
begin
if(cnt1==2*N) //2*N
begin cnt1[31:0]<=32'd0;end
else begin cnt1[31:0]<=cnt1[31:0]+32'd1;end
if(cnt1==32'd0) begin temp1<=1;end //高电平时间为N+1;
if(cnt1==N+1) begin temp1<=0;end //低电平时间为N;
end
always@(negedge clk) //temp2下降沿跳变
begin
if(cnt2==2*N) //2*N
begin cnt2[31:0]<=32'd0;end
else begin cnt2[31:0]<=cnt2[31:0]+32'd1;end
if(cnt2==32'd0) begin temp2<=0;end //低电平时间为N;
if(cnt2==N) begin temp2<=1;end //高电平时间为N+1;
end
assign clk_div=temp1&&temp2; //逻辑与
endmodule
//如果要进行N+0.5分频
//思路:总的来说要进行N+1+N=2N+1次分频
//在时钟的上升沿和下降沿都进行跳变
//上升沿进行占空比为N+1比N的时钟temp1;
//下降沿进行占空比为N比N+1的时钟temp2;
//最后div=temp1&&temp2 即可得到所需要的半整数分频
波形仿真图:
六.任意小数分频(例如,8.7分频,9.6分频…)
小数分频器的实现方法有很多中,但其基本原理都一样的,即在若干个分频周期中采取某种方法使某几个周期多计或少计一个数,从而在整个计数周期的总体平均意义上获得一个小数分频比。一般而言,这种分频由于分频输出的时钟脉冲抖动很大,故在设计中的使用已经非常少。但是,这也是可以实现的。以8.7倍分频为例,本文仅仅给出双模前置小数分频原理的verilog代码及其仿真图(如图6),具体原理可以参考刘亚海的《基于FPGA的小数分频器的实现》以及毛为勇的《基于FPGA的任意小数分频器的设计》。
代码如下:
//8分频
reg clk_div8;
reg[2:0]cnt_div8;
always@(posedge clk or posedge rst) begin
if(rst)begin //复位
clk_div8<=0;
cnt_div8<=0;
end
elseif(cnt_div8==3'd7) begin
clk_div8<=1; //置1
cnt_div8<=0;
end
elseif(cnt_div8==3'd0) begin
clk_div8<=0; //置0
cnt_div8<=cnt_div8+1;
end
else
cnt_div8<=cnt_div8+1;
end
//9分频
reg clk_div9;
reg[3:0]cnt_div9;
always@(posedge clk or posedge rst) begin
if(rst)begin //复位
clk_div9<=0;
cnt_div9<=0;
end
elseif(cnt_div9==3'd8) begin
clk_div9<=1; //置1
cnt_div9<=0;
end
elseif(cnt_div9==3'd0) begin
clk_div9<=0; //置0
cnt_div9<=cnt_div9+1;
end
else
cnt_div9<=cnt_div9+1;
end
//控制信号
parameterDiv8Num=3;
reg ctrl;
reg[3:0]AddValue;
always@(posedge clk or posedge rst) begin
if(rst)begin //复位
ctrl<=0;
AddValue<=10-7;
end
elseif(AddValue<10) begin
ctrl<=0;
AddValue<=AddValue+Div8Num;
end
else begin
ctrl<=1;
AddValue<=AddValue-10;
end
end
//选择输出
reg clk_out;
always @(ctrlor posedge clk or posedge rst) begin
if(rst) clk_out<=0; //复位
elseif(ctrl) clk_out<=clk_div8;
elseclk_out<=clk_div9;
end
8.7分频器仿真图: