1. 复位这件事远比想象中要命做FPGA开发到一定阶段你会发现一个很有意思的现象新手关注的是“功能能不能跑通”老手关注的是“跑通之后能不能稳定复现”。而复位系统恰恰是横在“能跑”和“稳定跑”之间那道最容易被忽视的坎。我见过太多项目逻辑写得漂漂亮亮仿真波形完美无缺一下板子就出问题——上电之后状态机跑飞了或者偶尔复位不干净重新上电又好了。这类问题排查起来极其痛苦因为它不是逻辑错误而是复位策略的问题。复位没做好整个系统的可靠性就是空中楼阁。这篇内容就是围绕FPGA复位系统与可靠启动设计展开的。我会从复位的基本概念讲起拆解同步复位和异步复位的本质区别然后深入到复位桥的设计、复位树的分布、上电启动时序的处理最后给出几套可以直接复用的代码模板和约束写法。不管你是刚接触FPGA没多久还是已经做过几个项目但总觉得复位这块“差点意思”这篇内容应该都能帮你把这块知识补扎实。需要提前说明的是下面涉及的具体代码和参数是基于常见工程实践给出的参考方案实际项目中你需要根据自己的器件型号、时钟频率和外围电路做调整。我踩过的坑会重点标出来这些才是真正值钱的东西。2. 复位系统的整体设计思路2.1 为什么复位不能随便写很多人写复位就是always块里加个if(!rst_n)觉得这就完事了。但复位信号在FPGA内部的行为远比这个复杂。它涉及到几个核心问题复位信号从哪来怎么分发到各个模块不同时钟域怎么处理释放复位的时候会不会产生亚稳态先说复位信号的来源。常见的复位源有三种外部按键或复位芯片提供的硬复位、内部上电复位电路产生的复位、以及软件触发的软复位。外部硬复位的好处是可靠按键一按或者复位芯片拉低整个系统强制回到初始状态。但问题是它有抖动机械按键的抖动通常在毫秒级别如果不做消抖处理复位信号会在释放时产生多次跳变导致系统状态不确定。内部上电复位依赖器件内部的POR电路不同厂商的FPGA行为不一样。有的器件上电后内部寄存器会自动初始化到确定状态有的则需要外部给一个复位脉冲。这个细节必须查你所用器件的手册不能想当然。软复位则是逻辑内部产生的复位信号比如看门狗超时、协议解析错误等情况下触发。软复位的好处是灵活但风险在于如果复位逻辑本身出了问题软复位可能失效。2.2 同步复位与异步复位的取舍这是复位设计里最经典的讨论。我先给结论推荐异步复位、同步释放。但这个结论背后的原因值得展开说。同步复位的意思是复位信号在时钟沿采样后才生效。它的优点是复位信号不需要满足时序要求对毛刺不敏感因为只要在时钟沿之前稳定就行。缺点是复位需要时钟参与如果时钟没起来复位就无效。而且同步复位会消耗额外的逻辑资源因为综合工具需要在数据路径上插入复位逻辑。异步复位则是复位信号直接作用到触发器的异步复位端不需要时钟参与。优点是响应快不依赖时钟复位路径独立。缺点是复位释放时如果刚好在时钟沿附近可能产生亚稳态——这就是经典的复位释放问题。异步复位、同步释放的方案结合了两者的优点复位断言时异步生效保证快速响应复位释放时通过同步器打两拍保证在时钟域内干净释放。这个方案在工程实践中被广泛验证是绝大多数场景下的最优解。2.3 复位树的分布策略复位信号和时钟信号一样需要分发到FPGA内部成千上万个触发器。如果复位信号扇出太大走线延迟会导致不同区域的触发器复位释放时间不一致这就是复位偏斜问题。解决方法是使用复位树结构通过多级缓冲器把复位信号逐级分发保证到达每个触发器的延迟尽可能一致。在FPGA内部通常使用全局时钟网络来分发复位信号因为全局网络的偏斜控制得最好。但全局资源有限如果复位信号太多就需要做取舍。我的经验是尽量合并复位域。不要每个模块都搞一个独立的复位信号能共用就共用。一般一个时钟域配一个复位信号就够了跨时钟域的地方单独处理。这样既节省全局资源又简化了复位逻辑的分析。3. 核心细节解析与实操要点3.1 异步复位同步释放的标准实现先看代码这是最基础也最重要的一个模块module reset_sync ( input wire clk, input wire rst_async_n, output wire rst_sync_n ); reg rst_meta, rst_sync; always (posedge clk or negedge rst_async_n) begin if (!rst_async_n) begin rst_meta 1b0; rst_sync 1b0; end else begin rst_meta 1b1; rst_sync rst_meta; end end assign rst_sync_n rst_sync; endmodule这段代码的逻辑很直接异步复位信号rst_async_n拉低时两个寄存器立即清零输出复位有效。当rst_async_n释放拉高时第一个寄存器在时钟沿采样到高电平第二个寄存器再延迟一拍最终输出在时钟域内同步释放。这里有个细节需要注意两级同步器的第一级输出rst_meta不能直接使用因为它可能处于亚稳态。必须经过第二级rst_sync才能作为复位信号分发出去。有些新手为了省资源只打一拍这是非常危险的做法。另外这个模块的复位信号输出是低有效。如果你的设计习惯用高有效复位把逻辑反过来就行但要注意保持一致不要混用。3.2 复位信号的消抖处理外部按键复位必须做消抖。机械按键的抖动时间通常在5ms到20ms之间具体取决于按键质量。消抖的思路很简单检测到电平变化后计数器开始计数计满一定时间后如果电平仍然保持才确认复位有效。module debounce ( input wire clk, input wire rst_n, input wire key_in, output reg key_out ); parameter CNT_MAX 20_000_000; // 200ms 100MHz reg [24:0] cnt; reg key_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 25d0; key_reg 1b1; key_out 1b1; end else begin key_reg key_in; if (key_reg ! key_in) begin cnt 25d0; end else if (cnt CNT_MAX) begin cnt cnt 1b1; end else begin key_out key_reg; end end end endmoduleCNT_MAX的计算方式是消抖时间乘以时钟频率。比如100MHz时钟消抖时间200ms那么CNT_MAX 0.2 * 100_000_000 20_000_000。计数器位宽要能容纳这个值25位可以表示到33_554_431够用。注意消抖后的复位信号仍然建议再过一级异步复位同步释放模块确保释放沿干净。3.3 多时钟域的复位处理一个FPGA设计里通常有多个时钟域比如系统时钟、DDR时钟、以太网时钟等。每个时钟域都需要自己的同步释放复位信号。做法是把同一个异步复位源分别送到每个时钟域的reset_sync模块各自产生该时钟域的同步复位。wire rst_async_n; wire rst_sys_n, rst_ddr_n, rst_eth_n; reset_sync u_rst_sys ( .clk (clk_sys), .rst_async_n(rst_async_n), .rst_sync_n (rst_sys_n) ); reset_sync u_rst_ddr ( .clk (clk_ddr), .rst_async_n(rst_async_n), .rst_sync_n (rst_ddr_n) ); reset_sync u_rst_eth ( .clk (clk_eth), .rst_async_n(rst_async_n), .rst_sync_n (rst_eth_n) );这样每个时钟域拿到的复位信号都是在该时钟域内同步释放的避免了跨时钟域的复位释放问题。注意异步复位源本身是同一个所以所有时钟域的复位断言是同时的释放则各自同步。3.4 复位释放顺序的控制有些系统对复位释放顺序有要求。比如DDR控制器必须在系统总线之前完成初始化否则总线访问会出错。这时候就需要控制复位释放的顺序。实现方式是给每个复位域加一个计数器复位释放后计数器开始计数计满之后才释放下一个域的复位。或者用状态机控制前一个域初始化完成信号作为下一个域复位释放的条件。reg [15:0] rst_cnt; reg rst_ddr_release; always (posedge clk_sys or negedge rst_sys_n) begin if (!rst_sys_n) begin rst_cnt 16d0; rst_ddr_release 1b0; end else if (rst_cnt 16hFFFF) begin rst_cnt rst_cnt 1b1; rst_ddr_release 1b0; end else begin rst_ddr_release 1b1; end end这个计数器的作用是给DDR控制器留出足够的上电初始化时间。具体计数值需要根据DDR控制器的初始化周期来定一般查IP手册会有说明。4. 实操过程与核心环节实现4.1 上电启动时序的完整设计一个可靠的上电启动流程应该包含以下阶段第一阶段是上电稳定期。FPGA配置完成后内部逻辑开始运行但此时电源可能还没完全稳定外部晶振也可能还在起振过程中。这个阶段通常需要等待几毫秒到几十毫秒具体取决于电源模块和晶振的规格。第二阶段是复位断言期。在这个阶段所有复位信号保持有效让所有触发器处于确定的初始状态。这个时间不能太短一般建议至少保持100ms以上确保所有逻辑都完成复位。第三阶段是复位释放期。复位信号按照预定顺序逐个释放每个域释放后等待其初始化完成再释放下一个域。第四阶段是正常运行期。所有复位释放完毕系统进入正常工作状态。实现这个流程可以用一个状态机localparam S_PWR_WAIT 3d0; localparam S_RST_HOLD 3d1; localparam S_RST_REL 3d2; localparam S_RUN 3d3; reg [2:0] state; reg [31:0] pwr_cnt; reg [31:0] hold_cnt; always (posedge clk_sys or negedge rst_ext_n) begin if (!rst_ext_n) begin state S_PWR_WAIT; pwr_cnt 32d0; hold_cnt 32d0; end else begin case (state) S_PWR_WAIT: begin if (pwr_cnt 32d10_000_000) begin pwr_cnt pwr_cnt 1b1; end else begin state S_RST_HOLD; end end S_RST_HOLD: begin if (hold_cnt 32d20_000_000) begin hold_cnt hold_cnt 1b1; end else begin state S_RST_REL; end end S_RST_REL: begin state S_RUN; end S_RUN: begin state S_RUN; end default: state S_PWR_WAIT; endcase end end这里pwr_cnt计到1000万在100MHz时钟下就是100ms。hold_cnt计到2000万就是200ms。这两个时间可以根据实际系统调整。4.2 复位信号的约束写法复位信号在时序约束里也需要处理。对于异步复位同步释放的模块需要告诉综合工具复位信号是异步的但释放是同步的。在Xilinx的XDC约束里可以这样写set_property ASYNC_REG TRUE [get_cells {u_rst_sync/rst_meta_reg}] set_property ASYNC_REG TRUE [get_cells {u_rst_sync/rst_sync_reg}]ASYNC_REG属性告诉工具这两个寄存器是同步器需要放在同一个slice里并且做特殊的时序处理。不加这个属性工具可能会把它们分散布局导致同步效果变差。对于复位信号本身的时序约束如果复位是从外部引脚输入的需要设置输入延迟set_input_delay -clock [get_clocks clk_sys] -max 2.0 [get_ports rst_ext_n] set_input_delay -clock [get_clocks clk_sys] -min 1.0 [get_ports rst_ext_n]具体数值需要根据PCB走线延迟和外部复位芯片的时序参数来确定。4.3 复位资源的评估与优化FPGA内部的复位资源是有限的。以常见的7系列器件为例每个slice里有专用的复位走线但全局复位网络只有几条。如果设计中复位信号太多工具会自动降级使用本地复位走线导致偏斜增大。评估复位资源的方法是在综合后的报告里看复位网络的扇出和偏斜。如果某个复位网络的偏斜超过时钟周期的10%就需要考虑优化。优化的手段有几种合并复位域把功能相关的模块放在同一个复位域里使用复位桥把多个复位信号合并成一个在模块内部再解码减少不必要的复位有些数据路径寄存器不需要复位去掉可以节省资源。实操心得不是所有寄存器都需要复位。数据流水线的中间寄存器通常不需要复位因为几个周期后有效数据就会覆盖掉无效数据。只有控制路径的寄存器才需要复位。去掉不必要的复位可以显著减少复位网络的扇出。5. 常见问题与排查技巧实录5.1 复位释放导致的亚稳态问题这是最经典的问题。现象是系统偶尔上电后工作不正常重新上电又好了。原因是异步复位释放时刚好在时钟沿附近触发器的输出进入亚稳态导致后续逻辑采样到不确定的值。排查方法是抓复位释放时刻的波形看复位信号和时钟沿的关系。如果复位释放沿和时钟沿非常接近基本可以确定是这个问题。解决方法就是前面说的异步复位同步释放。如果已经用了这个方案还有问题检查同步器的ASYNC_REG属性有没有加以及同步器的时钟是否稳定。5.2 复位不干净导致的状态机跑飞状态机跑飞是复位问题的另一个常见表现。现象是系统上电后状态机停在某个非法状态或者在一组状态之间循环跳转。原因通常是复位信号没有覆盖到状态机的所有寄存器或者复位释放时状态机的状态寄存器采样到了不确定值。排查方法是检查状态机的复位逻辑确保所有状态寄存器都在复位时被赋初值。另外检查状态机的默认分支确保非法状态能自动恢复到初始状态。always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: state S1; S1: state S2; S2: state IDLE; default: state IDLE; // 关键非法状态恢复 endcase end end这个default分支非常重要它保证了即使状态机因为某种原因进入非法状态也能在下一个时钟沿回到IDLE。5.3 跨时钟域复位释放的时序问题当复位信号需要跨时钟域时如果直接从一个时钟域传到另一个时钟域释放沿可能违反目标时钟域的建立保持时间。解决方法是在每个时钟域都做同步释放。注意是每个时钟域独立做而不是在一个时钟域做好再分发过去。常见问题速查表问题现象可能原因排查方法解决方案上电偶尔不工作复位释放亚稳态抓复位释放波形异步复位同步释放状态机跑飞复位覆盖不全检查状态机复位逻辑补全复位default分支复位后部分模块不工作复位域遗漏检查复位树补全复位连接复位释放后数据错误跨时钟域释放问题检查各时钟域复位每域独立同步释放复位信号抖动按键未消抖观察复位信号波形加消抖模块复位偏斜大复位扇出过大看综合报告合并复位域/复位桥5.4 复位信号毛刺的处理组合逻辑产生的复位信号容易有毛刺。比如用几个信号相与产生复位如果这些信号到达时间不一致输出就会有毛刺。处理方法是把组合逻辑产生的复位信号打一拍再使用或者直接用寄存器输出复位信号。// 不推荐组合逻辑直接产生复位 assign rst_n por_n pll_lock ext_rst_n; // 推荐寄存一拍 always (posedge clk or negedge por_n) begin if (!por_n) rst_n_reg 1b0; else rst_n_reg pll_lock ext_rst_n; end assign rst_n rst_n_reg;寄存一拍后毛刺被时钟沿过滤掉复位信号干净很多。5.5 复位释放时间的计算复位需要保持多长时间这个问题没有统一答案取决于你的系统。但有一个经验公式可以参考复位保持时间至少需要覆盖所有触发器的复位传播延迟加上最慢模块的初始化时间。对于大多数设计100ms是一个比较安全的默认值。如果系统里有DDR、以太网PHY等需要长时间初始化的外设需要根据它们的手册延长。计算方法是找到初始化时间最长的模块查它的手册得到初始化周期然后乘以安全系数1.5到2。比如DDR初始化需要200us那么复位保持时间至少300us。再加上电源稳定时间和晶振起振时间就是总的复位保持时间。6. 复位系统的验证与调试6.1 仿真验证的要点复位逻辑的仿真需要覆盖几个关键场景上电复位、按键复位、软复位、以及复位释放时刻与时钟沿的各种相位关系。在testbench里可以人为控制复位释放时刻让它逐渐逼近时钟沿观察系统行为。如果同步释放做得好无论复位在什么时刻释放系统都应该正常启动。initial begin clk 0; rst_n 0; #1000; // 让复位释放时刻逐渐逼近时钟沿 #99 rst_n 1; // 释放沿在时钟沿前1ns #200; $display(Test case 1 done); end这种边界测试能有效暴露复位释放的时序问题。6.2 在线调试的方法板级调试时可以用逻辑分析仪抓复位信号和关键状态机的波形。触发条件设为复位释放沿观察释放后几个时钟周期内各模块的状态。如果条件允许可以在FPGA内部例化一个ILA集成逻辑分析仪抓取复位信号、时钟、状态机状态等关键信号。ILA的采样深度要足够至少能覆盖复位释放前后各100个时钟周期。实操心得调试复位问题时一定要同时抓复位信号和时钟信号。单独看复位信号看不出问题必须结合时钟沿才能判断释放时刻是否安全。6.3 可靠性测试的流程复位系统的可靠性需要经过反复测试。我的做法是做一个自动测试脚本控制电源反复上下电每次上电后检查系统是否正常启动。循环几百次如果每次都正常说明复位系统基本可靠。测试时要注意记录失败次数和失败时的现象。如果失败率很低比如几百次里失败一次可能是亚稳态问题如果失败率较高可能是复位逻辑有明确缺陷。另外测试要覆盖不同的温度条件。有些亚稳态问题在常温下不出现高温或低温下才会暴露。如果产品有温度要求建议在高低温箱里做测试。7. 几套可直接复用的代码模板7.1 基础复位同步模块前面已经给出了reset_sync的代码这里补充一个带复位源选择的版本module reset_ctrl ( input wire clk, input wire ext_rst_n, input wire soft_rst_n, input wire por_done, output wire rst_sync_n ); wire rst_async_n ext_rst_n soft_rst_n por_done; reg rst_meta, rst_sync; always (posedge clk or negedge rst_async_n) begin if (!rst_async_n) begin rst_meta 1b0; rst_sync 1b0; end else begin rst_meta 1b1; rst_sync rst_meta; end end assign rst_sync_n rst_sync; endmodule这个模块把外部复位、软复位、上电复位完成信号合并成一个异步复位源然后同步释放。注意合并用的是与逻辑任何一个复位源有效都会触发复位。7.2 带消抖的复位输入模块module reset_input ( input wire clk, input wire rst_n, input wire key_n, output wire rst_out_n ); parameter DEBOUNCE_CNT 20_000_000; reg [24:0] cnt; reg key_reg; reg key_stable; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 25d0; key_reg 1b1; key_stable 1b1; end else begin key_reg key_n; if (key_reg ! key_n) begin cnt 25d0; end else if (cnt DEBOUNCE_CNT) begin cnt cnt 1b1; end else begin key_stable key_reg; end end end assign rst_out_n key_stable; endmodule这个模块可以直接接外部按键输出消抖后的复位信号。7.3 多域复位分发模块module reset_distributor ( input wire rst_async_n, input wire clk_sys, input wire clk_ddr, input wire clk_eth, output wire rst_sys_n, output wire rst_ddr_n, output wire rst_eth_n ); reset_sync u_sys ( .clk (clk_sys), .rst_async_n(rst_async_n), .rst_sync_n (rst_sys_n) ); reset_sync u_ddr ( .clk (clk_ddr), .rst_async_n(rst_async_n), .rst_sync_n (rst_ddr_n) ); reset_sync u_eth ( .clk (clk_eth), .rst_async_n(rst_async_n), .rst_sync_n (rst_eth_n) ); endmodule这个模块把同一个异步复位源分发到三个时钟域各自同步释放。实际项目中根据时钟域数量增减例化即可。8. 一些容易忽略的细节8.1 复位信号的命名规范建议统一使用低有效复位命名以_n结尾。比如rst_n、rst_sys_n、rst_ddr_n。这样在代码里看到_n就知道是低有效不容易搞混。如果项目中既有高有效又有低有效复位一定要在信号名里明确标注比如rst_high、rst_low_n。混用是很多bug的根源。8.2 复位释放后的等待周期复位释放后不要立即开始正常操作。建议等待几个时钟周期让所有寄存器都稳定下来。特别是状态机复位释放后应该先进入一个IDLE状态等待几个周期后再开始状态跳转。localparam IDLE 3d0; localparam WAIT 3d1; localparam RUN 3d2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; wait_cnt 4d0; end else begin case (state) IDLE: begin wait_cnt 4d0; state WAIT; end WAIT: begin if (wait_cnt 4d15) begin wait_cnt wait_cnt 1b1; end else begin state RUN; end end RUN: begin // 正常操作 end default: state IDLE; endcase end end这个WAIT状态等待16个时钟周期给系统一个缓冲时间。8.3 复位与时钟的关系复位信号和时钟信号的关系需要特别注意。如果时钟没起来异步复位仍然有效但同步释放无法完成。所以系统里如果有多个时钟必须确保所有时钟都稳定后复位才能释放。做法是把PLL的锁定信号作为复位释放的条件之一。PLL没锁定时复位保持有效PLL锁定后复位才允许释放。wire pll_locked; wire rst_async_n ext_rst_n pll_locked;这样保证了复位释放时所有时钟都已经稳定。8.4 复位信号的PCB设计注意事项如果复位信号来自外部芯片或按键PCB走线也需要注意。复位走线要尽量短远离高频信号线避免耦合噪声。如果走线较长建议加RC滤波。按键复位的话按键两端并联一个0.1uF电容可以有效滤除高频抖动。但电容太大会导致复位释放变慢需要权衡。实操心得我遇到过一个问题复位按键的走线和时钟线平行走了很长一段结果按键释放时时钟线上出现了毛刺导致系统偶尔复位失败。后来把复位走线改到另一层问题就解决了。PCB布局对复位可靠性的影响比想象中大。9. 复位系统的进阶话题9.1 局部复位与全局复位的配合大型设计中全局复位所有模块会导致复位释放时间很长而且不必要的模块也被复位了。更好的做法是全局复位加局部复位全局复位只复位控制路径数据路径的局部复位由各模块自己管理。比如一个视频处理流水线全局复位只需要复位输入输出接口和状态机流水线中间的寄存器不需要复位因为几个周期后有效数据就会覆盖。这样可以减少复位网络的扇出加快复位释放。9.2 复位与低功耗设计的结合如果设计有低功耗需求复位系统也需要配合。低功耗模式下部分模块的时钟被关断此时复位信号的处理需要特别注意。时钟关断时同步复位无法生效必须使用异步复位。另外从低功耗模式唤醒时复位释放的顺序也需要控制。通常先恢复时钟再释放复位最后恢复数据通路。9.3 复位系统的形式验证对于高可靠性要求的场景可以对复位系统做形式验证。用形式验证工具证明无论复位在什么时刻释放系统都能在有限个周期内进入确定状态。形式验证需要写断言比如“复位释放后N个周期内状态机必须处于合法状态”。这种验证比仿真更彻底能覆盖所有可能的时序组合。10. 我踩过的那些坑第一个坑是复位信号没加ASYNC_REG属性。代码逻辑完全正确仿真也没问题但下板子后偶尔启动失败。后来查了很久才发现是同步器寄存器被工具分散布局了导致同步效果不好。加上ASYNC_REG属性后问题消失。第二个坑是复位释放顺序没控制。DDR控制器和系统总线同时释放复位结果DDR还没初始化完成总线就开始访问导致系统挂死。后来加了复位释放顺序控制DDR先释放等待初始化完成后再释放总线复位问题解决。第三个坑是复位信号扇出太大。一个复位信号驱动了上万個触发器综合报告显示偏斜超过2ns导致部分区域复位释放时间不一致。后来把复位域拆分成四个每个域独立同步释放偏斜降到0.3ns以内。第四个坑是按键复位没消抖。按键按下去的时候系统正常松开的时候偶尔复位失败。用逻辑分析仪抓波形发现按键释放时有几十个毛刺。加了消抖模块后问题解决。第五个坑是复位保持时间不够。系统上电后偶尔工作不正常重新上电又好了。后来发现是复位保持时间只有10ms而电源稳定需要50ms。把复位保持时间延长到200ms后问题不再出现。这些坑的共同特点是仿真发现不了只有下板子才会暴露。所以复位系统的验证不能只靠仿真必须做板级测试而且要反复上下电测试。11. 复位系统设计检查清单最后整理一份检查清单每次做完设计可以对照检查所有复位信号是否都经过了同步释放处理同步器寄存器是否加了ASYNC_REG属性每个时钟域是否有独立的同步释放复位复位释放顺序是否满足系统要求外部复位输入是否做了消抖复位保持时间是否足够覆盖所有模块的初始化状态机是否有default分支恢复到初始状态复位信号是否避开了组合逻辑毛刺复位网络的扇出和偏斜是否在可接受范围PLL锁定信号是否参与了复位释放条件是否做了反复上下电的可靠性测试是否做了高低温条件下的复位测试这份清单看起来简单但每一条背后都是实际项目中踩过的坑。复位系统不像功能逻辑那样引人注目但它决定了系统能不能稳定运行。把复位做扎实后面调试会省很多时间。我在实际项目中的体会是复位系统值得花比预期更多的时间去设计和验证。前期多花一天把复位做可靠后期可能省下一周的调试时间。这个投入产出比非常高。