FPGA 入门 26:时钟与复位——时钟树/PLL 与异步断言同步释放
更新时间:2026-09-12。本文是
fpga/入门层第 28 篇。别用逻辑造时钟见 计数器与分频,跨时钟域见进阶 CDC 亚稳态,复位深讲见进阶 复位策略。
本文要回答的问题
- 时钟为什么必须走专用管脚和全局时钟树?普通布线「驱动一个 clk」到底危险在哪?
- BUFG、IBUFG、时钟区域、时钟偏斜 skew 分别是什么,为什么偏斜会直接造成保持时间违例?
- 同步复位、异步复位各自的生效时刻、资源代价和失败模式是什么?
- 「异步断言、同步释放」为什么能两全其美?复位同步器电路逐行怎么理解?
- 为什么不推荐给所有寄存器加复位?哪些寄存器必须复位、哪些不复位反而更好?
- 多时钟域、PLL locked、上电顺序下,复位应该怎样编排才不会在时钟未稳时启动?
- 按键复位、电源复位、看门狗复位在设计里怎么统一,复位为什么也要做 消抖?
一、时钟走专用资源
晶振 -> 专用时钟脚(CC) -> IBUFG -> PLL/MMCM(分频/移相) -> BUFG -> 全局时钟树 -> 所有 FF- 时钟树低偏斜、高扇出,保证各 FF 几乎同时收到;
- 多频率/相位用 PLL/MMCM,不要用逻辑分频造时钟;
- 时钟数量尽量少,异步时钟之间就是时钟域边界(要 CDC)。
二、同步 vs 异步复位
| 同步复位 | 异步复位 | |
|---|---|---|
| 生效 | 只在时钟沿 | 复位一有效立即 |
| 时钟停了能复位吗 | 不能 | 能 |
| 释放风险 | 无 | 释放踩沿→亚稳/偏斜 |
| 资源/时序 | 占数据通路 | 省,但复位树高扇出 |
推荐:异步断言、同步释放(取两者优点)。
三、复位同步器(异步断言同步释放)
module reset_sync(
input wire clk, rst_n_async, // 外部异步复位(低有效)
output wire rst_n_sync // 同步释放后的复位, 送本时钟域
);
reg r1, r2;
always @(posedge clk or negedge rst_n_async)
if (!rst_n_async) {r1,r2} <= 2'b00; // 断言: 立刻清0(异步)
else {r1,r2} <= {1'b1, r1}; // 释放: 两拍后才撤(同步)
assign rst_n_sync = r2;
endmodule多时钟域:每个时钟域各放一个复位同步器。
四、哪些寄存器要复位
| 类型 | 要不要复位 |
|---|---|
| 状态机 state、valid/握手、使能、控制计数器 | 要(否则上电乱跑) |
| FIFO/存储数据、流水数据、DSP/乘法中间结果 | 通常不要(valid 控制才用,利于进 BRAM/DSP) |
FPGA FF 上电有 GSR 确定初值,但跨域/控制逻辑仍建议显式复位。
五、时钟树详解:为什么「逻辑造时钟」是禁忌
要理解时钟约束,先看一个时钟沿在芯片内部的旅程。外部晶振输出的时钟从专用时钟输入管脚(CC 脚,Clock Capable)进入输入缓冲 IBUFG,可以选择送入 PLL/MMCM 做倍频、分频、移相,再经过全局时钟缓冲 BUFG 驱动全局时钟树。时钟树是一张精心设计的金属网络:通过多级等长分支,让同一个沿到达散布在芯片各处的成千上万个触发器时延迟几乎相同。工具报告里这个「到达时间差」就叫时钟偏斜(skew),全局时钟资源能把它压到极小。所有逻辑在同一个低偏斜时钟下工作,建立/保持时间检查才有意义(时序分析见 XDC 篇)。
如果用计数器某一位、或组合逻辑输出去「驱动另一个 always 的 clk」,这个信号只能走普通布线:普通布线既不等长也没有低偏斜保证,同一沿到达不同触发器的时间可能相差很大;工具默认它不是时钟,往往不会为它做专门的时钟树插入和时序分析,轻则报告里冒出大量未识别时钟、重则局部保持时间违例导致电路时好时坏。此外组合产生时钟还可能带毛刺,一个窄毛刺就被当成有效时钟沿误触发。因此铁律是:always 的时钟输入只能来自晶振经专用资源、PLL/MMCM 输出或全局缓冲。想要「慢一点的节拍」就用单时钟加时钟使能(counter + tick 模式,见 计数器篇),想要不同频率用 PLL,而不是自己造时钟。
一个设计里时钟数量要尽量少。每多一个独立时钟就多一条时钟域边界,边界上所有信号都要按跨时钟域(CDC)规则处理:单比特用两级同步、脉冲用握手或边沿检测、多比特用异步 FIFO(见进阶 CDC、异步 FIFO)。能用使能降速解决的问题,不要引入新时钟;能用一个 PLL 多路输出相关时钟解决的,不要用多个独立晶振。
六、同步复位与异步复位:逐行对比
两种复位风格的差异完全体现在 always 块的敏感列表和 if 条件上。
同步复位的敏感列表只有时钟:always @(posedge clk),块内先判 if(rst) 再判正常逻辑。它的含义是「复位只是一个在时钟沿被采样的特殊输入」:复位按下去后,要等下一个上升沿才真正清寄存器。优点是复位与时钟天然同步,不存在复位信号自身的亚稳问题,复位分布可以和普通数据一样被 STA 严格分析;缺点是复位必须进入每个触发器的数据选择路径,会多耗资源、稍微拖慢时序,而且时钟一旦停止(下电、门控、PLL 未锁定)复位就不起作用。
异步复位的敏感列表同时包含时钟和复位:always @(posedge clk or negedge rst_n),复位低电平一出现在敏感列表里,立刻走触发器专用的清零端 CLR 生效,与时钟无关。优点是一按即清、时钟停了也能复位、不占数据通路;真正的风险全在释放时刻:复位松开的瞬间在电气上是异步的,如果恰好在某个触发器要求的时钟建立/保持窗口附近变化,该触发器可能进入亚稳态;更麻烦的是复位信号也要经过一棵复位树到达全片,释放沿到达各触发器的时间有先后,偏斜叠加亚稳,可能出现「一部分寄存器已经开始按时钟跑、另一部分还保持在复位态」,状态机从撕裂的状态启动。
主流工程做法因此是把两者优点拼起来:断言异步(保证随时能复位)、释放同步(保证有序退出复位),这就是下一节复位同步器存在的理由。选型上,Xilinx FPGA 的触发器原生带异步置/复位端且上电有确定初值,异步断言同步释放最省资源;ASIC 或部分 Intel 流程则有团队偏好同步复位,跟随项目规范即可,但理解两种语义是通用要求。
七、复位同步器逐行理解
第三节给出的复位同步器只有两级触发器,但每一处写法都有讲究,值得逐行拆开:
- 输入是外部低有效复位 rst_n_async(来自复位按键、电源监控芯片或其他逻辑),输出 rst_n_sync 送给本时钟域所有需要复位的单元;
- 敏感列表
posedge clk or negedge rst_n_async让「按下复位」走异步清零通道:rst_n_async 一拉低,两个同步触发器立即被清 0,不等时钟沿,输出立刻变低完成全片断言; - 当外部复位松开(rst_n_async 变高),两个触发器不再被异步强制,开始正常跟随时钟:第一拍 r1 采样到 1,第二拍 r2 才采到 r1 的旧值并输出 1。也就是说松开后还要等两个上升沿,全片复位才撤除;
- 这两拍延迟把「释放时刻」挪到了由时钟沿决定的确定位置,并且即便第一级在松开瞬间亚稳,第二级还有整整一个周期去收敛,等价于对「复位释放事件」做了一次标准的两级同步;
- 输出 r2 驱动的复位树在时钟域内部统一变化,避免各触发器退出复位的时间撕裂。
多时钟域时,没有一个「全局正确」的释放点,因为各个时钟沿彼此异步,所以每个时钟域各放一个同步器,输入都接同一个异步复位源、各自用本域时钟打拍,得到各域独立的同步释放复位。这样按下时大家几乎同时被清,松开时每个域都在自己时钟的安全时刻退出。
实现层面还要配合时序约束:复位同步器内部两级路径按 CDC/最大延迟方式约束,同步器输出驱动的复位树不要被工具随便优化或塞进奇怪的布局;Xilinx 还提供可识别的异步复位同步器属性和 MAX_FAN_OUT 限制,必要时复制同步器降低复位树扇出。入门阶段记住「例化一个 reset_sync 模块、每域一个、输出只在本域使用」即可,深入技巧见进阶 复位策略。
八、复位范围:控制通路必须,数据通路克制
「是不是所有寄存器都要复位」的答案是明确的不需要,而且过度复位有害,但要按寄存器职责区分:
必须复位的是决定电路往哪里走的控制状态:状态机当前状态(不复位会从随机编码启动,可能跳进未定义状态)、valid/ready 等握手标志、FSM 计数器和指针、使能与模式配置、输入同步器各级(最好复位到无效电平)、边沿检测寄存器(避免上电第一拍假沿)。这些寄存器一旦初值非法,电路行为没有定义。
通常不复位的是纯数据流水寄存器:乘法器输入输出级、滤波器各级延迟线、AXI-Stream 的 data 通道、FIFO 内部存储阵列、待发送数据缓冲。理由有三层:第一,这些数据只在对应 valid 有效时才会被使用,握手协议保证垃圾初值不会被当真实数据;第二,少接一棵高扇出复位树能显著降低布线拥塞、改善时序和功耗;第三,也是最实际的,带复位或带非零初值的寄存器可能无法被综合器吸收进 BRAM、DSP48、SRL 等专用资源,导致本来一块硬核能放下的设计被拆成大量分散触发器(资源推断见 BRAM、DSP48)。
FPGA 上电时配置完成会释放全局置位信号 GSR,所有触发器有确定的上电初值(多数系列默认 0),所以 initial reg x=0 在 FPGA 里是可综合的初值声明。但要注意:它只在配置完成那一刻生效,热复位(按复位键)时不会重新执行;因此「需要能被按键复位的控制逻辑」必须接显式复位,初值声明只是兜底。一条好记的规则:初值用于配置完成后的已知起点,复位用于运行中可重复地回到起点;数据靠握手保证有效,控制靠复位保证合法。
九、上电与 PLL:复位要等时钟稳定之后
有 PLL 的系统里,复位与时钟稳定的先后顺序至关重要。上电后外部晶振先起振,PLL 需要若干微秒甚至几十微秒才能锁定输入频率并输出稳定时钟,期间 PLL 的输出可能抖动、停振或频率错误;PLL 提供一个锁定标志 locked,只有它拉高后输出时钟才可信。如果内部逻辑在 locked 为低期间就开始运行,计数器、状态机可能在畸形时钟沿下进入任意状态,之后即便时钟恢复也未必回到合法状态。
正确的启动编排是:外部异步复位源(上电复位或按键)与「PLL 未锁定」做逻辑或,共同作为复位同步器的异步输入——即「复位键按下」或「PLL 未锁定」都让全片保持复位;等外部复位释放且 locked 拉高之后,复位同步器再经过本域两拍同步释放,逻辑在干净时钟下有序启动。多时钟域时把「同一异步源与各自时钟就绪条件」组合后分别送入各域同步器;某些时钟可能来自不同 PLL,则各自等待自己的 locked。
实践中时钟向导 IP 常带 locked 输出,复位可以直接利用;若还需要更长的稳定等待(某些板卡电源斜坡慢),可以在锁定后再用本地稳定时钟计数若干毫秒,计数完成才允许复位释放。这个「先有时钟、再放复位」的顺序和电脑开机先稳定电源再释放复位线是一个道理。反之,复位按钮也建议接到 PLL 的复位端,使按复位时整个时钟系统和逻辑一起重启,避免只清逻辑不清时钟域导致的状态不一致(PLL 配置见 时钟向导)。
十、复位从哪里来:按键、电源与内部事件
设计里可能同时存在多个复位来源,工程上会先汇聚成一个异步复位请求,再进入各域同步器:
- 上电复位 POR:FPGA 配置完成本身就有全局初始化,板卡上还可能有电源监控芯片(电压 supervisor)在电源未达标时按住复位;
- 手动复位按键:机械按键会抖动,直接当复位线可能造成多次反复复位、复位宽度不足,因此按键信号要先做和普通按键一样的消抖,或经 RC 加施密特硬件处理(消抖方法见 按键消抖);
- 看门狗/软件复位:状态机跑飞检测、MCU 通过总线写复位寄存器、ILA 调试触发等内部事件,用一个置位锁存器产生至少若干时钟周期的复位脉冲,再由同步器释放;
- PLL/时钟丢失:locked 为低或时钟监控检测到停振,自动保持复位。
把这些来源「或」在一起(低有效则「与」在一起),并保证异步源都先同步化或已经是脉冲锁存后的电平,再统一进入每域复位同步器。复位极性要在全设计保持一致:建议统一低有效 rst_n(低电平复位),内部所有模块沿用同一约定,避免有的模块高有效有的低有效造成接线反转——「一直处于复位」和「永远不复位」是新手最常见的两种症状。复位信号命名也建议规范:异步原始源叫 ext_rst_n,同步器输出叫 clk_rst_n 或 aresetn,看到名字就知道处于复位链哪一级。
复位链本身要防的最后一个陷阱是「复位释放得比用户期望早」:同步器只保证两拍后释放,如果外部复位源本身是窄毛刺(干扰、抖动),可能还没来得及可靠复位就消失。解决方法是在汇聚处加复位展宽:一旦检测到复位请求,至少保持若干毫秒或若干万个时钟周期才允许释放,配合消抖可以得到干净可靠的系统复位。
十一、自查清单与练习
完成一个含时钟和复位的设计后,逐项确认:
- 所有 always 的时钟都来自晶振专用脚经 BUFG 或 PLL 输出,代码中没有用逻辑信号驱动时钟;
- 每个时钟在 XDC 里有 create_clock 或为 PLL 自动派生时钟,报告中无未约束时钟(见 XDC 篇);
- 每个独立时钟域有自己的复位同步器,同步器输出只在本域使用;
- 外部复位与 PLL locked 组合,保证时钟稳定后才同步释放;
- 复位按键经消抖或硬件稳定,复位请求有足够展宽;
- 复位极性全设计统一、命名分层清晰;
- 控制寄存器全部可复位,数据流水寄存器默认不复位且不影响专用资源推断;
- 仿真里覆盖了「复位按下/释放与时钟沿错开」「复位时时钟尚未锁定」「运行中再次复位」三种场景(见 仿真看波形)。
练习建议:先给单时钟计数器加「异步断言同步释放」复位,在 Testbench 里故意让复位释放沿紧贴时钟沿,观察同步器如何把它推到安全时刻;再例化一个 PLL 产生两个频率,做两个时钟域各带计数器和跨域握手,练习「每域一个同步器 + locked 门控」;最后加入消抖后的按键复位和一个运行中触发的软件复位,体会复位汇聚与展宽。时钟与复位平时隐形,却决定了系统「每次上电、每次复位都从同一个合法状态出发」的可重复性——这种可重复性正是区分 demo 和可靠产品的分界线。
十二、典型故障现象与定位思路
时钟和复位问题的共同特征是「偶发、难复现、与温度/布线/上电次数相关」,仿真常常抓不到。认识典型症状能大幅缩短定位时间:
- 上电偶尔正常、按几次复位才工作:高度怀疑复位释放时刻与时钟沿太近,或复位未等 PLL locked。检查复位同步器和 locked 门控;
- 状态机偶尔跳进未定义状态然后死机:可能是 state 未可靠复位、复位树释放有撕裂,或存在逻辑造时钟带来的毛刺。先确认 state 有异步断言复位,再全局搜索「敏感列表里出现非时钟信号」;
- 提高优化等级或换芯片版本后故障消失/出现:典型时序与时钟偏斜问题。检查是否有信号驱动时钟、是否所有时钟都走 BUFG,读时序报告里的时钟网络和偏斜;
- 低温正常高温出错,或反之:延迟随温度变化,恰好踩在建立/保持边界,本质仍是时序边界问题,需要真正收敛而不是挑环境;
- 部分模块正常、某几个寄存器行为怪:可能复位极性接反、复位树没有覆盖到、或同步器输出被误接到另一个时钟域;核对复位连接和时钟归属;
- 热复位后 FIFO/BRAM 残留旧数据:正常现象——不复位数据存储是有意为之,若业务要求复位清空,需显式清存储或用使能信号在退出复位后执行初始化序列;
- JTAG/ILA 一挂上去问题就变化:插入调试核改变了布线和时钟负载,再次提示问题在时序/复位,而不是功能逻辑(调试核见 ILA)。
定位顺序建议是「先确认时钟在跑且频率正确(用可分频到 LED 闪烁或 ILA 观测)、再确认复位波形(宽度、释放沿与时钟关系、locked 期间是否保持)、再看时序报告和时钟树报告、最后怀疑功能代码」。把最底层的心跳和起点证实了,上层逻辑才值得排查。记住一句经验之谈:当一个设计「没有道理地偶尔坏」,十有八九先查时钟与复位,而不是反复检查你最熟悉的那段业务代码。养成先验证心跳、再排查业务的习惯,会帮你省下大量时间。
十三、复位网络也是全局资源:扇出、时序与释放
复位看似只是"开机拉一下"的简单信号,实际上它和时钟一样连接海量触发器,是一条高扇出的全局网络,处理不当会引出布线拥塞、时序违例和不可靠启动等问题,值得专门理解。第一是扇出与布线。一个复位信号若接到几千上万个触发器,工具要把它以尽量小的偏斜送到每个角落,普通布线资源难以胜任,FPGA 提供专用的全局缓冲(如 BUFG)和全局布线网络,规范的复位(无论是异步复位还是经同步器处理的复位)通常应上全局网络,让所有触发器几乎同时退出复位;很多工具能自动识别高扇出复位并推上 BUFG,但你应在综合/实现报告里确认,而不是默认它一定做对。对局部模块的小复位,则不必占用宝贵的全局网络。
第二是异步断言、同步释放(async assert, sync deassert)这一经典折中。纯异步复位(直接接触发器 CLRN/PRE 脚)的好处是不依赖时钟、哪怕时钟没起来也能复位,且省组合逻辑;但"释放复位"的时刻如果恰好落在时钟沿附近,就可能让一部分触发器因建立/保持关系退出复位、另一部分还没退出,导致复位撤除不同步、状态机起点不一致。标准电路是:复位按钮/芯片复位请求先异步进入,复位的"释放"则经过本时钟域的两级同步寄存器后再送达各触发器,保证复位可以异步生效、但退出复位与时钟沿对齐。多时钟域系统里,每个时钟域都应有自己的、经本域时钟同步过的复位释放,而不是一个异步复位信号横扫所有域,否则跨域复位撤除顺序不受控。第三是最小脉冲宽度与去抖:上电复位(POR)电路要保证复位脉冲足够宽、电源稳定后才释放;板载机械复位按键会抖动,必要时像普通按键一样做消抖或直接依赖电源监控芯片。第四是确定哪些寄存器不需要复位(纯数据通路、首个有效数据即整体覆盖的流水线寄存器),少复位能显著降低复位网络扇出、改善时序和资源。把复位当作"第二全局网络"来规划扇出、同步释放和域隔离,是从能跑的实验设计走向可靠工程设计的重要一关。
十四、没有时钟怎么办:门控、动态频率与时钟监控
真实系统并不总是"一个晶振 + 永远稳定运行",工程师会出于功耗、热设计或兼容多速率协议的需要动态改变时钟,这带来若干"时钟不在了 / 时钟在变化"的特殊问题,入门阶段先建立概念即可。其一是时钟门控与启停。为省电想关掉某模块时钟时,不能用普通逻辑与门直接切(门上的毛刺会被当成时钟沿),在 FPGA 里优先使用器件提供的时钟使能(CE,逻辑不动而时钟照供)或专用全局时钟门控缓冲(如 BUFGCE),让时钟在安全边沿处干净地停住、恢复时也无毛刺。启停时钟的模块在时钟恢复后往往需要一次复位或握手,重新对齐状态。其二是动态重配置与频率切换。用 PLL/MMCM 的动态重配置端口可在运行中改变输出频率(如让收发器工作在不同线速、让 DSP 在高性能和低功耗档之间切换),切换期间输出时钟可能不稳定或出现短暂丢失,正确做法是在切换前后让依赖逻辑进入静默态、等待 LOCKED 重新有效、必要时复位下游,绝不能在时钟抖动的窗口里照常采样数据。
其三是时钟缺失/失效检测。在安全和高可靠应用里,要防止外部输入时钟断线或停振导致整个逻辑悄悄死掉,常用手段是用一个稳定的本地参考时钟(往往是独立低频振荡器)去"喂狗":在待监测时钟域周期性翻转一个心跳信号,在参考时钟域检测它是否长时间不变;若超时未见翻转就判定时钟丢失,置报警、切换到备用时钟或进入安全态。这其实是跨时钟域思想在"对端时钟可能根本不存在"时的延伸——正常的双触发器同步器无法处理对端停振(信号永远不变,无法判断是静止还是死机),必须靠超时检测。其四是多时钟选择(clock mux):需要在几个时钟源间切换时,要用专用无毛刺多路复用缓冲(如 BUFGMUX),它们内部处理了切换时的毛刺抑制,不能用普通 LUT mux 直接选时钟。理解这些场景后你会形成一条总原则:时钟是需要被当作模拟级敏感资源管理的对象,任何对它的启停、切换、门控都要用专用原语并配合状态隔离与复位,而"逻辑里造时钟""普通逻辑门控时钟""切频时不等待锁定"正是这类问题的三个典型反面教材。等后续学到时钟规划和低功耗设计,这些直觉会进一步体系化。
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 逻辑信号当时钟 | 偏斜大、时序差 | 晶振/PLL + BUFG |
| 异步复位直接释放 | 释放踩沿、部分 FF 乱 | 同步释放(打两拍) |
| 所有 reg 都复位 | 高扇出、难进 BRAM/DSP | 只复位控制逻辑 |
| 多时钟域共用一个复位 | 跨域释放不同步 | 每域各做同步器 |
| 复位极性搞反 | 一直复位/不复位 | 确认高/低有效 |
| 时钟没接专用脚 | 偏斜/布线问题 | 用 CC 时钟管脚 |
相关与延伸
入门:计数器与分频(时钟使能)、XDC 管脚与时序。 深入:进阶 复位策略(异步断言同步释放/复位树)、CDC 亚稳态、多时钟设计。
一句话总结
时钟要走专用资源:晶振接专用时钟脚、经 IBUFG 和 BUFG 驱动低偏斜的全局时钟树到所有 FF,要多频率/相位就用 PLL/MMCM,绝不用逻辑分频造时钟。复位推荐“异步断言、同步释放”——按下复位用异步(立即生效、时钟停了也能复位),松开时让复位经过两级触发器在目标时钟域同步后再撤除(复位同步器),避免释放瞬间踩亚稳/复位树偏斜导致部分触发器先脱离复位;每个时钟域各放一个同步器。不要给所有寄存器加复位:状态机/valid/使能等控制逻辑必须复位,FIFO 数据/流水/DSP 中间结果等数据通路通常不复位(在 valid 握手下才被用,还能更好映射进 BRAM/DSP)。时钟和复位处理不当是“偶发难复现”bug 的主要来源。
上一篇:28 XDC 管脚与时序(create_clock/管脚/电平) | 下一篇:30 BRAM vs 分布式 RAM(ram_style/推断) | FPGA 系列目录