时钟分频设计
1. 什么是时钟分频
时钟分频是FPGA设计中非常常见的操作,其本质是将输入的高频时钟信号转换为低频时钟信号。通过时钟分频,可以得到频率更低但周期更长的时钟信号,用于驱动不同速率要求的模块。
1.1 时钟分频的作用
- 降低功耗:某些模块不需要高速时钟,使用低频时钟可以降低动态功耗
- 满足时序要求:为速度较慢的外设或接口提供合适频率的时钟
- 系统时钟管理:在一个系统中,不同模块可能需要不同频率的时钟
- 接口适配:与外部器件通信时,需要产生符合其时序要求的时钟信号
1.2 时钟分频的基本概念
-
分频系数(N):输出时钟频率与输入时钟频率的比值关系
- N分频:输出时钟频率 = 输入时钟频率 / N
- 例如:100MHz时钟进行10分频,得到10MHz时钟
-
占空比:时钟信号在一个周期内高电平持续时间与整个周期的比值
- 占空比 = 高电平时间 / 时钟周期
- 50%占空比表示高电平和低电平时间相等
2. 时钟分频的分类
根据分频系数的不同,时钟分频可以分为以下几类:
2.1 分类概览
| 分频类型 | 分频系数 | 占空比 | 实现难度 | 典型应用 |
|---|---|---|---|---|
| 偶数分频 | N=2,4,6,8… | 可50% | 简单 | 最常用 |
| 奇数分频 | N=3,5,7,9… | 可50% | 中等 | 特定应用 |
| 半整数分频 | N=2.5,4.5… | 非50% | 较难 | 特殊需求 |
| 小数分频 | N=非整数 | 可调 | 复杂 | 精确频率 |
2.2 设计要点
- 偶数分频:最简单,直接使用计数器,容易产生50%占空比
- 奇数分频:需要上升沿和下降沿分别处理,才能得到50%占空比
- 半整数分频:通常需要结合奇偶分频和边沿检测
- 小数分频:需要使用累加器或DDS技术
3. 偶数分频设计
3.1 偶数分频原理
偶数分频是最简单的分频方式,可以很容易地产生50%占空比的输出时钟。
核心思想:使用计数器对输入时钟计数,当计数到N/2时翻转输出,计数到N时清零重新计数。如下图所示:

