Veloris.
返回索引
FPGA 2025-10-21

时钟分频设计

8 分钟
2.5k words

时钟分频设计

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时清零重新计数。如下图所示:

image.png

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%占空比。为了产生一个周期为奇数个输入时钟周期的信号,通常需要利用输入时钟的上升沿和下降沿。如下图

image.png

核心思想

  1. 上升沿采样:在输入时钟上升沿进行计数和翻转
  2. 下降沿采样:在输入时钟下降沿进行计数和翻转
  3. 或逻辑组合:将两路信号相或,得到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-1DIV_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分频等。

常用方法

  1. 相位累加法:类似DDS(直接数字频率合成)原理
  2. 模N计数法:通过控制计数器的溢出点实现
  3. 分数分频器:使用反馈计数器

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 毛刺问题

问题:分频时钟可能存在毛刺,不适合直接用作其他寄存器的时钟。

原因

  • 组合逻辑产生的时钟信号可能有毛刺
  • 异步设计的时钟分频器容易产生毛刺

解决方案

  1. 使用时钟使能信号代替分频时钟(最佳方案)
// 不推荐:使用分频时钟
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
  1. 使用PLL/MMCM生成时钟:Xilinx FPGA提供的时钟管理资源

  2. 使用寄存器输出时钟:确保时钟信号经过寄存器输出

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 关键要点

  1. 时钟使能优于时钟分频:在FPGA内部逻辑设计中,优先使用时钟使能而非生成新时钟

  2. 使用PLL进行精确分频:对于需要精确频率和相位关系的场合,使用FPGA内部的PLL/MMCM资源

  3. 注意时钟域问题:分频后的时钟属于新的时钟域,需要正确处理CDC问题

  4. 避免毛刺:通过寄存器输出时钟信号,使用同步设计方法

  5. 占空比考虑

    • 偶数分频容易实现50%占空比
    • 奇数分频需要双边沿处理才能接近50%占空比
    • 大多数应用对占空比要求不严格
  6. 时序约束:对生成的时钟进行正确的时序约束

  7. 复位同步:确保分频器的复位信号与时钟同步

11.3 编码规范总结

  • ✅ 使用参数化设计,增强模块可复用性
  • ✅ 优先使用同步高电平复位
  • ✅ 计数器初值为0,结束值为N-1
  • ✅ 使用$clog2自动计算位宽
  • ✅ 为时钟信号添加详细注释
  • ✅ 测试时验证分频精度和占空比
  • ❌ 避免使用组合逻辑生成时钟
  • ❌ 避免在多个模块中生成相同的分频时钟
  • ❌ 不要忽略跨时钟域的数据同步问题

通过掌握这些时钟分频技术,可以灵活地在FPGA设计中处理各种时钟频率需求,实现高效稳定的数字电路系统。

End of file.