2. 异步复位同步释放:经典双寄存器同步器的原理、Verilog实现、时序约束要求

好,咱们接着聊复位。上一章我讲了异步复位和同步复位各自的优缺点,你可能会问:有没有一种方案,能把两者的好处都占了?

答案是肯定的。这就是我今天要重点讲的——异步复位同步释放。说白了,就是复位信号异步生效,但释放(撤除)的时候,让它跟时钟同步。这个思路,我在好几个量产项目里都用过,效果很稳。

2.1 为什么需要同步释放?

先想想看,异步复位最大的问题是什么?

是复位释放时刻的不确定性。如果复位信号在时钟沿附近撤除,就可能产生亚稳态。寄存器可能采到0,也可能采到1,甚至可能震荡。这会导致整个系统行为不可预测。

我遇到过最头疼的一次:板子跑着跑着,偶尔会莫名其妙地死机。查了三天,最后发现是复位释放路径上有个寄存器进入了亚稳态,把状态机带飞了。从那以后,我对复位释放的时序格外敏感。

异步复位同步释放,就是来解决这个问题的。它让复位释放的时刻,严格对齐到时钟沿。这样,所有寄存器都能在同一个时钟周期内,稳定地退出复位状态。

2.2 经典双寄存器同步器原理

实现这个方案,核心就是两个寄存器串联。我画了个图,你一看就明白。

异步复位同步释放原理图 rst_async_n D Q reg1 D Q reg2 rst_sync_n clk clk rst_async_n rst_sync_n

原理其实很简单:

  • 异步复位信号 rst_async_n 直接接到两个寄存器的异步复位端(低电平有效)。所以复位一拉低,两个寄存器立刻清零,不需要等时钟。
  • 复位释放时,rst_async_n 变高。第一个寄存器(reg1)的D端是1,但它要等下一个时钟沿才能采到。第二个寄存器(reg2)再等一个时钟沿,输出才变高。
  • 最终输出的 rst_sync_n,释放时刻完全同步于时钟上升沿。

核心要点:复位异步生效,释放同步生效。这样既保证了复位的即时性,又避免了亚稳态。

2.3 Verilog实现

代码其实很短。我个人习惯把同步器封装成一个模块,方便复用。

module rst_sync (
    input  wire       clk,          // 系统时钟
    input  wire       rst_async_n,  // 异步复位输入(低有效)
    output wire       rst_sync_n    // 同步复位输出(低有效)
);

// 两级同步寄存器
reg reg1, reg2;

always @(posedge clk or negedge rst_async_n) begin
    if (!rst_async_n) begin
        reg1 <= 1'b0;
        reg2 <= 1'b0;
    end else begin
        reg1 <= 1'b1;
        reg2 <= reg1;
    end
end

assign rst_sync_n = reg2;

endmodule

嗯,这里要注意:

  • 两个寄存器都用异步复位,所以敏感列表里要写 posedge clk or negedge rst_async_n
  • 复位时,两个寄存器都清零。释放后,reg1先变1,reg2再变1。这样输出就延迟了两个时钟周期。
  • 如果你需要高电平有效的复位,把取反逻辑加在输入或输出端即可。

我的习惯:在顶层模块里只例化一个同步器,然后把 rst_sync_n 接到所有子模块的复位端。这样整个芯片的复位释放是统一的,不会出现有的模块已经释放了,有的还在复位中。

2.4 时序约束要求

代码写完了,但如果不加约束,工具可能不会按你期望的方式去优化。我曾经就吃过这个亏——仿真没问题,上板就出问题。后来发现是综合工具把同步器的路径优化掉了。

所以,时序约束一定要加。主要有两条:

约束类型 命令 说明
伪路径 set_false_path -from [get_ports rst_async_n] -to [get_pins reg1/D] 异步复位信号到第一级寄存器的D端,不需要时序检查
异步复位端约束 set_false_path -to [get_pins reg1/CLR] 复位信号到寄存器的异步复位端,也不做时序检查

为什么这么加?

  • rst_async_n 是异步信号,它和时钟没有固定的相位关系。如果让工具去分析这条路径,它会报一大堆违例,但其实这些违例是假的。
  • 我们真正关心的是第二级寄存器(reg2)的输出。它已经同步到时钟域了,所以后续路径需要正常做时序分析。

注意:千万不要把整个同步器都设成伪路径。我见过有人图省事,直接把 rst_sync_n 输出到所有寄存器的路径都设成 false path。这样做,复位释放后的时序就没人管了,很容易出问题。

2.5 实际项目中的避坑指南

最后,分享几个我在项目中踩过的坑:

  1. 同步器位置要放对。 同步器一定要放在时钟树的根部,靠近PLL或者时钟管理模块。如果放在远端,时钟偏斜可能会让两级寄存器采到不同的时钟沿。
  2. 不要用组合逻辑。 同步器的输出不要经过任何组合逻辑再接到寄存器的复位端。组合逻辑会引入毛刺,可能导致寄存器误复位。
  3. 多时钟域要分别处理。 如果你的设计有多个时钟域,每个时钟域都要有自己的同步器。不能用一个同步器的输出去复位所有时钟域的寄存器。
  4. 仿真时要检查。 我曾经在仿真里看到复位释放后,reg1和reg2之间有一个时钟周期的延迟。这是正常的。但如果延迟超过一个周期,就要检查是不是时钟有问题。

好了,关于异步复位同步释放,核心内容就这些。你想想看,这个方案是不是既简单又实用?我个人觉得,它是FPGA复位设计里最值得掌握的技术之一。

一句话总结:异步复位保证响应速度,同步释放保证系统稳定。双寄存器同步器,用两个触发器换一个可靠的复位信号,这笔买卖很划算。


公众号:蓝海资料掘金营,微信deep3321