做FPGA设计,复位电路几乎每个工程都会用到。很多人觉得复位嘛,不就是给个低电平或者高电平把寄存器清零?写代码的时候加个if(rst)就完事了。但真到了板子调试阶段,随机出现的死机、状态机跑飞、数据通路错乱,追来追去发现根子出在复位释放那一刻——亚稳态把系统坑了。
这个问题说实话不算新鲜,但踩坑的人还是一茬接一茬。今天就把异步复位释放时亚稳态这个事掰扯清楚,顺便给几个靠谱的解决思路。
一、异步复位为什么容易出亚稳态异步复位最大的问题在于:复位释放(也就是rst从有效变无效的那个沿)跟时钟沿之间没有任何固定的时间关系。如果复位释放的时刻刚好落在时钟沿附近的建立/保持时间窗口内,触发器就进入了亚稳态——输出既不是0也不是1,而是卡在中间一个不确定的电平上,而且这个状态持续多久谁也说不准。
这跟异步复位的"进入"不一样。复位有效的时候,不管时钟什么状态,触发器都会被强制拉到已知状态,这个没问题。问题出在"离开"复位的那一刻——释放沿跟时钟沿撞上了。

亚稳态的后果看具体场景。如果只是某个数据寄存器抖了一下,可能就是一帧数据出错;但如果复位释放时状态机的寄存器进了亚稳态,那状态机可能跳到一个非法状态,整个系统就卡死了。更麻烦的是,这种问题是随机的,复现概率低,排查起来很头疼。
二、异步复位同步释放——最经典的解决方案这个方案的核心思路很简单:异步复位、同步释放。也就是说,复位进入的时候仍然是异步的(保证及时响应),但释放的时候要跟时钟同步,避免释放沿落在时钟的建立保持窗口里。
实现方式通常用一个两三级的触发器链来同步复位释放信号:
always @(posedge clk or negedge rst_n) begin if (!rst_n) begin rst_sync1 <= 1'b0; rst_sync2 <= 1'b0; end else begin rst_sync1 <= 1'b1; rst_sync2 <= rst_sync1; end end // 用 rst_sync2 作为系统复位信号
第一级触发器可能进亚稳态,但经过一两个时钟周期的"沉淀"之后,第二级或第三级触发器的输出基本稳定了。两级同步能解决大部分场景,如果是高可靠性设计,可以加到三级。

有个常见的误解,以为用了同步释放电路之后,整个系统就变成同步复位了。其实不是。rst_sync2对下游逻辑来说,仍然是一个异步复位信号——只不过它的释放沿已经跟时钟对齐了。所以下游寄存器的always块里,还是要写成异步复位的写法(posedge clk or negedge rst_sync2)。
2. 复位网络的扇出问题同步释放电路输出的rst_sync2要接到系统中所有需要复位的寄存器上。在大设计中,这个信号的扇出可能非常大,导致布线延迟不一致——有的寄存器先收到释放,有的后收到,这就相当于不同的模块在不同时刻退出了复位状态。
解决这个问题的办法是插入BUF或者用多个同步器分别驱动不同模块的复位。FPGA厂商一般也会提供专门的BUFG资源来处理高扇出复位信号。
3. 上电复位和热复位的区别上电时FPGA内部有个全局的初始化过程(GSR),所有寄存器都会被初始化到指定值,这个是FPGA架构保证的。但热复位(运行中拉低rst_n再释放)就不一样了,没有GSR帮你兜底,完全靠你的复位逻辑。所以热复位场景下,异步复位同步释放电路就更关键了。

如果你的设计对复位没有严格要求——比如所有寄存器都有确定初始值、状态机用了独热编码且有非法状态恢复逻辑——那完全不用异步复位也行,纯同步复位或者干脆不复位。Xilinx和Intel的FPGA都支持寄存器初始化,很多简单设计确实不需要额外的复位信号。
但如果你的设计里有时序要求严格的控制逻辑、需要快速响应外部复位请求,那异步复位同步释放还是最稳妥的选择。
有个经验可以分享:做项目的时候,把复位策略想清楚再动手写代码。别等到调试阶段才发现复位有问题,那时候排查的成本比设计阶段加个同步器高太多了。

扫码关注





































