ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

异步复位同步释放:Verilog复位同步器设计与亚稳态处理

异步复位同步释放:Verilog复位同步器设计与亚稳态处理 在数字电路设计里有一类信号最容易被忽略但出了问题往往最难定位——复位信号。很多人写代码时always (posedge clk or negedge rst_n)几乎成了肌肉记忆但如果问一句“rst_n 释放的时候到底会不会出问题”能立刻讲清楚的人其实不多。我刚开始做数字电路那阵也在这上面吃过亏上电后状态机偶尔跑飞第一拍数据偶尔错位最后定位到复位释放时序上才意识到复位没有想象中那么简单。这篇笔记专门围绕“异步复位、同步释放”展开把复位同步器的电路结构、Verilog 实现、时序参数、约束处理以及工程里的常见坑都过一遍。适合正在打数字电路基础的同学也适合工作了一两年想系统整理复位设计思路的工程师。异步复位、同步释放的核心价值就一句话复位要来得及时释放要放得可靠。1. 复位信号为什么难设计先分清同步复位与异步复位1.1 一个触发器是怎么被复位的在动手设计复位方案之前得先清楚触发器内部到底发生了什么。CMOS 触发器里通常有一个数据输入端 D、一个时钟端 CK还可能有异步置位端或者异步复位端。所谓异步复位就是复位端只要拉低或拉高输出立刻被强制到固定值和时钟没有任何关系。所谓同步复位是指复位信号必须配合时钟沿才能生效时钟不来复位就只是一个普通输入。从电路行为上看这就像一个是“总闸断电”一个是“每个周期检查一下要不要断电”。异步复位非常直接但它很“硬”只要有效电平出现在复位端输出立刻变。同步复位则像定时巡检时钟沿到来时如果检测到复位条件就执行复位否则继续跑。两侧的代码风格也很典型// 异步复位低有效 always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end // 同步复位高有效 always (posedge clk) begin if (rst) q 1b0; else q d; end很多教材会把这段代码作为入门示例但很少有人告诉你这两种写法背后的时序风险完全不同。异步复位的效果非常立竿见影但它把“复位撤销”这个事件的可靠性压力全部压到了信号边沿上同步复位则把压力转交给了时钟和复位脉冲宽度。两边都不省心于是才需要一套更完整的处理思路。1.2 纯同步复位、纯异步复位为什么都不够先说纯异步复位。它的最大问题不是“复位有效”的时候而是“复位释放”的时候。假如外部 rst_n 信号直接连到几十个触发器的异步复位端当 rst_n 拉高时由于时钟树存在偏斜每个触发器看到时钟沿的先后会有差异每个触发器的内部复位释放时间也不完全一样。结果就是同一个时刻一部分寄存器已经退出复位开始采样新数据另一部分还在复位状态。对于状态机、握手逻辑这类对状态一致性要求高的电路这个窗口就是定时炸弹。更麻烦的是亚稳态。异步复位释放沿如果没有避开时钟有效沿触发器内部的复位端、时钟端会同时动作输出可能落在一个既不是 0 也不是 1 的不确定电平上。很多人以为亚稳态只会出现在异步数据信号上其实异步复位释放沿同样会触发亚稳态。一旦触发器进入亚稳态输出会在高低电平之间震荡很久下一级逻辑采到什么值完全不可预测。再看纯同步复位。它的问题更直白没有时钟就复位不了。芯片上电初期时钟往往还没有稳定这时候同步复位输入就像对着一个没通电的开关按下去什么反应都没有。于是寄存器保持上电时的随机值后面接的组合逻辑会输出一堆垃圾状态。另一个问题是复位脉冲宽度。板级电源监控、按键复位、软件配置复位这些源的脉冲宽度千差万别如果脉冲太窄没满足触发器对复位信号的建立时间要求同步复位就会漏采。你在波形上看着像复位了一次实际上内部什么都没有发生。两种方案的核心短板可以概括为异步复位怕“释放”同步复位怕“复位”。“异步复位、同步释放”正是把这两个短板分别补上的做法。对比项同步复位异步复位是否需要时钟需要时钟沿必须存在不需要有效电平立即生效上电时钟未稳定时能否复位不能能对复位脉冲宽度要求必须满足建立时间窄脉冲容易漏采相对宽松但也不能低于库单元要求释放时是否容易产生亚稳态释放由时钟沿决定风险较低直接释放容易落在时钟沿附近对综合工具的依赖可能被综合为数据端逻辑需要触发器带异步复位端2. 异步复位、同步释放的核心电路与实现2.1 复位同步器电路结构拆解“异步复位、同步释放”的核心电路通常叫复位同步器。它一般由两级 D 触发器构成但和普通数据同步器有个重要区别这两级触发器本身仍然使用异步复位端使用的复位源就是未经同步的原始复位信号 rst_n。第一级触发器的数据输入固定接高电平第二级触发器的数据输入接第一级输出。第二级输出作为处理后的复位信号再发给功能逻辑使用。电路行为可以拆成两段看。复位有效期间原始 rst_n 为低两级触发器都被异步复位到 0此时内部逻辑处于复位状态。复位释放发生时原始 rst_n 拉高但两级触发器并不会马上拉高输出它们要等到时钟沿到来后才依次发生变化第一个时钟沿让第一级寄存器输出变 1第二个时钟沿让第二级寄存器输出变 1。也就是说原始复位信号的“释放沿”被时钟重新对齐了全局复位释放时刻由两个时钟沿决定不再依赖外部信号的具体相位。这个结构里有一个很容易被忽视的设计点为什么第一级数据输入要固定为 1而不是直接把原始 rst_n 接到第一级的 D 端因为如果直接把外部异步复位信号接到 D 端采样外部释放沿还是异步的等效于把一个未经同步的异步信号直接喂给触发器数据端亚稳态问题又回来了。固定接高电平的真正意义是把异步复位信号对一级触发器的控制仅限制在异步复位端上然后依靠两个时钟沿把释放过程逐步“搬”到同步时钟域中。2.2 Verilog 实现与综合要点标准的两级复位同步器代码很简洁module reset_sync ( input wire clk, input wire rst_n, output wire rst_sync_n ); reg rst_n_r1; reg rst_n_r2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_n_r1 1b0; rst_n_r2 1b0; end else begin rst_n_r1 1b1; rst_n_r2 rst_n_r1; end end assign rst_sync_n rst_n_r2; endmodule习惯上我还会在模块上加参数允许配置成三级同步器因为有些安全等级高的场景需要额外冗余module reset_sync #(parameter STAGE_NUM 2) ( input wire clk, input wire rst_n, output wire rst_sync_n ); reg [STAGE_NUM-1:0] rst_sync_reg; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_sync_reg {STAGE_NUM{1b0}}; end else begin rst_sync_reg[0] 1b1; for (int i 1; i STAGE_NUM; i i 1) rst_sync_reg[i] rst_sync_reg[i-1]; end end assign rst_sync_n rst_sync_reg[STAGE_NUM-1]; endmodule在真正的工程里两级已经能满足绝大多数设计三级对“小概率亚稳态”给出了更奢侈的收敛时间。代价也很直观复位释放被延迟了更多时钟周期。对功能逻辑来说如果只是上电复位晚两拍退出复位几乎没有影响但对需要反复复位的场景多出的延迟可能会影响响应速度。综合时要注意一点要让 rst_n_r1、rst_n_r2 被综合成带异步复位端的触发器而不是把复位逻辑吸收进数据路径。大部分综合工具看到always (posedge clk or negedge rst_n)的结构就会自动推断异步复位但如果你把复位信号写进 else 分支的数据路径里或者对复位信号做了额外逻辑加工综合结果可能完全变样。所以代码里尽量不要在复位端前面加任何组合逻辑原始复位信号直接进同步器处理完的 rst_sync_n 再给全局用。2.3 为什么两级寄存器能解决释放亚稳态要理解两级为什么够用得回到亚稳态的时间特性。触发器进入亚稳态后输出不会永久停留在不确定电平上它会随时间推移逐渐向某个确定电平收敛。收敛时间服从指数分布等待时间越长仍然停留在亚稳态的概率越低。普通数据同步器防的是“异步数据采样”复位同步器防的其实是“异步释放沿采样”。如果不用两级而直接把原始异步复位信号接到所有功能触发器上那么每个功能触发器都面对同一个问题rst_n 释放沿和时钟沿之间的间隔是否足够大。只要其中一个触发器的释放沿逼近它的恢复时间余量这个触发器就可能亚稳态。而使用两级复位同步器后功能触发器看到的复位释放沿已经不是外部原始释放沿而是第二级输出边沿。这个释放沿和时钟边沿是对齐的等于把“对抗外部异步沿”的任务收敛到了复位同步器这一个点上。复位同步器内第一级允许产生亚稳态它亚稳态后第二级的采样等待了一个完整时钟周期。即使第一级没有在一个时钟周期内完全收敛第二级也会因为概率原因大幅减少亚稳态发生率。如果再发生说明外部释放沿距离时钟沿实在太近或者芯片本身时序边界就处于极端情况这时候再加一级就能把概率压到可以接受的范围。这也是为什么业界普遍采用两级为默认、三级做保险的根本原因。3. 关键时序参数恢复时间、移除时间与波形分析3.1 恢复时间和移除时间究竟怎么理解提起数字电路时序大家会条件反射想到建立时间和保持时间。复位信号也有两个对应的参数恢复时间 recovery time 和移除时间 removal time。拿异步复位来说复位释放动作发生在时钟沿附近时也存在类似 setup/hold 的要求。释放动作必须提前于时钟沿一段时间这段时间叫恢复时间复位断言动作必须在时钟沿之后继续稳定一段时间这段时间叫移除时间。如果释放沿离时钟沿太近内部节点没有足够时间从复位态恢复到正常采样状态触发器就可能从这一次沿的波形中“读”不出正确结果。为了帮助记忆我一直把恢复时间理解为“复位释放的建立时间”把移除时间理解为“复位断言的保持时间”。同步输入需要满足 setup/hold异步复位输入需要满足 recovery/removal两者是一一对应的。标准单元库会在 .lib 文件里为复位端标出这两项参数时序分析工具会单独检查它们。信号类型建立/保持要求复位恢复/移除要求数据输入 DSetup time无对应概念数据输入 D 的变化Hold time无对应概念异步复位端释放无对应概念Recovery time异步复位端断言无对应概念Removal time这个表格看起来简单但真到分析复位树的时候很容易搞混。我自己的经验是每次写复位相关约束之前先问自己一句“现在讨论的是释放这个沿还是断言这个沿”两个沿对时钟的先后要求不同搞反了后面全乱。3.2 复位释放时实际发生了什么波形推演用一个例子推演一下异步复位、同步释放的完整过程。设时钟周期为 10ns外部复位信号 rst_n 在一个宽的低脉冲后拉高释放时刻恰好距离下一拍时钟上升沿只有 0.5ns。如果这个 0.5ns 小于第一个复位同步寄存器的恢复时间第一级寄存器在下一个时钟沿处可能进入亚稳态。但因为它是复位同步器第二级寄存器不会立刻跟随第一级。第二级要再等一个时钟周期等到下一个上升沿才去采样第一级的输出。此时第一级有整整 10ns 的时间从亚稳态中收敛远超它的恢复时间需求。第二级输出就是实际给到内部逻辑的 rst_sync_n它在这个时钟沿才真正从低变高。内部所有功能寄存器被这个统一的释放沿驱动退出复位的行为在同一拍内完成。推演结果说明了一个很重要的结论外部异步释放沿本身不需要很干净它只要能让第一级寄存器大致感受到“复位已经释放”即可真正决定系统复位释放时刻的是时钟沿。两级结构等于把外部沿的不可控性吸收掉了对外只暴露一个干净的同步释放沿。这也是为什么异步复位、同步释放能同时规避纯异步复位和纯同步复位缺陷的原因。3.3 时钟偏斜、复位树对时序的影响异步复位、同步释放把“外部异步沿”问题解决了但接下来轮到内部的复位树和时钟树。复位同步器输出 rst_sync_n 会作为异步复位端接到大量功能触发器上触发器数量一多复位网本身就有负载和偏斜。如果 rst_sync_n 这个释放沿到达各功能触发器的时刻相差过大那么不同触发器退出复位的时刻照样有先后差整体一致性问题会再次出现。在 ASIC 设计里这个问题通常靠复位树综合解决。复位树和时钟树一样要控制 skew要保证所有触发器在各自时钟沿到来时对 rst_sync_n 的释放都能满足 recovery 和 removal 检查。FPGA 设计里有专门的全局复位资源虽然工具会自动处理但设计者还是要留意尽量不要自己用逻辑门把复位信号兜一圈再传出去复位树上的组合逻辑会带来额外的延迟和一个潜在的竞争冒险点。时钟偏斜的影响也类似。假设一个模块里同时存在 100MHz 和 50MHz 两个时钟域两边都使用同一个复位同步器输出。由于两个域的时钟沿不同步释放沿可能会出现不同相对相位进而导致两个域的复位释放行为不一样。处理这类场景时我通常会在每个时钟域各放一个复位同步器每个同步器只由本域时钟驱动。这样各域的复位释放沿都与自己的时钟对齐避免跨域时序上的不确定性。4. 工程落地的关键细节与工具约束4.1 综合、布局布线中的复位同步器处理复位同步器在综合和布局布线阶段需要特殊照顾。常用的 FPGA 工具里有一个属性叫ASYNC_REG用来标记同步器中的寄存器让布局工具把它们尽量放得靠近并且不对这两个寄存器做移位寄存器推断。XDC 约束可以这样写set_property ASYNC_REG TRUE [get_cells u_reset_sync/rst_n_r1_reg] set_property ASYNC_REG TRUE [get_cells u_reset_sync/rst_n_r2_reg]如果模块例化路径比较深我一般用 filter 加通配符确保约束不会因为路径变化而失效set_property ASYNC_REG TRUE [get_cells -hier -filter {NAME ~ *rst_n_r1_reg*}] set_property ASYNC_REG TRUE [get_cells -hier -filter {NAME ~ *rst_n_r2_reg*}]在 ASIC 工具里没有这个属性名但思路类似把同步器的寄存器设置成dont_touch不要优化合并在时序约束中确保复位同步器的输出路径不是被工具随意弱化或者裁剪。综合之后一定要回头看一下原理图确认 rst_n_r1、rst_n_r2 确实使用了异步复位端。很多时候问题不是逻辑写错了而是工具优化把异步复位优化没了或者把同步器的两级寄存器当普通移位寄存器处理导致后续时序分析和实际硬件出现偏差。4.2 多时钟域下的复位策略一个芯片里通常有多个时钟域。每个时钟域内复位信号必须由本域时钟完成同步释放。如果用一个全局复位同步器输出喂给所有时钟域就会出现两个问题第一异步释放沿对于不同时钟域的相位不同导致各域退出复位的时刻不统一第二跨时钟域传递复位信号本身会引入亚稳态风险。正确做法是“一个时钟域一套复位同步器”。原始复位源可以来自同一个 pad但进入每个时钟域之后都要先经过本域复位同步器再产生本域使用的 rst_sync_n。如果有软件复位、电源监控复位等多个复位源需要先把它们做逻辑与或逻辑或再进入同步器。这样能保证所有复位条件只要有一个成立系统就回到复位态所有条件都释放后系统也只在下一拍统一退出复位。多时钟域还涉及一种情况从复位释放到正常工作的过程中某些跨域握手信号可能还没有稳定。如果复位释放后立即进行跨域通信需要配合跨时钟域同步器一起处理把复位释放后的“等待就绪”状态设计进去。常见做法是让握手发起方在复位释放后多等几个周期再发有效信号这个等待时间要在状态机里明确体现不能只靠复位同步器兜底。4.3 复位线毛刺与竞争冒险处理异步复位端对毛刺非常敏感。因为复位有效电平只要一出现电路立刻被复位哪怕这个毛刺只有几百皮秒宽。板级外部复位源如果来自机械开关、电源监控芯片信号质量往往不好。为了抑制毛刺通常会在板级加 RC 滤波、施密特触发器或者在芯片内部专门加一个毛刺滤除模块再进入复位同步器。毛刺问题本质上是一种竞争冒险现象复位信号网络上存在多个路径延迟的相互作用可能产生瞬时脉冲。在数字电路的框架里竞争冒险常见于组合逻辑中不同输入同时变化时产生的输出毛刺复位树也不例外。如果复位信号从原始源到不同触发器之间经过不同长度的走线且中间有过组合逻辑释放沿到达各触发器的先后会产生竞态。这也是为什么我前面反复强调复位路径上尽量少加逻辑复位树要尽早扇出。对于内部产生的软复位最稳妥的做法是把它当成普通异步信号先同步到本时钟域再参与复位同步器的逻辑合并。不要把内部软复位直接接到功能寄存器的异步复位端否则你等于把另一个异步源引进了复位树把所有释放问题从头再体验一遍。5. 常见问题与排查技巧5.1 复位释放后状态机进不了正常工作态现象仿真里复位功能看着没问题但上板或者长时间跑起来后状态机偶尔进入非法状态或者复位后第一拍数据出错。这类问题十有八九出在复位释放一致性上。先查复位同步器是否在每一个时钟域都有再查全局复位释放沿是否和时钟沿严格对齐。如果某个时钟域直接使用了外部原始复位信号那它内部的寄存器释放行为就会不一致如果复位同步器只做了一级亚稳态的收敛时间不够也会暴露问题。排查时我会在波形里把外部 rst_n、时钟沿、rst_sync_n 三个信号一起拉出来看释放沿相对时钟的位置。通常只观察一两次可能看不出问题需要多次复现或者直接在仿真里强制将释放沿设置在时钟沿附近看电路还能不能自恢复。5.2 仿真中复位信号变 X 的问题很多 RTL 仿真模型对复位端的 X 状态很敏感。复位释放如果刚好卡在时钟沿附近或者复位源本身由 X 驱动仿真波形里经常会出现 rst_sync_n 变成 X导致整个模块输出大量 X。这不是硬件必然错误但会严重影响仿真调试。我常用的处理办法有两个。其一仿真激励里设定复位释放时机时要刻意避开时钟沿比如在时钟下降沿之后若干皮秒再释放或者干脆在时钟半周期的中间释放。这种做法不是在掩耳盗铃而是把测试重点放在复位逻辑本身的正确性上。其二如果需要专门测试亚稳态不建议用 X 传播来模拟最好用真实的单元级模型或者直接做门级仿真。RTL 仿真里强行注入 X 只会让所有东西都变成 X没法定位真正的问题来源。5.3 同步复位被综合成数据路径导致上板异常还有一种很常见的情况代码里写的是同步复位预期占用异步复位端但综合工具把复位逻辑优化成了数据输入端的选择 MUX导致大量组合逻辑出现在数据路径上。后果是复位扇出大、时序难收敛、上电时钟未稳定时寄存器状态不可控。要避免这个问题首先确认功能寄存器的敏感列表里是否写了异步复位端。如果你希望使用异步复位、同步释放策略就不要在功能逻辑里混用同步复位风格。其次检查综合工具对寄存器的推断选项有的工具默认会把复位信号优先实现为同步复位需要显式调整或添加编译属性。综合后我一般会查一下报告里的 register 类型统计确认有多少寄存器用了异步复位端有多少变成了数据路径逻辑。只要异步复位寄存器数量明显偏少第一反应不是去调时序约束而是回头检查代码风格和综合设置。6. 可照抄的复位模块模板与心得体会6.1 代码模板、约束模板和验收清单一个标准单时钟域复位同步器模块可以直接这样用module reset_sync ( input wire clk, input wire rst_n, output wire rst_sync_n ); reg rst_n_r1; reg rst_n_r2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_n_r1 1b0; rst_n_r2 1b0; end else begin rst_n_r1 1b1; rst_n_r2 rst_n_r1; end end assign rst_sync_n rst_n_r2; endmodule在顶层里例化reset_sync u_reset_sync_100m ( .clk (clk_100m), .rst_n (rst_n_global), .rst_sync_n (rst_sync_n_100m) );功能模块统一写成always (posedge clk_100m or negedge rst_sync_n_100m) begin if (!rst_sync_n_100m) ... end这部分代码模式可以当一个模板存在团队代码库里。每次新项目里有人问复位怎么写直接让他抄这个模块而不是现场写一个新的 always 块。代码少是一个优势但更重要的是一致性全项目所有模块都遵守同一个复位规范后面排错会轻松得多。验收时可以对照这份清单过一遍每个时钟域是否都有独立的复位同步器复位同步器的两级寄存器是否设置了 ASYNC_REG内部功能逻辑是否只使用本域 rst_sync_n而没有绕过同步器使用原始复位原始复位信号路径上是否没有额外组合逻辑功能逻辑复位输出是否真正接到异步复位端而不是数据路径仿真中是否检查过复位释放沿与时钟沿的相对关系状态机设计里是否预留了复位释放后的稳定等待周期6.2 我的一点实操体会这套复位设计方法看起来简单真正落实却需要很多细节配合。我个人最大的体会是千万不要把复位当一个“过场信号”去对待。它和时钟一样是全局性的控制信号一旦出问题影响范围是整个模块甚至整个芯片。上电后第一拍数据错了很多人会去查数据通路查组合逻辑查约束最后发现根因在复位释放上这种例子我见过不止一次。还有一点想特别提醒不要因为代码短就掉以轻心。复位同步器的代码只有几行但它的正确性依赖很多外部条件包括工具约束、复位树结构、仿真激励设计。永远不要在模块内部自己“加戏”把复位同步器输出当作普通信号又做一次逻辑运算。让复位信号保持纯粹让它只管复位这一件事后面的设计会省心得多。
返回列表