12-分频器 -偶分频
1.分频器
计数器是对于时钟信号进行计数,板载晶振的时钟频率是固定的,有时候需要进行分频和倍频才能满足需要
开发板上只有一种晶振,只有一种频率的时钟,想要通过对与固定时钟进行分频或者是倍频的方式得到各个模块所需的时钟频率,得到比固定时钟快的时钟通过倍频,得到比固定时钟慢的时钟通过分频
- 分频和倍频都有两种方式:第一种是通过锁相环(PLL),另外一种是编写verilog代码
- 分频器是数字系统设计中最常见的基本电路之一,所谓分频就是把输入信号的频率变成成倍数地低于输入频率的输出信号
- 分频器原理是将输入的信号做为计数脉冲,计数器的输出端口的脉冲是按一定频率输出的,就可以看作是输出端口的分频
- 分频器分为偶数分频器和奇数分频器,分频器和计数器非常类似,有时可以认为是同一种东西
2.FPGA实现
- 实现对于固定时钟6分频的电路
2.1 模块框图和波形图
2.2 RTL
module divider_six(
input wire sys_clk,
input wire sys_rst_n,
output reg clk_out
);
reg [1:0] cnt;
// cnt变量
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 2'd0;
else if(cnt == 2'd2)
cnt <= 2'd0;
else
cnt <= cnt + 2'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_out <= 1'b0;
else if(cnt == 2'd2)
clk_out <= ~clk_out;
else
clk_out <= clk_out;
endmodule
- 创建项目,编译代码
2.3 Testbench
`timescale 1ns/1ns
module tb_divider_six();
reg sys_clk;
reg sys_rst_n;
wire [1:0] clk_out;
// 初始化时钟和复位信号
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20;
sys_rst_n = 1'b1;
end
// 模拟时钟信号
always #10 sys_clk = ~sys_clk;
// 模块的实例化
divider_six divider_six_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_out (clk_out)
);
endmodule
- 加载仿真代码,进行仿真设置之后进行仿真
2.4 上板验证
- 将信号输出到扩展IO口,通过示波器进行观察波形
- 重新进行编译,连接板卡(下载器和电源)
- 添加sof文件,进行程序下载
- 连接示波器
2.5 优化
- 这种做法是不严谨的,在低速系统中不易察觉,在高速系统中就容易出现问题,通过这种分频的方式表面上是对系统时钟进行了分频产生了新的低频时钟,上述得到的分频时钟,实际上与真正的分频时钟是有不同的;
- 在FPGA当中凡是时钟信号都要连接到全局时钟网络,全局时钟网络也叫做全局时钟树,是FPGA厂商专门针对时钟路径进行设计的,能够使时钟信号到达各个寄存器的时间尽可能相同,减少时序问题的产生,上面产生的分频信号没有连接到全局时钟网络上,但是外部晶振产生的时钟信号,通过管脚连接到了专用时钟引脚上,自然连接到了FPGA全局时钟网络中
- 在系统时钟工作下的信号比在上述分频信号工作下的信号更能在高速工作下保持稳定,如何对上述代码进行改进?使用时钟标志信号cnt_flag
module divider_six(
input wire sys_clk,
input wire sys_rst_n,
// output reg clk_out
output reg clk_flag;
);
reg [2:0] cnt;
// cnt变量
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 2'd0;
else if(cnt == 3'd5)
cnt <= 3'd0;
else
cnt <= cnt + 3'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_flag <= 1'b0;
else if(cnt == 3'd4) // flag信号是在计数最大值减一的时候产生一个周期脉冲
clk_flag <= 1'b1;
else
clk_flag <= 1'b0;
// 按照之前产生的分频时钟给变量a赋值
reg a ;
always @(posedge clk_out or negedge sys_rst_n) // 使用产生的分频时钟clk_out
if(sys_rst_n == 1'b0)
a <= 1'b0;
else
a <= a + 1'b1;
// 时钟标志位产生的分频时钟对于变量进行赋值
always@(posedge sys_clk or sys_rst_n) // 仍然使用系统时钟,更加稳定
if(sys_rst_n == 1'b0)
a <= 1'b0;
else if(cnt_flag == 1'b1)
a <= 1'b1;
endmodule
`timescale 1ns/1ns
module tb_divider_six();
reg sys_clk;
reg sys_rst_n;
wire [2:0] clk_flag;
// 初始化时钟和复位信号
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#20;
sys_rst_n = 1'b1;
end
// 模拟时钟信号
always #10 sys_clk = ~sys_clk;
// 模块的实例化
divider_six divider_six_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.clk_flag (clk_flag)
);
endmodule
产生一个用于标记 6 分频的 clk_flag 标志信号,这样每两 clk_flag 脉
冲之间的频率就是对 sys_clk 时钟信号的 6 分频,但是计数器计数的个数我们需增加一些,
如图 18-4 所示需要从 0~5 共 6 个数,否则不能实现 6 分频的功能。和方法 1 对比可以发
现,相当于把 clk_out 的上升沿信号变成了 clk_flag 的脉冲电平信号cnt_flag 是一样的道理),为后级模块实现相同的降频效果。**虽然这样会多使用一些寄存器资源,不过不用担心我们的系统是完全可以承担的起的,而得到的好处却远远大于这点资源的使用,能让系统更加稳定。
在后级模块中需要使用低频时钟的情况,我们就可以不用 clk_out 这种信号作为时
钟了,而是继续使用 sys_clk 系统时钟来作为时钟,但让其执行语句的条件以 clk_flag 信号
为高电平的时候有效。