为什么要学习时钟分频
FPGA设计中,由于开发板上提供的晶振的频率是固定的,所以我们经常会对时钟进行分频和倍频,而时钟分频有两种方法,即使用IP核PLL和自己用verilog来描述,本文章重点讲解第二种方法。
奇数分频和偶数分频
第一种方法(不推荐)
上图为时钟的六分频波形图
-
偶数的分频较为简单,只需定义一个计数器,当计数到分频数的一半时,将输出信号取反即可
上图为时钟的五分频波形图
-
奇数的分频与偶数相比较为复杂,其中clk_1是根据时钟的上升沿对cnt进行判断是否翻转;而clk_2是根据时钟的下升沿对cnt进行判断是否翻转的。再通过观察波形得到规律,即clk_1与clk_2相与即为所需的五分频。
-
一般这种分频的时钟信号会被用于其他always模块中,例如
always @ (posedge clk_out)
,这样做和实际的硬件时钟相比还是有非常大的区别的,因为硬件厂商会专门为时钟信号设计了全局时钟网络,这会使所有的时钟信号到达寄存器的时间尽可能的相同,来保证最低的时钟抖动(两个时钟周期之间存在的差值)和时钟偏斜(同一个时钟信号到达两个不同寄存器之间的时间差值),所以使用系统时钟可以使高速系统更稳定,下面展示另一种改进的方法。
第二种方法
上图为时钟的六分频波形图
上图为时钟的五分频波形图
- 从上图我们可以看到五分频和六分频从设计上没有什么大的区别,只是输出的波形不在是按照占空比50%的输出了,而是在计数完成后输出一个时钟周期的高电平。而实际的使用例如控制动态数码管刷新,代码如下
//parameter define
parameter CNT_1MS = 16'd50_000 - 1'd1;
//cnt_1ms:从0计数到1ms的计数器
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_1MS)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'd1;
end
//flag_1ms :当cnt_1ms计数到CNT_1MS,产生一个时钟周期的高电平
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_1MS -1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
end
//sel:位选信号,用于数码管的快速刷新
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
sel <= 6'b111_110;
else if(flag_1ms == 1'b1)
sel <= {sel[4:0],sel[5]};
else
sel <= sel;
end
这个被拉高的信号就会被下一个always块中被使用
N分频模块代码
module divider_six(
input sys_clk ,
input sys_rst_n ,
output reg clk_flag
);
//parameter define
parameter N = 32'd5;
//通过修改N就可以更改为N分频
//reg define
reg [31:0] cnt;
//cnt:计数器从0到N循环计数
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 32'd0;
else if(cnt == N)
cnt <= 32'd0;
else
cnt <= cnt + 1'd1;
end
//clk_flag:脉冲信号指示N分频
always @ (posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk_flag <= 1'b0;
else if(cnt == N - 1'd1)
clk_flag <= 1'b1;
else
clk_flag <= 1'b0;
end
endmodule