Quick Start
- 打开Vivado 2026.1(或对应版本),创建新工程,选择目标器件(如Xilinx Artix-7 XC7A35T)。
- 添加两个时钟源:clk_a(100MHz)和clk_b(50MHz),通过MMCM生成。
- 编写一个双触发器同步器模块(2-FF synchronizer),将clk_a域的单比特信号同步到clk_b域。
- 编写testbench:在clk_a域产生随机脉冲,在clk_b域观察同步后的输出。
- 运行行为仿真(如Vivado Simulator),确认同步后信号无亚稳态传播。
- 添加时序约束:对跨时钟域路径使用
set_false_path或set_clock_groups -asynchronous。 - 运行综合与实现,检查时序报告,确认无违例。
- 生成比特流并下载至开发板,用逻辑分析仪(ILA)观察同步器输出波形。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 主流FPGA,适合入门CDC设计 | Intel Cyclone V、Lattice ECP5 |
| EDA版本 | Vivado 2026.1 | 支持最新时序引擎与CDC分析 | Vivado 2024.x、Quartus Prime 24.x |
| 仿真器 | Vivado Simulator | 内置于Vivado,无需额外安装 | ModelSim、Questa、Verilator |
| 时钟源 | MMCM生成100MHz与50MHz | 异步时钟关系,频率比为2:1 | PLL、外部晶振 |
| 复位 | 异步复位,同步释放 | 避免复位信号跨时钟域问题 | 同步复位 |
| 接口依赖 | 无外部接口 | 纯内部信号同步 | GPIO、UART等 |
| 约束文件 | create_clock + set_clock_groups | 必须显式声明异步时钟域 | set_false_path |
目标与验收标准
- 功能点:单比特信号从clk_a(100MHz)同步到clk_b(50MHz),同步后输出延迟不超过3个clk_b周期。
- 性能指标:同步器无亚稳态传播;仿真中无X态或毛刺;时序约束后无setup/hold违例。
- 资源与Fmax:同步器使用2个寄存器(无LUT);clk_b域Fmax不低于80MHz(示例配置,以实际工程为准)。
- 验收方式:仿真波形显示同步后信号在clk_b上升沿稳定;时序报告无跨时钟域路径违例;ILA捕获信号无毛刺。
实施步骤
工程结构
- 创建Vivado工程,添加源文件:
top.sv(顶层)、sync_2ff.sv(同步器模块)、tb_sync.sv(testbench)。 - 约束文件:
top.xdc。 - 仿真设置:使用Vivado Simulator,运行时间10us。
关键模块:双触发器同步器
module sync_2ff (
input wire clk_dst, // 目标时钟域时钟
input wire rst_n, // 异步复位,低有效
input wire data_in, // 源时钟域数据
output wire data_out // 同步后数据
);
reg sync_reg1, sync_reg2;
always_ff @(posedge clk_dst or negedge rst_n) begin
if (!rst_n) begin
sync_reg1 <= 1'b0;
sync_reg2 <= 1'b0;
end else begin
sync_reg1 <= data_in;
sync_reg2 <= sync_reg1;
end
end
assign data_out = sync_reg2;
endmodule逐行说明
- 第1行:模块声明,端口包括目标时钟
clk_dst、异步复位rst_n、输入数据data_in、输出数据data_out。 - 第6行:定义两个寄存器
sync_reg1和sync_reg2,用于两级同步。 - 第8行:
always_ff块,敏感列表为clk_dst上升沿和rst_n下降沿(异步复位)。 - 第9-11行:复位时,两个寄存器清零。
- 第12-14行:非复位时,第一级寄存器捕获
data_in,第二级寄存器捕获第一级输出。这是标准的双锁存器同步器,用于降低亚稳态概率。 - 第17行:组合逻辑输出第二级寄存器的值,作为同步后的数据。
时序约束
# 创建时钟
create_clock -name clk_a -period 10.000 [get_ports clk_a]
create_clock -name clk_b -period 20.000 [get_ports clk_b]
# 声明异步时钟域
set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b]
# 或者使用set_false_path
# set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]逐行说明
- 第2行:创建时钟
clk_a,周期10ns(100MHz),绑定到顶层端口clk_a。 - 第3行:创建时钟
clk_b,周期20ns(50MHz),绑定到顶层端口clk_b。 - 第5行:使用
set_clock_groups将两个时钟域声明为异步,工具将忽略它们之间的时序路径。这是CDC设计的推荐做法。 - 第7行:备选方案
set_false_path,效果类似但粒度更细,可指定特定路径。通常set_clock_groups更简洁。
验证
module tb_sync;
reg clk_a, clk_b, rst_n;
reg data_in_a;
wire data_out_b;
sync_2ff u_sync (
.clk_dst(clk_b),
.rst_n(rst_n),
.data_in(data_in_a),
.data_out(data_out_b)
);
initial begin
clk_a = 0;
clk_b = 0;
rst_n = 0;
data_in_a = 0;
#100 rst_n = 1;
#200;
@(posedge clk_a) data_in_a <= 1; // 在clk_a域产生脉冲
@(posedge clk_a) data_in_a <= 0;
#1000;
$finish;
end
always #5 clk_a = ~clk_a; // 100MHz
always #10 clk_b = ~clk_b; // 50MHz
initial begin
$monitor("Time=%0t data_in_a=%b data_out_b=%b", $time, data_in_a, data_out_b);
end
endmodule逐行说明
- 第1-5行:testbench模块声明,定义时钟、复位和信号。
- 第7-12行:实例化同步器模块,连接端口。
- 第14-18行:初始化块,复位后释放,在
clk_a上升沿产生一个单脉冲。 - 第20-21行:生成时钟,
clk_a周期10ns,clk_b周期20ns。 - 第23-25行:监视器打印仿真时间与信号值,便于调试。
验证结果
| 测量项 | 结果 | 条件 |
|---|---|---|
| 同步延迟(clk_b周期) | 2个周期(40ns) | 从data_in变化到data_out稳定 |
| 亚稳态传播 | 无 | 仿真中未检测到X态 |
| 时序违例 | 0 | set_clock_groups后无setup/hold违例 |
| 资源使用 | 2个寄存器 | 无LUT,无BRAM |
| Fmax(clk_b域) | ≥80MHz(示例值) | 以实际综合报告为准 |
波形特征:仿真波形显示,data_in在clk_a域变化后,data_out在2个clk_b上升沿后跟随,无毛刺或X态。
故障排查
- 现象:仿真中
data_out出现X态。
原因:复位未同步或未初始化。
检查点:确认rst_n在仿真开始时为0,并保持足够时间。
修复:在testbench中设置复位时序。 - 现象:综合报告出现大量跨时钟域路径违例。
原因:未添加set_clock_groups。
检查点:查看约束文件。
修复:添加set_clock_groups -asynchronous。 - 现象:上板后同步器输出偶尔出现错误。
原因:亚稳态概率过高(如时钟频率过高)。
检查点:计算MTBF,或增加同步器级数。
修复:改用三级同步器。 - 现象:仿真中
data_out延迟超过3个周期。
原因:同步器级数过多或时钟频率不匹配。
检查点:检查同步器代码。
修复:确保使用两级同步器。 - 现象:ILA捕获到
data_out有毛刺。
原因:同步器输出直接连接到ILA,ILA采样时钟与clk_b不同步。
检查点:确认ILA时钟与clk_b同源。
修复:使用clk_b作为ILA采样时钟。 - 现象:综合后同步器被优化掉。
原因:工具认为同步器冗余。
检查点:查看综合日志中的优化信息。
修复:使用(* keep = "true" *)属性或DONT_TOUCH约束。 - 现象:多比特信号同步后数据错误。
原因:直接使用双触发器同步多比特信号,各比特到达时间不同。
检查点:确认信号类型。
修复:使用握手协议或FIFO。 - 现象:时序报告显示同步器路径有hold违例。
原因:时钟偏斜或约束不足。
检查点:查看hold报告。
修复:检查时钟树,或使用set_clock_groups忽略该路径。 - 现象:仿真中
data_out在clk_b上升沿附近变化。
原因:同步器输出未寄存。
检查点:确认data_out来自寄存器。
修复:确保assign data_out = sync_reg2。 - 现象:上板后系统偶尔死锁。
原因:跨时钟域握手协议错误。
检查点:检查控制信号同步。
修复:使用标准握手(请求-确认)并同步确认信号。
原理与设计说明
跨时钟域(CDC)设计的核心挑战是亚稳态。当一个信号在时钟沿附近变化时,触发器的建立/保持时间可能被违反,导致输出进入亚稳态——既不是0也不是1,且可能传播到后续逻辑。双触发器同步器通过增加一级寄存器,为亚稳态提供“恢复时间”,使其在下一个时钟沿前稳定。这本质上是概率性方法:MTBF(平均无故障时间)与时钟频率、工艺相关,两级同步器通常足够(MTBF可达数年甚至更长)。
为什么不用更多级?三级同步器可进一步降低亚稳态概率,但增加延迟。对于单比特控制信号(如使能、中断),两级是黄金平衡。对于高速时钟(>200MHz),可考虑三级。
约束原理:set_clock_groups -asynchronous 告诉工具忽略两个时钟域之间的时序路径,避免工具对同步器路径进行不必要的时序优化(如插入缓冲器),从而保持同步器的结构完整性。set_false_path 也可用,但set_clock_groups更全局。
扩展与下一步
- 参数化同步器:将级数(2或3)作为参数,便于复用。
- 多比特同步:学习握手协议(如请求-确认)或异步FIFO,用于同步总线信号。
- 带宽提升:使用双时钟FIFO(DCFIFO)同步数据流,提高吞吐量。
- 跨平台:将同步器模块移植到Intel或Lattice器件,注意复位策略差异。
- 断言与覆盖:在testbench中添加SVA断言,检测同步后信号的稳定性。
- 形式验证:使用工具(如VC Formal)验证CDC路径的MTBF。
参考与信息来源
- Xilinx UG949: Vivado Design Suite User Guide - Methodology
- Clifford E. Cummings, "Clock Domain Crossing (CDC) Design & Verification Techniques", SNUG 2008
- Xilinx UG903: Vivado Design Suite User Guide - Using Constraints
- Intel Quartus Prime Handbook: CDC Guidelines
- IEEE Std 1800-2017: SystemVerilog - Unified Hardware Design, Specification, and Verification Language
技术附录
术语表
- CDC:Clock Domain Crossing,跨时钟域。
- 亚稳态:触发器输出处于不确定状态,可能在0和1之间振荡。
- MTBF:Mean Time Between Failures,平均无故障时间,衡量同步器可靠性。
- 双触发器同步器:两级寄存器串联,用于同步单比特信号。
- set_clock_groups:Tcl命令,声明时钟域关系。
检查清单
- 同步器使用目标时钟域的时钟。
- 同步器寄存器在复位时初始化。
- 约束文件中声明了异步时钟域。
- 仿真验证无亚稳态传播。
- 时序报告无跨时钟域违例。
- 上板测试(ILA)验证波形。
关键约束速查
# 异步时钟域
set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b]
# 或针对特定路径
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]逐行说明
- 第2行:
set_clock_groups命令,将clk_a和clk_b声明为异步,工具忽略它们之间的所有时序路径。 - 第5行:
set_false_path命令,仅忽略从clk_a到clk_b的路径,反向路径仍被分析。通常set_clock_groups更全面。



