引言
最近准备一些笔试面试,想再把时钟奇偶分频的再整理一下。
我之前写过一个PWM产生的模块,里面有任意频率/占空比的时钟生成。可以参考:
基于FPGA的PWM发生器设计https://blog.csdn.net/qq_43045275/article/details/128365705?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168362558616800227492685%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=168362558616800227492685&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-128365705-null-null.blog_rank_default&utm_term=PWM&spm=1018.2226.3001.4450
偶数分频
以4分频为例,时序图:
假设时钟分频系数:P_DIV_EVEN
那么偶数分频是在,计数器的计数值 = P_DIV_EVEN / 2 - 1 时,输出时钟翻转即可
// ========================================================================
// 功能描述:-1- 偶数倍时钟分频,50%占空比
// 作者:Xu Y. B.
// 时间:2023-05-09
// ========================================================================
`timescale 1ns / 1ps
module clk_div_even #(
parameter P_DIV_EVEN = 4
)(
input clk,
input rstn,
output reg clk_div
);
localparam N = (P_DIV_EVEN)/2;
reg [$clog2(P_DIV_EVEN)-1:0] r_cnt;
always @ (posedge clk)
begin
if(~rstn)
begin
r_cnt <= 0;
clk_div <= 0;
end
else if(r_cnt == (N-1))
begin
r_cnt <= 0;
clk_div <= ~clk_div;
end
else
begin
r_cnt <= r_cnt + 1;
end
end
endmodule
奇数分频
以5分频为例,时序图:
假设时钟分频系数:P_DIV_ODD
参数 N = (P_DIV_ODD - 1)/2
其中clk_div为最终输出时钟,clk1和clk2为辅助时钟生成。
计数器counter由0计数至(M-1)。
clk1在clk_in的上升延跳变,条件是r_cnt==(N-1)或(P_DIV_ODD-1)。
clk2在clk_in的下降延跳变,条件是r_cnt==(N-1)或(P_DIV_ODD-1)。
之后
clk_div = clk1 & clk2
即可得到对应奇数分频的时钟。
// ========================================================================
// 功能描述:-1- 奇数倍时钟分频,50%占空比
// 作者:Xu Y. B.
// 时间:2023-05-09
// ========================================================================
`timescale 1ns / 1ps
module clk_div_odd #(
parameter P_DIV_ODD = 3
)(
input clk,
input rstn,
output clk_div
);
localparam N = (P_DIV_ODD-1)/2;
reg [$clog2(P_DIV_ODD)-1:0] r_cnt;
reg clk1;
reg clk2;
// 原始时钟计数
always @ (posedge clk)
begin
if(~rstn)
begin
r_cnt <= 0;
end
else if(r_cnt == (P_DIV_ODD-1))
begin
r_cnt <= 0;
end
else
begin
r_cnt <= r_cnt + 1;
end
end
// 上升沿触发
always @ (posedge clk)
begin
if(~rstn)
begin
clk1 <= 0;
end
else if(r_cnt == (N-1))
begin
clk1 <= ~clk1;
end
else if(r_cnt == (P_DIV_ODD-1))
begin
clk1 <= ~clk1;
end
end
// 下降沿触发
always @ (negedge clk)
begin
if(~rstn)
begin
clk2 <= 0;
end
else if(r_cnt == (N-1))
begin
clk2 <= ~clk2;
end
else if(r_cnt == (P_DIV_ODD-1))
begin
clk2 <= ~clk2;
end
end
assign clk_div = clk1&clk2;
endmodule
仿真代码
// ========================================================================
// 功能描述:-1- 仿真测试模块 clk_div_odd , clk_div_even 功能
// 作者:Xu Y. B.
// 时间:2023-05-09
// ========================================================================
`timescale 1ns / 1ps
module tb_clk_div();
parameter P_DIV_EVEN = 4;
parameter P_DIV_ODD = 5;
reg clk;
reg rstn;
wire o_clk_div_odd;
wire o_clk_div_even;
initial clk = 0;
always #5 clk = ~clk;
initial
begin
rstn = 0;
#1034;
@(posedge clk)
rstn <= 1;
#2987;
$stop;
end
clk_div_odd #(.P_DIV_ODD(P_DIV_ODD)) INST_clk_div_odd (.clk(clk), .rstn(rstn), .clk_div(o_clk_div_odd));
clk_div_even #(.P_DIV_EVEN(P_DIV_EVEN)) INST_clk_div_even (.clk(clk), .rstn(rstn), .clk_div(o_clk_div_even));
endmodule
仿真结果: