FPGA 进阶 05:复位策略——异步断言同步释放、复位树与多时钟域顺序
本文是
fpga/进阶层第 09 篇,时序三连与 CDC 两连之后的收尾。复位看似「一键清零」最简单,实则是 FPGA 上板怪问题的重灾区——状态机启动不一致、偶发不工作、复位扇出拖垮时序,多和复位策略有关。通用原理可对照 复位设计:异步断言同步释放与复位树,本篇专注 FPGA 的复位同步器写法、GSR 初始化、工具综合行为和复位时序。
本文要回答的问题
- FPGA 上每个寄存器都要复位吗?哪些可以不复位?
- 同步复位和异步复位怎么选?
- 为什么「异步断言、同步释放」是推荐写法,电路长什么样?
- 多个时钟域、PLL 锁定、复位顺序怎么安排?
- 高扇出复位为什么是时序大敌,怎么治?
一、先想清楚:复位到底为了什么
复位的目的是让电路进入一个确定的已知状态。但「确定状态」不一定非得靠外部复位网络——FPGA 有个 ASIC 没有的天然优势:上电配置(GSR,Global Set/Reset)。
- FPGA 比特流下载/上电时,GSR 会把所有触发器置成它们的初始值,这个初值可在 RTL 里声明:
reg [3:0] state = IDLE; // 上电即 IDLE,GSR 保证,无需外部复位
reg [15:0] pipe_a, pipe_b; // 流水线打拍寄存器,初值无所谓、可不复位所以 FPGA 上「上电进入已知状态」很多时候 GSR + 初值就解决了。外部复位网络的价值在于「运行中能重新初始化」(按键复位、软件触发复位、错误恢复、多模块重新同步)。想清楚这一点,就不会给所有寄存器无脑加复位。
二、哪些寄存器该复位、哪些不该
| 类别 | 是否复位 | 理由 |
|---|---|---|
| 状态机当前状态 | 要 | 不复位可能从非法态启动,跑飞 |
| 控制标志、valid/ready、使能 | 要 | 初始电平必须确定,否则误动作 |
| 计数器、FIFO 指针、地址 | 要 | 初值不确定会错数/错地址 |
| AXI/总线配置寄存器 | 要 | 软件期望复位后默认值 |
| 数据流流水线打拍寄存器 | 通常不用 | 数据几拍后被真实数据冲走,初值无所谓 |
| BRAM 内容 | 不逐位复位 | 用初始化文件/使能;BRAM 无异步复位 |
| DSP48 内部寄存器 | 视情况 | 加异步复位可能阻止推断进 DSP,打拍寄存器常不复位 |
过度复位的三大代价:① 复位扇出巨大,布线大网、skew 大、时序差;② 带异步复位的寄存器有时无法被打包进 DSP/BRAM 硬核,被迫用 LUT/FF 实现,又慢又费资源;③ 复位网络自己可能成关键路径。原则:只给「不复位就无法正确启动」的寄存器加复位。
三、同步复位 vs 异步复位
| 同步复位 | 异步复位 | |
|---|---|---|
| 触发 | 复位只在时钟沿生效(always @(posedge clk) 里判断 rst) | 复位随时生效(always @(posedge clk or posedge rst)) |
| 优点 | 纯同步、无复位亚稳问题;利于工具优化/打包硬核 | 响应即时、不依赖时钟;即使时钟没起来也能复位 |
| 缺点 | 时钟没运行时复位无效;复位脉宽不足可能漏;消耗组合逻辑 | 释放(撤销)时可能撞上时钟沿,导致退出复位不一致 |
| FPGA 倾向 | 资源受限/想打包 DSP 时用 | 配合「同步释放」后最常用 |
异步复位的致命点在释放:复位对时钟是异步的(按键、上电、跨域来的),当复位撤销那一刻恰好在某些 FF 的时钟沿附近(recovery/removal 窗口,相当于复位版的 setup/hold),会出现一部分触发器这个沿就退出复位、另一部分下个沿才退出——复位撤不干净,状态机从「半复位」的不一致状态启动,表现为偶尔复位后不工作、重上電又好了。
四、正解:异步断言、同步释放
推荐结构:复位按下(assert)异步立即生效,复位撤销(deassert)经本时钟域同步后统一释放。 核心是一个复位同步器:
// 复位同步器:异步断言、同步释放
// async_rst_n:外部异步复位(低有效),如按键/PLL 未锁定
// clk:本时钟域时钟;rst_sync_n:给本域用的「干净」复位
module reset_sync (
input wire clk,
input wire async_rst_n, // 异步、低有效
output wire rst_sync_n
);
(* ASYNC_REG = "TRUE" *) reg r1, r2;
always @(posedge clk or negedge async_rst_n) begin
if (!async_rst_n) begin
r1 <= 1'b0; // 断言:复位按下,立即(异步)清
r2 <= 1'b0;
end else begin
r1 <= 1'b1; // 释放:外部复位撤销后,
r2 <= r1; // 经两级同步,一起干净退出
end
end
assign rst_sync_n = r2;
endmodule工作原理:
- 断言异步:
negedge async_rst_n在敏感列表里,复位一拉低,不管时钟来没来,r1/r2 立刻清 0 → 全设计立刻复位,响应快、时钟没起也能复位。 - 释放同步:外部复位撤销(async_rst_n 拉高)后,r1/r2 要等随后的时钟沿才逐级恢复成 1,输出
rst_sync_n在两级之后才拉高——所有用它的触发器都在同一个时钟沿之后一起退出复位,避开 recovery/removal 亚稳窗口。 ASYNC_REG="TRUE"让工具把 r1/r2 放进同一 SLICE 并保护起来(同 CDC 同步器 的道理)。
五、多时钟域:每个域一个复位同步器 + 释放顺序
多时钟设计里,不能把一个同步好的复位直接发给所有时钟域——它在自己的域里是同步释放的,对别的域仍是异步释放。正解:
- 每个时钟域各放一个复位同步器:同一个异步复位源(按键、或「PLL 未锁定」)分别进每个域的 reset_sync,用该域自己的 clk 打两拍,生成该域专用的
rst_n。 - 等 PLL/MMCM 锁定再释放:复位源里纳入
~pll_locked——PLL 没锁定时时钟不稳,必须保持复位;锁定后才允许同步释放。
// 顶层:异步复位源 = 外部按键复位 OR PLL 未锁定
wire async_rst_n = ext_rst_n & pll_locked;
reset_sync u_rst_clk100 (.clk(clk_100m), .async_rst_n(async_rst_n), .rst_sync_n(rst_100m_n));
reset_sync u_rst_clk125 (.clk(clk_125m), .async_rst_n(async_rst_n), .rst_sync_n(rst_125m_n));- 释放顺序:一般先释放时钟/基础设施域(时钟管理、总线互联、配置寄存器),再释放数据通路域,保证下游开始跑时上游时钟、配置、AXI 互连都已稳定。必要时用级联复位同步器(一个域的复位释放后,再触发下一个域同步器)来强制顺序。
- 跨域复位不需要 CDC 数据同步:复位是「把寄存器清 0」,不是传数据;关键是每个域释放要在本域同步(上面已保证),断言异步立即生效即可。
六、复位树与高扇出:时序大敌
一个 rst_n 若带几千个触发器,就成了高扇出网——布线绕远、skew 大,还可能拖慢时序(复位虽不在数据路径,但 recovery/removal 检查同样吃布线延迟)。治理:
- 源头降扇出:按第二节精简复位范围(流水线寄存器不复位),负载数量先减下来。
- 让工具复制/缓冲复位网:复位同步器输出加复制属性,Vivado 会自动把高扇出复位缓冲成多路、走全局/局部布线资源;可手动
(* MAX_FANOUT = 500 *)或让工具set_property MAX_FANOUT。 - 复位走专用资源:FPGA 的全局时钟/复位布线(BUFGBUFGCE、局部复位 BUFR 区域)skew 小,工具通常自动把复位/高扇出网推上去;别用普通逻辑布线拖复位。
- recovery/removal 违例:这是复位版的 setup/hold(检查复位释放相对时钟沿的时间)。用同步释放后,复位同步器输出对本域时钟是「干净同步」的,recovery/removal 违例基本消失——这正是同步释放除了功能一致外的时序好处。
七、典型现象与调试
| 现象 | 可能原因 | 排查/做法 |
|---|---|---|
| 复位后偶尔不工作、重上电又好 | 异步复位释放撞沿、退出不一致 | 改异步断言同步释放复位同步器 |
| PLL 还没稳就跑、乱码 | 没等 locked 就释放复位 | 复位源纳入 ~pll_locked |
| 某时钟域复位后状态错 | 各域共用一个同步复位 | 每个时钟域独立复位同步器 |
| 复位网时序差、WNS 红 | 复位扇出过大 | 精简复位范围 + 复制/走全局资源 |
| DSP/BRAM 没推断成硬核 | 给内部寄存器加了异步复位 | 打拍寄存器不复位,让工具推断硬核 |
| 上电正常、运行中复位不彻底 | 复位顺序错、部分模块未复位 | 检查复位树覆盖与释放顺序 |
八、无复位设计学派:哪些寄存器可以、甚至应该不复位
「所有寄存器都要复位」是新手被灌输的第一条规则,也是大规模设计里最该被修正的一条。理解哪些寄存器不需要复位,既能显著改善时序面积,也是读懂现代 IP 复位策略的前提。数据通路寄存器是主要的免复位候选:流水线各级的数据寄存器、FIFO/RAM 数据输出、乘法器结果寄存器——它们的内容在每个有效周期都会被新数据覆盖,只要控制面保证「只有有效数据才被使用」(valid 信号、空满标志、状态机不依赖数据初值),上电时这些寄存器是 0 是 1 还是 X 都无关紧要。给它们接复位的代价很实在:复位变成高扇出网线穿过整条流水线,挤占布线资源、增加负载、限制频率;复位网络还可能让工具无法把数据寄存器最优地打包进 DSP/BRAM(硬核的数据寄存器往往没有可用的复位端,强制复位会把计算逻辑逼回 LUT)。必须复位的是「定义系统身份」的寄存器:状态机当前状态(否则上电后状态机从任意态启动)、指针和地址产生逻辑、配置寄存器(软件读取前需要默认值或由软件显式初始化)、控制标志、中断使能。RAM 的策略:块 RAM 通常不做整体复位(资源代价大且无必要),由软件/固件在使用前初始化;依赖 RAM 初值的逻辑本身就是缺陷。免复位设计的配套纪律:一是 valid/ready 等有效位必须复位,数据面无复位、控制面全复位,边界清晰;二是上游不允许在数据无效时下读使能,保证垃圾永不被消费;三是仿真给数据寄存器赋 X 初值而不是 0——零初值会掩盖「错误依赖了未初始化数据」的 bug,X 传播能让这类问题在仿真里立即现形;四是 lint 规则区分「有意不复位」与「漏接复位」,用属性或注释显式声明,不能让设计里散落着无法解释的无复位寄存器。与工具原语的配合:DSP48、URAM 等硬核的复位规则由硬件决定(有的只复位某些寄存器、有的复位值固定),设计复位方案时先查目标资源支不支持、支持什么极性,再写 RTL,而不是写完综合发现大量资源推断失败。成熟团队的复位规范通常是一页纸:哪些必须复位、哪些禁止复位、其余按模块约定,评审照表核对。
九、多芯片与多板系统的复位编排:一张全局复位状态机
单片 FPGA 的复位策略延伸到多片原型或板级系统,就变成一个分布式状态机问题,57 篇从原型互联角度涉及过启动顺序,这里从复位设计方法论补全。编排器模式:指定一个主控(板载 MCU、主 FPGA 内的平台逻辑或专用复位控制器)集中管理全局生命周期,而不是把 N 个芯片的复位脚焊在一个按钮上。编排器按依赖图推进阶段:电源稳定与电压确认(等待电源好信号 PG,拒绝在斜坡期间释放)→ 时钟/PLL 锁定检测(所有 MMCM locked 汇聚)→ 基础设施复位释放(JTAG、配置、跨片 TDM 链路)→ 链路训练与帧同步确认→ 各功能域按序释放(先配置/存储控制,再数据通路,最后处理器核与软件入口)→ 上报系统就绪。每一步带超时,超时后进入可诊断的失败态(哪个阶段、哪片芯片、什么条件没满足)而不是无限等待或带着故障继续启动。跨芯片的同步释放:多片同源系统若需要功能域近似同时启动(例如跨片计数器对齐),编排器发「释放命令」、各芯片用自己的本地时钟同步后释放——命令跨异步边界仍走同步器,不追求纳秒级同相(做不到也不需要),只保证都在确定的几个周期内启动。局部复位不影响全局:处理器软复位、某加速器重配、某条链路重训应是局部域复位,编排器支持「复位子树」而不波及无关模块;这要求复位树按域可门控(见第五节)。调试钩子:编排器的状态寄存器可读(当前阶段、历史复位原因、各 PG/locked/训练状态),这些信息经调试总线导出,是 bring-up 阶段定位「系统起不来」的第一手证据;复位原因寄存器区分上电复位、按钮、软件请求、看门狗、欠压,对售后诊断极有价值。与软件的契约:处理器启动代码必须能从硬件寄存器读到「本次是冷启动还是复位恢复」「哪些子系统刚被重训」,避免在半初始化状态下跑业务。仿真先行:整棵复位状态机是纯数字逻辑,应该在仿真里用穷尽/随机测试覆盖所有阶段失败注入(某片锁不住、训练超时、中途按钮复位),上板时这部分逻辑应当已经零疑点——多板系统最不划算的调试方式,就是在硬件上用真实上电去试复位序列对不对。
十、复位方案的评审 checklist 与签收标准
把前面所有内容压缩成一份可直接用于评审的检查清单,是复位知识落地的最后一步。结构层面:每个寄存器的复位状态可归类(必须复位/有意不复位),无「说不清」的漏网项;全设计只有同步复位或「异步断言同步释放」两种范式,不存在模块级的第三种风格混用;每个时钟域恰好一个复位同步器(或一个主同步器加受控分发),不存在裸跨域的复位信号。时序层面:复位路径在时序报告中可识别,同步释放触发器被正确识别(综合报告里的复位同步器数量与设计一致);复位扇出、布线延迟、占用资源在预算内,必要时已复制且各副本同源同序;异步断言路径的恢复/移除检查按策略处理(要么放宽检查并靠同步释放保证安全,要么确认器件支持)。时钟联动:复位与 PLL locked 的关系正确——锁定期间保持复位、失锁能重新进入保护;门控时钟域在门控关闭/打开时复位行为已定义。功能层面:复位断言时刻任意(仿真里在任意周期注入复位,设计都能干净回到初始态);复位期间输出处于安全态(不向片外乱发事务、总线接口呈现 idle 而非 X 或毛刺);复位释放顺序符合模块依赖;局部复位不破坏其他域。验证层面:复位测试用例覆盖上电、运行中复位、复位中再复位(按钮抖动连按)、各域释放顺序、与流量并发的复位(FIFO 有残留数据时复位);形式或仿真检查「复位后 N 拍内状态机必在初始态、所有有效位为无效」这类不变量。系统层面(多片/板级):电源好、时钟锁定、链路就绪纳入启动序列;复位原因与阶段可观测。签收标准建议定为:清单各项有结论(通过/例外带签字)、复位相关 lint 零未解释项、复位注入回归在 CI 中常驻。复位是那种「设计时多花一天、量产少烧一万片」的基础设施——它的评审值得和时序收敛同等严肃。
十一、上电复位与掉电:模拟时刻的数字纪律
复位设计最棘手的部分发生在电源并不「干净存在」的两个时刻——上电爬坡和掉电崩塌,纯数字 RTL 思维在这里完全不够用。上电阶段的问题序列:核心电压和 IO 电压按规定顺序爬坡(多数 FPGA 要求 IO 先于或后于核心,看手册),未达到阈值前所有触发器行为未定义;时钟可能在振荡器稳定前输出杂乱脉冲;PLL 从起振到报告锁定需要毫秒级时间,锁定前输出频率不保证。把这三件事交给一个 RC 延迟加手动按钮处理是经典事故源。正确做法依赖器件内建的上电复位电路(POR,监控电压阈值、产生干净的内部复位)加上「锁定汇聚」逻辑:POR 与所有 PLL locked、必要时外部电源好信号相与后,才进入异步断言同步释放流程(第四节)。外部信号在上电期间的隔离:对侧器件可能比 FPGA 先或后就绪,接口逻辑在复位中应把输出钳在空闲电平(总线 valid 拉低、三态口置高阻),不能让未初始化寄存器向外乱发事务;热插拔场景还需要支持带电 IO 的器件并在端口侧做同步。掉电更危险:电源掉落到阈值以下但数字逻辑还在勉强运行的窗口里,FF 会随机翻转、状态机跑飞,可能写出错误的闪存内容、发出错误的电机指令。高可靠设计的手段包括:欠压检测中断(电压跌到早期阈值即触发软件紧急停车)、掉电保持区(用大容量电容维持几十毫秒,供关键状态写入非易失存储)、输出使能与电源联动(掉电硬件直接关断驱动器,不依赖 FPGA 的意志)。复位去抖:按钮和外部机械复位必须去抖并做脉冲展宽,一次按动可能被识别成多次复位序列。验证方法:用电源周期测试(千万次上下电、不同爬坡斜率、不同温度)逼出上电竞争;板级仿真或 IBIS/电源时序核对确认电源序;仿真里对「复位中输入任意」做随机化检查,保证不存在必须依赖特定上电初值才能工作的逻辑。一句话:复位策略要覆盖从电源开始上升到稳定、再到电源不可用的全过程,而不仅是「按下复位键之后」。
十二、仿真中的复位建模:X 传播、初值与复位测试
复位方案在 RTL 仿真中的可见性比多数人以为的差——仿真是确定性的,寄存器初值默认 X 或全零取决于工具选项,导致两类相反的错觉:要么「什么都没复位也能跑」(初值被隐式置零掩盖了缺陷),要么「全是 X 根本没法仿真」。正确的建模纪律让仿真真正检验复位设计。初值策略:功能验证用显式 X 初值(不要全局开关成零),让「使用了未复位数据」的问题沿数据通路传播成可见的 X;综合时 X 初值无物理意义,真实初值随机——因此仿真能跑而不报错的路径必须能被证明依赖有效位保护(第八节)。复位序列作为测试台第一件事:不要在 testbench 一开始就把复位释放,先验证复位断言期间设计确实处于初始态——输出空闲、计数器为零、状态机在初始状态、RAM 读端口不产生误动作;再在多个随机时刻释放复位,验证不同相位下都能干净启动。运行中复位测试:在任意流量、任意状态下触发复位(包括复位持续时间随机、复位中再次断言),复位释放后要求设计在固定拍数内恢复接收能力,且无残留事务泄漏到外部接口。这是验证「复位是否真的复位了一切该复位的东西」的唯一办法。多域顺序检查:对异步断言同步释放网络做定向检查:复位源异步撤除,各域释放时刻不同,断言跨域逻辑在窗口内不会产生错误输出(握手接口停在安全态)。门级/时序仿真中的复位:反标后检查复位释放路径没有时序问题、POR 序列与时钟锁定逻辑在实际延迟下仍成立。形式化补充:断言「复位期间输出恒为空闲值」「复位后 N 拍内进入指定状态」,形式工具能穷尽证明,对复位这类启动条件问题性价比极高。与 UVM/随机环境集成:复位作为可随机注入的环境事件(不只在开头一次),让全芯片随机回归自然经历大量复位场景——SoC 级死锁恢复、处理器看门狗复位本来就是真实用例。经过这样验证的复位,上板时才可能一次成功;反之,把复位排除在验证计划之外,是把最便宜阶段能抓住的问题留到了最贵的阶段。
十三、收束:复位设计的第一性原理
贯穿全篇的复位知识看似零散,实际上只建立在少数几个第一性原理上,记住原理,细节可以重新推导。第一,复位的唯一目的是把系统带入已知状态。任何不服务于这个目的的复位(给所有数据寄存器例行接复位、复位一个永远先写后读的存储)都是成本,应当被质询。第二,复位网络是物理网络,有时序和负载。它和时钟一样高扇出、跨模块、影响收敛,不能当作免费的逻辑连线;异步断言同步释放、按域同步、控制扇出,都是对这个物理事实的回应。第三,跨异步边界的任何信号都要遵守 CDC 规则,复位不例外——复位撤防本质是一次跨时钟事件,这就是同步释放存在的理由。第四,上电和掉电是模拟世界的事件,POR、电源好、时钟锁定、隔离输出是数字逻辑与物理现实之间的契约,忽略它们就会被真实硬件教育。第五,系统级启动是有序状态机:电源、时钟、基础设施、链路、功能、软件逐级就绪,局部复位不波及全局,编排器掌握全部状态。第六,复位行为必须被验证,而且要在它最可能失败的时刻验证——复位中、流量中、任意相位、任意顺序,而不是只在 testbench 开头按一次。第七,复位策略是团队契约:极性、同步方式、哪些寄存器不复位,必须有全项目统一的规范和评审,混合风格的代价最终由集成者承担。把这七条与前面的 checklist 放在一起,复位设计就从一堆「老工程师说」变成了一套可推导、可审查、可交接的工程体系。每次评审一个新模块的复位方案时,从第一性原理出发问一遍:这个复位服务于已知状态吗、它的物理网络健康吗、跨域了吗、上电序列覆盖了吗、验证过吗——能清楚回答这五问的设计,大概率不会在凌晨三点的上板现场带来惊喜。
常见坑对照
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 所有寄存器无脑加复位 | 扇出大、时序差、硬核推断失败 | 只复位「不复位就无法启动」的寄存器 |
| 异步复位不同步释放 | 复位撤不干净、偶发不启动 | 复位同步器:异步断言同步释放 |
| 多域共用一个复位 | 对别的域是异步释放 | 每域各一个 reset_sync |
| 不等 PLL locked | 时钟未稳就跑 | 复位源含 ~pll_locked |
| 复位高扇出不处理 | recovery 违例、布线拥塞 | 减范围 + 复制 + 走全局布线 |
| 忘了 GSR 初值 | 多余复位、或指望外部复位做初始化 | 用 reg 初值 + GSR 处理上电态 |
相关与延伸
前置:CDC 亚稳态与同步器(复位同步器本质也是两级同步器、
ASYNC_REG)、时序收敛(高扇出/MAX_FANOUT)。 工程规范:工程实践——CDC/复位/功耗。 通用原理(ASIC 视角):复位设计:异步断言同步释放与复位树。
一句话总结
FPGA 复位策略的核心是「想清楚哪些真需要复位,再让复位的来和走都干净」:上电已知状态靠 GSR + reg 初值即可,外部复位网络只为运行中重初始化服务,所以只给状态机/控制标志/计数器/指针/总线配置复位,数据流打拍寄存器和 BRAM/DSP 内部寄存器通常不复位(既降扇出又避免阻止硬核推断);复位方式选异步断言、同步释放——断言用 negedge rst_n 即时生效(时钟没起也能复位),释放则经本时钟域复位同步器(两级 FF + ASYNC_REG)同步后统一拉高,让所有触发器在同一时钟沿后一起退出复位,避开 recovery/removal 亚稳窗口;多时钟域每个域各放一个复位同步器(共用一个同步复位对别的域仍是异步释放),复位源纳入 ~pll_locked(PLL 锁定才释放)并按「先基础设施后数据通路」排序;高扇出复位是时序大敌,靠精简复位范围 + 工具复制缓冲 + 走全局布线资源治理——同步释放顺带把复位的 recovery/removal 时序问题也一并解决。