两级同步寄存器Verilog模板:单比特信号跨时钟域的最小可复制件
更新时间:2026-10-29。时钟域交叉(CDC)入门模板篇,属于 芯片设计入门系列。按钮、中断、状态标志这类单比特信号从一个时钟域跑进另一个时钟域时,标准处理就是本文的两级同步寄存器Verilog模板:两个触发器串联打两拍,把亚稳态「消化」在第一级。亚稳态机理与 MTBF 定量分析见 两级同步器 MTBF 实验,为什么这套打法不能照搬到多比特总线见 两级同步器为什么不能同步多比特。
首屏答案:单比特信号跨时钟域,就在目标时钟域用两个触发器串联采样:ff1 <= sig_async; ff2 <= ff1;,用 ff2 作为同步后的信号。第一级承担亚稳态风险,第二级在绝大多数情况下拿到已稳定的电平。三个前提必须同时成立:信号是单比特;输入变化慢于目标时钟(必要时先展宽脉冲);你能容忍 1-2 拍的不确定延迟。三者有一条不满足,就得换方案(异步 FIFO、握手),而不是往模板上堆触发器。
本文要回答的问题
- 两级同步器每一拍到底在干什么,亚稳态是怎么被「消化」掉的?
- 为什么不能只打一拍?什么时候需要加到三级?
- 基本版、带复位版、边沿检测版三段模板代码各用在什么场景?
- 这套模板有哪些硬性使用边界(多比特、窄脉冲、延迟敏感)?
一、使用场景与模板速览
两级同步器只解决一类问题:一个电平型或已展宽的单比特信号,要从异步域安全进入目标域。典型来源:机械按钮消抖后的电平、外部芯片的中断请求线、另一个时钟域产生的状态标志位。反过来,多比特计数器值、总线数据、紧贴目标时钟周期的窄脉冲,都不在它的能力范围内。
三个变体一张流程卡看全,颜色按角色区分:蓝是输入侧条件,橙是模板本体动作,绿是输出侧结果,紫是适用说明。
这张卡从上往下读就是选型路径:先做准入检查(第一行),再在三个变体里挑一个,最后记住消费规则(最后一行)。三个变体共享同一条主干——前两拍永远是 ff1 <= sig_async; ff2 <= ff1;,区别只在复位与第三拍。
二、基本版模板与逐段讲解
完整代码如下,可以直接复制进工程:
// two_flop_sync.v —— 单比特 CDC 两级触发器同步器(基本版)
module two_flop_sync (
input wire clk_dst, // 目标时钟域时钟
input wire sig_async, // 异步输入信号
output wire sig_sync // 同步后的信号
);
reg sync_ff1;
reg sync_ff2;
always @(posedge clk_dst) begin
sync_ff1 <= sig_async;
sync_ff2 <= sync_ff1;
end
assign sig_sync = sync_ff2;
endmodule逐段说清它在干什么。sig_async 是相对 clk_dst 完全异步的信号——它的翻转时刻不服从目标时钟的建立/保持时间约束。当翻转恰好落在 clk_dst 采样沿附近的「亚稳态窗口」内,第一级 sync_ff1 的输出可能在一段时间里既不是 0 也不是 1,而是悬在中间电平慢慢爬向某一端,这就是亚稳态。两级结构的核心思想是:让亚稳态在 sync_ff1 到 sync_ff2 之间的一整个时钟周期里自然衰减。sync_ff2 采样的是已经爬了将近一整拍的 sync_ff1,此时电平几乎必然已经稳定,于是 sig_sync 是干净的。窗口命中概率、衰减时间常数与 MTBF 的定量关系在 两级同步器 MTBF 实验 里有实测推导,本文只管模板。
为什么不能只打一拍?因为只打一拍时,亚稳态没有任何「消化时间」就直接被下游逻辑消费——下游多个触发器可能在同一拍对中间电平做出不同判决,有的认为是 0 有的认为是 1,系统层面就分裂了。那什么时候需要三级?当时钟频率高到一整拍的间隔都不足以让亚稳态衰减到安全电平(典型在深亚微米工艺、GHz 级时钟),或者功能安全等级要求高时,加第三级把失效概率再压低几个数量级。代价是每加一级多一拍延迟。
三、带复位版与边沿检测版
基本版上电后初值不定,对「复位期间必须是确定电平」的场景(中断使能、状态机入口条件)用带异步复位的版本:
// two_flop_sync_rst.v —— 两级同步器(带异步复位版)
module two_flop_sync_rst (
input wire clk_dst,
input wire rst_n, // 异步复位,低有效
input wire sig_async,
output wire sig_sync
);
reg sync_ff1;
reg sync_ff2;
always @(posedge clk_dst or negedge rst_n) begin
if (!rst_n) begin
sync_ff1 <= 1'b0;
sync_ff2 <= 1'b0;
end else begin
sync_ff1 <= sig_async;
sync_ff2 <= sync_ff1;
end
end
assign sig_sync = sync_ff2;
endmodule改动只有一处:always 敏感表加 negedge rst_n,复位分支把两级都钉到 0。注意两级一起复位,只复位第二级会让第一级的亚稳态在复位释放瞬间直穿输出。复位策略本身的取舍(同步复位还是异步复位同步释放)见 时钟与复位概念。
第三个常见需求:异步侧来了一个脉冲或电平跳变,目标域想拿到恰好一个时钟周期的脉冲去触发计数器或状态跳转。做法是在两级同步后加第三拍做边沿检测:
// two_flop_sync_edge.v —— 两级同步 + 上升沿检测
module two_flop_sync_edge (
input wire clk_dst,
input wire rst_n,
input wire sig_async,
output wire sig_pulse // 输出一个 clk_dst 周期的脉冲
);
reg sync_ff1;
reg sync_ff2;
reg sync_ff3;
always @(posedge clk_dst or negedge rst_n) begin
if (!rst_n) begin
sync_ff1 <= 1'b0;
sync_ff2 <= 1'b0;
sync_ff3 <= 1'b0;
end else begin
sync_ff1 <= sig_async;
sync_ff2 <= sync_ff1;
sync_ff3 <= sync_ff2;
end
end
assign sig_pulse = sync_ff2 & ~sync_ff3;
endmodulesync_ff3 是 sync_ff2 的延迟一拍副本,sync_ff2 & ~sync_ff3 只在同步后电平刚从 0 变 1 的那一拍为真,正好输出单周期脉冲。它常被误认为「三级同步器」——不是,第三拍不承担抗亚稳态任务,纯粹是边沿检测的延迟元件。
四、使用注意事项(三条硬边界)
第一,多比特禁用。两级同步器每一位独立打拍,不同比特在亚稳态窗口里的判决结果可能不一致,采到的组合值可能是发送方从未发出过的中间态。多比特跨域的标准答案是异步 FIFO 或握手协议,入门读物见 FIFO 与仲裁器基础 与 为什么不能同步多比特。
第二,窄脉冲必须先展宽。两级同步器是「采样」而不是「捕捉」:如果异步脉冲宽度窄于目标时钟周期,它可能落在两个采样沿之间被整个漏掉。经验规则是脉冲宽度至少 1.5 倍目标时钟周期;源域时钟已知时,在源域把脉冲展宽到足够拍数再送同步器,脉冲同步的完整讨论见 脉冲同步。
第三,消费端只接最后一级。sync_ff1 是承担亚稳态的牺牲品,任何逻辑都不许引用它;综合时给两级加 ASYNC_REG(Xilinx)或等价属性,让布局工具把两级触发器放得尽量近,保住亚稳态衰减路径的延迟预算。
五、模板验证:lint 与跨域仿真
模板本身也要过工具关。三个文件分别跑 verilator --lint-only -Wall 均零告警退出;仿真用 Icarus Verilog 搭了一个双时钟 testbench:源时钟 100MHz(10ns 周期)、目标时钟 40MHz(25ns 周期),源域把脉冲展宽到 6 个源周期(60ns,大于 25ns 目标周期)后异步注入,连发 5 个脉冲,检查基本版输出上升沿数不丢、边沿检测版每个输入脉冲恰好产出一个单目标周期脉冲。真实输出如下:
读这段输出抓三个数:src pulses sent: 5 是源域发出的脉冲数;basic sync rises seen: 5 与 edge pulses seen: 5 说明基本版和边沿版都一个不漏地收到了全部 5 个脉冲;edge bad width count: 0 表示边沿版每次输出都恰好宽一个目标时钟周期,没有出现连续两拍为高的超标情况。三项计数全对上加 PASS,说明三段模板在 2.5:1 的时钟比下行为正确。三次 lint 调用全部 exit_code: 0,-Wall 全开无告警。
实验环境
- 机器/系统:本机 macOS + geek-eda:0.2 docker 镜像
- 工具版本:Verilator 5.020(docker 内)/ Icarus Verilog 13.0(stable,v13_0)
- 日期:2026-10-29
- 完整命令:
verilator --lint-only -Wall two_flop_sync.v(另两文件同命令);iverilog -g2012 -o tb_two_flop_sync two_flop_sync.v two_flop_sync_rst.v two_flop_sync_edge.v tb_two_flop_sync.v && vvp tb_two_flop_sync
代码获取
三段模板源码与双时钟 testbench 在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/tpl-two-flop-sync-verilog。装好 Icarus Verilog 后按 README 跑 iverilog -g2012 加 vvp 即可复现上面的 PASS 输出;想改时钟比,直接改 testbench 顶部的两个 always # 半周期值。
这一篇在知识体系里的位置
本篇是时钟域交叉(CDC)入门簇的「可复制模板」篇:亚稳态与 MTBF 的定量机理在 两级同步器 MTBF 实验;窄脉冲跨域的展宽与握手打法在 脉冲同步;多比特跨域为什么不能用本模板在 两级同步器为什么不能同步多比特;触发器与复位的更基础概念分别见 锁存器与触发器 和 时钟与复位概念。
总结
两级同步寄存器Verilog模板就是单比特信号跨时钟域的标准答案:目标域两个触发器串联打两拍,第一级吃亚稳态、第二级出稳定电平;基本版、带复位版、边沿检测版三段代码按场景复制,复位要两级一起复、第三拍只做边沿检测不抗亚稳态;三条硬边界——多比特禁用、窄脉冲先展宽到 1.5 倍目标周期以上、消费端只接最后一级——一条都不能破;iverilog 双时钟实测 100MHz→40MHz 跨域 5 个脉冲全同步、边沿版输出严格单周期,Verilator -Wall lint 零告警。