1、介绍
分频器是一种常用的电路,主要用于降低输入信号的频率。它通过将输入时钟信号的频率分频,生成更低频率的时钟信号。分频器在数字电路、FPGA、单片机以及通信系统中都有广泛的应用。
其工作原理:是对输入信号进行计数。当计数到一定值后,输出信号状态发生变化,从而实现频率的降低。例如,二分频器每两个输入周期输出一个高电平和一个低电平,使输出频率为输入频率的一半。说到这里是不是有些小伙伴认为和计数器差不多,其实计数器主要用于计数输入脉冲的数量,能够输出当前的计数值,通常具有多个状态;而分频器专门用于降低信号频率,通过计数输入脉冲并在达到特定计数时改变输出状态,通常只有一个输出信号,频率为输入信号的某个分数。
实现分频通常有两种方法:一种是使用相位锁定环(PLL),在 FPGA 设计中可以直接利用 PLL 进行分频,但其受限于 PLL 本身的特性,例如输入 50MHz 时钟时,很多 PLL 可能无法实现 1MHz 的分频甚至更小的频率;另一种方法是通过 Verilog 代码直接实现分频器电路,这次我将带领大家手搓一个分频器。
2、实战任务
用逻辑派设计一个 5MHz 的频率输出电路
3、系统框图
根据实战任务要求,则本次实验需要两个输入的端口,分别为系统时钟( clk 表示本模块的时钟,如果前面加上 sys 则表示全局时钟,也就是整个工程的时钟都由它提供。 )和系统复位( rst 的全称是 Reset,在 RAM 、FAPG 中 Reset 代表重置信号,用于将系统或电路恢复到初始状态。如果前面加上 sys 则表示全局复位,也就是整个工程的复位都由它靠它。),输出为分频后的 clk_5m 的输出时钟,模块框图如下图所示:
4、波形图
我们需要得到5MHz的输出时钟,则需要对输入的系统时钟进行 10 分频,那需计数器计数 0~9 这 10 个数吗?不需要计数器计数 0 到 9 这 10 个数,因为我们实际上只需要计数 0 到 4(5 个状态),在每 5 个输入时钟周期内翻转一次输出时钟,从而实现 10 分频的目标。具体来说,输入的 50 MHz 时钟周期为 20 ns,因此每 5 个时钟周期(即 100 ns)就对应一个输出时钟周期,而这个周期正好是 5 MHz。因此,通过计数 0 到 4 实现 5 次计数,翻转输出时钟,就能有效地生成 5 MHz 的时钟信号,而不必完整计数 0 到 9。
5、程序编写
module divider(
input sys_clk, // 系统时钟输入
input sys_rst_n, // 系统复位信号,低电平有效
output reg clk_5m // 5 MHz 输出时钟
);
// 定义分频系数
parameter DIV_10 = 3'd5; // 最大计数值
// 3 位计数器,用于计数 0 到 4
reg [2:0] count;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
count <= 3'd0;
else if (count == DIV_10 - 3'd1) // 当计数到 4 时(5 次周期)
count <= 3'd0; // 计数器清零,准备下一个周期
else
count <= count + 3'd1; // 计数器加 1
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n)
clk_5m <= 1'd0;
else if (count == DIV_10 - 3'd1) // 当计数到 4 时(5 次周期)
clk_5m <= ~clk_5m; // 翻转输出时钟状态
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
程序注释:
第 1 - 5 行:根据系统框图的功能定义,两个输入信号,系统时钟 和 系统复位 ,输出信号为 5Mhz 的信号。
第 8 行: 宏定义一个最大计数值,方便后期修改(一般宏定义尽量大写,方便区分)。
第 11 行: 定义了一个三位宽的计数器。
第 13 - 20 行:该 always 语句块实现了一个计数器功能。
第 22 - 27 行:每当 count 达到最大值 - 1 的时候,就该将输出取反(为什么是最大值 - 1,因为计算机是从 0 开始计数的) 。
6、仿真编写
`timescale 10ns/10ns // 定义时间单位为10ns,时间精度为10ns
module divider_mod(); // 测试模块定义
// regdefine
reg sys_clk; // 系统时钟寄存器
reg sys_rst_n; // 系统复位信号寄存器,低电平有效
// wire define
wire clk_5m; // 输出的 5 MHz 时钟信号
always #10 sys_clk = ~sys_clk;
initial begin
sys_clk = 1'd0; // 初始化系统时钟为低电平
sys_rst_n = 1'd0; // 初始化复位信号为低电平,系统复位状态
#200; // 等待200ns
sys_rst_n = 1'd1; // 将复位信号拉高,解除复位,准备进入正常工作模式
end
// 例化模块
divider u_divider (
.sys_clk (sys_clk), // 连接系统时钟输入
.sys_rst_n (sys_rst_n), // 连接系统复位信号
.clk_5m (clk_5m) // 连接输出的 5 MHz 时钟信号
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
程序注释:
第 5 - 10 行:功能定义重定义。
第 14 - 21 行:初始系统时钟 和 系统复位,用于确定状态。
第 24 - 28 行:进行I/O绑定,例化。
7、工程创建
开始使用 Gowin 创建项目工程了,接着在 Gowin 的 Design 窗口空白地方右键点击选择 New Files... 添加文件,分别创建所需的文件 divider.v 和 divider_mod.v ,如下图所示:
开始新建工程将 Project_Name 和 Default Library Name 都改成 divider 名称,如下图所示:
分别添加我们仿真所需的文件divider.v 和 divider_mod.v,如下图所示:
接下来选择 Compile->Compile All ,编译没有问题后,在 Add Simulation Configuration 配置好对应的文件,如下图所示:
仿真结果如下:
由上面的仿真图,可以看出与我们的波形图的结果是一致的,证明我们的程序没有问题。
8、I/O 引脚绑定
在仿真验证完成后,开始 Synthesize 编译判断 half_adder.v 文件是否有语法错误,执行 Synthesize 编译,接下来使用 Gowin 对 时钟约束 和 引脚 进行分配并上板验证。引脚分配如下图所示:
本实验中,系统时钟、复位按键以及一个输出端口的管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | F10 | 复位 | LVCMOS33 |
clk_5m | output | R16 | 输出时钟 | LVCMOS33 |
Gowin 软件中 I/O Constraints 界面如下图所示:
时钟约束步骤如下图所示:
添加时钟约束,如下图所示:
需要更改内容,如下图所示:
注:为什么要做时钟约束这个操作呢!
1、确保信号在时钟边缘变化的时机是正确的。这有助于验证电路在预定频率下的功能和性能。
2、约束时钟信号的频率和相位可以确保设计在不同的工作条件下(如温度、电源电压)能够稳定运行。没有正确的约束,时钟可能会导致不稳定或不确定的行为。
3、时钟约束能够指导 FPGA 的布局和路由工具如何优化时钟路径,减少时钟延迟和抖动。
4、通常需要满足一定的时序规范,如最小和最大时钟周期、建立时间和保持时间等。时钟约束帮助确保设计符合这些要求。
然后点击保存即可,如下图所示:
此次分频器所需要文件,如下图所示:
9、RTL 原理图
接下来在过程管理窗口 Process > Place & Route > Run,来对代码布局布线进行综合,再点击 tools -> Shematic Viewer -> RTL Design Viewer 然后查看 RTL 电路图,如下图所示:
然后查看 RTL 电路图,在软件里 分频器 是怎样的,如下图所示:
10、程序下载
连接开发板的下载器,下载码流文件到开发板里面,下载完成之后,将示波器接入输出时钟引脚上面,即可查看波形,如下图所示: