一文搞懂FPGA的Verilog分频

0.引言

分频器是指输出信号频率为输入信号频率整数分支一的电子电路,在许多的电子设备中需要各种不同的信号协同工作,例如电子钟,频率合成器,常用的方法是以稳定度高的晶体振荡器为主振源,通过变换得到多需要的各种频率成分,分频器是一种主要的变换手段。早期的分频器多为正弦分频器,随着数字集成电路的发展,脉冲分频器渐渐取代了正弦分频器。下面以Verilog HDL为基础介绍占空比为50%的分频器。

1.偶分频

偶分频是指分频系数为2,4,6,8等偶数,我们可以直接分频。
例如,对一个输入时钟进行6分频,clk为100MHz,分频后的频率为100/6(MHz)。
分频思路:因为是偶分频,所以分频系数必然是2的倍数,我们只需要利用一个计数器计数到分频系数的一半减一(减一是因为从零开始计数),令其为高电位,令一半为低电位,即可实现分频。
Verilog代码如下:
module fenpin(
clk_in, //定义输入时钟
rst_n, //系统时钟
clk_out); //定义输出时钟

input clk_in;
input rst_n;
output reg clk_out; //在always 模块内被赋值的每一个信号都必须定义成reg型(其默认初始值为不定值x

parameter N = 6; //定义分频参数
reg [3:0]cnt; //定义了一个四位的寄存器

always @(posedge clk_in or negedge rst_n)
begin
if(!rst_n)
begin
cnt <= 4’b0000;
clk_out <= 1’b0;
end
else if(cnt ==(N/2-1))
begin
clk_out <= ~clk_out;//反转波形
cnt <= 4’b0000;
end
else
cnt = cnt +1;
end
endmodule

仿真程序:

·timescale 1ns/1ps //时间单位/时间精度
module fenpin_tb();
reg clk_in;
reg rst_n;
wire clk_out;
parameter DELY = 10;
fenpin u_fenpin(
.clk_in(clk_in),
.rst_n(rst_n),
.clk_out(clk_out)
);
always #(DELY/2) clk_in = ~clk_in;

initial begin
$fsdbDumpfile(“fenpin_even.fsdb”);//指定fsdb的文件名
$fsdbDumpvars(0,u_fenpin);//要记录的信号,为0表示记录所有
end

initial begin
clk_in = 0;
rst_n = 0;
#DELY rst_n =1;
#((DELY)*20)
$finish;//退出仿真器,返回主操作系统(默认参数为1)
end
endmodule

一文搞懂FPGA的Verilog分频_第1张图片
可以看到,clk上升沿时,采样到cnt=2时,就翻转,采样到0和1时,就保持,这样就可以做到一半低电平,一半高电平了。

2.奇数分频

由于奇数分频要保持50%的占空比,所以不能像偶数分频时那样,直接在分频系数的一半时时电平翻转。在此,我们需要利用输入时钟的上升沿和下降沿来进行设计:
例如我们要设计一个5分频的模块,设计思路如下:
采用计数器cnt1进行计数,在时钟上升沿开始加一操作,计数器数值为0和1时,输出时钟信号clk_div为高电平,计数器为2,3,4时,输出时钟信号clk_div为低电平,计数到5时清零,从头开始计数,我们可以得到占空比为40%的波形clk_div1.
采用计数器cnt2进行计数,在时钟下降沿开始加一操作,计数器数值为0和1时,输出时钟信号clk_div为高电平,计数器为2,3,4时,输出时钟信号clk_div为低电平,计数到5时清零,从头开始计数,我们可以得到占空比为40%的波形clk_div2.
clk_div1和clk_div2上升沿到来时间相差半个输入时钟周期,所以将这两个信号进行或操作,即可得到占空比为50%的5分频时钟周期。

奇分频代码:

//奇数分频
module fenpin_qi(
clk,
rst_n,
clk_div
);
input clk;
input rst_n;
output clk_div;
wire clk_div;

parameter NUM_DIV = 5;//分频参数
//define counter
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;//assign语句只能对wire型赋值,在always块中只能对reg型赋值

endmodule

仿真代码:

//奇数分频仿真
module tb_fenpin_qi();
reg clk;
reg rst_n;
wire clk_div;
parameter DELY=100;
fenpin_qi u_fenpin_qi(
.clk (clk ),
.rst_n (rst_n ),
.clk_div (clk_div)
);

always #(DELY/2) clk=~clk;//产生时钟波形

initial begin
$fsdbDumpfile(“fenpin_qi.fsdb”);//指定fsdb文件名
$fsdbDumpvars(0,u_fenpin_qi);//指定需要保存记录的变量,0表示记录所有
end
initial begin
clk=0;
rst_n=0;
#DELY rst_n=1;
#((DELY*20)) $finish;
end
endmodule
一文搞懂FPGA的Verilog分频_第2张图片

