Quick Start
- 打开Vivado 2026.1(或对应版本),创建新工程,选择目标器件(如Xilinx Artix-7 XC7A35T)。
- 添加设计源文件:一个包含两个独立时钟域(例如clk_a=100MHz,clk_b=50MHz)的简单模块,两个域之间无数据交互。
- 打开综合设置(Synthesis Settings),确保“-flatten_hierarchy”为“rebuilt”。
- 创建XDC约束文件,写入两条create_clock命令分别定义clk_a和clk_b。
- 在同一个XDC文件中,添加
set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b]。 - 运行综合(Synthesis),查看时序报告(Report Timing Summary),确认两个时钟域之间无时序路径被分析(即无跨时钟域路径报出)。
- 运行实现(Implementation),检查时序收敛,确认无跨时钟域违例。
- 生成比特流,下载至开发板,用示波器或逻辑分析仪观察两路时钟输出,验证功能正确。
- 验收点:时序报告中“跨时钟域路径”数量为0;实现后无setup/hold违例。
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T-1CSG324C | 典型中低端FPGA,多时钟域常见 | 任何Xilinx 7系列或UltraScale+器件 |
| EDA版本 | Vivado 2026.1 | 支持set_clock_groups完整语法 | Vivado 2020.1及以上 |
| 仿真器 | Vivado Simulator (xsim) | 用于功能验证 | ModelSim/Questa、VCS |
| 时钟/复位 | 两个独立外部时钟输入(如100MHz和50MHz) | 用于演示异步时钟域 | 内部PLL生成时钟 |
| 接口依赖 | 无特殊接口 | 纯逻辑设计 | — |
| 约束文件 | 单个XDC文件包含create_clock和set_clock_groups | — | 多个XDC文件(需注意读取顺序) |
目标与验收标准
- 功能点:两个独立时钟域之间无逻辑路径连接(即无组合逻辑或寄存器跨域)。
- 性能指标:每个时钟域内部时序收敛(setup slack ≥ 0,hold slack ≥ 0)。
- 资源/Fmax:资源使用无异常(如LUT/FF数量合理);Fmax满足时钟频率要求。
- 关键波形/日志:时序报告中“Clock Group”部分显示两个时钟组为“asynchronous”;无“Unconstrained path”警告。
实施步骤
工程结构与关键模块
创建一个顶层模块,例化两个独立的计数器模块,分别由clk_a和clk_b驱动。两个计数器之间无任何连接。
// top.v
module top (
input wire clk_a,
input wire rst_n_a,
input wire clk_b,
input wire rst_n_b,
output wire [7:0] cnt_a_out,
output wire [7:0] cnt_b_out
);
counter #(.WIDTH(8)) u_cnt_a (
.clk (clk_a),
.rst_n (rst_n_a),
.cnt (cnt_a_out)
);
counter #(.WIDTH(8)) u_cnt_b (
.clk (clk_b),
.rst_n (rst_n_b),
.cnt (cnt_b_out)
);
endmodule逐行说明
- 第1行:模块声明,命名为top。
- 第2-3行:输入端口clk_a和rst_n_a,属于时钟域A。
- 第4-5行:输入端口clk_b和rst_n_b,属于时钟域B。
- 第6-7行:输出端口cnt_a_out和cnt_b_out,分别来自两个时钟域。
- 第9-13行:例化计数器模块u_cnt_a,使用clk_a和rst_n_a。
- 第15-19行:例化计数器模块u_cnt_b,使用clk_b和rst_n_b。
- 第21行:结束模块。
时序约束(XDC文件)
# constraints.xdc
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]逐行说明
- 第1行:注释,说明文件用途。
- 第2行:创建名为clk_a的时钟,周期10ns(100MHz),绑定到顶层端口clk_a。
- 第3行:创建名为clk_b的时钟,周期20ns(50MHz),绑定到顶层端口clk_b。
- 第5行:set_clock_groups命令,指定时钟组为异步关系。
- 第6行:第一个时钟组,包含clk_a。
- 第7行:第二个时钟组,包含clk_b。反斜杠表示续行。
常见坑与排查
- 坑1:时钟组名称拼写错误。 如果get_clocks名称与create_clock的-name不匹配,set_clock_groups会忽略该组,导致路径仍被分析。检查方法:运行“report_clocks”查看已定义的时钟名称。
- 坑2:未包含所有时钟。 如果设计中有第三个时钟(如PLL输出)未被分组,它仍会与所有时钟进行时序分析。建议将所有时钟显式分组,或使用“-group [all_clocks]”作为最后一个组(但需谨慎)。
- 坑3:在综合后添加约束。 set_clock_groups应在综合前或综合后立即添加,避免实现阶段才加入导致重分析。最佳实践:在综合前XDC中写入。
原理与设计说明
set_clock_groups的核心作用是切断时钟域之间的时序分析路径。在默认情况下,Vivado会分析所有时钟域之间的路径,即使设计者明确知道这些域是异步的。这会导致大量假违例(false violations),浪费调试时间,并可能掩盖真正的时序问题。
为什么不用set_false_path?
set_false_path也可以切断路径,但它是路径级别的约束,需要指定起点和终点。在多时钟域设计中,如果域间有多个路径(例如几十个),set_false_path会变得冗长且易漏。set_clock_groups是时钟组级别的约束,一条命令即可切断两个时钟域之间的所有路径,更简洁、更不易出错。
关键权衡
- 资源 vs Fmax:set_clock_groups不影响资源使用,但通过消除假违例,可让时序分析工具聚焦于真实路径,从而优化Fmax。
- 吞吐 vs 延迟:无直接影响,但正确的约束可避免设计因假违例而被过度约束,从而维持预期性能。
- 易用性 vs 可移植性:set_clock_groups是XDC标准命令,在所有主流工具(Vivado、Quartus、Synplify)中均受支持,但语法略有差异(例如Quartus使用derive_clock_uncertainty)。跨平台时需调整。
边界条件
- 如果两个时钟域之间确实存在同步器(如双级触发器),set_clock_groups仍然适用,因为同步器本身不要求时序分析(它容忍亚稳态)。但需要确保同步器路径不被错误地切断——实际上,set_clock_groups切断的是所有路径,包括同步器路径,这是正确的,因为同步器的时序已由MTBF保证。
- 如果设计中有异步FIFO,同样适用set_clock_groups。
验证与结果
| 指标 | 无set_clock_groups | 有set_clock_groups | 说明 |
|---|---|---|---|
| 跨时钟域路径数 | 2(假违例) | 0 | 直接反映约束效果 |
| Setup slack (clk_a) | 0.123 ns | 0.123 ns | 内部路径不受影响 |
| Hold slack (clk_b) | 0.045 ns | 0.045 ns | 内部路径不受影响 |
| 总WNS | 0.123 ns | 0.123 ns | 最差负slack不变 |
| 综合时间 | 45秒 | 42秒 | 略有减少(路径减少) |
测量条件:Vivado 2026.1,Artix-7 XC7A35T,默认综合策略,实现使用“Performance_Explore”。数据为示例值,实际以工程为准。
故障排查(Troubleshooting)
- 现象:时序报告仍显示跨时钟域路径 → 原因:set_clock_groups语法错误或时钟名称不匹配 → 检查点:运行report_clocks确认时钟名称 → 修复:修正名称或使用通配符(如get_clocks *)。
- 现象:实现后出现大量hold违例 → 原因:set_clock_groups未正确切断路径,导致工具尝试优化异步路径 → 检查点:查看违例路径的时钟域 → 修复:确保set_clock_groups包含所有相关时钟。
- 现象:综合时警告“Unconstrained path” → 原因:某些时钟未被约束 → 检查点:report_clock_interaction → 修复:为所有时钟添加create_clock或set_clock_groups。
- 现象:set_clock_groups被忽略 → 原因:XDC文件读取顺序问题(例如在综合后XDC中写入) → 检查点:查看综合日志中约束是否被应用 → 修复:将约束放在综合前XDC中。
- 现象:上板后功能异常(数据丢失) → 原因:实际存在跨时钟域数据交互但被set_clock_groups切断 → 检查点:审查RTL中是否有跨域路径 → 修复:添加同步器或使用异步FIFO,并移除set_clock_groups(改为set_max_delay等)。
- 现象:使用“-asynchronous”后仍有路径 → 原因:时钟通过MMCM/PLL生成,但未包含在组中 → 检查点:report_clocks列出所有生成时钟 → 修复:将生成时钟也加入对应组。
- 现象:多组时钟时出现意外切断 → 原因:set_clock_groups的-group参数顺序导致逻辑错误 → 检查点:理解“-asynchronous”是组间异步,组内时钟仍同步 → 修复:确保同一域内的时钟放在同一组。
- 现象:工具报告“set_clock_groups与set_false_path冲突” → 原因:两者同时作用于同一路径 → 检查点:查看约束冲突报告 → 修复:统一使用set_clock_groups,删除冗余set_false_path。
扩展与下一步
- 参数化时钟组:在Tcl脚本中使用变量定义时钟组,便于在不同设计中复用。
- 带宽提升:对于有数据交互的异步域,使用异步FIFO并配合set_clock_groups,可提升跨域吞吐。
- 跨平台:将XDC约束移植到Intel Quartus时,使用derive_clock_uncertainty实现类似效果。
- 加入断言与覆盖:在仿真中为异步域添加断言(如SVA),验证跨域数据完整性。
- 形式验证:使用形式化工具(如Vivado Formal)验证set_clock_groups是否意外切断了关键路径。
参考与信息来源
- Xilinx UG903: Vivado Design Suite User Guide - Using Constraints (v2026.1)
- Xilinx UG949: Vivado Design Suite User Guide - Methodology (v2026.1)
- Intel Quartus Prime Pro Edition User Guide: Timing Analyzer
- IEEE Std 1800-2017: SystemVerilog (用于断言)
技术附录
术语表
- CDC:Clock Domain Crossing,时钟域交叉。
- WNS:Worst Negative Slack,最差负slack。
- XDC:Xilinx Design Constraints,Xilinx约束文件格式。
检查清单
- [ ] 所有时钟已通过create_clock定义。
- [ ] set_clock_groups包含所有时钟。
- [ ] 时钟组名称与create_clock的-name一致。
- [ ] 约束文件在综合前被读取。
- [ ] 时序报告中跨时钟域路径数为0。
关键约束速查
# 异步时钟组(常用)
set_clock_groups -asynchronous -group [get_clocks clk1] -group [get_clocks clk2]
# 物理互斥时钟组(用于多路选择时钟)
set_clock_groups -physically_exclusive -group [get_clocks clk_a] -group [get_clocks clk_b]
# 逻辑互斥时钟组(用于仿真模式切换)
set_clock_groups -logically_exclusive -group [get_clocks clk_fast] -group [get_clocks clk_slow]逐行说明
- 第2行:异步组,最常用,切断所有路径。
- 第5行:物理互斥组,用于两个时钟不会同时存在的场景(如通过MUX选择)。
- 第8行:逻辑互斥组,用于功能模式切换(如测试模式与正常模式)。


