1 功能需求
输入时钟 A 的频率是 25MHz ,产生一个400KHz的低频时钟 B 。
2 分频思路
频率是时钟周期的倒数。
时钟周期的单位是,秒/周期,即完成一个时钟需要多少秒。频率的单位是,周期/秒,即一秒内产生多少个时钟。
频率越大的时钟,相同时间内产生的时钟越多。
输入时钟 A 的频率是 25MHz ,低频时钟 B 的频率是 400KHz 。说明相同时间内,A 的周期数是 B 的 25M/400K = 62.5 倍。也可以理解为 ,A 产生一个时钟,B 产生 1/62.5 个时钟。
因此得到,A 产生 62.5 个时钟,B产生 1 个时钟。
3 分频方法
由上得知,62.5 个时钟 A ,产生 1 个低频时钟 B ( 62.5 分频,即 1 个输入时钟 A ,产生 1/62.5 个低频时钟 B)。
但电路无法产生 0.5 个时钟,因此将其扩大为:625 个时钟 A ,产生 10 个低频时钟 B 。
使用 “”双模前置小数分频” ,取 62 (分频的整数部分)与 63 (分频的整数部分加 1 )作为两个分频周期,公式如下:
解得 M = 5 ,N = 5 。
说明需要 5 个 62 分频,5 个 63 分频,共 625 个输入时钟 A ,产生 10 个 低频时钟 B 。
取公约数 5 进行化简:需要 1 个 62 分频,1 个 63 分频,共 125 个输入时钟 A ,产生 2 个 低频时钟 B 。
在 62 分频和 63 分频内,各有 1 个 低频时钟 B 。
在 62 分频中,前 31 分频对应低频时钟 B 的高电平,后 31 分频对应低频时钟 B 的低电平。在 63 分频中,前 31 分频对应低频时钟 B 的高电平,后 32 分频对应低频时钟 B 的低电平。如下图:
4 Verilog 代码
`timescale 1ns/1ps
module Divider(
input clk, // 输入时钟 A ,频率是 25MHz
input rst_n,
output reg clk_400KHz // 输出低频时钟 B ,频率是 400KHz
);
// 在 165 个输入时钟内输出 2 个低频时钟。
// 125 个输入时钟分成两个时钟周期,分别有 62 个时钟,63 个时钟。
// 62 个时钟的周期内,前 31 个时钟对应的低频时钟为高电平,后 31 个时钟对应的低频时钟为低电平。
// 63 个时钟的周期内,前 31 个时钟对应的低频时钟为高电平,后 32 个时钟对应的低频时钟为低电平。
// 以 165 个时钟作为大周期进行循环。
localparam div_62 = 8'd62; // 前 62 个输入时钟的周期
localparam div_63 = 8'd63; // 后 63 个输入时钟的周期
localparam div_165 = 8'd125; // 总体 165 个输入时钟的大周期
reg [7:0] cnt_62; // 前 62 个输入时钟的周期的计数器
reg [7:0] cnt_63; // 后 63 个输入时钟的周期的计数器
reg [7:0] cnt; // 总体 165 个输入时钟的大周期的计数器
// 对三个计数器初始化及赋值。需要注意,所有计数器都是从 0 开始。
// 在前 62 个输入时钟的周期时,cnt_62 大于等于 0 ,小于等于 61 。cnt 大于等于 0 ,小于等于 61 。
// 在后 63 个输入时钟的周期时,cnt_63 大于等于 0 ,小于等于 62 。cnt 大于等于 62 ,小于等于 164 。
// 对前 62 个时钟周期的计数器 cnt_62 的初始化及赋值。 cnt_62 实际从 0 到 61 。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_62 <= 8'b0;
else begin
if (cnt_62 < div_62 - 1)
cnt_62 <= cnt_62 + 1'b1;
else
cnt_62 <= 8'd0;
end
end
// 对后 63 个时钟周期的计数器 cnt_63 的初始化及赋值。 cnt_63 实际从 0 到 62 。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_63 <= 8'b0;
else begin
if (cnt_63 < div_63 - 1)
cnt_63 <= cnt_63 + 1'b1;
else
cnt_63 <= 8'd0;
end
end
// 对 165 个时钟周期的计数器 cnt 初始化及赋值。 cnt 实际从 0 到 164 。
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt <= 8'd0;
end
else begin
if(cnt < div_165 - 1) // 在前 62 个时钟周期,cnt 大于等于 0 ,小于等于 61 。
cnt <= cnt + 1'b1;
else
cnt <= 8'd0;
end
end
// 在 125 个输入时钟内,低频时钟变化为:高、低、高、低。产生两个低频时钟周期。
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
clk_400KHz <= 1'b0;
else begin
if(cnt < 8'd62) begin
if(cnt_62 < 8'd31)
clk_400KHz <= 1'b1;
else
clk_400KHz <= 1'b0;
end
else begin
if(cnt_63 < 8'd31)
clk_400KHz <= 1'b1;
else
clk_400KHz <= 1'b0;
end
end
end
endmodule
5 testbench 代码
// 声明仿真的单位和精度
`timescale 1ns / 1ps
// 定义测试模块
module Divider_tb;
// 宏定义
parameter CLK_PERIOD = 40 ; // 25MHz 的时钟周期为 40ns ( 1秒/25MHz=40纳秒 )
// 声明 tb 中的信号
// tb 中声明的信号中,reg 表示激励信号,wire 表示输出信号
reg clk;
reg rst_n;
wire clk_400KHz;
// 模块实例化
// 将 tb 中的激励信号和输出信号与待测的顶层模块连接
Divider Divider_0( .clk(clk), .rst_n(rst_n), .clk_400KHz(clk_400KHz) );
// 输入时钟信号和复位信号的初始化
initial begin
// 输入时钟信号
clk = 0;
// 复位信号
rst_n = 1; // 复位信号初始为高电平
#2; // 开始后 2ns ,复位信号变低电平,复位一次
rst_n = 0;
#(CLK_PERIOD*3); // 复位后,经过 3 个 CLK_PERIOD 周期,复位信号恢复高电平,开始分频
rst_n = 1;
end
// 时钟信号的产生
always #(CLK_PERIOD/2) clk = ~clk; // 输入时钟每 20ns 跳变一次
// 测试激励产生
initial begin
@(posedge clk); // 时钟上升沿激励有效
@(posedge rst_n); // 等待复位完成
repeat(1000) begin // 执行循环体中的语句 10 次
@(posedge clk);
end
#10_000; // 总共执行 10_000 ns 后结束仿真
$stop;
end
endmodule
6 仿真波形
低频时钟周期为 2520000 ps ,即 396.83 KHz ,有误差。
注意到低频时钟波形产生了毛刺。