ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

异步复位同步释放:FPGA复位设计从入门到工程落地

异步复位同步释放:FPGA复位设计从入门到工程落地 1. 那一次上电概率性死机把我逼进了复位设计先讲一段真实的调试经历。前几年做一块多通道数据采集板卡FPGA负责采集、缓存和上传。板卡每次上电后大约有五分之一的概率整机不工作——示波器抓时钟、抓电源都是正常的主控和FPGA之间的通信链路就是起不来要手动按一次复位键才能恢复正常。当时第一反应是怀疑电源上电时序有问题改过好几次电源斜坡问题依旧。后来被逼得没办法把FPGA内部所有寄存器的复位信号单独引出来观察才发现了真正的猫腻板卡上电瞬间外部复位芯片输出的复位信号释放时间和FPGA内部时钟的某个上升沿恰好撞在一起了。一部分寄存器采到了复位释放后的高电平另一部分寄存器还停在复位状态整个逻辑处于一种不上不下的中间态。从那一刻起我才真正意识到数字电路设计里最容易被忽视、又最容易埋雷的就是复位信号设计。这篇文章不打算讲太多教科书定义就以做过的项目为线索把复位信号的几种方案从头到尾理一遍重点说说异步复位、同步释放这种组合策略为什么是工程上的主流选项以及它背后的时序原理、代码写法和实际落地时躲不开的那几个坑。对于刚接触数字电路设计和FPGA开发的读者这篇文章可以当一份入门加进阶的复位设计笔记来读。2. 同步复位与异步复位的本质区别以及为什么快和稳经常打架2.1 同步复位逻辑上最简单但前提是时钟必须存在同步复位顾名思义就是复位信号只会在时钟有效沿到来时被采样然后作用于寄存器。它的电路实现通常是在D触发器的数据输入路径上插入一个二选一多路器当复位信号有效时选择器把0送到D端复位信号无效时选择器把正常数据送到D端。用Verilog描述就是always (posedge clk) begin if (!rst_n) q 1b0; else q d; end注意这里的敏感列表里只有posedge clk没有rst_n。综合工具会把它推断成一个不带异步复位端的普通D触发器前面挂一个多路选择器。同步复位最大的好处是天然抗毛刺。因为复位信号必须经过时钟沿采样才会生效任何在两个时钟沿之间出现的窄脉冲干扰都不会被寄存器采到。同时它对时序分析工具非常友好STA静态时序分析可以把复位路径和普通数据路径一样对待不需要额外的约束类型。不过同步复位的缺点也很明显没有时钟复位就失效。芯片上电初期时钟可能还没有稳定起振或者时钟门控把时钟关掉了这时候即使复位信号已经拉低寄存器也收不到复位命令。更麻烦的是在某些低功耗设计里某个子系统的时钟会被关断如果这个子系统内部有寄存器依赖同步复位那这个子系统就永远无法复位。有些设计为了规避这个问题会专门为复位逻辑设计一个常开的复位时钟但这又增加了时钟树的复杂度和功耗。2.2 异步复位响应快、不依赖时钟但释放时刻是个火药桶异步复位则是直接把复位信号接到触发器的异步复位端通常是低有效的CLR端只要复位信号一拉低不管时钟有没有寄存器的输出立刻被清零。always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end敏感列表里出现了negedge rst_n综合工具会把它推断成带异步复位端的D触发器。这类触发器在标准单元库里是真实存在的异步复位端直接控制寄存器内部输出节点响应时间非常短通常只有几纳秒。异步复位的好处很明显复位不依赖时钟上电就能复位复位响应快适合对复位时间有严格要求的场景不需要额外插入多路选择逻辑面积上也有优势。但它有一个非常隐蔽的问题复位什么时候撤销时钟完全不知道。如果复位信号刚好在时钟上升沿附近从有效变为无效那就会和时钟沿产生一个微小的时间竞争。这个竞争可能导致触发器进入亚稳态——输出既不是0也不是1而是介于两者之间的振荡状态。更糟糕的是亚稳态还会通过寄存器链一级一级往下传让整个模块的状态全部错乱。此外异步复位信号对毛刺是裸奔的。只要复位引脚上出现一个足够窄的低电平毛刺寄存器就会被误复位。2.3 两种方案的分工逻辑全局复位用异步内部数据复位用同步在实际项目里我倾向于这样分工芯片级、板卡级的全局复位信号走异步复位因为上电初期最需要快速把整个电路拉到已知状态那时候时钟往往还不可靠模块内部、数据通路上的局部复位走同步复位因为局部复位通常发生在系统已经稳定运行之后时钟是可靠的而且局部复位信号往往由状态机内部产生本身就和时钟同步没必要再用异步复位引入不确定性。当然这只是一个大方向具体还要看应用场景。下面要说的异步复位、同步释放本质上就是想把这两种方案的优势合并把各自的坑都填上。3. 为什么异步复位的释放时刻会翻车恢复时间与移除时间3.1 恢复时间和移除时间不是冷门概念是异步复位的生死线大多数资料在讲异步复位的时候只提一句在复位释放时可能产生亚稳态这句话没错但不透彻。要真正理解为什么要在释放时刻做文章必须搞清楚两个概念恢复时间和移除时间。恢复时间Recovery Time复位信号从无效沿对低有效复位来说就是上升沿到下一个时钟有效沿之间的最短时间间隔。这个概念可以类比建立时间——数据信号必须在时钟沿之前稳定下来同理复位释放信号也必须提前于某个时钟沿稳定下来触发器才能可靠地退出复位状态。移除时间Removal Time时钟有效沿到复位信号无效沿之间的最短时间间隔。类比保持时间——时钟沿到来之后复位信号不能太早撤掉否则触发器可能又变回复位状态。可以这样类比把触发器想象成一扇门时钟沿是关门指令复位信号是保持打开的指令。如果你在关门的一瞬间把手从门上抽走可能会出现门卡在半开半关的状态——这就是亚稳态。恢复时间和移除时间就是规定你提前多久把手抽出和抽出手之后多久才能关门。3.2 仿真正常、上电就挂的根本原因很多人在仿真里验证异步复位逻辑波形一切正常一上板就概率性出问题核心原因是仿真器的时序模型是理想的默认复位撤销和时钟沿永远不打架而真实芯片里的时钟和复位信号都有传播延迟、抖动和占空比偏差两者在某个时钟沿附近打架的可能性是真实存在的。尤其在上电阶段外部复位芯片的释放延迟、板级RC充放电、FPGA内部复位网络的布线延迟这些误差叠加在一起复位释放沿的位置相对于时钟沿是随机游走的。哪怕只有一次释放沿贴着时钟沿那一拍寄存器就可能进入亚稳态进而把X态未知态传播下去。我在项目里抓到的那个概率性死机本质上就是复位释放时间与时钟沿冲突的典型案例。外部的复位信号是一个缓慢上升的RC充放电信号上升沿非常缓在FPGA输入引脚那里经过施密特触发器整形之后释放位置漂移很大有时候就漂到了时钟沿的正中央。3.3 亚稳态之后还会X态传播仿真里尤其明显亚稳态不只是物理上输出不稳这么简单。在仿真工具里亚稳态的后果通常是触发器输出变成X态。而X态会顺着组合逻辑和时序逻辑一路传播——与门输出可以是X选择器输出可以是X进位链也可以是X。到最后整个仿真波形上全是红色的X问题定位无从下手。虽然真实芯片里亚稳态最终会稳定到0或1但稳定到哪一边是随机的不同寄存器可能稳定到不同值这就造成了内部状态不一致。也就是说仿真里你看到的是红成一片实际板子上是状态错乱但波形上全是正常电平——后者反而更难排查。4. 异步复位、同步释放电路结构、工作过程与Verilog实现4.1 电路结构其实特别简单两级触发器异步复位、同步释放的电路结构网上能找到很多版本但核心思想都一样复位信号进入各个寄存器之前先经过一个由两级触发器构成的同步器这两级触发器本身使用异步复位同步器的输出再作为真正的内部复位信号分发到各个功能寄存器。示意图可以这么理解外部复位rst_n | ---- [D触发器1] ---- [D触发器2] ---- 内部复位rst_sync_n | | | | ---- 时钟clk ---- 所有功能寄存器也接clk --------- 时钟clk或外部复位rst_n异步复位端第一级触发器的作用是把异步的复位释放沿抓到本地时钟域第二级触发器的作用是消除第一级可能出现的亚稳态。经过两级之后输出的复位释放沿已经和本地时钟完全对齐了。细心的读者可能会问为什么不直接用一级触发器因为一级触发器只能让释放沿同步一次如果它恰好进入亚稳态输出端的毛刺会直接广播到所有功能寄存器等于把风险放大了。两级触发器之后即使第一级进入亚稳态第二级会再有一个完整的时钟周期让信号稳定下来亚稳态被稀释掉了。4.2 工作过程拆解什么时候快什么时候稳下面把这个电路的工作过程分成两个阶段复位生效阶段异步路径外部复位rst_n拉低时无论此时时钟处于什么位置两级触发器各自通过自己的异步复位端立刻清零输出rst_sync_n直接变为低电平。这个阶段的响应速度只是一个反相器一个传输门的延迟量级基本可以看作立即复位。所以异步复位保证了复位的快速响应不依赖时钟。复位释放阶段同步路径外部复位rst_n撤掉拉高后第一级触发器的异步复位端恢复无效但它不会立即把数据端D的高电平传过去——必须等到下一个时钟上升沿第一级触发器才从复位状态走出来变成输出数据端此时是高电平。再等到第二个时钟上升沿第二级触发器才变成高电平。此时rst_sync_n才真正释放。从时序上看外部复位释放之后内部复位还要再等两个时钟周期才释放。这个多等两拍正是刻意为之的它让所有功能寄存器的复位释放沿都对齐到clk的上升沿消除了异步释放带来的recovery/removal随机性问题。4.3 标准Verilog写法与综合结果我比较常用的写法是这样的module reset_synchronizer #( parameter SYNC_STAGES 2 ) ( input wire clk, input wire rst_n, // 外部异步复位低有效 output wire rst_sync_n // 同步释放后的复位低有效 ); reg [SYNC_STAGES-1:0] rst_shift; // 第一级触发器的D端恒为1复位无效后等待时钟上升沿采集高电平 always (posedge clk or negedge rst_n) begin if (!rst_n) rst_shift {SYNC_STAGES{1b0}}; else rst_shift {rst_shift[SYNC_STAGES-2:0], 1b1}; end assign rst_sync_n rst_shift[SYNC_STAGES-1]; endmodule这里把第一级寄存器的D端恒接高电平复位释放后第一个时钟沿就采样为1然后逐级向后移动。两级链路的参数化也方便如果你觉得两级不够稳可以改成三级、四级。注意一个细节外部复位rst_n在这个模块里同时出现在敏感列表里作为异步复位端这使得两级触发器在复位生效时是瞬间清零的。如果写成下面这样always (posedge clk) begin if (!rst_n) rst_shift 0; else rst_shift {rst_shift[SYNC_STAGES-2:0], 1b1}; end那就变成了不带异步复位端的同步复位写法rst_n只是一个普通数据信号不仅复位生效变慢而且综合工具可能把它识别成普通的移位寄存器加门控逻辑完全违背初衷。在这里敏感列表里必须显式加上negedge rst_n综合工具才会调用带异步复位端的D触发器。4.4 多时钟域设计里每个时钟域都要有自己的复位同步器不少初学者会犯一个错误整个FPGA只做一个复位同步器输出接到所有模块以为同步一次就万事大吉。这不对。复位同步器的作用是把异步复位释放沿同步到某个特定的时钟域。如果芯片里有clk_a和clk_b两个异步时钟域你把复位释放沿同步到clk_a那么clk_b域的寄存器依然面临异步释放的风险。因为rst_sync_n的变化沿相对clk_b来说仍然是异步的。正确的做法是每个时钟域都放一个独立的复位同步器。外部复位信号rst_n作为公共的异步复位源分别进入每个时钟域的同步器同步器使用各域自己的时钟输出的rst_sync_n_a、rst_sync_n_b再分别接到对应时钟域的寄存器上。在多时钟域系统里复位同步器的位置也很讲究。如果可能尽量把同步器放在时钟域的入口附近让复位信号在同步器输出后走一个相对干净的全局网络尽量避免它跨越时钟域边界。5. 复位设计落地时躲不开的三个工程细节5.1 复位树的扇出与全局布线别让复位信号成了另一种时钟芯片规模一大复位信号的扇出绝不小于时钟信号。一个全局复位信号可能要接到几万甚至几十万个寄存器的异步复位端每个复位端的输入电容都非常大。如果直接用一个普通缓冲器驱动不仅上升下降沿会变缓关键路径延迟也会变得不可控。我在FPGA项目里的做法是优先使用全局时钟/复位网络资源。多数FPGA器件都有专门的全局低skew网络可以用原语把复位信号分配到全局网络上尽量让复位到达每个寄存器的时间差控制在极小范围内。这和时钟树的设计思路一模一样。在综合约束里把复位网络声明为set_property或set_clock_groups的高扇出网络让工具优先为它分配布线资源。不要在复位路径上插入额外的组合逻辑。比如有人在复位通路上加一个上电初始化完成的与门这会让复位释放沿又多了一个组合逻辑的延迟而且这个与门可能产生毛刺。如果确实需要组合条件应该在复位同步器之前做而不是之后。5.2 复位信号路径上的毛刺与竞争冒险怎么做去抖前文说异步复位对毛刺敏感这里展开讲一下。在板级电路里按键复位、电源监控芯片的复位输出都可能因为在走线上耦合到干扰而产生窄脉冲。而在芯片内部如果复位信号由其他逻辑组合产生也容易因为多路信号的竞争冒险产生毛刺。处理毛刺有几个层次板级方案在复位信号进入芯片之前加RC低通滤波再接施密特触发器整形。RC时间常数要选得比干扰脉宽大几个数量级同时不能比真正需要的复位脉宽还大否则复位响应太慢。芯片级方案如果复位信号是由芯片内部逻辑产生的不要直接拿组合逻辑输出当异步复位而是先把它打成寄存器信号。组合逻辑产生的复位信号即使只有一个毛刺也会被异步复位端立刻响应。利用复位同步器本身过滤异步复位、同步释放电路里的两级触发器在复位释放方向天然增加了延迟窄毛刺如果落在复位无效阶段第一级触发器会在下一个时钟沿才采样毛刺不一定能穿过去但如果毛刺落在复位有效阶段也就是额外产生了一个低电平毛刺那依然可能导致误复位。因此同步器不是万能的去抖电路板级滤波还是少不得。5.3 时钟没起来之前怎么办复位与时钟启动顺序还有一个常被忽略的问题如果时钟还没稳定异步复位同步器里的触发器本身处于什么状态芯片上电初期时钟源可能在锁定过程中比如PLL输出频率还在爬坡此时触发器如果既没有复位有效又没有稳定的时钟沿输出状态完全不可预测。工程上建议的时序是先让外部复位源保持足够长时间的有效低电平把整个芯片上电过程中所有寄存器的状态都锁到复位态。等待时钟源锁定稳定PLL的locked信号拉高之后。再释放复位让复位同步器把释放沿对齐到已经稳定的时钟上。很多外部复位监控芯片都有上电延迟释放功能目的就是保证时钟已经稳定后才放行复位。FPGA内部一般也要求PLL locked以后再解除复位具体可以在状态机里把locked信号与外部复位做逻辑与之后再送入复位同步器。但要注意这个与操作本身是组合逻辑务必要把外部复位和locked信号先消除毛刺再做与或者将结果在同步器前级打一拍避免毛刺进入异步复位链。6. 在实际项目里我最终落地的复位方案6.1 单时钟域设计无脑推荐异步复位、同步释放如果整个设计只有一个时钟域比如一个纯粹的SPI从机控制器、I2C接口或其他外设逻辑我的建议非常直接外部进来的复位信号先进入复位同步器同步器输出作为全局复位分发给所有功能寄存器。这样做的好处是在几乎不增加逻辑资源的情况下把异步复位的快速响应和同步释放的安全性全部拿到手。两级触发器只占用几十个LUT或触发器资源成本几乎可以忽略。做单时钟域复位时我还会顺手在复位同步器的输出端加一个计数器形成一个复位展宽逻辑让复位有效时间至少保持若干个时钟周期。这样做可以确保在复位信号到来时所有寄存器都稳定复位如果外部复位脉宽太窄展宽逻辑也能保证内部复位条件满足最低脉宽要求。6.2 多时钟域设计每个时钟域一个同步器尤其注意异步FIFO在多时钟域设计里我踩过最大的坑是异步FIFO的复位。异步FIFO的读写时钟分别来自两个不同时钟域它的内部指针同步器、状态标志判断逻辑都很依赖复位状态的一致性。如果两个时钟域各自的复位释放时间不同步可能导致FIFO在刚复位完就出现空/满标志的错误状态。我现在的做法是每个时钟域独立放置复位同步器同步器用本域时钟。异步FIFO的读侧复位和写侧复位分别用两侧各自时钟域的同步复位输出不要跨域去拉另一侧的复位。在跨时钟域的握手协议里把复位状态也当作一种有效状态参与握手。比如在初始化完成标志置位之前让对端时钟域认为本侧尚未就绪避免提前发起数据传输。6.3 不用复位行不行基于寄存器初始化的替代方案有些设计为了提高时序收敛性能会想尽办法减少复位信号的扇出甚至提出不复位的方案——利用FPGA上电时寄存器配置成初始值比如通过比特流初始化来让设计进入已知状态。这种方案在FPGA领域确实存在而且对于某些数据通路设计是可以接受的。原理是FPGA上电配置时位流里已经包含了每个寄存器的初始值寄存器在被配置完成时直接处于这个初始值不需要额外的复位脉冲。但它有一个很明显的局限初始状态只在配置完成那一瞬间有效。如果系统运行过程中某个模块因为异常进入了错误状态没有复位信号就把它拉不回来。因此我只会把无复位设计用在那些出错时可以通过重新配置整个FPGA来恢复的场景比如数据处理链路上可以被清空重灌的流水线寄存器而控制器、状态机这类对错误敏感的模块一定保留复位。ASIC设计则完全不同芯片没有上电初始化配置这个过程所有寄存器在上电后的状态都是不确定的必须靠复位来固定初始状态。所以同步释放这类电路在ASIC里几乎是标配。6.4 时序约束里怎么处理复位信号把复位同步器加到RTL里只完成了一半工作另一半在约束文件里。如果约束不做STA工具会把外部异步复位rst_n到第一级触发器的路径也当作普通路径分析然后报出一堆违例。我在SDC里的做法是# 外部异步复位信号相对于系统时钟是异步的 set_false_path -from [get_ports rst_n] -to [get_pins reset_synchronizer_inst/rst_shift_reg[0]/CLR] # 或者更宽松的做法声明异步时钟组 set_clock_groups -asynchronous -group {clk}这里的意思是外部复位信号释放沿相对clk不需要做严格的recovery/removal检查因为它在同步器第一级之后已经通过两级触发器被同步到时钟域了第一级即使进入亚稳态第二级也能把它滤掉。对于同步器输出rst_sync_n到后续功能寄存器的复位端这部分路径因为输出沿已经与clk对齐STA工具可以当作普通recovery/removal路径分析。只要同步器输出走的是低skew网络且复位树设计合理通常不会出现违例。7. 最后再分享一点个人的排错心得做复位设计我最深的体会是复位这个看似只是个0或1的信号其实牵涉到时序分析、异步处理、上电流程、板级设计等多个层面。如果你在调试中遇到上电后偶发不正常、按一下复位键就恢复这类现象我的优先排查顺序是用示波器同时抓外部复位释放沿和时钟上升沿看两者是否存在紧贴的关系。检查复位信号在PCB上是否有明显的过冲、振铃或抖动。在RTL里确认所有涉及异步复位的地方复位释放是不是都和时钟同步过。在约束文件里确认异步复位路径有没有被正确排除在普通时序分析之外。上面这条排查链路至少帮我解决过三块问题板卡比一开始怀疑电源、怀疑代码逻辑要高效得多。如果你想在下一个项目里抄作业直接在例化复位同步器的时候把SYNC_STAGES设为4而不是2并在外部加一个RC滤波器这个小改动不会增加多少资源却能让复位信号的鲁棒性上一个台阶。我后来在正式产品里就用的是四级同步器加RC滤波的组合再也没有出现过上电概率性死机。
返回列表