外部时钟 → PLL → 产生系统时钟 clk_sys
→ 产生锁定信号 locked(异步)
你想把 locked 直接连到各个模块的复位端。那么上电后的流程是:
-
PLL 开始锁定,
locked = 0,clk_sys不稳定 -
一段时间后 PLL 锁定,
locked = 1,clk_sys稳定 -
各模块复位释放,开始工作
看起来逻辑很顺,但问题就出在 第2步到第3步的切换过程。
⚠️ 主要问题(在你这种结构下更明显)
1. 异步释放导致的亚稳态(依然存在,甚至更隐蔽)
locked 信号的上升沿与 clk_sys 之间没有固定相位关系。
-
有可能
locked的变化正好发生在clk_sys的采样沿附近 -
此时用
clk_sys采locked的寄存器会进入亚稳态 -
这个寄存器通常是你第一个状态机或控制逻辑的复位判断点
结果:整个系统的启动行为可能随机失败,偶尔上电能工作,偶尔不行。
2. 复位释放后,第一拍数据不可靠
即使没有明显亚稳态,也可能出现:
-
复位释放的时钟周期内,
locked还未完全稳定 -
部分寄存器提前退出复位,部分还卡在复位
-
组合逻辑路径上出现短时冒险
这类问题极难复现和定位。
3. PLL 短暂失锁会复位整板(设计上不想要)
你的系统时钟完全依赖这个 PLL。如果:
-
输入电源抖动
-
温度变化
-
输入时钟短暂异常
导致 PLL 重新锁定,locked 会短暂变低再变高。此时:
-
整板复位
-
系统时钟一直存在(但短暂不稳定)
-
系统状态被清空
这在很多产品里是不可接受的(例如正在传输数据、正在控制电机)。
✅ 正确做法(对你这个结构是最匹配的)
因为你系统时钟就来自这个 PLL,最标准、最安全的做法是:
用一个简单的同步器,把 locked 变成同步复位释放
电路结构:
text
复制
下载
locked ──┬──→ DFF1 ──→ DFF2 ──→ 全局复位输出
│ ↑ ↑
└─ 异步清零 │ │
└─ 都用 clk_sys ┘
效果:
-
复位释放与
clk_sys完全同步 -
消除亚稳态
-
PLL 短暂失锁不会立刻复位(取决于你设计)
代码示例(直接可用):
verilog
复制
下载
module pll_reset_sync (
input wire clk_sys, // 来自同一个PLL的系统时钟
input wire locked, // PLL的锁定信号
output wire sys_rst_n // 安全、同步的复位信号
);
reg rst_meta, rst_sync;
always @(posedge clk_sys or negedge locked) begin
if (!locked) begin
rst_meta <= 1'b0;
rst_sync <= 1'b0;
end else begin
rst_meta <= 1'b1;
rst_sync <= rst_meta;
end
end
assign sys_rst_n = rst_sync;
endmodule
然后你把 sys_rst_n 给所有模块用,而不是直接给 locked。
🔁 和"外部复位 + PLL"的区别
你可能见过另一种常见方案:
-
外部复位芯片 / 按钮 → 异步复位
-
PLL 锁定 → 释放复位
那种场景下,复位源不是 PLL 自身,问题轻一些。
但在你的情况下:
-
复位源 = PLL
-
时钟源 = PLL
-
两者高度耦合
异步释放风险 + 复位 / 时钟同时建立 → 更容易出问题。