简介围绕Verilog复位机制展开的这份docx文档面向FPGA开发初学者与数字逻辑设计人员聚焦同步复位、异步复位、同步释放的选型比较以及PLL配置复位设计。文档从两种复位写法的代码与综合后RTL视图差异入手说明异步复位对寄存器CLRN专用端口的利用、同步复位带来的额外逻辑资源以及两级寄存器异步复位在建立/保持时间窗口内撤销时可能引发的亚稳态与系统不同步问题随后给出同一时钟域两级寄存器实现同步释放的思路并延伸到PLL输出前保持系统复位的配置方法。压缩包仅含1个docx文件大小约217KB单文档结构便于按知识点查阅。已有2369人学习适合希望厘清复位信号撤销风险、掌握异步复位同步释放写法并为多时钟域PLL复位设计积累参考的读者。1. 一块偶发跑飞的板子复位没做对时到底会出什么现象FPGA 项目里 Verilog 的复位写法就那么几种同步复位、异步复位、异步复位同步释放抄模板五分钟就能抄完。但一块带 PLL 的板子功能仿真波形干净得像教科书上板跑几个小时偶尔卡死断电重来又恢复正常这种问题查起来就很折磨人。我遇到过的一次根因是异步复位撤销的那一刻正好撞上时钟沿两级寄存器的复位状态一个退出了一个没退出后级状态机拿着半截复位就进入了工作状态。这份文档把同步复位、异步复位、同步释放三种机制的电路真相、资源代价和 PLL 场景下的复位链设计拆开讲了一遍我把里面能直接落到代码和约束上的部分整理出来。它适合已经能写 Verilog、但复位还停留在“加个 rst_n 就行”阶段的人也适合正在排查上电偶发故障的同行。2. 同步复位与异步复位的电路真相CLRN 端口、LUT 开销与选型边界复位机制在代码层面只差一个敏感列表但综合出来的电路结构完全不同。选型的时候如果只看“哪种写法短”很容易在资源或者可靠性上做出错误判断。这一章先把两种写法的综合结果拆开看清楚复位信号最终接到了触发器的哪个引脚上。2.1 异步复位在 RTL 视图里长什么样异步复位的定义是复位信号有效期间不管时钟沿有没有到来寄存器立刻被清零。代码上体现为敏感列表里多了一个negedge rst_n。// 异步复位复位不依赖时钟敏感列表必须带上复位沿 module async_rst_demo ( input wire clk, input wire rst_n, // 低有效异步复位 input wire d, output reg q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; // 异步清零路径 else q d; end endmodule综合之后打开 RTL 视图或者技术映射视图可以看到每个触发器都多了一个清零端。主流 FPGA 的触发器本身就带这个专用端口低电平有效代码里的rst_n直接接到这个端上。如果设计里用的是高有效复位综合工具会自己插一个反相器把信号反过来再接到清零端不需要手工处理。这段代码的关键在敏感列表negedge rst_n必须写而且复位分支必须放在if的第一句。漏写敏感列表的复位沿有的工具会直接报错有的工具只给一条警告然后按同步复位综合仿真和综合结果就对不上了。资源上这条路径几乎零开销复位信号走的是触发器自带的专用网络不占查找表。2.2 同步复位为什么把复位塞进了数据通路同步复位的写法是敏感列表里只有时钟沿复位信号作为普通的条件判断参与逻辑。// 同步复位复位只在时钟沿被采样敏感列表只有时钟沿 module sync_rst_demo ( input wire clk, input wire rst_n, // 低有效但只在 clk 上升沿生效 input wire d, output reg q ); always (posedge clk) begin if (!rst_n) q 1b0; else q d; end endmodule综合工具会把这个if-else展开成数据输入端的选择逻辑q 的 D 端 rst_n ? d : 1b0。触发器自带的清零端被接到了常数上实际不参与复位。复位信号变成了一个二选一的选择信号挂在触发器 D 端前面的组合逻辑上。这里有一个细节容易被误传同步复位并不是无条件多消耗一个查找表。如果 D 端前面的查找表还有空余输入端口复位信号直接占用一个端口就行查找表数量不变。只有当这个查找表的输入已经用满复位信号再进来才会撑出额外一级。所以对于位宽大的总线比如 64 位数据通路同步复位可能额外占用一批查找表代价就开始显眼了。另一个常被忽略的代价是扇出。复位信号要送到每一个受控寄存器的选择端扇出可能上千这条路径上的布线延迟会随着设计规模增长。异步复位走的是专用清零网络扇出压力由器件内部的专用资源分担这是它在大项目里更受欢迎的实际原因之一。2.3 一张表看清两种方式的边界条件对比项异步复位同步复位是否依赖时钟不依赖时钟停了也能复位依赖时钟必须翻转综合后接哪个端口触发器专用清零端数据输入端前级的选择逻辑额外逻辑资源基本为零占用查找表输入输入满时增加一级对复位毛刺敏感窄毛刺也能触发复位不敏感宽度不足一个周期会被漏掉复位撤销风险撤销时刻落在建立保持窗口内会亚稳态复位信号本身仍需对时钟满足建立保持复位最小宽度无要求至少一个时钟周期典型适用场景上电复位、全局复位源小规模逻辑、对毛刺极敏感的场景表里最后一行值得展开。同步复位常被推荐用于“复位信号可能带毛刺”的场合因为窄毛刺在时钟沿采不到。但代价是复位脉冲必须足够宽如果外部复位源给出的脉冲只有一个很窄的尖峰同步复位可能整个漏掉系统根本不复位。异步复位则相反毛刺会立刻生效但如果毛刺出现在状态机运行中途系统会被打进一个非预期状态。所以真实项目里更常见的组合是外部复位源用异步方式立刻生效撤销的时候用同步器对齐时钟沿。这就是后面要讲的异步复位同步释放。2.4 敏感列表写错仿真和综合会分家// 错误示范本意是异步复位敏感列表却漏了 negedge rst_n always (posedge clk) begin if (!rst_n) q 1b0; else q d; end这段代码在行为仿真里看起来“能用”因为仿真器在rst_n变化时不会重新执行这个 always 块要等到下一个时钟沿才响应行为上已经退化成同步复位。综合工具的处理策略不完全一致有的直接按同步复位综合并给警告有的会推断出异步复位但敏感列表不匹配导致前后仿真不一致。血泪经验是综合日志里所有跟复位相关的警告都要逐条看尤其是“incomplete sensitivity list”和“async reset inferred”这两类。前者说明你写的和工具理解的不一样后者说明工具替你改了电路结构。3. 复位撤销那一刻两级寄存器的亚稳态是怎么被放大成系统故障的异步复位真正的问题不在“复位有效”期间而在“复位撤销”的那一瞬间。复位拉低的时候所有寄存器都被强行按住状态是确定的复位拉高的时候如果没有和时钟对齐各个寄存器什么时候退出复位就不确定了。3.1 复位撤销点与时钟沿的相对位置假设时钟上升沿在 t100ns、110ns、120ns 到来。复位信号在 t105ns 从低变高距离下一个时钟沿还有 5ns大于触发器的建立时间加保持时间窗口这时候所有寄存器会在 110ns 这个沿统一采样到“复位已撤销”行为一致没有问题。如果复位信号在 t109.5ns 撤销恰好落在建立保持窗口内情况就变了。触发器在这个时刻对复位端的采样结果处于不确定状态输出可能停在中间电平也可能震荡一段时间才稳定下来这就是亚稳态。更麻烦的是同一个复位信号送到不同触发器由于布线延迟差异有的触发器在窗口前一点点撤销有的在窗口后一点点撤销采样结果就可能不一致。下面这段两级流水是文档里分析的例子用来放大这个问题。// 两级流水复位撤销不同步时b 和 c 可能退出状态不一致 module pipe_stage ( input wire clk, input wire rst_n, input wire a, output reg b, output reg c ); always (posedge clk or negedge rst_n) begin if (!rst_n) begin b 1b0; // 与 c 共用同一个异步复位源 c 1b0; end else begin b a; c b; end end endmodule单独看这段代码逻辑没有问题复位时 b、c 同时清零正常工作时 c 比 b 晚一拍。问题出在综合之后的物理实现上rst_n到达 b 寄存器和 c 寄存器的清零端路径延迟不一样。3.2 四种结局与流水线错位复位撤销落在窗口内时b 和 c 的退出状态有四种组合b 复位、c 复位都还按着下一拍再看通常没事b 退出、c 退出都放开了正常b 复位、c 退出c 提前一拍开始工作拿到的是 b 的旧值b 退出、c 复位b 已经开始更新c 还在清零下一拍 c 采到的 b 值可能是错的。后两种就是流水线错位。在一个只有两级的小模块里错一拍可能只是数据晚一个周期但在一个大项目里几十个模块、上百个状态机同时受同一个复位信号控制退出复位的顺序只要有一处不一致握手信号就会丢状态机就会卡在非法状态。表现出来就是“上电偶尔不工作重新配置一次就好”或者“跑几个小时才出错一次”排查难度极高。文档里提到的“系统工作不同步”就是这个意思。它不是逻辑写错了而是复位释放这个动作本身没有和时钟对齐属于物理层面的确定性缺失。3.3 同步复位能降低但消除不了这个风险看到这里很容易得出“那就全用同步复位”的结论。同步复位确实把复位采样放到了时钟沿上只要复位信号对时钟满足建立时间采样结果就是确定的。但它有两个绕不过去的前提。第一复位信号本身要满足时序。如果复位源是外部按键或者另一颗芯片送来的信号它相对本地时钟就是异步的仍然可能在建立保持窗口内变化只是这个窗口从触发器的清零端转移到了数据输入端风险位置变了没有消失。第二同步复位需要时钟在跑。系统刚上电、PLL 还没锁定的时候时钟是没有的或者不稳定的这时候同步复位根本执行不了。如果一个模块的复位完全依赖 PLL 输出的时钟而 PLL 本身又需要复位才能启动就形成了循环依赖上电第一次大概率起不来。所以文档推荐的方向是异步复位、同步释放复位有效期间用异步的方式立刻生效保证时钟没起来也能把系统按住复位撤销的时候用本地时钟同步一次保证所有寄存器在同一拍退出复位。3.4 用相位扫描把窗口在门级仿真里逼出来RTL 仿真里不会出现亚稳态因为仿真器的寄存器赋值是确定性的复位撤销在窗口内也好、窗口外也好结果都是 0 或 1看不出差别。想看到真实行为需要做门级仿真并反标延时。// 相位扫描每次仿真把复位撤销点相对时钟沿偏移 0.5ns timescale 1ns / 1ps module tb_reset_scan; reg clk 0; reg rst_n 0; real RELEASE_OFFSET; always #5 clk ~clk; // 100MHz时钟沿在 5, 15, 25 ... initial begin if (!$value$plusargs(OFFSET%f, RELEASE_OFFSET)) RELEASE_OFFSET 4.0; #(10.0 RELEASE_OFFSET); // 在第二个时钟沿附近撤销 rst_n 1; #200; $finish; end endmodule这段 testbench 的用法是配合仿真器的 plusargs 参数在脚本里循环传入 0.0 到 10.0 的不同偏移量每个偏移跑一次门级仿真观察 DUT 输出有没有出现 X 或者功能错误。偏移量覆盖时钟周期内的所有位置就能把最危险的窗口区间扫出来。参数说明RELEASE_OFFSET是复位撤销点相对时钟沿的偏移取值 0 到时钟周期#10.0是保证至少经历一个完整的复位周期避免复位宽度不足引入额外变量。实际扫描时步进取 0.5ns 已经足够定位窗口精细定位可以取 0.1ns。注意门级仿真需要工艺库的延时模型和 SDF 文件跑一次的时间可能是 RTL 仿真的几十倍所以不要全回归跑只挑复位相关的用例扫一遍。4. 异步复位同步释放与 PLL 复位链从外部晶振到系统复位的两段式实现异步复位同步释放这个词听起来玄落到电路上就是两三个触发器串联。难点不在结构在于放的位置、复位的极性、以及和 PLL 锁定信号怎么咬合。4.1 复位同步器的标准结构// 复位同步器异步置位复位有效同步撤销复位释放 module reset_sync #( parameter integer STAGES 2 // 同步级数最少 2 )( input wire clk, input wire arst_n, // 异步复位输入低有效 output wire srst_n // 同步释放后的复位低有效 ); // 约束属性让工具把这几级寄存器摆在一起减小布线偏差 (* ASYNC_REG TRUE *) reg [STAGES-1:0] sync_ff; always (posedge clk or negedge arst_n) begin if (!arst_n) sync_ff {STAGES{1b0}}; // 异步生效 else sync_ff {sync_ff[STAGES-2:0], 1b1}; // 移位串 1同步撤销 end assign srst_n sync_ff[STAGES-1]; endmodule逻辑说明复位有效时arst_n拉低sync_ff被异步清零srst_n立刻变低不依赖时钟这是“异步复位”的部分。复位撤销时arst_n拉高sync_ff开始在每个时钟上升沿把 1 往高位推推满STAGES级之后srst_n才变高这个上升沿和时钟严格对齐这是“同步释放”的部分。参数说明STAGES最少取 2因为第一级要留给亚稳态衰减。取 3 会增加复位释放延迟一般场景没必要。副作用是复位释放被推迟了STAGES个时钟周期100MHz 下两级是 20ns对系统启动没有感知。ASYNC_REG这个属性是给综合和布局工具看的告诉它这几级寄存器属于同步器不要优化掉、不要打散摆放。主流的几家工具都认这个属性名写上去没有副作用。4.2 参数、极性与综合属性极性处理是最容易翻车的地方之一。外部复位按键通常是低有效内部某些 IP 又要求高有效来回取反很容易把某一路忘了。// 高有效输入的包装内部统一成低有效处理 module reset_sync_ah #( parameter integer STAGES 2 )( input wire clk, input wire arst, // 高有效异步复位 output wire srst // 高有效同步释放复位 ); wire arst_n, srst_n; assign arst_n ~arst; reset_sync #(.STAGES(STAGES)) u_reset_sync ( .clk (clk), .arst_n (arst_n), .srst_n (srst_n) ); assign srst ~srst_n; endmodule这段包装的意义在于把极性转换收敛到一个地方。项目里所有模块统一用低有效复位只有在对接外部高有效信号的地方做一次取反避免出现“某一路忘了取反”的低级错误。综合属性方面还有两个常用的(* dont_touch true *)防止工具把同步器整体优化掉(* keep true *)防止信号网络被合并。这两个不是必须加的但如果综合报告里发现同步器寄存器数量对不上或者复位网络在网表里消失了先加上排查。4.3 PLL 场景下的复位链锁定之前系统必须一直复位这是文档里最有价值的一段。带 PLL 的项目系统时钟不是晶振直接给的而是 PLL 倍频分频出来的。PLL 从复位释放到输出稳定、locked拉高中间有一段几十微秒到几毫秒的窗口这段时间输出时钟的频率和相位都不可信。如果系统在这段时间已经退出复位寄存器就会在不可信时钟下工作行为完全随机。设计目标很明确PLL 的参考时钟从外部晶振来外部复位先在参考时钟域内做一次异步复位同步释放用这个结果去复位 PLLPLL 锁定之后再用 PLL 输出的系统时钟做第二次同步释放产生最终的系统复位。// PLL 复位链两段式同步释放 module reset_gen #( parameter integer SYNC_STAGES 2 )( input wire clk_in, // 板载晶振PLL 参考时钟 input wire ext_rst_n, // 外部复位低有效 input wire pll_locked, // PLL 锁定指示 input wire clk_sys, // PLL 输出系统时钟 output wire pll_areset, // PLL 异步复位高有效 output wire sys_rst_n // 系统复位低有效已同步释放 ); // 第一段外部晶振域异步复位同步释放 (* ASYNC_REG TRUE *) reg [SYNC_STAGES-1:0] in_sync; always (posedge clk_in or negedge ext_rst_n) begin if (!ext_rst_n) in_sync {SYNC_STAGES{1b0}}; else in_sync {in_sync[SYNC_STAGES-2:0], 1b1}; end wire rst_in_n in_sync[SYNC_STAGES-1]; // PLL 复位高有效外部复位期间保持 PLL 复位 assign pll_areset ~rst_in_n; // 第二段系统时钟域PLL 锁定后才允许释放 wire sys_arst_n rst_in_n pll_locked; (* ASYNC_REG TRUE *) reg [SYNC_STAGES-1:0] sys_sync; always (posedge clk_sys or negedge sys_arst_n) begin if (!sys_arst_n) sys_sync {SYNC_STAGES{1b0}}; else sys_sync {sys_sync[SYNC_STAGES-2:0], 1b1}; end assign sys_rst_n sys_sync[SYNC_STAGES-1]; endmodule逻辑说明按时间顺序走一遍。上电后ext_rst_n为低第一段in_sync异步清零rst_in_n为低pll_areset为高PLL 被按住。同时第二段的sys_arst_n rst_in_n pll_locked为低sys_sync清零sys_rst_n为低系统处于复位态。外部复位撤销后in_sync在clk_in的两个上升沿后推满rst_in_n变高pll_areset拉低PLL 开始启动。此时pll_locked还是低sys_arst_n保持低系统仍然在复位态这段时间正好覆盖 PLL 输出不稳定的窗口。PLL 锁定后pll_locked拉高sys_arst_n变高sys_sync在clk_sys的两个上升沿后推满sys_rst_n释放系统开始工作。如果运行过程中 PLL 失锁pll_locked掉低sys_arst_n立刻变低sys_sync被异步清零系统立即回到复位态。这是需要的行为避免系统在失锁时钟下继续跑。参数说明SYNC_STAGES两段都用同一个参数最少 2。pll_areset的极性取决于 PLL IP 的定义绝大多数是高有效但一定要去核对 IP 手册接反了 PLL 永远起不来。还有一个时序细节pll_areset需要一个最小脉宽才能真正复位 PLL通常在几个参考时钟周期的量级。外部按键按下的时间远大于这个值所以正常使用没问题但如果用仿真或者上电复位电路产生的窄脉冲就要加一级计数器扩展脉宽。4.4 多时钟域的复位分发一个项目里往往有三四个时钟域系统时钟、DDR 时钟、视频像素时钟、低速外设时钟。这时候不能只做一个同步器然后把输出分发给所有域因为每个域的时钟沿位置不同同一个复位信号进入不同域又会回到亚稳态问题。正确做法是每个时钟域各实例化一个复位同步器输入都是同一个异步复位源输出只服务本域。// 多时钟域复位分发每个域一份同步器源头相同 wire rst_100m_n, rst_200m_n, rst_pix_n; reset_sync #(.STAGES(2)) u_sync_100m ( .clk (clk_100m), .arst_n (ext_rst_n), .srst_n (rst_100m_n) ); reset_sync #(.STAGES(2)) u_sync_200m ( .clk (clk_200m), .arst_n (ext_rst_n), .srst_n (rst_200m_n) ); reset_sync #(.STAGES(3)) u_sync_pix ( .clk (clk_pixel), // 像素时钟频率低多一级更稳 .arst_n (ext_rst_n), .srst_n (rst_pix_n) );这样的结构保证了每个域内复位撤销与本地时钟对齐但不同域之间退出复位的时刻不保证一致。如果两个域之间有握手或者数据传递需要在协议层面处理比如发起方在退出复位后先发一个同步脉冲接收方在自己的复位撤销后等待这个脉冲再开始收数据。不要在复位链上追求跨域严格同步那是做不到的应该把同步责任放到数据通路上。5. 排查复位设计里最容易翻车的五个现场复位相关的故障有个共同特征功能仿真过了时序报告也没有明显违例但上板就是偶发异常。下面五条是按排查频次排的每一条都给出可操作的定位手段。5.1 同步器本身用了同步复位上电第一次起不来现象板子重新上电后第一次配置不工作用调试工具重新下载一次就正常了。原因复位同步器的 always 块写成了always (posedge clk)只有时钟沿才响应。而clk来自 PLLPLL 又需要复位才能启动形成死锁。上电瞬间时钟没有同步器不动作PLL 的复位信号也出不来整个系统卡死。重新下载时 PLL 可能还保持着上一次的锁定状态时钟恰好在跑于是就“自己好了”。解决同步器的第一级必须用异步复位端口always (posedge clk or negedge arst_n)这是整条复位链能够自启动的前提。检查方法很简单打开综合后的网表看同步器那几个触发器的清零端是不是接到了复位网络如果接的是常数或者数据逻辑就是写错了。5.2 按键复位不做去抖一次按键触发多次复位现象按一次复位键系统有时候正常启动有时候停在一个奇怪的状态再按一次又好了。原因机械按键的触点在闭合和断开时有几十毫秒的抖动电平在高低之间反复跳变。异步复位对这个抖动是完全透明的每一次跳变都会真的复位一次。如果抖动过程中某个寄存器已经退出复位、另一个还在复位就会出现 5.1 里说的状态错位。解决在复位同步器前面加一级去抖用计数器确认电平稳定之后再输出。// 按键去抖电平稳定超过 CNT_MAX 个时钟周期才更新输出 module btn_debounce #( parameter integer CNT_W 20 // 100MHz 下 20 位约 10ms )( input wire clk, input wire btn_n, // 原始按键输入低有效 output reg btn_clean_n // 去抖后的复位源 ); reg [1:0] sync; reg [CNT_W-1:0] cnt; always (posedge clk) begin sync {sync[0], btn_n}; // 两级同步先在时钟域内稳住 if (sync[1] ! btn_clean_n) begin if (cnt {CNT_W{1b1}}) begin btn_clean_n sync[1]; // 稳定足够久采纳新电平 cnt {CNT_W{1b0}}; end else begin cnt cnt 1b1; end end else begin cnt {CNT_W{1b0}}; // 电平回跳计数清零 p a hrefhttps://download.csdn.net/download/sinat_41653350/12315229 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p