3.2 偶数分频实现方法
方法:计数器翻转法
module clk_div_even #(
parameter DIV_NUM = 4 // 分频系数,必须为偶数
)(
input wire clk, // 输入时钟
input wire rst, // 同步复位,高电平有效
output reg clk_out // 输出时钟
);
// 计数器位宽计算
localparam CNT_WIDTH = 4;
reg [CNT_WIDTH-1:0] cnt;
wire cnt_end;
// 计数器逻辑
always @(posedge clk) begin
if (rst)
cnt <= 'd0;
else if (cnt_end)
cnt <= 'd0;
else
cnt <= cnt + 1'b1;
end
assign cnt_end = (cnt == (DIV_NUM >> 1) - 1'b1);
// 输出时钟生成(计数到N/2时翻转)
always @(posedge clk) begin
if (rst)
clk_out <= 1'b0;
else if (cnt_end)
clk_out <= ~ clk_out;
end
3.3 2分频特例
2分频是最简单的情况,可以直接使用D触发器翻转:
module clk_div2(
input wire clk,
input wire rst,
output reg clk_out
);
always @(posedge clk) begin
if (rst)
clk_out <= 1'b0;
else
clk_out <= ~clk_out; // 每个时钟周期翻转一次
end
endmodule
3.4 偶数分频波形示例
以4分频为例(100MHz → 25MHz):
输入clk: __|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|
计数cnt: 0 1 2 3 0 1 2 3
输出clk_out: ______|‾‾‾‾‾‾‾|_______|‾‾‾‾‾‾‾|_____
- 输入时钟周期:10ns
- 输出时钟周期:40ns
- 占空比:50%(高电平20ns,低电平20ns)
4. 奇数分频设计
4.1 奇数分频原理
奇数分频无法仅通过一个计数器在单个时钟沿触发来实现50%占空比。为了产生一个周期为奇数个输入时钟周期的信号,通常需要利用输入时钟的上升沿和下降沿。如下图

核心思想:
- 上升沿采样:在输入时钟上升沿进行计数和翻转
- 下降沿采样:在输入时钟下降沿进行计数和翻转
- 或逻辑组合:将两路信号相或,得到50%占空比的输出
4.2 奇数分频实现(50%占空比)
module clk_div_odd #(
parameter DIV_NUM = 5 // 奇数分频系数
)(
input wire clk,
input wire rst,
output wire clk_out
);
localparam CNT_WIDTH = $clog2(DIV_NUM);
// 上升沿计数器和输出
reg [CNT_WIDTH-1:0] cnt_pos;
reg clk_pos;
// 上升沿处理
always @(posedge clk) begin
if (rst) begin
cnt_pos <= 'd0;
clk_pos <= 1'b0;
end else if (cnt_pos == DIV_NUM - 1) begin
cnt_pos <= 'd0;
clk_pos <= ~clk_pos;
end else begin
cnt_pos <= cnt_pos + 1'b1;
// 在计数到(DIV_NUM+1)/2-1时也翻转
if (cnt_pos == (DIV_NUM+1)/2 - 1) begin
clk_pos <= ~clk_pos;
end
end
end
// 下降沿计数器和输出
reg [CNT_WIDTH-1:0] cnt_neg;
reg clk_neg;
// 下降沿处理
always @(negedge clk) begin
if (rst) begin
cnt_neg <= 'd0;
clk_neg <= 1'b0;
end else if (cnt_neg == DIV_NUM - 1) begin
cnt_neg <= 'd0;
clk_neg <= ~clk_neg;
end else begin
cnt_neg <= cnt_neg + 1'b1;
if (cnt_neg == (DIV_NUM+1)/2 - 1) begin
clk_neg <= ~clk_neg;
end
end
end
// 将上升沿和下降沿产生的时钟相或
assign clk_out = clk_pos | clk_neg;
endmodule
设计要点:
- 使用两个独立的计数器,分别在上升沿和下降沿工作
- 每个计数器在计数到
(DIV_NUM+1)/2-1和DIV_NUM-1时翻转输出 - 两路时钟相或后得到接近50%占空比的输出
- 对于N分频,输出时钟高电平持续
(N+1)/2个输入周期,低电平持续(N-1)/2个输入周期
4.3 奇数分频简化实现(非50%占空比)
如果不要求50%占空比,可以使用更简单的实现:
module clk_div_odd_simple #(
parameter DIV_NUM = 5
)(
input wire clk,
input wire rst,
output reg clk_out
);
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
clk_out <= 1'b0;
end else begin
if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
end else begin
cnt <= cnt + 1'b1;
end
// 前半周期输出高电平
if (cnt < (DIV_NUM+1)/2) begin
clk_out <= 1'b1;
end else begin
clk_out <= 1'b0;
end
end
end
endmodule
特点:
- 代码简单,只需单个always块
- 占空比不是严格的50%
- 对于5分频:高电平3个周期,低电平2个周期,占空比60%
4.4 奇数分频波形示例
以5分频为例(100MHz → 20MHz,50%占空比):
输入clk: __|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|
cnt_pos: 0 1 2 3 4 0 1 2 3 4 0
clk_pos: ______|‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾‾‾‾‾‾|____
clk_neg: ___|‾‾‾‾‾‾‾‾‾‾‾|___________|‾‾‾‾‾‾‾‾‾‾‾|_______
clk_out: ___|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|_______|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|___
- 输出时钟周期:50ns
- 占空比:约50%
5. 半整数分频设计
5.1 半整数分频原理
半整数分频是指分频系数为N.5的分频,如2.5分频、4.5分频等。
核心思想:
- 将半整数分频看作两个整数分频的交替
- 例如:2.5分频 = 2分频 + 3分频交替
- 通过控制两种分频模式的切换实现平均分频效果
5.2 半整数分频实现(以2.5分频为例)
module clk_div_2p5(
input wire clk, // 输入时钟
input wire rst,
output reg clk_out // 输出时钟
);
reg [1:0] cnt;
reg mode; // 模式切换标志:0表示2分频,1表示3分频
// 计数器逻辑
always @(posedge clk) begin
if (rst) begin
cnt <= 2'd0;
mode <= 1'b0;
clk_out <= 1'b0;
end else begin
// 根据模式选择不同的计数终点
if ((mode == 1'b0 && cnt == 2'd1) ||
(mode == 1'b1 && cnt == 2'd2)) begin
cnt <= 2'd0;
mode <= ~mode; // 切换模式
clk_out <= ~clk_out; // 翻转输出
end else begin
cnt <= cnt + 1'b1;
// 在计数到一半时翻转输出
if (cnt == 2'd0) begin
clk_out <= ~clk_out;
end
end
end
end
endmodule
5.3 通用半整数分频实现
module clk_div_half_integer #(
parameter DIV_INT = 4 // 整数部分(4表示4.5分频)
)(
input wire clk,
input wire rst,
output reg clk_out
);
localparam CNT_WIDTH = $clog2(DIV_INT + 1);
localparam DIV_LOW = DIV_INT; // 低分频值
localparam DIV_HIGH = DIV_INT + 1; // 高分频值
reg [CNT_WIDTH-1:0] cnt;
reg mode; // 0: DIV_LOW分频, 1: DIV_HIGH分频
// 计数终点判断
wire cnt_end;
assign cnt_end = (mode == 1'b0) ? (cnt == DIV_LOW - 1) : (cnt == DIV_HIGH - 1);
// 计数器
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
end else if (cnt_end) begin
cnt <= 'd0;
end else begin
cnt <= cnt + 1'b1;
end
end
// 模式切换
always @(posedge clk) begin
if (rst) begin
mode <= 1'b0;
end else if (cnt_end) begin
mode <= ~mode;
end
end
// 输出时钟生成
always @(posedge clk) begin
if (rst) begin
clk_out <= 1'b0;
end else if (cnt_end) begin
clk_out <= ~clk_out;
end else if (cnt == ((mode == 1'b0) ? DIV_LOW : DIV_HIGH) / 2 - 1) begin
clk_out <= ~clk_out;
end
end
endmodule
5.4 半整数分频波形示例
以2.5分频为例(100MHz → 40MHz):
输入clk: __|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|__|‾|
cnt: 0 1 0 1 2 0 1 0 1 2 0
mode: 0 0 1 1 1 0 0 1 1 1 0
clk_out: __|‾‾‾‾‾|_____|‾‾‾‾‾‾‾‾|___|‾‾‾‾‾|_____|‾‾‾‾‾‾
<--2--> <----3----> <--2--> <----3---->
- 交替进行2分频和3分频
- 平均分频系数:(2+3)/2 = 2.5
- 输出频率:100MHz / 2.5 = 40MHz
6. 小数分频设计
6.1 小数分频原理
小数分频用于产生非整数比例的分频时钟,如3.7分频、5.25分频等。
常用方法:
- 相位累加法:类似DDS(直接数字频率合成)原理
- 模N计数法:通过控制计数器的溢出点实现
- 分数分频器:使用反馈计数器
6.2 相位累加法实现
module clk_div_fractional #(
parameter FRAC_WIDTH = 16, // 小数位宽
parameter FREQ_WORD = 16'd26214 // 频率控制字:2^16 / 分频系数
)( // 例如:2.5分频时,FREQ_WORD = 65536/2.5 = 26214
input wire clk,
input wire rst,
output reg clk_out
);
reg [FRAC_WIDTH-1:0] phase_acc; // 相位累加器
wire carry;
// 相位累加器溢出检测
assign carry = (phase_acc[FRAC_WIDTH-1] == 1'b1);
// 相位累加
always @(posedge clk) begin
if (rst) begin
phase_acc <= 'd0;
end else begin
phase_acc <= phase_acc + FREQ_WORD;
end
end
// 输出时钟生成(累加器溢出时翻转)
always @(posedge clk) begin
if (rst) begin
clk_out <= 1'b0;
end else if (carry) begin
clk_out <= ~clk_out;
end
end
endmodule
频率控制字计算:
- FREQ_WORD = 2^FRAC_WIDTH / 分频系数
- 例如:16位累加器,3.5分频
- FREQ_WORD = 65536 / 3.5 ≈ 18725
特点:
- 输出时钟占空比不固定
- 频率精度高
- 适合不需要严格占空比的应用
6.3 模N计数法实现
module clk_div_fractional_mod #(
parameter DIV_INT = 5, // 整数部分
parameter DIV_FRAC = 3, // 分子
parameter DIV_MOD = 4 // 分母,表示 DIV_INT + DIV_FRAC/DIV_MOD 分频
)( // 例如:5 + 3/4 = 5.75分频
input wire clk,
input wire rst,
output reg clk_out
);
localparam CNT_WIDTH = $clog2(DIV_INT + 1);
localparam MOD_WIDTH = $clog2(DIV_MOD);
reg [CNT_WIDTH-1:0] cnt;
reg [MOD_WIDTH-1:0] mod_cnt;
reg [CNT_WIDTH-1:0] div_value;
// 动态分频值选择
always @(*) begin
if (mod_cnt < DIV_FRAC)
div_value = DIV_INT + 1; // 分频值+1
else
div_value = DIV_INT; // 标准分频值
end
// 主计数器
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
end else if (cnt == div_value - 1) begin
cnt <= 'd0;
end else begin
cnt <= cnt + 1'b1;
end
end
// 模计数器(控制何时增加分频值)
always @(posedge clk) begin
if (rst) begin
mod_cnt <= 'd0;
end else if (cnt == div_value - 1) begin
if (mod_cnt == DIV_MOD - 1)
mod_cnt <= 'd0;
else
mod_cnt <= mod_cnt + 1'b1;
end
end
// 输出时钟生成
always @(posedge clk) begin
if (rst) begin
clk_out <= 1'b0;
end else if (cnt == div_value - 1) begin
clk_out <= ~clk_out;
end else if (cnt == div_value / 2 - 1) begin
clk_out <= ~clk_out;
end
end
endmodule
7. 任意分频器通用设计
7.1 自动识别奇偶分频的通用模块
module clk_divider #(
parameter DIV_NUM = 5 // 分频系数,支持任意正整数
)(
input wire clk,
input wire rst,
output wire clk_out
);
// 判断奇偶性
localparam IS_EVEN = (DIV_NUM % 2 == 0);
// 根据奇偶性例化不同的分频模块
generate
if (IS_EVEN) begin: even_div
// 偶数分频实例
clk_div_even #(
.DIV_NUM(DIV_NUM)
) u_clk_div_even (
.clk (clk),
.rst (rst),
.clk_out (clk_out)
);
end else begin: odd_div
// 奇数分频实例
clk_div_odd #(
.DIV_NUM(DIV_NUM)
) u_clk_div_odd (
.clk (clk),
.rst (rst),
.clk_out (clk_out)
);
end
endgenerate
endmodule
7.2 可配置使能的分频器
module clk_div_with_enable #(
parameter DIV_NUM = 4
)(
input wire clk,
input wire rst,
input wire enable, // 分频使能信号
output reg clk_out
);
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
clk_out <= 1'b0;
end else if (enable) begin
if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
end else begin
cnt <= cnt + 1'b1;
end
// 生成分频时钟
if (cnt == DIV_NUM/2 - 1) begin
clk_out <= 1'b1;
end else if (cnt == DIV_NUM - 1) begin
clk_out <= 1'b0;
end
end else begin
// 不使能时,保持低电平
cnt <= 'd0;
clk_out <= 1'b0;
end
end
endmodule
8. 时钟分频设计注意事项
8.1 时钟域处理
问题:分频后的时钟与原时钟不同步,属于不同的时钟域。
解决方案:
- 如果分频时钟用于驱动其他逻辑,需要注意跨时钟域问题
- 使用CDC(跨时钟域)技术处理数据传递:
- 单bit信号:双寄存器同步器
- 多bit信号:握手协议、异步FIFO、格雷码
// 双寄存器同步器示例
reg sync1, sync2;
always @(posedge clk_div) begin
sync1 <= signal_from_src_clk;
sync2 <= sync1;
end
assign synced_signal = sync2;
8.2 毛刺问题
问题:分频时钟可能存在毛刺,不适合直接用作其他寄存器的时钟。
原因:
- 组合逻辑产生的时钟信号可能有毛刺
- 异步设计的时钟分频器容易产生毛刺
解决方案:
- 使用时钟使能信号代替分频时钟(最佳方案)
// 不推荐:使用分频时钟
always @(posedge clk_div) begin
data_reg <= data_in;
end
// 推荐:使用时钟使能
reg clk_en;
always @(posedge clk) begin
if (clk_en)
data_reg <= data_in;
end
-
使用PLL/MMCM生成时钟:Xilinx FPGA提供的时钟管理资源
-
使用寄存器输出时钟:确保时钟信号经过寄存器输出
8.3 占空比要求
偶数分频:
- 容易实现50%占空比
- 直接在计数器中点翻转输出
奇数分频:
- 单边沿设计占空比不是50%
- 需要双边沿设计才能接近50%占空比
选择建议:
- 如果下游电路对占空比敏感,需要仔细设计
- 大多数数字电路对占空比要求不严格
8.4 复位策略
问题:分频时钟的复位需要与系统时钟同步。
设计要点:
// 推荐:同步复位
always @(posedge clk) begin
if (rst) begin // rst已经与clk同步
cnt <= 'd0;
clk_out <= 1'b0;
end else begin
// 正常逻辑
end
end
8.5 资源占用
| 分频类型 | 寄存器数量 | LUT数量 | 综合性能 |
|---|---|---|---|
| 偶数分频 | 低 | 低 | 优秀 |
| 奇数分频(50%) | 中(双倍) | 中 | 良好 |
| 半整数分频 | 中 | 中 | 良好 |
| 小数分频 | 高 | 高 | 一般 |
优化建议:
- 如果对占空比要求不高,优先使用简单的单边沿设计
- 能用偶数分频就不用奇数分频
- 复杂分频可以考虑使用PLL
8.6 时序约束
问题:生成的分频时钟需要进行时序约束。
Vivado约束示例:
# 创建生成时钟约束
create_generated_clock -name clk_div \
-source [get_pins u_clk_div/clk] \
-divide_by 4 \
[get_pins u_clk_div/clk_out]
# 如果分频时钟与源时钟存在异步关系,设置伪路径
set_false_path -from [get_clocks clk] -to [get_clocks clk_div]
9. 时钟使能 vs 时钟分频
9.1 两种方法对比
| 特性 | 时钟使能 | 时钟分频 |
|---|---|---|
| 实现方式 | 门控数据路径 | 生成新时钟 |
| 时钟域 | 单一时钟域 | 多时钟域 |
| 毛刺风险 | 无 | 有 |
| 时序约束 | 简单 | 复杂 |
| CDC问题 | 无 | 需要处理 |
| 功耗 | 稍高 | 稍低 |
| 推荐度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
9.2 时钟使能实现
module clk_enable_example #(
parameter DIV_NUM = 4
)(
input wire clk,
input wire rst,
input wire [7:0] data_in,
output reg [7:0] data_out
);
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
reg clk_en;
// 生成时钟使能信号
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
clk_en <= 1'b0;
end else if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
clk_en <= 1'b1; // 产生一个周期的使能脉冲
end else begin
cnt <= cnt + 1'b1;
clk_en <= 1'b0;
end
end
// 使用时钟使能控制数据采样
always @(posedge clk) begin
if (rst) begin
data_out <= 8'd0;
end else if (clk_en) begin // 仅在使能时更新数据
data_out <= data_in;
end
end
endmodule
优势:
- 所有逻辑在同一时钟域
- 无需处理CDC问题
- 时序约束简单
- 无毛刺风险
- FPGA设计中的首选方法
9.3 使用建议
推荐使用时钟使能的场景:
- 内部逻辑需要降速工作
- 希望避免多时钟域问题
- 时序要求严格的设计
可以使用时钟分频的场景:
- 需要输出时钟信号到芯片外部
- 驱动外部器件的时钟输入
- 系统中确实需要多个不同频率的时钟
- 使用PLL/MMCM等专用时钟资源
10. 实际应用示例
10.1 LED闪烁(分频应用)
module led_blink(
input wire clk, // 50MHz系统时钟
input wire rst,
output reg led
);
// 1Hz闪烁需要50M分频
localparam DIV_NUM = 25_000_000; // 0.5秒
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
led <= 1'b0;
end else if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
led <= ~led; // 每0.5秒翻转一次,实现1Hz闪烁
end else begin
cnt <= cnt + 1'b1;
end
end
endmodule
10.2 数码管动态扫描(时钟使能应用)
module seg_scan #(
parameter CLK_FREQ = 50_000_000, // 系统时钟频率
parameter SCAN_FREQ = 1000 // 扫描频率1KHz
)(
input wire clk,
input wire rst,
input wire [15:0] display_data, // 4位数码管显示数据
output reg [3:0] seg_sel, // 数码管位选
output reg [7:0] seg_data // 数码管段选
);
// 计算分频系数
localparam DIV_NUM = CLK_FREQ / SCAN_FREQ;
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
reg scan_en; // 扫描使能
// 生成扫描使能信号
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
scan_en <= 1'b0;
end else if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
scan_en <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
scan_en <= 1'b0;
end
end
// 位选扫描计数器
reg [1:0] scan_cnt;
always @(posedge clk) begin
if (rst) begin
scan_cnt <= 2'd0;
end else if (scan_en) begin
scan_cnt <= scan_cnt + 1'b1;
end
end
// 位选译码
always @(posedge clk) begin
if (rst) begin
seg_sel <= 4'b0000;
end else begin
case(scan_cnt)
2'd0: seg_sel <= 4'b0001;
2'd1: seg_sel <= 4'b0010;
2'd2: seg_sel <= 4'b0100;
2'd3: seg_sel <= 4'b1000;
default: seg_sel <= 4'b0000;
endcase
end
end
// 段选数据
always @(posedge clk) begin
if (rst) begin
seg_data <= 8'h00;
end else begin
case(scan_cnt)
2'd0: seg_data <= display_data[3:0];
2'd1: seg_data <= display_data[7:4];
2'd2: seg_data <= display_data[11:8];
2'd3: seg_data <= display_data[15:12];
default: seg_data <= 8'h00;
endcase
end
end
endmodule
10.3 串口波特率生成
module baud_rate_gen #(
parameter CLK_FREQ = 50_000_000, // 50MHz
parameter BAUD_RATE = 115200 // 115200波特率
)(
input wire clk,
input wire rst,
output reg baud_clk_en // 波特率时钟使能
);
// 计算分频系数:每个波特周期需要的时钟周期数
localparam DIV_NUM = CLK_FREQ / BAUD_RATE;
localparam CNT_WIDTH = $clog2(DIV_NUM);
reg [CNT_WIDTH-1:0] cnt;
always @(posedge clk) begin
if (rst) begin
cnt <= 'd0;
baud_clk_en <= 1'b0;
end else if (cnt == DIV_NUM - 1) begin
cnt <= 'd0;
baud_clk_en <= 1'b1; // 产生一个周期的波特率使能脉冲
end else begin
cnt <= cnt + 1'b1;
baud_clk_en <= 1'b0;
end
end
endmodule
11. 总结
11.1 设计选择流程图
需要不同频率的时钟?
├─ 是 → 能否使用PLL/MMCM?
│ ├─ 是 → 使用PLL(最佳方案)
│ └─ 否 → 使用时钟分频器
│ ├─ 偶数分频 → 简单计数器翻转
│ ├─ 奇数分频 → 双边沿或非50%占空比
│ └─ 小数分频 → 相位累加或模N计数
└─ 否 → 逻辑需要降速运行?
└─ 是 → 使用时钟使能(推荐)
11.2 关键要点
-
时钟使能优于时钟分频:在FPGA内部逻辑设计中,优先使用时钟使能而非生成新时钟
-
使用PLL进行精确分频:对于需要精确频率和相位关系的场合,使用FPGA内部的PLL/MMCM资源
-
注意时钟域问题:分频后的时钟属于新的时钟域,需要正确处理CDC问题
-
避免毛刺:通过寄存器输出时钟信号,使用同步设计方法
-
占空比考虑:
- 偶数分频容易实现50%占空比
- 奇数分频需要双边沿处理才能接近50%占空比
- 大多数应用对占空比要求不严格
-
时序约束:对生成的时钟进行正确的时序约束
-
复位同步:确保分频器的复位信号与时钟同步
11.3 编码规范总结
- ✅ 使用参数化设计,增强模块可复用性
- ✅ 优先使用同步高电平复位
- ✅ 计数器初值为0,结束值为N-1
- ✅ 使用
$clog2自动计算位宽 - ✅ 为时钟信号添加详细注释
- ✅ 测试时验证分频精度和占空比
- ❌ 避免使用组合逻辑生成时钟
- ❌ 避免在多个模块中生成相同的分频时钟
- ❌ 不要忽略跨时钟域的数据同步问题
通过掌握这些时钟分频技术,可以灵活地在FPGA设计中处理各种时钟频率需求,实现高效稳定的数字电路系统。