文章目录
那么在上一章节当中,我们对分频器的相关理论知识做了一个系统性的讲解,那么重点讲解了我们的偶数分频,并且通过实验工程使用分频和降频两种方法,设计并实现了将系统时钟进行六分频的偶数分频电路,而且通过了上板验证。那么既然有偶数分频就一定存在奇数分频,那么本章节的主要内容就针对我们的奇数分频。
在分频功能的实现方式上,我们的奇数分频和偶数分频有很大的差别。那么偶数分频使用计数器计数就可以实现,而我们的奇数分频相对于偶数分频要更复杂一些。
那么在上一章节当中已经对分频器的相关理论知识做了系统性的讲解,那么这儿就不再进行理论知识的讲解,我们直接开始实战演练。
1 实战演练
1.2 设计规划
1.2.1 实验目标
在实战演练部分我们使用实验工程,采用与六分频相同的方法就是:降频和分频的方法,设计并实现一个对系统时钟进行五分频的奇数分频电路。
1.2.2 硬件资源
同样的使用我们的扩展 I/O 口,输出我们生成的分频时钟,使用逻辑分析仪对它进行测量,来验证我们的实验工程。
1.3 程序设计
实验目标和验证方法了解了之后,我们开始程序的设计。
首先是搭建文件体系
然后打开 doc 文件夹,新建一个 Visio 文件,用来绘制模块框图和我们的波形图
1.3.1 模块框图
那么首先开始模块框图的设计。那么模块框图的设计可以参照我们的六分频。那么输入信号和输出信号与我们六分频是一样的,那么输入信号只有时钟信号和我们的复位信号,那么输出信号是连接到我们开发板的扩展 I/O 口,方便我们的测量
模块框图绘制完成,接下来开始波形图的绘制。
1.3.2 波形绘制
我们刚才已经提到了:我们将采用与六分频相同的方法来实现我们的分频器。第一种方法就是分频的方法,第二种方法是降频的方法。首先是分频的方法。
我们先来绘制输入信号的波形
那么输入信号的波形绘制完成。
我们先来看一下我们六分频它的波形是怎么绘制的。那么使用分频的方法来实现的六分频,它的波形是这样的
计数器初值为 0 从 0 开始计数,每个时钟周期自加一,计数到最大值是 2 计数到最大值波形进行反转一次,就生成了我们输出的时钟信号。
但是我们的五分频不太适合这种方法,因为我们不可能计数到 2.5 对它进行一个反转;所以说,我们这儿的计数值最大值应该计数到 4,就是 0~4 计数五次,刚好对应我们的五分频;那么计数器它的初值同样为 0 那么在时钟的上升沿进行计数,那么下一个就是 1 那么后面就是 2、3 那么它计数到最大值是 4 那么 0~4 是 5 次计数,刚好与五分频对应
那么计数器的波形绘制完成,那么接下来开始我们输出波形的绘制。那么输出信号的波形如何绘制呢?
我们先来尝试一下。首先给它一个初值为 0 当我们的计数器处于 0~2 的计数范围时让它保持低电平,那么计数到 2 时把它拉高为高电平,然后让它保持这个高电平;当计数到 4 的时候再把高电平拉低,然后在下一个循环 0~2 的计数范围内还是让它保持低电平。这样我们尝试着绘制了一下输出信号的波形
那么这样看似是分频,但是它的占空比并不是 50%。
那么通过观察我们发现,输出信号我们采用的是上升沿采样。那如果我们使用下降沿采样呢?我们来试一下:初值仍为 0 使用下降沿采样,那么计数到 2 时,然后在下降沿将它变为高电平,然后让它保持高电平;当我们计数值为 4 且在时钟的下降沿,把它拉低;这儿添加一条参考线
那么这儿我们采用下降沿采样的方式,尝试着绘制了我们的输出信号的波形。
但是通过观察我们发现:它的占空比也不是 50% 其实它可以看作是第一次绘制波形,向左平移了半个时钟周期。这也不是我们想要的输出信号的波形。那么输出信号的波形应该是什么样子呢?
它应该是这个样子
初值还是低电平,那么在 ❶ 这个位置把它拉高,然后保持一段时间的高电平;到 ❷ 这个位置给它拉低,然后保持它的低电平;到 ❸ 这个位置给它拉高。那么这个波形才是我们想要的输出信号的一个波形,那么这个波形应该怎么得到呢?
通过观察我们发现:如果说我们前面绘制的两路输出信号的波形,在组合逻辑下进行一个或运算就可以得到这个波形。0 与 0 或运算结果是 0,那么 0 与 1 或运算还是 1。刚好能够实现一个占空比为 50% 的五分频信号。
那 ❶ 到 ❸ 是它的一个时钟周期,刚好对应我们五个系统时钟的周期,那么这个波形就实现了我们系统时钟的五分频。
我们来把它整理一下
那么刚刚绘制的这两路波形,就可以作为中间变量。
1.4 代码编写
然后编写我们的代码
然后参照我们的波形图来进行代码的编写。模块开始、模块名称,然后是端口列表,然后是我们的模块结束
然后输入时钟信号,然后是我们的复位信号,然后是输出信号
那么端口列表编写完成,下面声明我们的变量。
第一个变量是我们的计数器 cnt
那么 0~4 计数需要 3 个位宽就是 [2:0];然后是我们的 clk_1
、我们的 clk_2
那么接下来开始变量的赋值,我们同样使用 always 语句,我们使用异步复位;当我们的复位信号有效时也就是低电平时,我们的计数器给它一个初值 0;然后当它计数到最大值 4 让它归零;当我们的复位信号无效时,就是复位信号为高电平无效时,我们的计数器并没有计数到最大值,没有计数到最大值就是 0、1、2、3 的时候,让它进行自加一
那么下面开始 clk_1
变量的赋值,同样的我们使用 always 语句,使用的是异步复位;然后在复位信号有效时,给它一个初值是低电平;然后当计数器计数到 2 时把它拉高;当计数器计数到最大值 4 的时候,再把它拉低;当我们的复位信号无效时为高电平,那么计数器不是 2 也不是 4 的时候,让它保持原来的电平
那么这样,变量 clk_1
已经赋值完成。下面开始变量 clk_2
的赋值。我们的变量 clk_2
同样使用 always 语句进行赋值,但是这儿有一点要注意:我们的 clk_2
它使用的是时钟信号的下降沿,那么敏感列表要做一下修改;当我们的复位信号有效时,它的初值为 0;在我们时钟信号的下降沿,计数器计数到 2 时把低电平拉高;在时钟的下降沿,计数器计数到最大值 4 把高电平拉低;其他时刻保持电平
那么这样,两个变量 clk_1
、clk_2
它们的代码编写完成。
下面开始输出信号代码的编写。我们的输出信号使用组合逻辑进行赋值,这样就不会延迟一个时钟周期。我们使用 assign 语句。我们的输出信号是由两个信号 clk_1
、clk_2
它们取或运算得到的
代码编写完成,我们保存
divider_five.v
module divider_five
(
input wire sys_clk ,
input wire sys_rst_n ,
output reg clk_out
);
reg [2:0] cnt;
reg clk_1;
reg clk_2;
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt <= 1'b0;
else if (cnt == 3'd4)
cnt <= 1'b0;
else
cnt <= cnt + 3'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_1 <= 1'b0;
else if (cnt == 3'd2)
clk_1 <= 1'b1;
else if (cnt == 3'd4)
clk_1 <= 1'b0;
else
clk_1 <= clk_1;
always@(negedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_2 <= 1'b0;
else if (cnt == 3'd2)
clk_2 <= 1'b1;
else if (cnt == 3'd4)
clk_2 <= 1'b0;
else
clk_2 <= clk_2;
assign clk_out = clk_1 | clk_2;
endmodule
1.5 代码编译
下面对代码进行编译,检验我们的语法错误。
我们回到桌面,建立实验工程
然后添加我们的代码,然后进行一次全编译;出现了报错信息,更正代码后重新编译,编译完成,点击 OK
divider_five.v
module divider_five
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire clk_out
);
reg [2:0] cnt;
reg clk_1;
reg clk_2;
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt <= 1'b0;
else if (cnt == 3'd4)
cnt <= 1'b0;
else
cnt <= cnt + 3'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_1 <= 1'b0;
else if (cnt == 3'd2)
clk_1 <= 1'b1;
else if (cnt == 3'd4)
clk_1 <= 1'b0;
else
clk_1 <= clk_1;
always@(negedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_2 <= 1'b0;
else if (cnt == 3'd2)
clk_2 <= 1'b1;
else if (cnt == 3'd4)
clk_2 <= 1'b0;
else
clk_2 <= clk_2;
assign clk_out = clk_1 | clk_2;
endmodule
查看一下 RTL 视图
这个就是我们的 RTL 代码综合出来的 RTL 视图。那么这个 RTL 视图已经比较复杂了,但是如果仔细分析还是可以进行分析的;如果说我们的系统再大一些就很难分析了,更复杂的系统我们如果再对其内部继续和之前一样的面面俱到的分析,意义不是很大;因为我们使用 Verilog 硬件描述语言来描述硬件的行为,目的就是要跳出这种最底层的复杂设计,只是关心功能的实现;那么所以说后面我们将会把重点放在对行为和层次化结构的实现上,但有时候在进行局部优化时我们还会进行局部的分析,不会采用这种低效率的全局分析;所以这儿 RTL 视图不再进行分析了,我们只是看一下。
那编译通过之后下面开始仿真验证。
1.6 逻辑仿真
那么仿真文件我们可以直接使用六分频的仿真文件,但是这儿需要进行一些修改:首先是改一下名称,然后打开它
改一下模块内部的名称,还有实例化的名称和模块的名称,那么输出信号名称也需要做一些修改,因为我们这儿的输出端口是 clk_out
;选中它,使用快捷键 Ctrl+H 在这儿输入 clk_out
全部替换
那么修改完成之后保存
tb_divider_five.v
`timescale 1ns/1ns
module tb_divider_five();
reg sys_clk;
reg sys_rst_n;
wire clk_out;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
divider_five divider_five_inst
(
.sys_clk (sys_clk),
.sys_rst_n(sys_rst_n),
.clk_out (clk_out)
);
endmodule
回到我们的实验工程,加载我们的仿真文件,然后进行仿真设置
然后点击 RTL Simulation 这个位置开始仿真
那么仿真编译完成,打开 sim 窗口添加我们的模块波形;全选、分组、消除前缀,然后选择 Restart 那么这儿的时间参数设置为 500ns 运行一次、全局视图;我们调整一下波形的位置,方便我们的查看
我们参照着绘制的波形图来看一下我们的仿真波形图。
首先是计数器:那么计数器初值为 0 每个时钟周期加一 1、2、3、4 每个时钟周期加一;计数到最大值归零,然后开始下一个循环的计数
那么计数器是没有问题的。
然后看一下变量 clk_1,在计数到 2 时拉高;然后计数到 4 的时候拉低,对应时钟的上升沿,没有问题
看一下变量 clk_2,初值为 0;那么在时钟的下降沿,计数器计数到 2 时拉高;然后在时钟的下降沿,计数器计数到 4 的时候拉低
然后看一下输出信号。首先使用两个参考线看一下它的频率,它的频率是 10MHz 刚好是 50MHz 的五分频;输出时钟信号是由两个时钟变量取或运算得到的,而且使用的是组合逻辑
仿真波形与我们绘制的波形图是一致的,仿真验证通过。
1.7 上板验证
我们回到我们的实验工程,开始绑定我们的管脚。我们将我们的输出信号与扩展 I/O 口 F15 端口相绑定;我们的时钟输入端口是 E1;复位信号输入端口是 M15
那么引脚绑定完成之后回到实验工程,进行一次全编译,编译完成,点击 OK
那么如下图所示连接下载器、电源,下载器的另一端连接我们的电脑,给开发板上电
回到我们的实验工程,点击 Programmer 这个位置,打开我们的下载界面;添加我们的 SOF 文件,点击开始,进行程序的下载,那么程序下载成功
将逻辑分析仪的通道0——CH0和征途Pro开发板上扩展IO口的F15引脚相连接,存储深度设置为 200KSa、采样率设置为 100MHz,执行单次采样得到的结果是
输出的时钟分频信号的时钟频率是 10MHz 刚好是系统时钟的五分频。那么这样上板验证正确,验证通过。
1.8 使用降频的方法实现五分频
回到我们的 Visio 文件。我们可以参照一下六分频的波形,使用降频的方法实验六分频它的波形如图所示
它计数的最大值是 5,如果使用降频的方法实现五分频,这儿计数的最大值应该是 4 所以说我们只需要简单的修改就可以使用
那么这样波形图绘制完成,我们参照波形图修改一下我们的代码。
我们的输出信号重新命名,这儿使用 reg 型;输出信号初值为低电平,也就是 0;然后当计数器计数到最大值减一的时候,拉高一个时钟周期的高电平;那么其他时刻让它保持低电平
这样代码修改完成,保存
divider_five.v
module divider_five
(
input wire sys_clk ,
input wire sys_rst_n ,
// output wire clk_out
output reg clk_flag
);
reg [2:0] cnt;
/* reg clk_1;
reg clk_2;
*/
always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
cnt <= 1'b0;
else if (cnt == 3'd4)
cnt <= 1'b0;
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'd3)
clk_flag <= 1'b1;
else
clk_flag <= 1'b0;
/* always@(posedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_1 <= 1'b0;
else if (cnt == 3'd2)
clk_1 <= 1'b1;
else if (cnt == 3'd4)
clk_1 <= 1'b0;
else
clk_1 <= clk_1;
always@(negedge sys_clk or negedge sys_rst_n)
if (sys_rst_n == 1'b0)
clk_2 <= 1'b0;
else if (cnt == 3'd2)
clk_2 <= 1'b1;
else if (cnt == 3'd4)
clk_2 <= 1'b0;
else
clk_2 <= clk_2;
assign clk_out = clk_1 | clk_2;
*/
endmodule
回到我们的实验工程,重新进行编译;那么编译完成,点击 OK
然后看一下 RTL 视图
那么这儿生成的 RTL 视图与我们六分频采用降频的方式生成的 RTL 视图,是一样的。
下面进行代码的仿真验证。修改一下仿真代码
然后保存
tb_divider_five.v
`timescale 1ns/1ns
module tb_divider_five();
reg sys_clk;
reg sys_rst_n;
wire clk_flag;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
divider_five divider_five_inst
(
.sys_clk (sys_clk),
.sys_rst_n(sys_rst_n),
.clk_flag (clk_flag)
);
endmodule
回到我们的 ModelSim 全选,然后删除所有的波形;回到我们的 Library 对修改的两个代码进行重编译,那么这儿显示重编译完成,我们回到 sim 重新添加我们的模块波形;然后我们点击一下 Restart 清除所有的波形;然后运行 500ns
下面参照我们绘制的波形图,来看一下我们的仿真波形图
那么计数器部分,初值为 0 最大值为 4 计到最大值归零,开始下一循环计数;那么时钟频率也是 10MHz 刚好是系统时钟的五分频。
仿真验证通过,回到我们的实验工程,然后重新绑定引脚,因为输出信号的名称已经改变了。
选择我们的 clk_out 使用键盘上的 delete 进行删除,点击 OK、Yes 然后这儿重新绑定 F15 端口
然后重新进行一个全编译,编译完成之后点击 OK
下载我们的程序
它的频率也是 10MHz
那么上板验证完成,验证通过。
那么以上就是本章节的全部内容。经过本章节与上一章节的讲解,我们了解了时序逻辑电路当中最常用的偶数分频和奇数分频的实现方法,而且详细讲解了,仅实现分频功能的分频器和实用的降频方法。希望大家能够理解这两种方法的差别和产生这种用法的意义,那么在后面的章节我们还会讲到,通过 PLL(锁相环)实现时钟的任意分频、倍频、相位移动,它的功能是非常强大的。
参考资料: