4. 分频器设计(二):小数分频器的RTL实现(累加器法、Sigma-Delta调制)

上一章我们聊了整数分频器,说白了就是计数器数到N就翻转。但实际项目中,你总会遇到一些“不凑巧”的需求——比如你需要一个 2.5 分频,或者 3.125 分频。整数分频器这时候就傻眼了。

小数分频器,就是用来解决这个问题的。它不能真的输出一个“2.5倍”的时钟周期,而是通过周期性的抖动,让平均频率等于目标值。我刚开始接触这个时也觉得挺玄乎,后来亲手写过一次累加器法,才明白原理其实很简单。

4.1 为什么需要小数分频?

举个例子:你的系统需要 100MHz 时钟,但参考晶振只有 50MHz。整数分频只能得到 50/2=25MHz、50/3≈16.67MHz…… 永远凑不出 100MHz。这时候你需要一个倍频器(PLL),而 PLL 的反馈分频器如果支持小数,就能精确锁定到目标频率。

另一个常见场景是时钟数据恢复(CDR),需要产生一个与数据速率精确匹配的采样时钟。数据速率往往是 2.5Gbps、10.3125Gbps 这种带小数的值,整数分频根本搞不定。

核心思想: 小数分频器不是真的输出一个非整数周期,而是让输出时钟在某些周期多等一拍,某些周期少等一拍,使得长时间平均后的频率等于目标值。

4.2 累加器法(Accumulator Method)

累加器法是最直观的小数分频实现方式。我习惯叫它“相位累加器”,因为它的本质就是控制何时插入一个额外的等待周期。

4.2.1 原理

假设我们要实现 N + M/K 分频,其中 N 是整数部分,M/K 是小数部分(M < K)。

  • 每个参考时钟周期,累加器加上 M
  • 当累加器溢出(≥K)时,这一周期执行 N+1 分频(多等一拍)
  • 否则执行 N 分频

举个例子:实现 2.5 分频,即 N=2, M=1, K=2。

周期累加器值是否溢出分频比输出周期
11否22T
22 (溢出→0)是33T
31否22T
42 (溢出→0)是33T

平均周期 = (2+3)/2 = 2.5T,完美。

4.2.2 RTL 实现

直接上代码。我个人习惯用参数化写法,方便后期调整分频比。

module frac_divider #(
    parameter N = 2,      // 整数部分
    parameter M = 1,      // 小数分子
    parameter K = 2       // 小数分母
)(
    input  clk_in,
    input  rst_n,
    output reg clk_out
);

    reg [31:0] acc;
    reg [31:0] counter;
    reg        div_sel;   // 0: N分频, 1: N+1分频

    // 累加器
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            acc <= 0;
        end else begin
            if (acc + M >= K) begin
                acc <= acc + M - K;  // 溢出后保留余数
                div_sel <= 1'b1;
            end else begin
                acc <= acc + M;
                div_sel <= 1'b0;
            end
        end
    end

    // 分频计数器
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;
            clk_out <= 0;
        end else begin
            if (counter == (div_sel ? N : N-1)) begin
                counter <= 0;
                clk_out <= ~clk_out;
            end else begin
                counter <= counter + 1;
            end
        end
    end

endmodule
避坑指南: 我曾经在项目中直接用 N 和 N+1 切换,结果发现累加器溢出时余数没保留,导致分频比周期性漂移。记住:溢出后 acc 要减去 K,而不是清零,这样才能保证小数精度。

4.3 累加器法的局限性

累加器法虽然简单,但有个致命问题——确定性抖动。你想想看,输出时钟的周期在 N 和 N+1 之间来回跳,这种低频抖动在锁相环里会产生很大的相位噪声。

我做过一个测试:用 2.5 分频的累加器输出直接驱动 ADC,结果频谱上出现了明显的杂散(spur),频率正好在 0.5×参考频率处。这就是周期性抖动带来的后果。

4.4 Sigma-Delta 调制(ΣΔ Modulation)

Sigma-Delta 调制,说白了就是把低频的确定性抖动,推到高频去。高频抖动可以被 PLL 的低通滤波器滤掉,从而获得干净的输出频谱。

4.4.1 一阶 ΣΔ 调制器

