一、从复位信号说起:为什么需要“同步释放”?

在数字电路的世界里,复位信号就像是一个总开关,它能让整个系统瞬间回到一个已知的、确定无误的初始状态。无论是系统刚上电,还是运行中出现了异常需要重启,都离不开它。

根据复位信号与时钟的关系,我们通常把它分为两种:同步复位和异步复位。同步复位,意思是复位信号的有效和撤销,都必须和时钟信号的边沿对齐,就像大家要等口令“一、二、三”一起行动。异步复位则“霸道”得多,它不管时钟在干什么,只要它一有效,电路就必须立刻复位,非常直接。

异步复位虽然响应快,但它有一个著名的“坑”:当复位信号撤销时,如果撤销的瞬间刚好落在时钟信号的敏感边沿(比如上升沿)附近,电路里触发器的数据输入端口可能还处于一个不稳定的状态。这会导致触发器的输出在一个短时间内既不是1也不是0,处于一种“亚稳态”。这种状态会像多米诺骨牌一样在电路中传播,导致逻辑错误,整个系统可能因此“卡死”或行为异常。

这就引出了我们今天要解决的核心问题:如何保留异步复位“响应快”的优点,同时避免它撤销时带来的“亚稳态”风险?答案就是设计一个“异步复位,同步释放”的电路。简单说,就是让复位信号可以随时生效(异步置位),但撤销时,必须等时钟来“批准”一下,确保安全落地(同步释放)。

二、核心原理:两级触发器构成的同步器

消除亚稳态风险,在数字电路设计中有一个经典且可靠的方法:使用同步器。最常见的同步器就是由两级触发器串联而成。它的工作原理很好理解:如果第一个触发器因为输入信号不稳定而进入了亚稳态,那么在下一个时钟周期到来时,它有额外的时间来稳定下来。第二个触发器采样第一个已经(大概率)稳定下来的输出,从而将一个可能不稳定的信号,整形成一个与时钟同步的、稳定的信号。

我们将这个思想应用到复位信号上。具体做法是:我们不再将外部的异步复位信号直接连接到系统内所有触发器的复位端,而是先让这个异步复位信号通过一个由两级触发器构成的同步器。这个同步器的输出,才是我们最终送给系统内部各个模块使用的“全局复位信号”。这样,外部复位信号的撤销动作,需要经过两个时钟周期的“缓冲”和“整形”,才能传递到系统内部,从而完美避开了撤销瞬间与时钟沿竞争的风险。

下面,让我们通过一个最标准、最经典的Verilog代码示例,来具体看看这个电路是如何实现的。

技术栈:Verilog HDL

module reset_sync (
    input  wire clk,       // 系统时钟
    input  wire rst_async_n, // 外部输入的异步低电平复位信号
    output wire rst_sync_n   // 同步释放后的全局低电平复位信号
);

// 定义两个寄存器,用于构成同步链
reg rst_reg1, rst_reg2;

// 核心同步释放逻辑
always @(posedge clk or negedge rst_async_n) begin
    if (!rst_async_n) begin
        // 异步复位阶段:只要外部复位有效,立即清零两个寄存器
        rst_reg1 <= 1'b0;
        rst_reg2 <= 1'b0;
    end else begin
        // 同步释放阶段:外部复位撤销后,在时钟驱动下逐级传递高电平
        rst_reg1 <= 1'b1;   // 第一级同步,在第一个时钟上升沿后变为1
        rst_reg2 <= rst_reg1; // 第二级同步,输出最终稳定的复位信号
    end
end

// 将第二级寄存器的输出作为同步后的全局复位信号
assign rst_sync_n = rst_reg2;

endmodule

