
简介面向FPGA与Verilog初学者及需要夯实复位设计基础的工程师这份文档围绕同步复位、异步复位与同步释放三种机制展开对比剖析各自优缺点及适用场景并进一步延伸到PLL配置下的复位设计思路帮助读者理解复位信号在时钟域中的处理要点。包内共1个docx文件约217KB内容涵盖异步复位综合后寄存器CLRN端口的连接方式、同步复位带来的额外资源消耗、两级寄存器异步复位撤销时可能出现的亚稳态问题以及用同一时钟域寄存器实现同步释放的具体做法并给出PLL时钟有效输出前保持系统复位的设计流程。已有2374人学习下载适合作为复位电路设计与项目排错的参考材料读者可从中获得复位方案选型依据与PLL复位电路搭建思路。1. 三种复位方式选谁先看一个反直觉的结论先说结论在 FPGA 里纯异步复位和纯同步复位都不是大多数项目的最终答案异步复位、同步释放才是那个“看起来绕、用起来稳”的方案。我见过不少设计把always (posedge clk or negedge rst_n)一写了事仿真波形干干净净上板跑几天也没事于是默认这套写法没问题。真正翻车往往发生在复位撤销的那一两个时钟周期里——复位信号从低跳高的时刻恰好落在时钟建立保持窗口内某个寄存器认了 0隔壁寄存器认了 1系统从一个“半复位”状态开始跑现象是偶发、不可复现、换个批次板子又好了。这篇围绕 fpga verilog 同步复位、异步复位、同步释放三种机制把综合后的电路差异、亚稳态的成因、两级同步释放的写法以及多时钟项目里 PLL 配置的复位顺序讲透。新手能直接抄走代码结构熟手能看到每个判断背后的资源与timing依据。2. 同步复位与异步复位综合之后电路到底差在哪2.1 异步复位为什么“省资源”却埋了雷异步复位的写法是敏感列表里同时挂时钟沿和复位沿always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end综合后打开 RTL 视图能看到每个寄存器都挂着一个异步清零端 CLRN复位信号直接接到这个专用端口上低有效就接低有效代码里写高有效综合工具也会在端口前反相。这条路径不经过任何组合逻辑所以复位一拉低寄存器立刻清零不等时钟。资源上确实划算FPGA 寄存器自带这个端口不用额外 LUT。问题出在“撤销”而不是“置位”。复位拉低时所有寄存器同时清零没问题复位从低变高时这个跳变和时钟沿没有任何约定关系。如果它恰好落在某个寄存器的 tsuth 窗口内该寄存器采到的复位值就是不确定的可能认成 0 也可能认成 1。两级串联的寄存器里前一级认了 0、后一级认了 1系统就从一个自相矛盾的状态起步。项目小的时候这种概率低到被忽略寄存器数量一多、时钟域一多这就是一颗定时炸弹。2.2 同步复位为什么“费资源”但行为可预期同步复位把复位信号当普通数据看待always (posedge clk) begin if (!rst_n) q 1b0; else q d; end综合后 CLRN 端不再被使用复位信号 rst_n 变成输入数据逻辑的一个使能项通常表现为寄存器 D 端前面多一级与门或选择器。代价是每个被复位的寄存器多消耗一点组合资源复位信号扇出大时还要考虑布线和时序。好处是复位只在时钟沿生效撤销也只在时钟沿被采样天然避开了异步跳变落在窗口内的问题。但同步复位不是万能的。它要求复位脉冲宽度至少覆盖一个时钟周期否则窄脉冲可能整个被漏掉它也无法在时钟还没起来的时候完成复位——这一点在 PLL 项目里是致命的后面会展开。另外同步复位只是降低了亚稳态出现的概率并没有从根上消除“复位信号本身是异步的”这个事实如果复位源来自按键或外部引脚进同步逻辑之前该做的同步还是得做。2.3 一张表看清两种机制的取舍维度异步复位同步复位综合后电路接寄存器 CLRN 专用端复位信号进入 D 端组合逻辑资源消耗低复用专用端口较高增加 LUT 与布线复位生效时机立即不等时钟需时钟沿脉宽需大于一个周期复位撤销风险高可能落入建立保持窗口低撤销被时钟沿采样时钟未起时能否复位能不能对复位脉冲宽度要求无有至少一个时钟周期这张表是我一般用来跟人解释“为什么不能只看资源”的依据。单看第二行异步复位赢看第四行和第五行结论就反过来了。真实项目里两个需求同时存在时钟没起来时要能复位复位撤销时又不能出亚稳态。所以答案不是二选一。3. 异步复位同步释放两级寄存器怎么接才有效3.1 核心思路是把“撤销”这件事交给时钟域异步复位同步释放的做法是复位有效拉低时仍然走异步路径立刻生效复位撤销拉高时先让复位信号经过两级同一时钟域触发的寄存器打拍再由这两级寄存器的输出作为整个系统真正的复位信号。这样复位撤销的跳变被“同步化”了目标寄存器看到的复位释放沿一定与时钟沿对齐不会落在窗口里。// 复位同步器异步置位、同步释放 reg rst_sync_r1, rst_sync_r2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin rst_sync_r1 1b0; rst_sync_r2 1b0; end else begin rst_sync_r1 1b1; rst_sync_r2 rst_sync_r1; end end wire rst_sync_n rst_sync_r2; // 送给系统各模块的复位关键点在敏感列表这两级寄存器本身仍然是异步复位结构所以外部 rst_n 一拉低它们立刻清零rst_sync_n立刻变低系统立即进入复位。而 rst_n 拉高时第一级在下一个时钟沿采到 1第二级再下一个时钟沿采到 1rst_sync_n的上升沿被推迟了两个周期且严格对齐时钟沿。系统看到的复位释放是同步的亚稳态窗口被绕开了。3.2 参数与写法上的几个硬要求第一两级寄存器必须用同一个时钟且这个时钟就是目标寄存器所在时钟域的时钟。拿 A 时钟域同步出来的复位去复位 B 时钟域的寄存器等于把问题平移没解决。第二两级之间不要插组合逻辑不要为了“省一级”只打一拍。只打一拍时第一级本身仍可能进入亚稳态虽然概率低但同步器的意义就是让第二级有整整一个周期去稳定。这是血泪经验省这一级省不出资源只省出调试时间。第三rst_sync_n的扇出会很大综合时注意给它加约束或者让工具自动插缓冲别让它变成一条又长又慢的线。复位网络的时序问题在中等规模设计里很常见表现是某些角落的模块复位释放比别处晚行为诡异。第四如果设计里有多个时钟域每个时钟域各配一套同步器复位源是同一个外部 rst_n但释放各自同步。不要指望一套同步器通吃所有时钟。3.3 复位同步器本身要不要再同步有人会问外部 rst_n 进同步器第一级的时候不也是异步的吗是的但第一级即使进入亚稳态它的输出会在一个周期内稳定下来第二级采到的是稳定值所以rst_sync_n是干净的。这正是两级结构的意义——把不确定性关在同步器内部不让它泄漏到系统逻辑。理解这一点就不会再纠结“为什么第一级不用同步”。4. PLL 项目的复位顺序先复位谁、后放谁4.1 时钟没起来之前同步复位是失效的多时钟项目基本都会用片内 PLL 做分频倍频。这里有个容易被忽略的顺序问题系统上电后PLL 需要一段锁定时间才输出稳定时钟在这段时间里挂在 PLL 输出时钟上的模块如果用的是同步复位时钟根本没有复位逻辑一行都不会执行模块处于未知状态。等 PLL 时钟突然出来这些模块带着不确定的初值开始跑后面发生什么全看运气。所以 PLL 项目的复位设计必须保证在 PLL 时钟有效输出之前系统其他部分一直保持在复位状态。这句话是整段设计的目标所有接线都是为它服务的。4.2 用外部时钟先做异步复位同步释放再喂给 PLL设计思路是分两层。第一层用 FPGA 的外部输入时钟 clk 对输入复位信号 rst_n 做异步复位、同步释放得到一个“相对于外部时钟干净”的复位信号这个信号同时送给 PLL 的复位端和后续逻辑。第二层等 PLL 锁定、输出时钟稳定后再用 PLL 输出时钟对 PLL 的锁定指示做同步生成各时钟域自己的复位释放信号。// 第一层外部时钟域下的复位同步 reg rst_ext_r1, rst_ext_r2; always (posedge clk_ext or negedge rst_n) begin if (!rst_n) begin rst_ext_r1 1b0; rst_ext_r2 1b0; end else begin rst_ext_r1 1b1; rst_ext_r2 rst_ext_r1; end end wire rst_ext_n rst_ext_r2; // PLL 例化复位用外部时钟域同步后的信号 // pll_core u_pll ( // .refclk (clk_ext), // .rst (~rst_ext_n), // 注意极性按 IP 要求 // .locked (pll_locked), // .clk_out (clk_sys) // ); // 第二层PLL 输出时钟域下等 locked 后再释放复位 reg rst_sys_r1, rst_sys_r2; always (posedge clk_sys or negedge rst_ext_n) begin if (!rst_ext_n) begin rst_sys_r1 1b0; rst_sys_r2 1b0; end else begin rst_sys_r1 pll_locked; rst_sys_r2 rst_sys_r1; end end wire rst_sys_n rst_sys_r2;第一层保证 PLL 在外部时钟有效、复位释放之后才开始工作第二层保证 PLL 输出时钟域里的模块要等locked拉高并且经过两级同步之后才退出复位。这样“时钟未稳时保持复位”这个目标就落地了。极性上要注意不同 IP 的复位端口有效电平不一样代码里~rst_ext_n这种反相要按实际 IP 手册核对别照抄。4.3 多时钟域各自同步别共用一条释放路径PLL 往往输出好几个时钟每个时钟域都要有自己的第二层同步器输入都是rst_ext_n和pll_locked但打拍用的是各自的时钟。共用一条rst_sys_n去复位所有时钟域等于让某个域的寄存器去采另一个域的释放沿前面讲的亚稳态问题原样回来。这一点在模块划分时就要定好复位同步器跟着时钟域走一个域一套放在该域的顶层。5. 避坑清单五个真实踩过的复位问题5.1 仿真通过、上板偶发跑飞现象功能仿真全过上板后系统偶尔从错误状态启动复位几次又正常。原因异步复位直接接系统寄存器复位撤销沿落在时钟窗口内部分寄存器提前退出复位。解决改成异步复位同步释放系统寄存器统一使用同步器输出的复位信号复位撤销沿对齐时钟。5.2 复位脉冲太窄被同步逻辑漏掉现象按键复位偶尔不生效短按没反应。原因同步复位要求脉宽至少覆盖一个时钟周期按键抖动产生的窄脉冲被时钟沿跳过。解决复位输入先做消抖保证脉宽足够或者复位路径保留异步置位能力只在释放侧做同步。5.3 PLL 输出时钟域的模块上电状态不确定现象上电后部分模块行为异常重新按复位才正常。原因这些模块用 PLL 输出时钟做同步复位PLL 未锁定前没有时钟复位逻辑未执行。解决按第 4 章的两层结构用外部时钟域复位控制 PLL用locked控制各输出时钟域的复位释放。5.4 复位同步器只打了一拍现象系统大部分时间正常极少数情况下某个模块状态错乱。原因同步器只做了一级寄存第一级亚稳态直接传给了系统。解决老老实实两级第二级输出才作为复位使用别为了省一个寄存器破坏同步器结构。5.5 复位信号扇出过大导致释放不同步现象大设计里不同角落的模块退出复位的时间不一致出现跨模块握手失败。原因复位网络布线延迟大综合工具没有对复位信号做专门约束。解决给复位网络加时序约束必要时手动插缓冲或做复位树确保同一时钟域内释放沿到达时间可控。6. 落到代码上的最终选择一套可复用的复位骨架把前面的结论收拢成一套可以直接搬进项目的结构。我的习惯是每个时钟域顶层放一个复位同步模块输入是全局异步复位rst_n和该域的时钟输出是该域的同步复位PLL 单独按第 4 章的两层顺序处理。判断一个复位设计是否合格我会走一遍这个检查顺序先看敏感列表里有没有异步沿、再看同步器是不是两级、再看每个时钟域是不是各有一套、最后看 PLL 的locked有没有参与复位释放。四步都过基本不会在复位上翻车。检查项合格标准不合格的典型后果系统寄存器复位方式使用同步器输出非直接接外部 rst_n复位撤销亚稳态偶发跑飞同步器级数两级同域时钟亚稳态泄漏到系统多时钟域处理每域一套同步器跨域释放不同步PLL 复位顺序外部时钟先同步locked 后释放上电状态不确定复位网络约束有扇出与时序约束释放沿到达不一致回过头看我早期最常犯的错就是把“仿真没问题”当成“设计没问题”复位这种只在边界时刻出问题的机制恰恰是仿真最容易放过的。从那以后我每次新项目搭顶层都强制先把复位骨架和 PLL 顺序画出来再写功能代码宁可多花半小时也不在上板后花三天抓一个偶发 bug。希望这套顺序帮到你。本文还有配套的精品资源点击获取