一阶 ΣΔ 的原理很简单:在累加器的基础上,加入一个误差反馈。每次量化(决定用 N 还是 N+1)产生的误差,会被累加到下一次决策中。

数学表达:

  • 误差 e(n) = 量化输出 - 输入
  • 下一次输入 = 原始输入 + e(n)

这样做的效果是:误差不再周期性出现,而是被噪声整形到了高频区域。

4.4.2 RTL 实现(一阶 ΣΔ)

module sigma_delta_frac_div #(
    parameter N = 2,
    parameter M = 1,
    parameter K = 2
)(
    input  clk_in,
    input  rst_n,
    output reg clk_out
);

    reg [31:0] acc;
    reg [31:0] error;
    reg [31:0] counter;
    reg        div_sel;

    // ΣΔ 累加器(带误差反馈)
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            acc <= 0;
            error <= 0;
        end else begin
            // 核心:输入 = M + 上一周期的误差
            if (acc + M + error >= K) begin
                acc <= acc + M + error - K;
                div_sel <= 1'b1;
                error <= acc + M + error - K;  // 量化误差
            end else begin
                acc <= acc + M + error;
                div_sel <= 1'b0;
                error <= acc + M + error;      // 量化误差
            end
        end
    end

    // 分频计数器(同累加器法)
    always @(posedge clk_in or negedge rst_n) begin
        if (!rst_n) begin
            counter <= 0;
            clk_out <= 0;
        end else begin
            if (counter == (div_sel ? N : N-1)) begin
                counter <= 0;
                clk_out <= ~clk_out;
            end else begin
                counter <= counter + 1;
            end
        end
    end

endmodule
注意: 一阶 ΣΔ 虽然能改善杂散,但会引入量化噪声。如果 PLL 的环路带宽不够窄,这些噪声会泄漏到输出。我建议二阶或三阶 ΣΔ 用于实际产品,一阶只适合教学或低频应用。

4.5 高阶 ΣΔ 调制

高阶 ΣΔ(二阶、三阶)通过级联多个积分器,把噪声进一步推向高频。代价是稳定性变差,需要仔细设计系数。

二阶 ΣΔ 的传递函数:

  • 信号传递函数 STF = z⁻¹
  • 噪声传递函数 NTF = (1 - z⁻¹)²

这意味着低频噪声被衰减了 40dB/dec,而高频噪声被放大。但没关系,PLL 的环路滤波器会把这些高频成分滤除。

我记得有一次做 SerDes 的时钟生成,用了三阶 ΣΔ 调制器,配合 1MHz 的 PLL 带宽,最终输出抖动只有 2ps RMS。而用累加器法,同样的条件抖动超过 20ps。

4.6 实际设计中的注意事项

  1. 累加器位宽: 至少 16 位,推荐 24 位以上。位宽不够会导致频率分辨率不足。
  2. 分频比范围: 小数分频器通常要求 N ≥ 2,否则 N+1 分频无法实现(N=1 时 N+1=2 没问题,但 N=0 不行)。
  3. 输出抖动: 累加器法的抖动是确定性的,ΣΔ 法的抖动是随机的。测量时要用频谱分析仪看相位噪声,不要只看时域波形。
  4. 复位行为: 我建议复位时把累加器清零,同时让输出时钟处于已知状态(比如低电平),避免 PLL 锁定过程中出现意外。
一句话总结: 累加器法适合对抖动不敏感的场景(比如数字逻辑时钟),ΣΔ 法适合对频谱纯度要求高的场景(比如射频、高速 SerDes)。

4.7 本章知识体系

下面这张图展示了小数分频器的设计脉络,从需求到实现,再到优化方向。

小数分频器设计知识体系 小数分频器 累加器法 Sigma-Delta调制 原理:累加溢出 实现简单 确定性抖动 噪声整形 一阶/二阶/三阶 稳定性需注意 选择依据:抖动容忍度 vs 频谱纯度 累加器法:数字逻辑时钟 | ΣΔ法:射频/SerDes

好了,这一章的内容就到这里。累加器法和 ΣΔ 调制法各有千秋,关键看你的应用场景。下一章我们会聊多模分频器,那是整数分频和小数分频的混合体,也是 PLL 设计中非常实用的结构。