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。
| 周期 | 累加器值 | 是否溢出 | 分频比 | 输出周期 |
|---|---|---|---|---|
| 1 | 1 | 否 | 2 | 2T |
| 2 | 2 (溢出→0) | 是 | 3 | 3T |
| 3 | 1 | 否 | 2 | 2T |
| 4 | 2 (溢出→0) | 是 | 3 | 3T |
平均周期 = (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
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
4.5 高阶 ΣΔ 调制
高阶 ΣΔ(二阶、三阶)通过级联多个积分器,把噪声进一步推向高频。代价是稳定性变差,需要仔细设计系数。
二阶 ΣΔ 的传递函数:
- 信号传递函数 STF = z⁻¹
- 噪声传递函数 NTF = (1 - z⁻¹)²
这意味着低频噪声被衰减了 40dB/dec,而高频噪声被放大。但没关系,PLL 的环路滤波器会把这些高频成分滤除。
我记得有一次做 SerDes 的时钟生成,用了三阶 ΣΔ 调制器,配合 1MHz 的 PLL 带宽,最终输出抖动只有 2ps RMS。而用累加器法,同样的条件抖动超过 20ps。
4.6 实际设计中的注意事项
- 累加器位宽: 至少 16 位,推荐 24 位以上。位宽不够会导致频率分辨率不足。
- 分频比范围: 小数分频器通常要求 N ≥ 2,否则 N+1 分频无法实现(N=1 时 N+1=2 没问题,但 N=0 不行)。
- 输出抖动: 累加器法的抖动是确定性的,ΣΔ 法的抖动是随机的。测量时要用频谱分析仪看相位噪声,不要只看时域波形。
- 复位行为: 我建议复位时把累加器清零,同时让输出时钟处于已知状态(比如低电平),避免 PLL 锁定过程中出现意外。
4.7 本章知识体系
下面这张图展示了小数分频器的设计脉络,从需求到实现,再到优化方向。
好了,这一章的内容就到这里。累加器法和 ΣΔ 调制法各有千秋,关键看你的应用场景。下一章我们会聊多模分频器,那是整数分频和小数分频的混合体,也是 PLL 设计中非常实用的结构。