我们来仔细分析一下这段代码的工作流程:

  1. 复位有效期(rst_async_n = 0:无论时钟clk处于什么状态,两个寄存器rst_reg1rst_reg2都被立刻清零。因此,输出rst_sync_n也为0,系统处于复位状态。这是“异步复位”的体现。
  2. 复位撤销时刻(rst_async_n从0变为1):这个变化可能发生在任何时刻,完全与clk不同步。
  3. 第一个时钟上升沿:此时rst_async_n已经为1,所以always块执行else分支。rst_reg1被赋值为1,但rst_reg2仍然采样上一周期rst_reg1的值(0)。所以rst_sync_n仍然为0,系统继续保持复位。
  4. 第二个时钟上升沿rst_reg1保持为1,rst_reg2采样到此时rst_reg1的值(1)。于是,rst_sync_n从0变为1,复位状态正式撤销。

可以看到,外部异步复位信号的撤销,经过两个时钟周期的延迟,才最终反映到系统的全局复位信号上。这个延迟,就是消除亚稳态风险的“黄金时间”。

三、深入与变体:高电平复位与注意事项

上面的例子是针对低电平有效的复位信号。在实际项目中,高电平有效的复位也很常见。其设计原理完全一致,只是逻辑取反。我们来看一个高电平复位的版本,以加深理解。

技术栈:Verilog HDL

module reset_sync_high (
    input  wire clk,       // 系统时钟
    input  wire rst_async,  // 外部输入的异步高电平复位信号
    output wire rst_sync    // 同步释放后的全局高电平复位信号
);

reg rst_reg1, rst_reg2;

// 注意:这里变为 posedge rst_async
always @(posedge clk or posedge rst_async) begin
    if (rst_async) begin
        // 异步复位有效时,置位两个寄存器
        rst_reg1 <= 1'b1;
        rst_reg2 <= 1'b1;
    end else begin
        // 异步复位撤销后,同步释放
        rst_reg1 <= 1'b0;      // 第一级同步
        rst_reg2 <= rst_reg1;  // 第二级同步
    end
end

assign rst_sync = rst_reg2;

endmodule

关键注意事项:

  1. 时钟域:这个同步器模块必须使用目标系统(或子模块)的时钟clk。一个clk域需要一个独立的同步器。如果你的设计中有多个时钟域,切记要为每个时钟域都实例化一个复位同步器,不能混用。
  2. 复位树与扇出:生成的rst_sync_n信号可能会驱动成百上千个触发器,负载很大。直接连出去可能导致时序问题。好的做法是,将这个同步器输出的复位信号作为“根复位”,再通过几级寄存器进行缓冲和扇出控制,形成一颗“复位树”,以保证复位信号到达各个端点时的质量和延迟相对一致。
  3. 仿真与综合:确保RTL仿真行为与综合后网表的行为一致。上述代码模板是公认的、可综合的且行为明确的描述方式。
  4. 功耗考虑:复位网络通常翻转率极低,但布线资源多。在先进工艺下,也需要关注其静态功耗。

四、应用场景与优劣分析

应用场景:

  • FPGA/ASIC芯片的顶层复位设计:几乎所有需要可靠启动的数字芯片,都会在顶层使用此类电路来处理外部引脚输入的复位信号。
  • 多时钟域系统:每个独立的时钟域入口处,处理跨时钟域传来的复位信号时,必须使用该时钟域的同步器。
  • 软件可控复位:当通过写寄存器产生一个软件复位脉冲时,这个脉冲也需要同步到目标时钟域后再使用。

技术优缺点:

  • 优点
    • 可靠性高:从根本上消除了复位撤销时的亚稳态风险,是工业界的标准做法。
    • 保留异步复位优点:复位依然可以随时、立即生效,响应速度快。
    • 对时钟质量要求降低:即使在复位撤销后时钟才稳定出现,电路也能正确工作(因为复位一直有效,直到时钟来后才释放)。
  • 缺点
    • 增加延迟:复位撤销会有至少一个时钟周期的延迟(通常设计为两个周期)。对于复位后需要立即进行操作的极端场景,需要仔细规划时序。
    • 增加少量资源:需要额外的两个触发器,但对于整个系统来说开销微乎其微。

五、总结与最佳实践

“异步复位,同步释放”电路是数字逻辑设计工程师工具箱中的必备基础元件。它用简单的结构(两个触发器)解决了复杂且致命的问题(亚稳态传播)。理解并熟练运用它,是设计稳定可靠系统的基石。

最佳实践可以总结为以下几点:

  1. 模板化:将标准的复位同步器代码封装成模块(如上面的reset_sync),在项目中反复调用,避免重复编写和出错。
  2. 时钟域隔离:牢记“一个时钟域,一个同步器”的原则。
  3. 复位树规划:对于大型设计,考虑使用复位控制器来管理不同模块的复位顺序和复位树网络。
  4. 前端与后端协同:告知后端工程师复位网络是关键网络,需要保证其布线质量和延迟。

希望这篇文章能帮你彻底理解同步释放的原理与实现。下次当你编写always @(posedge clk or negedge rst_n)时,不妨想一想,这个rst_n信号,是否已经安全地通过了同步器的“安检”呢?