3.任意占空比的任意分频

假设FPGA的系统时钟为50MHz,而我们要产生的频率为880Hz,那么我们需要对系统时钟进行分频,很容易用计数的方式可以想到50,000,000/880=56818。显然这个数字不是2的整数幂次方,那么我们可以设定一个参数,让它到56818重新计数就可以实现了。
程序代码如下:

module fenpin_renyi(
clk,
rst_n,
clk_div
);

input clk,rst_n;
output clk_div;
wire clk_div;

reg [15:0] counter;
//定义一个计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
counter <= 0;
else if(counter==56817)
counter <= 0;
else
counter <= counter+1;

assign clk_div = counter[15];
endmodule

仿真代码:

//tb
module tb_fenpin_renyi();
reg clk;
reg rst_n;
wire clk_div;
parameter DELY=100;

fenpin_renyi u_fenpin_renyi(
.clk (clk ),
.rst_n (rst_n),
.clk_div(clk_div)
);
always #(DELY/2) clk=~clk;//产生时钟波形

initial begin
$fsdbDumpfile(“fenpin_renyi.fsdb”);
$fsdbDumpvars(0,u_fenpin_renyi);
end
initial begin
clk=0;
rst_n=0;
#DELY rst_n=1;
#((DELY*80000)) $finish;
end
endmodule

分频的应用很广泛,一般的做法先用高频时钟计数,然后使用计数器的某一位输出为工作时钟进行其他的逻辑设计,上面的逻辑就是一个体现,下面让我们来计算一下占空比,我们知道,输出波形在0到32767(2的14次方)的时候为低,在32768到56817的时候为高,占空比为40%多一些。
如果我们需要占空比为50%,那么只需要设置一个参数,使它为56817的一半,达到的时候翻转波形,就可以实现波形了。
程序如下:28408=56817/2-1,计数到28408时就清零,翻转,其余的计数期间保持不变。
设计代码

module fenpin_renyi(
clk,
rst_n,
clk_div
);

input clk,rst_n;
output clk_div;
wire clk_div;

reg [14:0] counter;
//定义一个计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
counter <= 0;
else if(counter==28408)
counter <= 0;
else
counter <= counter+1;

assign clk_div = counter[15];
endmodule

仿真代码:

//tb
module tb_fenpin_renyi();
reg clk;
reg rst_n;
wire clk_div;
parameter DELY=100;

fenpin_renyi u_fenpin_renyi(
.clk (clk ),
.rst_n (rst_n),
.clk_div(clk_div)
);
always #(DELY/2) clk=~clk;//产生时钟波形

initial begin
$fsdbDumpfile(“fenpin_renyi.fsdb”);
$fsdbDumpvars(0,u_fenpin_renyi);
end
initial begin
clk=0;
rst_n=0;
#DELY rst_n=1;
#((DELY80000)) $finish;
end
endmodule
一文搞懂FPGA的Verilog分频_第3张图片
让我们继续来看如何实现任意占空比,比如还是有50MHz分频产生880Hz,而分频得到的信号的占空比为30%,56818
30%=17045
设计代码:

module fenpin_renyi(
clk,
rst_n,
clk_div,
counter
);

input clk,rst_n;
output clk_div;
wire clk_div;
output [15:0]counter;
reg [15:0] counter;
//定义一个计数器
always @(posedge clk or negedge rst_n)
if(!rst_n)
counter <= 0;
else if(counter==56817)
counter <= 0;
else
counter <= counter+1;

always @(posedge clk or negedge rst_n)
if(!rst_n)
clk_div <= 0;
else if(counter<17045)
clk_div <= 1;
else
clk_div <= 0;
endmodule

仿真代码:

//tb
module tb_fenpin_renyi();
reg clk;
reg rst_n;
wire clk_div;
wire [15:0]counter;
parameter DELY=100;

fenpin_renyi u_fenpin_renyi(
.clk (clk ),
.rst_n (rst_n),
.counter(counter),
.clk_div(clk_div)
);
always #(DELY/2) clk=~clk;//产生时钟波形

initial begin
$fsdbDumpfile(“fenpin_renyi.fsdb”);
$fsdbDumpvars(0,u_fenpin_renyi);
end
initial begin
clk=0;
rst_n=0;
#DELY rst_n=1;
#((DELY*80000)) $finish;
end
endmodule

4.总结及注意事项

(1)bject “count_clr” on left-hand side of assignment must have a net type
这个意思是assign语句只能对wire型变量赋值。
(2)Error (10137): Verilog HDL Procedural Assignment error at fre_ctr.v(6): object “count_en” on left-hand side of assignment must have a variable data type
在always块语句里只能是reg型变量赋值

你可能感兴趣的:(verilog学习,verilog,嵌入式,fpga)