12. 分频器¶
时钟对于FPGA是非常重要的,但板载晶振提供的时钟信号频率是固定的,不一定满足工程需求,所以使用分频或倍频产生需要的时钟是很有必要的。本章节我们带领读者开始分频器的学习。
12.1. 理论学习¶
数字电路中时钟占有很重要的地位,时间的计算都要以时钟作为基本的单元。一般来说我们使用的开发板上面只有一个晶振,即只有一种频率的时钟。但在数字系统设计中,经常需要对基准时钟进行不同倍数的分频而得到各模块所需的时钟频率,若是想得到比固定的时钟频率更慢的时钟,可以将该固定时钟进行分频,若是想得到比固定时钟 频率更快的时钟,则可以在固定时钟频率的基础上进行倍频。无论分频和倍频,我们都有两种方式可以选择,一种是器件厂商提供的锁相环(PLL,后面章节会讲解),另一种是自己动手来用 Verilog代码描述。
而我们用Verilog代码描述的往往是分频电路,即分频器。分频器是数字系统设计中最常见的基本电路之一。所谓“分频”,就是把输入信号的频率变成成倍数地低于输入频率的输出信号。它的原理是:把输入的信号作为计数脉冲,由于计数器的输出端口是按一定规律输出脉冲的,所以对不同的端口输出的信号脉冲,就可以看作是对输入信号的“分频”。至于分频频率是怎样的,由选用的计数器所决定。如果是十进制的计数器那就是十分频,如果是二进制的计数器那就是二分频,还有四进制、八进制、十六进制等等以此类推。
分频器是和计数器非常类似的功能,有时候甚至可以说就是一个东西,我们可以回想上一节中计数器的内容,可以观察我们的仿真波形,对比下时钟信号sys_clk和led_out信号的关系,你会发现led_out信号实际上就是对时钟信号sys_clk进行了分频。
12.2. 实战演练一¶
12.2.1. 实验目标¶
分频器分为偶数分频器和奇数分频器,我们在上一章计数器的例子中其实实现的就是偶数分频,这里我们也先偶数分频器开始介绍。本例我们将实现对系统时钟进行6分频的偶数分频电路的设计。
12.3. 程序设计¶
12.3.1. 模块框图¶
因为我们设计的是6分频电路,所以模块取名为divider_six,然后是端口信号的设计,首先必不可少的是时钟sys_clk和复位信号sys_rst_n,分频器和计数器一样一般都只作用于FPGA内部的信号,这里我们没有其他的外部输入信号,其输出也往往提供给FPGA内部信号使用,这里我们将分频模设计为一 个独立的模块。根据上面的分析设计出的Visio框图如图 18‑2所示。
图 18‑2 六分频模块框图
端口列表与功能总结如表格 18‑1所示。
表格 18‑1 六分频模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1Bit |
Input |
工作时钟,频率50MHz |
sys_rst_n |
1Bit |
Input |
复位信号,低电平有效 |
clk_out |
1Bit |
Output |
对系统时钟6分频后的信号 |
12.3.1.1. 波形图绘制¶
方法1实现:仅实现分频功能
首先绘制两个输入信号的波形sys_clk时钟信号和sys_rst_n复位信号,既然需要分频那肯定需要一个计数器,所以我们定义一个名为cnt的计数器,根据上一章的分析得知对于计数器我们要精确的控制它何时计数、何时清零,这里计数的开始我们没有特殊要求,即只要时钟正常工作且复位被释放我们就可以立刻进行计数 。那计数器计数到多少清零呢?我们需要对输入的系统时钟时钟进行6分频,那需计数器计数0~5这6个数吗?当然不需要,和上一章的思考过程是一样的,我们只需要让计数器从0计数到2,即计3个数就可以了,然后每当计数器计数到2的时候就让clk_out输出信号取反即可,如图 18‑3所示产生的clk_out输出信号就是对sys_clk时钟信号的6分频。
图 18‑3 六分频波形图
方法2实现:实用的降频方法
方法一中的clk_out输出信号是我们想要的分频后的信号,然后很多同学就直接把这个信号当作新的低频时钟来使用,并实现了自己想要的功能。大家肯定会觉得能够实现功能就一切OK了,而往往忽略了一些隐患的存在,如果你对FPGA的了解多一些就会理解其实这是不严谨的做法,这种做法所衍生的潜在问题在低速系统中不易 察觉,而在高速系统中就很容易出现问题。因为我们通过这种方式分频得到的时钟虽然表面上是对系统时钟进行了分频产生了一个新的低频时钟,但实际上和真正的时钟信号还是有很大区别的。因为在FPGA中凡是时钟信号都要连接到全局时钟网络上,全局时钟网络也称为全局时钟树,是FPGA厂商专为时钟路径而特殊设计的,它能够 使时钟信号到达每个寄存器的时间都尽可能相同,以保证更低的时钟偏斜(Skew)和抖动(Jitter)。而我们用这种分频的方式产生的clk_out信号并没有连接到全局时钟网络上,但sys_clk则是由外部晶振直接通过管脚连接到了FPGA的专用时钟管脚上,自然就会连接到全局时钟网络上,所以在sys_clk 时钟工作下的信号要比在clk_out时钟工作下的信号更容易在高速系统中保持稳定,既然发现了问题那我们改怎么办呢?这时可不要忘记了上一章中刚学到的flag标志信号,这里我们就可以用上了,我们可以产生一个用于标记6分频的clk_flag标志信号,这样每两clk_flag脉冲之间的频率就是对sys_clk 时钟信号的6分频,但是计数器计数的个数我们需增加一些,如图 18‑4所示需要从0~5共6个数,否则不能实现6分频的功能。和方法1对比可以发现,相当于把clk_out的上升沿信号变成了clk_flag的脉冲电平信号(和上一章方法2中的cnt_flag是一样的道理),为后级模块实现相同的降频效果。虽然这 样会多使用一些寄存器资源,不过不用担心我们的系统是完全可以承担的起的,而得到的好处却远远大于这点资源的使用,能让系统更加稳定。
图 18‑4 六分频降频方法波形图
这样在后级模块中需要使用低频时钟的情况,我们就可以不用clk_out这种信号作为时钟了,而是继续使用sys_clk系统时钟来作为时钟,但让其执行语句的条件以clk_flag信号为高电平的时候有效。
后级模块使用方法1 clk_out作为时钟信号工作的情况
always@(posedge clk_out or negedge sys_rst_n)
if(sys_rst_n == 1’b0)
A <= 4’b0;
else
A <= A + 1’b1;
后级模块使用方法2 sys_clk系统时钟继续作为工作时钟的情况
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1’b0)
A <= 4’b0;
else if(clk_flag == 1’b1)
A <= A + 1’b1;
上面两种例子实现的最终效果都是相同的,而方法2中的信号A是在sys_clk系统时钟的控制下产生的,和所有在sys_clk系统时钟下产生的信号都保持几乎相同的时钟关系,方法更优,也推荐大家在以后的设计中都使用方法2。
12.3.1.2. 代码编写¶
方法1实现:仅实现分频功能
1 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 | module divider_six
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
output reg clk_out //对系统时钟6分频后的信号
);
reg [1:0] cnt; //用于计数的寄存器
//cnt:计数器从0到2循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 2'b0;
else if(cnt == 2'd2)
cnt <= 2'b0;
else
cnt <= cnt + 1'b1;
//clk_out:6分频50%占空比输出
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;
endmodule
|
根据上面RTL代码综合出的RTL视图如图 18‑5所示,我们发现和上一章中方法1实现的不带标志信号的计数器所综合出的RTL视图是一模一样的,这也印证了他们之间的关系。
图 18‑5 六分频RTL视图(一)
方法2实现:实用的降频方法
1 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 | module divider_six
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
output reg clk_flag //指示系统时钟6分频后的脉冲标志信号
);
reg [2:0] cnt; //用于计数的寄存器
//cnt:计数器从0到5循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 3'b0;
else if(cnt == 3'd5)
cnt <= 3'b0;
else
cnt <= cnt + 1'b1;
//clk_flag:脉冲信号指示6分频
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_flag <= 1'b0;
else if(cnt == 3'd4)
clk_flag <= 1'b1;
else
clk_flag <= 1'b0;
endmodule
|
根据上面RTL代码综合出的RTL视图如图 18‑6所示,该RTL视图的结构分析和上一章方法2的带标志信号的计数器的RTL视图分析相同,只是结构上少了最后一级寄存器,其他都一样,这里不再进行详细介绍。
图 18‑6 六分频RTL视图(二)
12.3.1.3. 仿真验证¶
仿真文件编写
仿真方法2的RTL代码时,把Testbench中的“clk_out”全改为“clk_flag”即可。
1 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 | \`timescale 1ns/1ns
module tb_divider_six();
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
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
//--------------------divider_sixht_inst--------------------
divider_six divider_six_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.clk_out (clk_out ) //output clk_out
);
endmodule
|
仿真波形分析
方法1实现:仅实现分频功能
打开ModelSim执行仿真,仿真出来的波形如图 18‑7所示,我们让仿真运行了500ns,可以发现clk_out信号产生了几个周期的完整波形,我们在clk_out相邻两个上升沿的位置处分别放置参考线并添加频率显示,我们可以看到显示的频率为8.333MHz,而我们系统时钟sys_clk是50MHz的 ,大约为6分频的关系,从而验证了我们设计的是正确的。
图 18‑7 六分频仿真波形图(一)
方法2实现:实用的降频方法
打开ModelSim执行仿真,仿真出来的波形如图 18‑8所示,我们也让仿真运行了500ns,可以发现clk_flag脉冲标志信号每当计数器计数到5时产生一个时钟周期的脉冲,我们在clk_flag相邻两个下升沿的位置(也是下一级模块可以采集到clk_flag为高电平的位置)处分别放置参考线并添加频率 显示,我们可以看到显示的频率为8.333MHz,也是对sys_clk系统时钟的6分频,和我们绘制的波形图一致。
图 18‑8 六分频仿真波形图(二)
12.4. 上板验证¶
12.4.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 18‑2所示。引脚配置如图 18‑9所示。
表格 18‑2 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
Input |
E1 |
时钟 |
sys_rst_n |
Input |
M15 |
复位按键 |
clk_out |
Output |
F15 |
引出I/O口 |
图 18‑9 引脚配置图
12.4.1.1. 结果验证¶
如图 18‑10所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口。线路正确连接后,打开开关为板卡上电。
图 18‑10 程序下载连线图
如图 18‑11所示,使用“Programmer”为开发板下载程序。
图 18‑11 程序下载窗口
程序下载完毕后,如图 18‑12所示,使用示波器对输出I/O口F15进行频率测量,分频时钟信号频率为8.333MHz,刚好为输入50MHz时钟信号的6分频,达到预期效果。
图 18‑12 结果验证
12.5. 实战演练二¶
12.5.1. 实验目标-1¶
有偶数分频就有奇数分频,仅实现分频功能来讲其中的差别和实现方式还是很大的,奇数分频相对于偶数分频要复杂一些,并不是简单的用计数器计数就可以实现的。我们本节要实现将一个系统时钟进行5分频的奇数分频的功能。可以用于将高频的时钟降低为低频的时钟工作使用。
12.6. 程序设计-1¶
12.6.1. 模块框图-1¶
这里我们设计一个5分频的奇数分频器,模块我们取名为divider_five。奇数分频和偶数分频在模块的设计和分析上其实是一样的,只是其内部逻辑的实现上有所不同,所以模块的输入仍然是有时钟和复位信号,输出为对输入时钟分频后的结果clk_out。根据上面的分析设计出的Visio框图如图 18‑14所示。
图 18‑14 五分频模块框图
端口列表与功能总结如表格 18‑3所示:
表格 18‑3 无分频模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1Bit |
Input |
工作时钟,频率50MHz |
sys_rst_n |
1Bit |
Input |
复位信号,低电平有效 |
clk_out |
1Bit |
Output |
对系统时钟5分频后的信号 |
12.6.1.1. 波形图绘制¶
方法1实现:仅实现分频功能
奇数分频仅实现分频功能的实现方式不像偶数分频仅实现分频功能那样直接计数就可以了,而是需要我们先思考探索一下。在波形图的设计上首先画出时钟和复位两个输入信号,然后我们可以简单的画出5分频的波形大概是什么样子,如果我们依然采用偶数分频的方法,可以发现计数器计数变化的位置总是对应系统时钟sys_clk的上 升沿,所以分频后clk_out信号变化的位置也是对应系统时钟sys_clk的上升沿,这样我们最终得到的波形如图 18‑15所示,虽然也实现了奇数分频,但占空比却不是50%,同理如图 18‑16所示,使用下降沿的效果也是一样,也就是说如果我们像之前一样只用上升沿或下降沿计数的话显然是无法实现奇数分频效果的。那改怎么办呢?
图 18‑15 波形图(一)
图 18‑16 波形图(二)
于是我们先画出5分频的效果波形图再来分析,看看能发现什么规律。如图 18‑17所示,我们发现要实现5分频需要在系统时钟sys_clk的上升沿和下降沿都工作,我们之前的例子从没有遇到在一个模块中既使用上升沿又下降沿的情况。我们尝试着把图 18‑15、图 18‑16、图 18‑17的clk_out输出信号的波形放到一起来寻找规律,变成图 18‑18所示的波形,clk1波形的变化都是在系统时钟sys_clk上升沿时进行,clk2波形的变化都是在系统时钟sys_clk下降沿时进行,clk_out输出信号是我们想要的5分频的,红色加粗的虚线是5分频的变化位置,clk1和c lk2我们经过之前的练习可以很容易的根据cnt计数器的计数来产生,那clk_out输出信号该如何产生呢?我们仔细观察,发现clk1和clk2相与的结果就是clk_out的波形,真是太好了!可能有同学并不能立刻想到这种方法,但是如果比较熟练或者善于观察还是很容易发现规律的。那么奇数分频的波形设计我们就 算完成了,这里clk1和clk2都是低电平2个时钟周期,高电平3个时钟周期,大家可以尝试下如果低电平是三个时钟周期,高电平时2个时钟周期,我们的波形该怎么画。
图 18‑17 五分频波形图
图 18‑18 五分频产生波形图
方法2实现:实用的降频方法
奇数分频同样也会遇到和偶数分频相同的问题,就是我们也不能直接将奇数分频的信号作为下一级的时钟,所以我们也要使用clk_flag的方式实现。虽然奇数分频仅实现分频功能的实现方式和偶数分频仅实现分频功能的差异很大,但他们实用的降频方法都是相同的道理,如图 18‑19所示,在波形上除了cnt计数器计数的个数不同外,其他的都是一样的。
图 18‑19 五分频降频方法波形图
12.6.1.2. 代码编写¶
方法1实现:仅实现分频功能
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | module divider_five
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
output wire clk_out //对系统时钟5分频后的信号
);
reg [2:0] cnt;
reg clk1;
reg clk2;
//cnt:上升沿开始从0到4循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 3'b0;
else if(cnt == 3'd4)
cnt <= 3'b0;
else
cnt <= cnt + 1'b1;
//clk1:上升沿触发,占空比高电平维持2个系统时钟周期,低电平维持3个系统时钟周期
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk1 <= 1'b1;
else if(cnt == 3'd2)
clk1 <= 1'b0;
else if(cnt == 3'd4)
clk1 <= 1'b1;
//clk2:下降沿触发,占空比高电平维持2个系统时钟周期,低电平维持3个系统时钟周期
always@(negedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk2 <= 1'b1;
else if(cnt == 3'd2)
clk2 <= 1'b0;
else if(cnt == 3'd4)
clk2 <= 1'b1;
//clk_out:5分频50%占空比输出
assign clk_out = clk1 & clk2;
endmodule
|
根据上面RTL代码综合出的RTL视图如图 18‑20所示,该RTL视图已经比较复杂了,但是如果仔细分析还是可以进行分析的,如果系统再大一些就很难分析了,而更复杂的系统我们如果再对其内部继续和之前一样面面俱到的分析意义不是很大,因为我们使用Verilog硬件描述语言来描述硬件的行为就是要跳出这种对底层 的复杂设计,而只关心其功能的实现,所以后面我们将主要把重点放在对行为和层次化结构的实现。但有时候在进行局部优化时我们还会进行局部的分析,而不是低效率的全局分析,但是无论怎样,通过前面的讲解大家一定要有一个硬件的思想。
图 18‑20 五分频波形图(一)
方法2实现:实用的降频方法
1 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 | module divider_five
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
output reg clk_flag //指示系统时钟5分频后的脉冲标志信号
);
reg [2:0] cnt; //用于计数的寄存器
//cnt:计数器从0到4循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 3'b0;
else if(cnt == 3'd4)
cnt <= 3'b0;
else
cnt <= cnt + 1'b1;
//clk_flag:脉冲信号指示5分频
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;
endmodule
|
根据上面RTL代码综合出的RTL视图如图 18‑21所示,该RTL视图的结构分析和上一章方法2的带标志信号的计数器的RTL视图分析相同,结构上也是少了最后一级寄存器,这里不再进行详细介绍。
图 18‑21 五分频波形图(二)
12.6.1.3. 仿真验证¶
仿真文件编写
仿真方法2的RTL代码时,把Testbench中的“clk_out”全改为“clk_flag”即可。
1 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 | \`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
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 sys_clk = ~sys_clk;
//-----------------------divider_five_inst------------------------
divider_five divider_five_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.clk_out (clk_out ) //output clk_out
);
endmodule
|
仿真波形分析
方法1实现:仅实现分频功能
打开ModelSim执行仿真,仿真出来的波形如图 18‑22所示,我们让仿真运行了500ns,可以发现clk_out信号产生了几个周期的完整波形,我们在clk_out相邻两个上升沿的位置处分别放置参考线并添加频率显示,我们可以看到显示的频率为10MHz,而我们系统时钟sys_clk是50MHz的,大约为5分频的关系,从而验证了我们设计的是正确的。
图 18‑22 五分频仿真波形图(一)
方法2实现:实用的降频方法
打开ModelSim执行仿真,仿真出来的波形如图 18‑23所示,我们也让仿真运行了500ns,和偶数的降频方法一样,可以发现clk_flag脉冲标志信号每当计数器计数到4时产生一个时钟周期的脉冲,我们在clk_flag相邻两个下升沿的位置(也是 下一级模块可以采集到clk_flag为高电平的位置)处分别放置参考线并添加频率显示,我们可以看到显示的频率为10MHz,也是对sys_clk系统时钟的5分频,和我们绘制的波形图一致。
图 18‑23 五分频仿真波形图(二)
12.7. 上板验证-1¶
12.7.1. 引脚约束-1¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 18‑4所示。引脚配置如图 18‑24所示。
表格 18‑4 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
Input |
E1 |
时钟 |
sys_rst_n |
Input |
M15 |
复位按键 |
clk_out |
Output |
F15 |
引出I/O口 |
图 18‑24 引脚配置图
12.7.1.1. 结果验证¶
如图 18‑25所示,开发板连接12V直流电源和USB-Blaster下载器JTAG端口。线路正确连接后,打开开关为板卡上电。
图 18‑25 程序下载连线图
如图 18‑26所示,使用“Programmer”为开发板下载程序。
图 18‑26 程序下载窗口
程序下载完毕后,如图 18‑27所示,使用示波器对输出I/O口F15进行频率测量,分频时钟信号频率为10MHz,刚好为输入50MHz时钟信号的5分频,达到预期效果。
图 18‑27 结果验证
12.8. 章末总结¶
本章主要讲解了时序逻辑电路中最常用的偶数分频和奇数分频的实现,并详细讲解了仅实现分频功能的分频器和实用的降频方法,希望大家能够理解其中的差别和产生这种用法的意义。学会一步步根据需求分析问题如何实现,要多尝试、敢尝试、多联系,学过的知识要能够用上。对于实用的降频方法除了本章讲解的以外,后面的IP核章节 还会讲解到通过PLL(Phase Locked Loop,即锁相环)的方法来实现对时钟的任意分频、倍频、相位移动,非常强大。
知识点总结
1、能够自己实现任意整数的分频;
2、进一步学会使用操作计数器、flag标志信号等常用到的知识点;
3、深刻体会仅实现分频功能和实用的降频方法的区别;
4、理解实用的降频方法的产生原因实用意义。
12.9. 拓展训练¶
根据上一章中我们学习到的parmaeter参数化的方法将偶数分频和奇数分频都做成通用的任意分频模块,方便以后的调用。