Quick Start
打开Vivado 2026.1(或对应版本),创建一个空工程,目标器件选择Xilinx Artix-7 XC7A35T(或手头板卡)。添加两个异步时钟源:一个100 MHz系统时钟(clk_100),一个50 MHz外设时钟(clk_50)。编写一个简单的双域同步器模块,用于将单比特信号从clk_100域传递到clk_50域。在XDC约束文件中添加以下命令:set_clock_groups -asynchronous -group [get_clocks clk_100] -group [get_clocks clk_50]。运行综合(Synthesis),确认无CRITICAL WARNING。运行实现(Implementation),查看时序报告:跨时钟域路径应被标记为“False Path”,WNS(最差负时序裕量)应为正数。生成比特流并下载到板卡,用逻辑分析仪(ILA)观察同步器输出,应无亚稳态导致的毛刺。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T | 典型中低端FPGA,多时钟域场景常见 | Intel Cyclone V、Lattice ECP5 |
| EDA版本 | Vivado 2026.1 | 支持最新set_clock_groups语法与CDC分析 | Vivado 2023.x、Quartus Prime 24.x |
| 仿真器 | Vivado Simulator(xsim) | 内置于Vivado,无需额外安装 | ModelSim、Questa、Verilator |
| 时钟/复位 | 100 MHz系统时钟,50 MHz外设时钟,异步复位(低有效) | 两个时钟互不同源,典型异步场景 | 任意频率组合,只要明确异步关系 |
| 接口依赖 | 无外部接口,仅内部逻辑 | 本例聚焦内部CDC,不涉及IO | 若涉及IO,需额外IO约束 |
| 约束文件 | XDC格式 | Vivado原生约束格式 | SDC(Quartus/其他工具) |
目标与验收标准
- 功能点:单比特信号从clk_100域正确同步到clk_50域,无数据丢失或错误。
- 性能指标:WNS ≥ 0 ns,无时序违规路径。
- 资源:同步器占用约2-3个寄存器,无额外LUT。
- Fmax:两个时钟域各自满足目标频率(100 MHz和50 MHz)。
- 验收方式:
实施步骤
1. 工程结构与RTL设计
创建Vivado工程,添加以下RTL文件。核心模块是一个双域同步器,用于演示set_clock_groups的效果。
// sync_2ff.v
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 @(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-6行:模块声明。clk_dst是目标时钟域时钟;rst_n是异步复位(低有效);data_in来自源时钟域;data_out是同步后输出。
- 第8行:定义两个寄存器sync_reg1和sync_reg2,构成双级同步器。
- 第10-16行:always块在clk_dst上升沿触发,rst_n低电平时复位两个寄存器为0。否则,sync_reg1捕获data_in,sync_reg2捕获sync_reg1。这是标准双级同步器结构,用于消除亚稳态。
- 第18行:将sync_reg2赋值给输出data_out。同步器延迟2个目标时钟周期。
2. 顶层模块与时钟生成
顶层模块实例化同步器,并生成两个异步时钟(通过PLL或MMCM)。本例使用Vivado IP Catalog中的Clocking Wizard生成100 MHz和50 MHz时钟。
// top.v
module top (
input wire clk_100, // 板级100 MHz时钟
input wire rst_n, // 板级复位
output wire sync_out // 同步后输出
);
// 实例化PLL生成50 MHz时钟
wire clk_50;
clk_wiz_0 u_clk_wiz (
.clk_in1(clk_100),
.clk_out1(clk_50),
.resetn(rst_n),
.locked()
);
// 源时钟域:直接使用clk_100产生一个翻转信号
reg toggle;
always @(posedge clk_100 or negedge rst_n) begin
if (!rst_n)
toggle <= 1'b0;
else
toggle <= ~toggle;
end
// 实例化同步器
sync_2ff u_sync (
.clk_dst(clk_50),
.rst_n(rst_n),
.data_in(toggle),
.data_out(sync_out)
);
endmodule逐行说明
- 第1-6行:顶层模块声明,clk_100是板级输入时钟,rst_n是异步复位,sync_out是输出。
- 第9-15行:实例化PLL IP(clk_wiz_0),输入clk_100,输出clk_50(50 MHz)。locked信号未使用,可忽略。
- 第18-24行:在clk_100域中生成一个翻转信号toggle,用于模拟源时钟域数据变化。
- 第27-32行:实例化同步器,将toggle同步到clk_50域,输出sync_out。
3. 约束文件(XDC)
约束文件是set_clock_groups的核心。首先定义时钟,然后设置异步时钟组。
# constraints.xdc
# 定义主时钟
create_clock -name clk_100 -period 10.000 [get_ports clk_100]
# 定义PLL输出时钟(由工具自动推导,但手动指定更清晰)
create_clock -name clk_50 -period 20.000 [get_pins u_clk_wiz/clk_out1]
# 设置异步时钟组
set_clock_groups -asynchronous -group [get_clocks clk_100] -group [get_clocks clk_50]
# 可选:设置输入输出延迟(本例无IO,省略)逐行说明
- 第2行:
create_clock -name clk_100 -period 10.000 [get_ports clk_100]:定义100 MHz主时钟,周期10 ns,绑定到顶层端口clk_100。 - 第5行:
create_clock -name clk_50 -period 20.000 [get_pins u_clk_wiz/clk_out1]:定义50 MHz时钟,周期20 ns,绑定到PLL输出引脚。注意:Vivado通常自动推导PLL输出时钟,但手动定义可避免歧义。 - 第8行:
set_clock_groups -asynchronous -group [get_clocks clk_100] -group [get_clocks clk_50]:将clk_100和clk_50声明为异步时钟组。这告诉工具:两个时钟域之间的所有路径都是false path,无需时序分析。这是最关键的约束。
4. 综合与实现
在Vivado中运行Synthesis,观察Tcl Console无CRITICAL WARNING关于跨时钟域路径。运行Implementation,完成后打开“Report Timing Summary”。在“Paths”选项卡中,过滤“Inter-clock paths”,应显示0条违规路径。若仍有跨时钟域路径被分析,检查XDC中时钟名称是否匹配(如get_clocks拼写错误)。
常见坑与排查
- 坑1:时钟名称不匹配。现象:set_clock_groups报WARNING“找不到时钟”。检查:在Tcl Console运行
report_clocks,确认时钟名称与get_clocks中一致。 - 坑2:PLL输出时钟未被自动推导。现象:set_clock_groups仅作用于主时钟,PLL输出路径仍被分析。解决:手动添加
create_clock约束PLL输出引脚。
原理与设计说明
set_clock_groups的本质是告诉时序分析工具:指定时钟组之间的所有路径都是false path(虚假路径),不需要进行时序计算。为什么需要这个?在多时钟域设计中,源时钟域寄存器到目标时钟域寄存器的路径,由于时钟相位关系不确定,无法保证建立/保持时间满足。工具若强行分析,会报告大量违规(WNS为负),但这些违规是“假”的——因为设计者已经通过同步器(如双级触发器)处理了亚稳态。set_clock_groups的作用就是屏蔽这些路径,让工具专注于真正的时序关键路径。
关键矛盾:工具默认假设所有时钟相关(即使不同源),并分析所有跨时钟域路径。这会导致:
- 大量时序违规淹没真正需要关注的路径。
- 工具在布局布线时可能为跨时钟域路径优化,浪费资源。
set_clock_groups通过-asynchronous选项声明时钟间无固定相位关系,工具直接跳过这些路径。注意:-asynchronous与-logically_exclusive或-physically_exclusive不同。后者用于时钟多路复用或物理互斥场景,而异步是跨时钟域最常用选项。
边界条件:
set_clock_groups只影响时序分析,不影响仿真或功能。仿真中跨时钟域路径仍会传播信号(可能产生亚稳态,但仿真模型通常不会模拟)。- 如果两个时钟域之间有同步器以外的组合逻辑路径(如直接连接),
set_clock_groups仍会将其标记为false path,这可能导致功能错误。因此,必须确保所有跨时钟域路径都经过同步器。
验证与结果
| 指标 | 测量条件 | 结果(示例) |
|---|---|---|
| WNS(最差负时序裕量) | 实现后,clk_100域内部路径 | +0.125 ns(满足) |
| 跨时钟域路径违规数 | 实现后,过滤Inter-clock paths | 0条 |
| 同步器延迟 | 仿真,从toggle变化到sync_out变化 | 2个clk_50周期(40 ns) |
| 资源占用 | 实现后报告 | 2个寄存器(sync_reg1, sync_reg2) |
| Fmax(clk_100域) | 实现后报告 | ≥ 125 MHz(裕量充足) |
测量条件:Vivado 2026.1,Artix-7 XC7A35T,速度等级-1。以上结果基于典型配置,实际值以工程为准。
故障排查(Troubleshooting)
- 现象:实现后时序报告仍有跨时钟域违规路径。原因:set_clock_groups未生效或时钟名称错误。检查点:在Tcl Console运行
report_clock_interaction,查看时钟组关系。修复建议:确认XDC中时钟名称与report_clocks输出一致;检查XDC是否被包含在工程中(Set as Target Constraint)。 - 现象:综合时出现CRITICAL WARNING“Clock has no target”或“Unconstrained clock”。原因:PLL输出时钟未被定义。检查点:查看综合日志,搜索“unconstrained”。修复建议:手动添加
create_clock约束PLL输出引脚。 - 现象:仿真中同步器输出出现不定态(X)。原因:复位未正确连接或同步器初始值未定义。检查点:检查rst_n是否连接到同步器;检查仿真波形中复位时序。修复建议:确保复位信号在仿真开始时有效;或给寄存器赋初值(initial块,仅仿真用)。
- 现象:上板后同步器输出数据错误(如丢失脉冲)。原因:源时钟域数据变化太快,目标时钟域采样不足(违背奈奎斯特)。检查点:测量源时钟域数据最小脉宽,确保大于目标时钟周期。修复建议:在源时钟域对数据做展宽(至少保持2个目标时钟周期),或使用握手协议。
- 现象:set_clock_groups报语法错误。原因:XDC中使用了不支持的选项或拼写错误。检查点:查看Vivado文档(UG903)中set_clock_groups语法。修复建议:确保使用
-asynchronous(不是-async),且每个-group后跟[get_clocks ...]。 - 现象:实现后资源报告中同步器使用了LUT而非寄存器。原因:综合工具可能优化了寄存器为LUT(如移位寄存器推断)。检查点:查看综合后的网表,确认同步器结构。修复建议:在RTL中使用
(* keep = "true" *)属性防止优化。 - 现象:跨时钟域路径被标记为“False Path”,但WNS仍为负。原因:其他路径(如同一个时钟域内部)存在违规。检查点:在时序报告中过滤“Intra-clock paths”。修复建议:优化内部逻辑(如流水线、降低频率)。
- 现象:使用多个set_clock_groups命令时,后一个覆盖前一个。原因:Vivado中最后一个set_clock_groups生效,之前被覆盖。检查点:查看Tcl Console中命令执行顺序。修复建议:将所有时钟放在一个set_clock_groups命令中,用多个
-group。
扩展与下一步
- 参数化同步器级数:将双级同步器改为参数化模块,支持2-5级,以应对不同亚稳态MTBF要求。
- 多比特CDC:对于多比特信号(如总线),使用握手协议或异步FIFO,而非简单同步器。set_clock_groups同样适用。
- 跨平台约束:若使用Intel Quartus,对应命令是
set_clock_groups(相同语法)或derive_clock_uncertainty。Lattice Radiant使用set_false_path逐条设置。 - 加入断言与覆盖:在仿真中添加SVA断言,检查同步器输出是否在预期延迟内变化;使用功能覆盖点确保所有CDC路径被测试。
- 形式验证:使用工具(如JasperGold)形式化验证CDC结构是否正确(如无组合逻辑路径)。
参考与信息来源
- Xilinx UG903: Vivado Design Suite User Guide: Using Constraints (v2026.1)
- Xilinx UG949: Vivado Design Suite User Guide: Methodology (v2026.1)
- Clifford E. Cummings, “Clock Domain Crossing (CDC) Design & Verification Techniques”, SNUG 2008
- Intel Quartus Prime Pro Edition User Guide: Timing Analyzer (v24.x)
技术附录
术语表
- CDC (Clock Domain Crossing):时钟域交叉,信号从一个时钟域传递到另一个时钟域。
- False Path:虚假路径,时序分析中忽略的路径。
- WNS (Worst Negative Slack):最差负时序裕量,负值表示时序违规。
- MTBF (Mean Time Between Failures):平均故障间隔时间,衡量亚稳态可靠性。
检查清单
- [ ] 所有跨时钟域路径都经过同步器(双级或更多)。
- [ ] XDC中定义了所有时钟(包括PLL输出)。
- [ ] set_clock_groups命令包含所有异步时钟组。
- [ ] 实现后时序报告无跨时钟域违规。
- [ ] 仿真验证了同步器功能正确。
关键约束速查
# 定义主时钟
create_clock -name clk_a -period 10.000 [get_ports clk_a]
# 定义衍生时钟(PLL输出)
create_clock -name clk_b -period 20.000 [get_pins pll_instance/clk_out]


