第四章 鉴频鉴相器(PFD)设计
好,咱们进入PLL设计里最核心也最容易被忽视的一个模块——鉴频鉴相器。说实话,我见过不少新手一上来就盯着VCO和环路滤波器猛调,结果锁相环要么锁不住,要么锁住了但抖动大得离谱。问题出在哪?十有八九是PFD没设计好。
4.1 PFD工作原理
鉴频鉴相器,名字已经说得很清楚了——它既要鉴频,又要鉴相。说白了,就是同时比较输入参考时钟和反馈时钟的频率差和相位差。
我习惯把PFD理解成一个“三态状态机”。它有三个输出状态:UP、DOWN、以及中间态(什么都不做)。
- UP有效:参考时钟领先反馈时钟,需要把反馈时钟“拉快”
- DOWN有效:反馈时钟领先参考时钟,需要把反馈时钟“拉慢”
- 两者都无效:已经锁定,不需要调整
你想想看,如果两个时钟完全同频同相,PFD的输出就是稳定的中间态。一旦有偏差,它就会输出一个宽度正比于相位差的脉冲。这个脉冲宽度,直接决定了后续电荷泵的充放电时间。
核心要点:PFD的工作频率范围可以很宽,理论上从直流到几百MHz都没问题。但实际设计中,我们更关心的是它在锁定点附近的线性度。
4.2 死区问题与消除方法
死区,这是PFD设计里最坑人的地方。我在项目中遇到过两次因为死区导致PLL锁定失败的案例,后来每次做PFD都会格外小心。
什么是死区?简单说,就是当两个时钟的相位差非常小的时候,PFD“感觉不到”这个差异,输出脉冲宽度为零。这样一来,电荷泵就不会动作,环路就失去了对微小相位误差的校正能力。
为什么会这样?原因在于数字电路的建立时间和保持时间。当UP和DOWN脉冲宽度都小于某个阈值时,后续的电荷泵开关管根本来不及完全开启,脉冲就消失了。
我曾经踩过的坑:有一次做65nm工艺的PLL,仿真时一切正常,流片回来却发现锁定时间比预期长了3倍。查了两个月,最后发现是PFD的死区宽度达到了200ps,导致锁定点附近环路增益骤降。从那以后,我每个PFD设计都会做死区宽度仿真,确保它小于50ps。
消除死区的方法,业内公认最有效的是插入延迟单元。具体做法是:
- 在PFD的复位路径上加入一个延迟缓冲器
- 确保UP和DOWN脉冲的最小宽度大于电荷泵开关管的开启时间
- 延迟时间通常取200ps~500ps,具体看工艺
还有一种方法是用预充电技术,让PFD的输出节点在每次比较前先预充到一个中间电平。这样即使相位差很小,也能产生可检测的脉冲。不过这种方法功耗会大一些,我一般只在高速PLL里用。
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 延迟单元插入 | 实现简单,功耗低 | 增加额外延迟 | 通用PLL |
| 预充电技术 | 死区消除彻底 | 功耗较高 | 高速PLL |
| 动态逻辑PFD | 面积小,速度快 | 设计复杂 | 先进工艺 |
4.3 PFD的Verilog实现
嗯,这里我直接给出一段我常用的PFD代码。注意,这不是行为级模型,而是可综合的RTL代码。
module pfd (
input wire clk_ref, // 参考时钟
input wire clk_fb, // 反馈时钟
input wire rst_n, // 异步复位,低有效
output reg up, // 上升沿有效
output reg dn // 下降沿有效
);
reg ref_dly, fb_dly;
wire reset;
// 延迟单元,用于消除死区
always @(posedge clk_ref or negedge rst_n) begin
if (!rst_n) ref_dly <= 1'b0;
else ref_dly <= #1 1'b1;
end
always @(posedge clk_fb or negedge rst_n) begin
if (!rst_n) fb_dly <= 1'b0;
else fb_dly <= #1 1'b1;
end
// 复位信号:两个延迟信号同时为高时复位
assign reset = ref_dly & fb_dly;
// 状态机:三态输出
always @(posedge clk_ref or posedge reset or negedge rst_n) begin
if (!rst_n) up <= 1'b0;
else if (reset) up <= 1'b0;
else up <= 1'b1;
end
always @(posedge clk_fb or posedge reset or negedge rst_n) begin
if (!rst_n) dn <= 1'b0;
else if (reset) dn <= 1'b0;
else dn <= 1'b1;
end
endmodule
个人建议:代码里的#1延迟是用于仿真验证的,综合时会被工具忽略。实际流片时,这个延迟由工艺决定。我一般会在综合后做后仿真,确认延迟是否足够消除死区。
4.4 PFD的时序约束
PFD的时序约束,很多人会忽略。其实这里门道不少。
首先,PFD的输入是异步的——参考时钟和反馈时钟之间没有固定的相位关系。所以你不能用常规的setup/hold约束来约束它们。我习惯的做法是:
- 对参考时钟和反馈时钟分别做false_path约束
- 对PFD内部的复位路径做max_delay约束
- 对UP/DOWN输出到电荷泵的路径做set_output_delay
举个例子,在SDC文件里我会这样写:
# 参考时钟和反馈时钟之间是异步的
set_false_path -from [get_clocks clk_ref] -to [get_clocks clk_fb]
set_false_path -from [get_clocks clk_fb] -to [get_clocks clk_ref]
# 复位路径约束,确保延迟单元能正常工作
set_max_delay 1.0 -from [get_pins pfd_inst/ref_dly_reg/C] -to [get_pins pfd_inst/reset]
# 输出到电荷泵的路径
set_output_delay -clock clk_ref -max 0.5 [get_ports {up dn}]
set_output_delay -clock clk_ref -min 0.1 [get_ports {up dn}]
你可能会问,为什么复位路径要单独约束?因为PFD的复位信号是由两个延迟信号产生的,如果这个路径太长,会导致复位脉冲宽度不够,死区消除效果就打折扣了。
避坑指南:我曾经在一个28nm项目里,因为忘了约束PFD的复位路径,导致后仿时发现复位脉冲宽度只有80ps,而电荷泵需要至少150ps才能完全开启。结果就是PLL在锁定点附近来回振荡,始终无法稳定。加了max_delay约束后,问题立刻解决。
最后说一句,PFD的时序约束不要做得太紧。留一些余量,因为工艺角变化会影响延迟单元的延迟时间。我一般会留20%的余量,这样在SS corner下也能正常工作。
好了,PFD的设计要点就这些。记住,死区消除是重中之重,时序约束也不能马虎。下一章咱们聊电荷泵设计,那又是另一个容易踩坑的地方。