跳到主要内容
FPGA 时钟约束完全攻略:create_clock 与 create_generated_clock 实战 | 极客日志
编程语言 算法
FPGA 时钟约束完全攻略:create_clock 与 create_generated_clock 实战 FPGA 时钟约束核心概念,涵盖主时钟、衍生时钟及虚拟时钟的定义与约束方法。深入解析 create_clock 和 create_generated_clock 命令参数,包括频率换算、占空比调整、相位偏移及边沿选择技巧。提供 PLL/MMCM、分频器、时钟选择器等场景的实战案例,并介绍时钟组(Clock Groups)配置与时序检查方法,帮助设计者实现时序收敛。
灭霸 发布于 2026/4/5 更新于 2026/5/24 32 浏览FPGA 时钟约束完全攻略
概述
时钟约束是 FPGA 设计中最核心、最重要的约束类型,可以说是整个时序约束体系的基石。正确的时钟约束是时序收敛的前提,所有其他约束 (I/O 延迟、时序例外等) 都建立在时钟约束之上。
为什么时钟约束如此重要?
在 FPGA 设计中,时钟信号驱动着整个数字系统的运行。如果没有正确的时钟约束,会导致:
综合工具无法正确优化设计
布局布线工具无法满足时序要求
静态时序分析 (STA) 无法准确评估设计性能
最终导致硬件功能异常或不稳定
本文将帮助您:
深入理解时钟约束的原理和作用机制
掌握 create_clock 和 create_generated_clock 的正确用法
学会处理各种复杂时钟场景 (分频、倍频、相移等)
了解虚拟时钟和时钟组的应用
通过实战案例巩固时钟约束技能
避免常见的时钟约束错误
一、时钟约束基础概念
1.1 为什么需要时钟约束
时钟约束不仅仅是告诉工具时钟频率那么简单,它在 FPGA 设计流程中扮演着多重关键角色。
1.1.1 指导综合优化
综合器需要知道时钟频率才能进行合理的优化:
没有时钟约束的情况:
// Verilog 代码
always @(posedge clk) begin
result <= a + b + c + d;
end
如果没有时钟约束,综合器不知道目标频率,可能会:
选择面积最小但速度慢的实现
无法判断是否需要插入流水线
无法优化关键路径
有时钟约束的情况:
# 约束时钟为 200MHz (周期 5ns)
create_clock -period 5.000 -name sys_clk [get_ports clk]
综合器知道需要在 5ns 内完成运算,会:
选择更快的加法器实现
可能自动插入流水线寄存器
优化关键路径以满足时序
1.1.2 指导布局布线
布局布线器根据时钟约束来放置逻辑单元和规划布线:
# 高速时钟约束
create_clock -period 2.000 -name clk_500m [get_ports clk_in]
布局布线器会:
将相关逻辑放置得更紧密
使用更优质的布线资源
减少时钟偏斜 (Clock Skew)
优化关键路径的物理实现
1.1.3 进行静态时序分析
静态时序分析 (STA) 是验证设计时序正确性的关键步骤:
时序检查的基本原理:
发起触发器 (Launch FF) → 组合逻辑 → 捕获触发器 (Capture FF)
↓ ↓
时钟边沿 T0 时钟边沿 T1
Setup 时间检查:
数据必须在时钟边沿前稳定 ≥ Setup 时间
Tclk ≥ Tco + Tlogic + Trouting + Tsetup
Hold 时间检查:
数据必须在时钟边沿后保持 ≥ Hold 时间
Tco + Tlogic Trouting ≥ Thold
+
1.1.4 定义时钟域关系 # 定义两个独立时钟
create_clock -period 10.000 -name clk_100m [get_ports clk_a]
create_clock -period 8.000 -name clk_125m [get_ports clk_b]
# 声明它们是异步的
set_clock_groups -asynchronous \
-group [get_clocks clk_100m] \
-group [get_clocks clk_125m]
这两个时钟域之间的路径不需要时序分析
设计者需要自行处理跨时钟域 (CDC) 问题
避免工具报告大量无意义的时序违例
1.2 时钟约束的分类 FPGA 设计中的时钟可以分为三大类,每类都有对应的约束方法:
1.2.1 主时钟 (Primary Clock)
来自外部晶振、时钟芯片或其他器件
通过 FPGA 的时钟输入端口进入
是整个时钟树的根源
create_clock -period <周期> -name <时钟名> [get_ports <端口名>]
# 板载 50MHz 晶振
create_clock -period 20.000 -name clk_50m [get_ports sys_clk]
# 外部 125MHz 以太网时钟
create_clock -period 8.000 -name eth_rxclk [get_ports gmii_rx_clk]
# 差分时钟输入
create_clock -period 5.000 -name clk_200m [get_ports clk_200m_p]
1.2.2 衍生时钟 (Generated Clock) 定义: 由 FPGA 内部逻辑产生的时钟,来源于主时钟或其他衍生时钟
通过 PLL/MMCM、分频器等产生
与源时钟有确定的频率和相位关系
自动继承源时钟的抖动特性
create_generated_clock -name <时钟名> \
-source <源时钟位置> \
[-divide_by <分频>] [-multiply_by <倍频>] \
[get_pins <输出引脚>]
# PLL 2 倍频输出
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
# 计数器 2 分频
create_generated_clock -name clk_50m \
-source [get_pins div_reg/C] \
-divide_by 2 \
[get_pins div_reg/Q]
1.2.3 虚拟时钟 (Virtual Clock) 定义: 不直接驱动 FPGA 内部逻辑的时钟,主要用于 I/O 时序约束
不存在于 FPGA 内部
用作输入/输出延迟的参考时钟
定义板级接口的时序关系
create_clock -period <周期> -name <虚拟时钟名>
# 注意:不指定目标对象
# 外部 ADC 的时钟 (不进入 FPGA)
create_clock -period 10.000 -name virt_adc_clk
# 使用虚拟时钟定义输入延迟
set_input_delay -clock virt_adc_clk -max 2.0 [get_ports adc_data[*]]
1.3 时钟约束与时序分析的关系 时钟约束是静态时序分析 (STA) 的基础,两者密不可分。
1.3.1 时序路径的分类 📊 时序路径类型 │
├─ 1 ️⃣ 寄存器到寄存器 (Reg2Reg)
│ └─ 同一时钟域内的路径
├─ 2 ️⃣ 输入到寄存器 (In2Reg)
│ └─ 外部输入到内部寄存器
├─ 3 ️⃣ 寄存器到输出 (Reg2Out)
│ └─ 内部寄存器到外部输出
└─ 4 ️⃣ 输入到输出 (In2Out)
└─ 纯组合路径 (通常不推荐)
# 主时钟约束
create_clock -period 10.000 -name clk_sys [get_ports clk]
# Reg2Reg 路径:自动分析
# 工具会检查同一时钟域内所有寄存器间的时序
# In2Reg 路径:需要输入延迟约束
set_input_delay -clock clk_sys -max 3.0 [get_ports data_in[*]]
# Reg2Out 路径:需要输出延迟约束
set_output_delay -clock clk_sys -max 2.0 [get_ports data_out[*]]
1.3.2 时钟不确定性 时钟不确定性 (Clock Uncertainty) 包括时钟抖动 (Jitter) 和时钟偏斜 (Skew):
# 晶振抖动:±50ps
set_clock_uncertainty 0.1 [get_clocks clk_sys]
时钟树延迟差异导致不同寄存器的时钟到达时间不同,工具会自动计算,但可以手动指定额外的裕量。
1.3.3 跨时钟域 (CDC) 分析 # PLL 输出的时钟是同步的
create_clock -period 10.000 -name clk_in [get_ports clk_in]
create_generated_clock -name clk_100m \
-source [get_pins pll/CLKIN1] \
[get_pins pll/CLKOUT0]
create_generated_clock -name clk_200m \
-source [get_pins pll/CLKIN1] \
-multiply_by 2 \
[get_pins pll/CLKOUT1]
# 工具会分析 clk_100m 和 clk_200m 之间的路径
# 两个独立的时钟源
create_clock -period 10.000 -name clk_a [get_ports clk_a]
create_clock -period 8.000 -name clk_b [get_ports clk_b]
# 声明异步关系
set_clock_groups -asynchronous \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
# 工具不会分析这两个时钟域之间的时序
# 设计者需要使用 CDC 同步器 (如双触发器)
二、主时钟约束 (create_clock) 详解
2.1 基本语法与参数
2.1.1 完整语法格式 create_clock \
-period <period_value> \
[-name <clock_name>] \
[-waveform <edge_list>] \
[-add] \
[get_ports <port_name>]
2.1.2 参数详解 # 单位:纳秒 (ns)
create_clock -period 10.000 -name clk_100m [get_ports clk]
# 100MHz = 1000MHz / 100 = 10ns 周期
周期 (ns) = 1000 / 频率 (MHz)
频率 (MHz) = 1000 / 周期 (ns)
# ✅ 推荐:使用有意义的名称
create_clock -period 10.000 -name clk_sys_100m [get_ports sys_clk]
# ❌ 不推荐:使用默认名称或无意义名称
create_clock -period 10.000 [get_ports sys_clk]
# 默认使用端口名
包含频率信息:clk_100m, clk_125m
体现用途:clk_sys, clk_ddr, clk_eth
避免使用 clk1, clk2 等无意义名称
# 默认波形:50% 占空比,从 0 开始
create_clock -period 10.000 -name clk [get_ports clk]
# 等效于:-waveform {0 5.0}
# 自定义波形
create_clock -period 10.000 -waveform {0 2.5} -name clk_25duty [get_ports clk]
# 25% 占空比:高电平 2.5ns,低电平 7.5ns
# 同一端口定义多个时钟 (用于时钟切换场景)
create_clock -period 10.000 -name clk_normal [get_ports clk_in]
create_clock -period 100.000 -name clk_slow [get_ports clk_in] -add
# 需要配合时钟组使用
set_clock_groups -physically_exclusive \
-group [get_clocks clk_normal] \
-group [get_clocks clk_slow]
2.2 标准时钟约束
2.2.1 单端时钟 # 100MHz 系统时钟
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
# 125MHz 以太网时钟
create_clock -period 8.000 -name clk_eth [get_ports eth_rxc]
# 200MHz DDR 时钟
create_clock -period 5.000 -name clk_ddr [get_ports ddr_clk]
2.2.2 常用频率快速参考 频率 周期 (ns) 约束命令 10MHz 100.000 create_clock -period 100.00025MHz 40.000 create_clock -period 40.00050MHz 20.000 create_clock -period 20.000100MHz 10.000 create_clock -period 10.000125MHz 8.000 create_clock -period 8.000150MHz 6.667 create_clock -period 6.667200MHz 5.000 create_clock -period 5.000250MHz 4.000 create_clock -period 4.000300MHz 3.333 create_clock -period 3.333400MHz 2.500 create_clock -period 2.500500MHz 2.000 create_clock -period 2.000
2.3 自定义波形约束
2.3.1 占空比调整 create_clock -period 10.000 \
-waveform {0 2.5} \
-name clk_25duty \
[get_ports clk]
# 波形:
# _____|‾‾|_______|‾‾|_______
# 0 2.5 10 12.5 20
create_clock -period 10.000 \
-waveform {0 7.5} \
-name clk_75duty \
[get_ports clk]
# 波形:
# _____|‾‾‾‾‾‾|___|‾‾‾‾‾‾|___
# 0 7.5 10 17.5 20
2.3.2 相位偏移 create_clock -period 10.000 \
-waveform {2.5 7.5} \
-name clk_90deg \
[get_ports clk]
# 波形:
# ________|‾‾‾‾‾|_____|‾‾‾‾‾|
# 0 2.5 7.5 10 12.5 17.5
create_clock -period 10.000 \
-waveform {5.0 10.0} \
-name clk_180deg \
[get_ports clk]
# 波形:
# ‾‾‾‾‾|_____|‾‾‾‾‾|_____
# 0 5 10 15 20
2.4 差分时钟约束
2.4.1 LVDS 差分时钟 # ✅ 正确:只约束 P 端
create_clock -period 5.000 -name clk_200m [get_ports clk_200m_p]
# 设置差分 IO 标准
set_property IOSTANDARD LVDS [get_ports clk_200m_p]
set_property IOSTANDARD LVDS [get_ports clk_200m_n]
# ❌ 错误:不要约束 N 端
# create_clock -period 5.000 -name clk_200m_n [get_ports clk_200m_n]
2.4.2 其他差分标准 create_clock -period 4.000 -name clk_250m_lvds [get_ports clk_p]
set_property IOSTANDARD LVDS_25 [get_ports clk_p]
set_property IOSTANDARD LVDS_25 [get_ports clk_n]
create_clock -period 3.333 -name clk_ddr3 [get_ports ddr3_clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports ddr3_clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports ddr3_clk_n]
2.5 多时钟系统约束
2.5.1 独立多时钟 # 系统时钟:100MHz
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
# 以太网接收时钟:125MHz
create_clock -period 8.000 -name clk_eth_rx [get_ports eth_rx_clk]
# 以太网发送时钟:125MHz
create_clock -period 8.000 -name clk_eth_tx [get_ports eth_tx_clk]
# USB 时钟:60MHz
create_clock -period 16.667 -name clk_usb [get_ports usb_clk]
# 声明异步关系
set_clock_groups -asynchronous \
-group [get_clocks clk_sys] \
-group [get_clocks {clk_eth_rx clk_eth_tx}] \
-group [get_clocks clk_usb]
2.5.2 同一端口多时钟 # 正常模式:100MHz
create_clock -period 10.000 -name clk_normal [get_ports clk_in]
# 低功耗模式:10MHz
create_clock -period 100.000 -name clk_lowpower [get_ports clk_in] -add
# 声明物理互斥 (同一时刻只有一个有效)
set_clock_groups -physically_exclusive \
-group [get_clocks clk_normal] \
-group [get_clocks clk_lowpower]
三、主时钟约束实战技巧
3.1 频率与周期换算
3.1.1 常用频率换算表 频率 周期 (ns) 应用场景 10MHz 100.000 UART、I2C 等低速接口 12MHz 83.333 USB 全速 (FS) 25MHz 40.000 以太网 MII 27MHz 37.037 视频像素时钟 33.33MHz 30.000 PCI 总线 48MHz 20.833 USB 高速 (HS) 50MHz 20.000 通用系统时钟
频率 周期 (ns) 应用场景 60MHz 16.667 USB 2.0 74.25MHz 13.468 720p 视频 100MHz 10.000 通用系统时钟 125MHz 8.000 千兆以太网 RGMII 148.5MHz 6.734 1080p 视频 156.25MHz 6.400 10G 以太网 200MHz 5.000 DDR3-1600
频率 周期 (ns) 应用场景 250MHz 4.000 DDR3-2000 300MHz 3.333 DDR3-2400 400MHz 2.500 DDR4-3200 500MHz 2.000 高速 SerDes
3.1.2 精确换算方法 周期 (ns) = 1000 / 频率 (MHz)
周期 (ps) = 1000000 / 频率 (MHz)
# 133.33MHz
# 周期 = 1000 / 133.33 = 7.500ns
create_clock -period 7.500 -name clk_133m [get_ports clk]
# 66.67MHz
# 周期 = 1000 / 66.67 = 15.000ns
create_clock -period 15.000 -name clk_66m [get_ports clk]
# 245.76MHz(通信基站常用)
# 周期 = 1000 / 245.76 = 4.069ns
create_clock -period 4.069 -name clk_rf [get_ports clk]
3.2 占空比与相位设置
3.2.1 占空比计算 # 周期 10ns,高电平 2ns
create_clock -period 10.000 -waveform {0 2.0} -name clk [get_ports clk]
# 周期 10ns,高电平 3ns
create_clock -period 10.000 -waveform {0 3.0} -name clk [get_ports clk]
# 周期 10ns,高电平 4ns
create_clock -period 10.000 -waveform {0 4.0} -name clk [get_ports clk]
# 周期 10ns,高电平 6ns
create_clock -period 10.000 -waveform {0 6.0} -name clk [get_ports clk]
3.2.2 相位偏移计算 时间偏移 = 周期 × (相位角度 / 360 °)
# 周期 10ns,45 度 = 10 × (45/360) = 1.25ns
create_clock -period 10.000 -waveform {1.25 6.25} -name clk [get_ports clk]
# 周期 10ns,90 度 = 10 × (90/360) = 2.5ns
create_clock -period 10.000 -waveform {2.5 7.5} -name clk [get_ports clk]
# 周期 10ns,180 度 = 10 × (180/360) = 5ns
create_clock -period 10.000 -waveform {5.0 10.0} -name clk [get_ports clk]
# 周期 10ns,270 度 = 10 × (270/360) = 7.5ns
create_clock -period 10.000 -waveform {7.5 12.5} -name clk [get_ports clk]
3.3 常见错误与调试
3.3.1 常见错误类型 # ❌ 错误:误将频率当作周期
create_clock -period 100 -name clk_100m [get_ports clk]
# 这会被理解为周期 100ns = 10MHz,而不是 100MHz!
# ✅ 正确:100MHz = 10ns 周期
create_clock -period 10.000 -name clk_100m [get_ports clk]
# ❌ 错误:P 端和 N 端都约束
create_clock -period 5.000 -name clk_p [get_ports clk_p]
create_clock -period 5.000 -name clk_n [get_ports clk_n]
# ✅ 正确:只约束 P 端
create_clock -period 5.000 -name clk_200m [get_ports clk_p]
# ❌ 错误:下降沿时间小于上升沿时间
create_clock -period 10.000 -waveform {5.0 2.5} -name clk [get_ports clk]
# ✅ 正确:上升沿 < 下降沿
create_clock -period 10.000 -waveform {0 5.0} -name clk [get_ports clk]
# ❌ 错误:只约束了部分时钟
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
# 忘记约束 eth_clk!
# ✅ 正确:约束所有外部时钟
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
create_clock -period 8.000 -name clk_eth [get_ports eth_clk]
# ❌ 错误:使用相同的时钟名称
create_clock -period 10.000 -name clk [get_ports clk_a]
create_clock -period 8.000 -name clk [get_ports clk_b]
# 名称冲突!
# ✅ 正确:使用唯一的时钟名称
create_clock -period 10.000 -name clk_100m [get_ports clk_a]
create_clock -period 8.000 -name clk_125m [get_ports clk_b]
3.4 时钟约束检查方法
3.4.1 综合后检查 # 查看所有时钟
report_clocks
# 检查时钟网络
report_clock_networks
# 检查未约束的时钟
check_timing -verbose
Clock Name Period Waveform Source
---------------------------------------------------------
clk_sys 10.000 {0.000 5.000} sys_ clk
clk_eth 8.000 {0.000 4.000} eth_ clk
clk_ddr 5.000 {0.000 2.500} ddr_ clk
3.4.2 check_timing 常见警告 [Timing 38 -313 ] There are no user specified timing constraints.
警告 2:unconstrained_internal_endpoints
[Timing 38-316 ] Clock 'clk_sys' has unconstrained internal endpoints.
[Timing 38-304 ] Multiple clocks are defined on port 'clk_in' .
3.4.3 实现后检查 opt_design place_design route_design
# 生成时序报告
report_timing_summary -file timing_summary.rpt
# 查看最差路径
report_timing -max_paths 10 -nworst 1 -file worst_timing.rpt
# 检查时钟偏斜
report_clock_utilization
3.4.4 时钟质量检查清单 # 检查时钟组
report_clock_groups
# 检查是否有 N 端的时钟约束
report_clocks | grep "_n"
# 验证公式:周期 (ns) = 1000 / 频率 (MHz)
# 检查命令
report_clocks
check_timing -verbose
四、衍生时钟约束 (create_generated_clock) 详解
4.1 什么是衍生时钟?
4.1.1 衍生时钟的概念 衍生时钟 (Generated Clock) 是指由主时钟经过 FPGA 内部逻辑处理后产生的时钟信号。
特征 说明 来源 由主时钟 (Primary Clock) 派生而来 产生方式 通过 PLL、MMCM、分频器、选择器等逻辑产生 频率关系 与源时钟存在确定的频率关系 (倍频/分频) 相位关系 可能存在相位偏移 约束方式 使用 create_generated_clock 命令
4.1.2 为什么需要衍生时钟约束? 1. 时序分析不完整
└─ 工具无法正确分析跨时钟域路径
2. 时钟关系丢失
└─ 无法识别同步/异步时钟关系
3. 优化效果差
└─ 布局布线工具无法进行针对性优化
4. 时序收敛困难
└─ 可能出现意外的时序违例
# 示例:PLL 输出时钟
create_clock -period 10.000 -name clk_in [get_ports clk_in]
# 约束 PLL 输出 (100MHz -> 200MHz)
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
# 工具能够:
# 1. 自动计算衍生时钟周期 (5ns)
# 2. 识别与源时钟的同步关系
# 3. 正确分析跨时钟域路径
# 4. 优化时钟树结构
4.1.3 常见衍生时钟场景 📊 衍生时钟产生方式 │
├─ 1 ️⃣ PLL/MMCM 输出
│ ├─ 倍频时钟 (如 100 MHz -> 200 MHz)
│ ├─ 分频时钟 (如 100 MHz -> 50 MHz)
│ └─ 相移时钟 (如 90 度相移)
├─ 2 ️⃣ 时钟分频器
│ ├─ 计数器分频 (偶数/奇数分频)
│ └─ 触发器分频
├─ 3 ️⃣ 时钟选择器
│ ├─ BUFGMUX 输出
│ └─ 多路选择器输出
└─ 4 ️⃣ 组合逻辑
├─ 门控时钟 (Clock Gating)
└─ 时钟反相
4.2 create_generated_clock 基本语法
4.2.1 完整语法格式 create_generated_clock \
-name <clock_name> \
# 时钟名称 (必需)
-source <source_pin> \
# 源时钟引脚 (必需)
[-master_clock <master_clock>] \
# 主时钟名称 (可选)
[-divide_by <divisor>] \
# 分频系数
[-multiply_by <multiplier>] \
# 倍频系数
[-duty_cycle <percent>] \
# 占空比
[-invert] \
# 反相
[-edge_shift <edge_list>] \
# 边沿偏移
[-edges <edge_list>] \
# 边沿选择
[-add] \
# 添加模式
<target_pin> # 目标引脚 (必需)
4.2.2 关键参数详解 # 命名规范建议
create_generated_clock -name clk_pll_200m ...
# ✅ 清晰表明来源和频率
create_generated_clock -name clk_div2 ...
# ✅ 表明分频关系
create_generated_clock -name clk1 ...
# ❌ 名称不明确
# 必须指向源时钟所在的引脚
# 对于 PLL:指向 PLL 的输入引脚
create_generated_clock -name clk_out \
-source [get_pins pll_inst/CLKIN1] \
[get_pins pll_inst/CLKOUT0]
# 对于分频器:指向分频器的时钟输入
create_generated_clock -name clk_div \
-source [get_pins clk_div_reg/C] \
[get_pins clk_div_reg/Q]
# 2 分频示例
create_generated_clock -name clk_50m \
-source [get_pins pll_inst/CLKIN1] \
-divide_by 2 \
[get_pins pll_inst/CLKOUT1]
# 100MHz -> 50MHz
# 4 分频示例
create_generated_clock -name clk_25m \
-source [get_pins pll_inst/CLKIN1] \
-divide_by 4 \
[get_pins pll_inst/CLKOUT2]
# 100MHz -> 25MHz
# 2 倍频示例
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
# 100MHz -> 200MHz
# 4 倍频示例
create_generated_clock -name clk_400m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 4 \
[get_pins pll_inst/CLKOUT3]
# 100MHz -> 400MHz
# 当源引脚有多个时钟时,需要明确指定主时钟
create_clock -period 10.000 -name clk_a [get_ports clk_in]
create_clock -period 8.000 -name clk_b [get_ports clk_in] -add
# 指定使用 clk_a 作为源
create_generated_clock -name clk_gen \
-source [get_ports clk_in] \
-master_clock clk_a \
-divide_by 2 \
[get_pins div_inst/Q]
4.3 PLL/MMCM 时钟约束
4.3.1 Xilinx 7 系列 PLL 约束 # 输入时钟:100MHz
create_clock -period 10.000 -name clk_in [get_ports sys_clk]
# PLL 输出 1:200MHz(2 倍频)
create_generated_clock -name clk_200m \
-source [get_pins pll_base_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_base_inst/CLKOUT0]
# PLL 输出 2:50MHz(2 分频)
create_generated_clock -name clk_50m \
-source [get_pins pll_base_inst/CLKIN1] \
-divide_by 2 \
[get_pins pll_base_inst/CLKOUT1]
# PLL 输出 3:100MHz(同频,90 度相移)
create_generated_clock -name clk_100m_90 \
-source [get_pins pll_base_inst/CLKIN1] \
[get_pins pll_base_inst/CLKOUT2]
4.3.2 MMCM 完整约束示例 # 输入时钟
create_clock -period 10.000 -name clk_100m [get_ports clk_in]
# MMCM 反馈时钟 (内部)
create_generated_clock -name clkfbout \
-source [get_pins mmcm_inst/CLKIN1] \
-multiply_by 10 -divide_by 1 \
[get_pins mmcm_inst/CLKFBOUT]
# MMCM 输出时钟 1:250MHz
create_generated_clock -name clk_250m \
-source [get_pins mmcm_inst/CLKIN1] \
-multiply_by 10 -divide_by 4 \
[get_pins mmcm_inst/CLKOUT0]
# MMCM 输出时钟 2:125MHz
create_generated_clock -name clk_125m \
-source [get_pins mmcm_inst/CLKIN1] \
-multiply_by 10 -divide_by 8 \
[get_pins mmcm_inst/CLKOUT1]
# MMCM 输出时钟 3:62.5MHz
create_generated_clock -name clk_62m5 \
-source [get_pins mmcm_inst/CLKIN1] \
-multiply_by 10 -divide_by 16 \
[get_pins mmcm_inst/CLKOUT2]
1. 反馈时钟通常不需要约束 (工具自动处理)
2. multiply_by 和 divide_ by 要与 IP 配置一致
3. 相移时钟可以不指定 multiply/divide(自动继承)
4. 输出使能信号不影响时钟约束
4.4 时钟分频器约束
4.4.1 偶数分频约束 // RTL 代码
module clk_div2 (
input wire clk_in,
input wire rst_n,
output reg clk_out
);
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) clk_out <= 1'b0;
else clk_out <= ~clk_out;
end
endmodule
# 输入时钟:100MHz
create_clock -period 10.000 -name clk_100m [get_ports clk_in]
# 2 分频输出:50MHz
create_generated_clock -name clk_50m \
-source [get_pins clk_div2_inst/clk_in] \
-divide_by 2 \
[get_pins clk_div2_inst/clk_out]
// RTL 代码
module clk_div4 (
input wire clk_in,
input wire rst_n,
output reg clk_out
);
reg [1:0] cnt;
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
cnt <= 2'b00;
clk_out <= 1'b0;
end else begin
cnt <= cnt + 1'b1;
if (cnt == 2'b01) clk_out <= ~clk_out;
end
end
endmodule
# 4 分频输出:25MHz
create_generated_clock -name clk_25m \
-source [get_pins clk_div4_inst/clk_in] \
-divide_by 4 \
[get_pins clk_div4_inst/clk_out]
4.4.2 奇数分频约束 // RTL 代码
module clk_div3 (
input wire clk_in,
input wire rst_n,
output reg clk_out
);
reg [1:0] cnt;
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
cnt <= 2'b00;
clk_out <= 1'b0;
end else begin
if (cnt == 2'd2) begin
cnt <= 2'b00;
clk_out <= 1'b1;
end else begin
cnt <= cnt + 1'b1;
if (cnt == 2'd0) clk_out <= 1'b0;
end
end
end
endmodule
# 3 分频输出:33.33MHz(占空比 33.33%)
create_generated_clock -name clk_33m \
-source [get_pins clk_div3_inst/clk_in] \
-divide_by 3 \
[get_pins clk_div3_inst/clk_out]
# 工具会自动计算占空比
module clk_div5 (
input wire clk_in,
input wire rst_n,
output reg clk_out
);
reg [2:0] cnt;
always @(posedge clk_in or negedge rst_n) begin
if (!rst_n) begin
cnt <= 3'b000;
clk_out <= 1'b0;
end else begin
if (cnt == 3'd4) cnt <= 3'b000;
else cnt <= cnt + 1'b1;
if (cnt < 3'd2) clk_out <= 1'b1;
else clk_out <= 1'b0;
end
end
endmodule
# 5 分频输出:20MHz
create_generated_clock -name clk_20m \
-source [get_pins clk_div5_inst/clk_in] \
-divide_by 5 \
[get_pins clk_div5_inst/clk_out]
4.5 时钟选择器约束
4.5.1 BUFGMUX 时钟选择 // 使用 Xilinx 原语
BUFGMUX #( .CLK_SEL_TYPE("SYNC") // SYNC 或 ASYNC )
bufgmux_inst (
.O(clk_out), // 输出时钟
.I0(clk_in0), // 输入时钟 0
.I1(clk_in1), // 输入时钟 1
.S(clk_sel) // 选择信号
);
# 输入时钟
create_clock -period 10.000 -name clk_100m [get_ports clk_in0]
create_clock -period 8.000 -name clk_125m [get_ports clk_in1]
# 方法 1:为每个输入创建衍生时钟
create_generated_clock -name clk_out_100m \
-source [get_pins bufgmux_inst/I0] \
-master_clock clk_100m \
[get_pins bufgmux_inst/O]
create_generated_clock -name clk_out_125m \
-source [get_pins bufgmux_inst/I1] \
-master_clock clk_125m \
-add \
[get_pins bufgmux_inst/O]
# 方法 2:使用时钟组 (推荐)
set_clock_groups -physically_exclusive \
-group [get_clocks clk_out_100m] \
-group [get_clocks clk_out_125m]
4.5.2 多路选择器时钟约束 // 4 选 1 时钟选择器
module clk_mux4 (
input wire [3:0] clk_in,
input wire [1:0] sel,
output reg clk_out
);
always @(*) begin
case (sel)
2'b00: clk_out = clk_in[0];
2'b01: clk_out = clk_in[1];
2'b10: clk_out = clk_in[2];
2'b11: clk_out = clk_in[3];
endcase
end
endmodule
# 输入时钟
create_clock -period 10.000 -name clk0 [get_ports clk_in[0]]
create_clock -period 8.000 -name clk1 [get_ports clk_in[1]]
create_clock -period 6.667 -name clk2 [get_ports clk_in[2]]
create_clock -period 5.000 -name clk3 [get_ports clk_in[3]]
# 为每个输入创建衍生时钟
create_generated_clock -name clk_out0 \
-source [get_ports clk_in[0]] \
-master_clock clk0 \
[get_pins clk_mux4_inst/clk_out]
create_generated_clock -name clk_out1 \
-source [get_ports clk_in[1]] \
-master_clock clk1 \
-add \
[get_pins clk_mux4_inst/clk_out]
create_generated_clock -name clk_out2 \
-source [get_ports clk_in[2]] \
-master_clock clk2 \
-add \
[get_pins clk_mux4_inst/clk_out]
create_generated_clock -name clk_out3 \
-source [get_ports clk_in[3]] \
-master_clock clk3 \
-add \
[get_pins clk_mux4_inst/clk_out]
# 声明互斥关系
set_clock_groups -physically_exclusive \
-group [get_clocks clk_out0] \
-group [get_clocks clk_out1] \
-group [get_clocks clk_out2] \
-group [get_clocks clk_out3]
4.6 组合逻辑产生的时钟
4.6.1 门控时钟约束 // 不推荐的门控时钟写法
module gated_clock (
input wire clk_in,
input wire enable,
output wire clk_out
);
assign clk_out = clk_in & enable; // ❌ 产生毛刺
endmodule
# 输入时钟
create_clock -period 10.000 -name clk_in [get_ports clk_in]
# 门控时钟 (不推荐)
create_generated_clock -name clk_gated \
-source [get_ports clk_in] \
[get_pins gated_clock_inst/clk_out]
# 添加时钟不确定性 (考虑毛刺)
set_clock_uncertainty 0.5 [get_clocks clk_gated]
// 推荐的时钟使能写法
module clock_enable (
input wire clk,
input wire enable,
input wire data_in,
output reg data_out
);
always @(posedge clk) begin
if (enable) data_out <= data_in;
end
endmodule
4.6.2 时钟反相约束 // 时钟反相
assign clk_inv = ~clk_in;
# 输入时钟
create_clock -period 10.000 -name clk_in [get_ports clk_in]
# 反相时钟
create_generated_clock -name clk_inv \
-source [get_ports clk_in] \
-invert \
[get_nets clk_inv]
4.7 常见错误与调试
4.7.1 常见错误类型 # ❌ 错误:source 指向输出引脚
create_generated_clock -name clk_out \
-source [get_pins pll_inst/CLKOUT0] \
-divide_by 2 \
[get_pins div_inst/Q]
# ✅ 正确:source 指向输入引脚
create_generated_clock -name clk_out \
-source [get_pins pll_inst/CLKIN1] \
-divide_by 2 \
[get_pins div_inst/Q]
# RTL 实现是 4 分频,但约束写成 2 分频
# ❌ 错误
create_generated_clock -name clk_div \
-source [get_pins clk_div_reg/C] \
-divide_by 2 \
[get_pins clk_div_reg/Q]
# ✅ 正确
create_generated_clock -name clk_div \
-source [get_pins clk_div_reg/C] \
-divide_by 4 \
[get_pins clk_div_reg/Q]
# ❌ 只约束了输入,忘记约束输出
create_clock -period 10.000 -name clk_in [get_ports clk_in]
# ✅ 完整约束
create_clock -period 10.000 -name clk_in [get_ports clk_in]
create_generated_clock -name clk_pll_out \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
# ❌ 只创建了衍生时钟,未声明互斥关系
create_generated_clock -name clk_out0 ...
create_generated_clock -name clk_out1 -add ...
# ✅ 添加互斥声明
set_clock_groups -physically_exclusive \
-group [get_clocks clk_out0] \
-group [get_clocks clk_out1]
4.7.2 调试技巧 # 查看所有时钟
report_clocks
# 检查特定时钟
report_clocks -name clk_pll_out
# 查看时钟树
report_clock_networks
# 查看时钟间的关系
report_clock_interaction
# 检查时钟组
report_clock_groups
# 检查未约束的时钟
check_timing -verbose
# 查看时钟覆盖率
report_clock_utilization
五、衍生时钟高级用法
5.1 边沿选择 (-edges 参数)
5.1.1 边沿选择原理 -edges 参数允许您精确指定衍生时钟使用源时钟的哪些边沿。
create_generated_clock -name <name> \
-source <source_pin> \
-edges {edge1 edge2 edge3} \
<target_pin>
源时钟波形:___ __ _ ___ __ _ ___ __ _| |___| |__ _ | |___| |_ __ | |___
1 2 3 4 5 6 7 8 9 10
边沿编号从 1 开始:
- 奇数边沿:上升沿 (1,3,5,7,9...)
- 偶数边沿:下降沿 (2,4,6,8,10...)
5.1.2 2 分频边沿选择 # 源时钟:100MHz
create_clock -period 10.000 -name clk_100m [get_ports clk_in]
# 2 分频:选择第 1、3 个边沿 (两个上升沿)
create_generated_clock -name clk_50m \
-source [get_ports clk_in] \
-edges {1 3 5} \
[get_pins div2_reg/Q]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9
# clk_50m: _|‾‾‾|___|‾‾‾|___#
# 1 3 5
# 2 分频:选择第 2、4 个边沿 (两个下降沿)
create_generated_clock -name clk_50m_inv \
-source [get_ports clk_in] \
-edges {2 4 6} \
[get_pins div2_reg/Q]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9
# clk_50m: ‾‾‾|___|‾‾‾|___|
# 2 4 6
5.1.3 3 分频边沿选择 # 3 分频:选择第 1、4、7 个边沿
create_generated_clock -name clk_33m \
-source [get_ports clk_in] \
-edges {1 4 7} \
[get_pins div3_reg/Q]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9 0 1 2 3
# clk_33m: _|‾‾‾‾‾|_____|‾‾‾‾‾|_____
# 1 4 7
# 周期 = (7-1)/2 * 10ns = 30ns = 33.33MHz
5.1.4 非整数分频 # 1.5 分频:100MHz -> 66.67MHz
create_generated_clock -name clk_66m \
-source [get_ports clk_in] \
-edges {1 2 4} \
[get_pins div_reg/Q]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9
# clk_66m: _|‾|___|‾|___|‾|_
# 1 2 4 6 8
# 周期 = (4-1)/2 * 10ns = 15ns = 66.67MHz
# 2.5 分频:100MHz -> 40MHz
create_generated_clock -name clk_40m \
-source [get_ports clk_in] \
-edges {1 3 6} \
[get_pins div_reg/Q]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9 0 1
# clk_40m: _|‾‾‾|_____|‾‾‾|_____
# 1 3 6
# 周期 = (6-1)/2 * 10ns = 25ns = 40MHz
5.2 边沿偏移 (-edge_shift 参数)
5.2.1 边沿偏移原理 -edge_shift 参数用于对选定的边沿进行时间偏移,实现相位调整。
create_generated_clock -name <name> \
-source <source_pin> \
-edges {edge1 edge2 edge3} \
-edge_shift {shift1 shift2 shift3} \
<target_pin>
5.2.2 相位偏移示例 # 源时钟:100MHz,周期 10ns
create_clock -period 10.000 -name clk_100m [get_ports clk_in]
# 90 度相移:延迟 2.5ns
create_generated_clock -name clk_100m_90 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {2.5 2.5 2.5} \
[get_pins phase_shift_reg/Q]
# 波形分析:
# clk_100m: _|‾‾‾‾‾|_____|‾‾‾‾‾|_____
# 0 5 10 15 20
# clk_100m_90: _____|‾‾‾‾‾|_____|‾‾‾‾‾|_
# 0 2.5 7.5 12.5 17.5
# 180 度相移:延迟 5ns(半个周期)
create_generated_clock -name clk_100m_180 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {5.0 5.0 5.0} \
[get_pins phase_shift_reg/Q]
# 等效于使用-invert 参数
create_generated_clock -name clk_100m_inv \
-source [get_ports clk_in] \
-invert \
[get_pins phase_shift_reg/Q]
# 270 度相移:延迟 7.5ns
create_generated_clock -name clk_100m_270 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {7.5 7.5 7.5} \
[get_pins phase_shift_reg/Q]
5.2.3 不对称偏移 # 调整占空比为 25%(高电平 2.5ns,低电平 7.5ns)
create_generated_clock -name clk_25duty \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {0 2.5 0} \
[get_pins duty_reg/Q]
# 波形分析:
# clk_100m: _|‾‾‾‾‾|_____|‾‾‾‾‾|_____
# 0 5 10 15 20
# clk_25duty: _|‾‾|_______|‾‾|________
# 0 2.5 10 12.5 20
5.3 组合倍频分频
5.3.1 同时倍频和分频 示例:100MHz -> 150MHz(×3/2)
# 方法 1:使用 multiply_by 和 divide_by
create_generated_clock -name clk_150m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 3 \
-divide_by 2 \
[get_pins pll_inst/CLKOUT0]
# 计算:100MHz × 3 ÷ 2 = 150MHz
示例:100MHz -> 133.33MHz(×4/3)
create_generated_clock -name clk_133m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 4 \
-divide_by 3 \
[get_pins pll_inst/CLKOUT1]
# 计算:100MHz × 4 ÷ 3 = 133.33MHz
5.3.2 使用 edges 实现组合倍频分频 # 使用边沿选择实现×3/2
create_generated_clock -name clk_150m \
-source [get_ports clk_in] \
-edges {1 2 4} \
[get_pins pll_out]
# 波形分析:
# clk_100m: _|‾|_|‾|_|‾|_|‾|_
# 1 2 3 4 5 6 7 8 9
# clk_150m: _|‾|_|‾|_|‾|_|‾|_
# 1 2 4 6 8
# 周期 = (4-1)/2 * 10ns = 6.67ns = 150MHz
5.4 级联时钟约束
5.4.1 多级分频 示例:100MHz -> 50MHz -> 25MHz
# 第一级:100MHz 输入
create_clock -period 10.000 -name clk_100m [get_ports clk_in]
# 第二级:2 分频得到 50MHz
create_generated_clock -name clk_50m \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins div2_inst/clk_out]
# 第三级:再 2 分频得到 25MHz
create_generated_clock -name clk_25m \
-source [get_pins div2_inst/clk_out] \
-divide_by 2 \
[get_pins div4_inst/clk_out]
# 注意:第三级的-source 指向第二级的输出
5.4.2 PLL 级联 # 输入时钟:100MHz
create_clock -period 10.000 -name clk_in [get_ports sys_clk]
# PLL1 输出:200MHz
create_generated_clock -name clk_200m \
-source [get_pins pll1_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll1_inst/CLKOUT0]
# PLL2 以 PLL1 输出为输入:400MHz
create_generated_clock -name clk_400m \
-source [get_pins pll2_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll2_inst/CLKOUT0]
5.5 占空比控制
5.5.1 使用-duty_cycle 参数 # 25% 占空比
create_generated_clock -name clk_25duty \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
-duty_cycle 25 \
[get_pins pll_inst/CLKOUT0]
# 75% 占空比
create_generated_clock -name clk_75duty \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
-duty_cycle 75 \
[get_pins pll_inst/CLKOUT1]
5.5.2 使用 edges 和 edge_shift 控制占空比 # 100MHz 时钟,周期 10ns
# 20% 占空比:高电平 2ns,低电平 8ns
create_generated_clock -name clk_20duty \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {0 2.0 0} \
[get_pins duty_reg/Q]
5.6 高级应用场景
5.6.1 DDR 时钟约束 # 数据时钟:200MHz
create_clock -period 5.000 -name clk_ddr [get_ports clk_in]
# 数据采样时钟 (90 度相移)
create_generated_clock -name clk_ddr_90 \
-source [get_ports clk_in] \
-edges {1 2 3} \
-edge_shift {1.25 1.25 1.25} \
[get_pins ddr_clk_90_reg/Q]
# DQS 选通信号 (与数据对齐)
create_generated_clock -name dqs_clk \
-source [get_ports clk_in] \
[get_ports ddr_dqs]
5.6.2 SERDES 时钟约束 # 并行时钟:100MHz
create_clock -period 10.000 -name clk_parallel [get_ports clk_in]
# 串行时钟:700MHz(7 倍频)
create_generated_clock -name clk_serial \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 7 \
[get_pins pll_inst/CLKOUT0]
# 位时钟 (串行时钟的 1/7)
create_generated_clock -name clk_bit \
-source [get_pins pll_inst/CLKOUT0] \
-divide_by 7 \
[get_pins serdes_inst/clk_bit]
5.6.3 视频时钟约束 # 像素时钟:148.5MHz(1080p@60Hz)
create_clock -period 6.734 -name clk_pixel [get_ports pixel_clk]
# 行同步时钟 (像素时钟/1920)
create_generated_clock -name clk_hsync \
-source [get_ports pixel_clk] \
-divide_by 1920 \
[get_pins hsync_gen/clk_out]
六、虚拟时钟与时钟组
6.1 虚拟时钟 (Virtual Clock)
6.1.1 什么是虚拟时钟? 虚拟时钟 (Virtual Clock) 是一种不直接驱动 FPGA 内部逻辑的时钟,主要用于定义 I/O 接口的时序约束。
特征 说明 物理存在 不存在于 FPGA 内部,仅用于约束 主要用途 定义输入/输出延迟的参考时钟 创建方式 使用 create_clock 但不指定目标对象 典型场景 板级接口时序约束
6.1.2 为什么需要虚拟时钟? 📊 虚拟时钟使用场景 │
├─ 1 ️⃣ 源同步接口
│ └─ 数据和时钟来自同一源,但时钟不进入 FPGA
├─ 2 ️⃣ 系统同步接口
│ └─ 外部器件使用不同频率时钟,但与 FPGA 同步
├─ 3 ️⃣ 中心对齐接口
│ └─ 数据在时钟中心采样
└─ 4 ️⃣ 复杂 I /O 时序
└─ 需要独立定义输入输出时序
6.1.3 虚拟时钟创建 # 创建虚拟时钟 (不指定目标对象)
create_clock -period <period> -name <virtual_clock_name>
# 外部器件使用 100MHz 时钟
create_clock -period 10.000 -name virt_clk_100m
# FPGA 内部时钟
create_clock -period 10.000 -name clk_fpga [get_ports sys_clk]
# 使用虚拟时钟定义输入延迟
set_input_delay -clock virt_clk_100m -max 2.0 [get_ports data_in[*]]
set_input_delay -clock virt_clk_100m -min 0.5 [get_ports data_in[*]]
# 外部 ADC 使用 80MHz 时钟
create_clock -period 12.500 -name virt_clk_adc
# FPGA 内部使用 100MHz 时钟
create_clock -period 10.000 -name clk_fpga [get_ports sys_clk]
# ADC 数据输入延迟
set_input_delay -clock virt_clk_adc -max 3.0 [get_ports adc_data[*]]
set_input_delay -clock virt_clk_adc -min 1.0 [get_ports adc_data[*]]
6.2 虚拟时钟应用实例
6.2.1 DDR 接口约束 # 虚拟时钟:DDR3 时钟 (800MHz DDR = 400MHz 时钟)
create_clock -period 2.500 -name virt_ddr_clk
# FPGA 内部时钟
create_clock -period 2.500 -name clk_ddr [get_ports ddr_clk_p]
# DQ 数据线约束
set_input_delay -clock virt_ddr_clk -max 0.6 [get_ports ddr_dq[*]]
set_input_delay -clock virt_ddr_clk -min -0.3 [get_ports ddr_dq[*]]
set_output_delay -clock virt_ddr_clk -max 0.4 [get_ports ddr_dq[*]]
set_output_delay -clock virt_ddr_clk -min -0.2 [get_ports ddr_dq[*]]
# DQS 选通信号 (90 度相移)
create_clock -period 2.500 -name virt_dqs_clk \
-waveform {0.625 1.875}
set_input_delay -clock virt_dqs_clk -max 0.3 [get_ports ddr_dqs_p[*]]
set_input_delay -clock virt_dqs_clk -min -0.1 [get_ports ddr_dqs_p[*]]
6.2.2 高速串行接口 # 虚拟时钟:外部发送端时钟 (500MHz)
create_clock -period 2.000 -name virt_lvds_tx_clk
# FPGA 接收时钟
create_clock -period 2.000 -name clk_lvds_rx [get_ports lvds_clk_p]
# LVDS 数据输入约束
set_input_delay -clock virt_lvds_tx_clk -max 0.5 \
[get_ports lvds_data_p[*]]
set_input_delay -clock virt_lvds_tx_clk -min -0.2 \
[get_ports lvds_data_p[*]]
# 声明时钟关系 (同步)
set_clock_groups -physically_exclusive \
-group [get_clocks virt_lvds_tx_clk] \
-group [get_clocks clk_lvds_rx]
6.3 时钟组 (Clock Groups)
6.3.1 时钟组概念 关系类型 命令 说明 异步时钟 -asynchronous时钟之间完全独立,无相位关系 物理互斥 -physically_exclusive同一时刻只有一个时钟有效 逻辑互斥 -logically_exclusive逻辑上互斥,但可能同时存在
6.3.2 异步时钟组 set_clock_groups -asynchronous \
-group [get_clocks <clock_list1>] \
-group [get_clocks <clock_list2>] \
...
# 系统时钟:100MHz
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
# 以太网时钟:125MHz
create_clock -period 8.000 -name clk_eth [get_ports eth_clk]
# USB 时钟:60MHz
create_clock -period 16.667 -name clk_usb [get_ports usb_clk]
# 声明异步关系
set_clock_groups -asynchronous \
-group [get_clocks clk_sys] \
-group [get_clocks clk_eth] \
-group [get_clocks clk_usb]
# 效果:工具不会分析这些时钟域之间的时序路径
# 输入时钟
create_clock -period 10.000 -name clk_in [get_ports clk_in]
# PLL 输出多个时钟
create_generated_clock -name clk_100m \
-source [get_pins pll_inst/CLKIN1] \
[get_pins pll_inst/CLKOUT0]
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT1]
create_generated_clock -name clk_50m \
-source [get_pins pll_inst/CLKIN1] \
-divide_by 2 \
[get_pins pll_inst/CLKOUT2]
# 如果这些时钟域之间没有数据交互,声明为异步
set_clock_groups -asynchronous \
-group [get_clocks clk_100m] \
-group [get_clocks clk_200m] \
-group [get_clocks clk_50m]
6.3.3 物理互斥时钟组 # 输入时钟
create_clock -period 10.000 -name clk_in0 [get_ports clk_in[0]]
create_clock -period 8.000 -name clk_in1 [get_ports clk_in[1]]
# 时钟选择器输出
create_generated_clock -name clk_out0 \
-source [get_ports clk_in[0]] \
-master_clock clk_in0 \
[get_pins mux_inst/clk_out]
create_generated_clock -name clk_out1 \
-source [get_ports clk_in[1]] \
-master_clock clk_in1 \
-add \
[get_pins mux_inst/clk_out]
# 声明物理互斥 (同一时刻只有一个有效)
set_clock_groups -physically_exclusive \
-group [get_clocks clk_out0] \
-group [get_clocks clk_out1]
# 正常模式时钟:100MHz
create_clock -period 10.000 -name clk_normal [get_ports clk_in]
# 低功耗模式时钟:10MHz
create_clock -period 100.000 -name clk_lowpower [get_ports clk_in] -add
# 声明互斥 (两种模式不会同时存在)
set_clock_groups -physically_exclusive \
-group [get_clocks clk_normal] \
-group [get_clocks clk_lowpower]
6.3.4 逻辑互斥时钟组 # 时钟 A 和时钟 B 在逻辑上互斥,但可能物理上同时存在
set_clock_groups -logically_exclusive \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
6.4 时钟组高级应用
6.4.1 复杂多时钟系统 # ========== 输入时钟 ==========
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
create_clock -period 8.000 -name clk_eth_rx [get_ports eth_rx_clk]
create_clock -period 8.000 -name clk_eth_tx [get_ports eth_tx_clk]
create_clock -period 20.000 -name clk_uart [get_ports uart_clk]
# ========== PLL 输出时钟 ==========
create_generated_clock -name clk_core \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
create_generated_clock -name clk_ddr \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 4 \
[get_pins pll_inst/CLKOUT1]
# ========== 时钟组划分 ==========
# 组 1:系统核心时钟 (同步)
set_clock_groups -name sys_clocks -asynchronous \
-group [get_clocks {clk_sys clk_core}]
# 组 2:以太网时钟 (RX 和 TX 异步)
set_clock_groups -name eth_clocks -asynchronous \
-group [get_clocks clk_eth_rx] \
-group [get_clocks clk_eth_tx]
# 组 3:DDR 时钟 (独立域)
set_clock_groups -name ddr_clocks -asynchronous \
-group [get_clocks clk_ddr]
# 组 4:低速外设时钟
set_clock_groups -name periph_clocks -asynchronous \
-group [get_clocks clk_uart]
# 声明所有组之间异步
set_clock_groups -asynchronous \
-group [get_clocks {clk_sys clk_core}] \
-group [get_clocks {clk_eth_rx clk_eth_tx}] \
-group [get_clocks clk_ddr] \
-group [get_clocks clk_uart]
6.4.2 时钟组与 CDC 约束 # 定义异步时钟组
set_clock_groups -asynchronous \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
# 对于 CDC 路径,添加 false path
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_false_path -from [get_clocks clk_b] -to [get_clocks clk_a]
# 或者使用 max_delay 约束 CDC 路径
set_max_delay 5.0 -from [get_clocks clk_a] -to [get_clocks clk_b]
set_max_delay 5.0 -from [get_clocks clk_b] -to [get_clocks clk_a]
6.5 时钟约束检查
6.5.1 检查时钟定义 # 查看所有时钟
report_clocks
# 查看时钟树
report_clock_networks
# 检查未约束的时钟
check_timing -verbose
6.5.2 检查时钟组 # 查看时钟组定义
report_clock_groups
# 查看时钟交互关系
report_clock_interaction
# 检查时钟域交叉
report_cdc
6.5.3 常见问题排查 # 检查命令
report_clocks -name virt_clk_100m
# 如果显示"No paths",说明虚拟时钟未被引用
# 解决:检查 set_input_delay/set_output_delay 是否正确使用
# 错误示例:同一时钟在多个互斥组中
set_clock_groups -physically_exclusive \
-group [get_clocks {clk_a clk_b}] \
-group [get_clocks {clk_b clk_c}]
# ❌ clk_b 重复
# 正确示例
set_clock_groups -physically_exclusive \
-group [get_clocks clk_a] \
-group [get_clocks clk_b] \
-group [get_clocks clk_c]
# ❌ 错误:PLL 输出的同步时钟被声明为异步
create_generated_clock -name clk_100m ...
create_generated_clock -name clk_200m ...
set_clock_groups -asynchronous \
-group [get_clocks clk_100m] \
-group [get_clocks clk_200m]
# ✅ 正确:PLL 输出时钟是同步的,不应声明为异步
# 只有在确实没有数据交互时才声明异步
七、完整实战案例
7.1 案例 1:高速数据采集系统
7.1.1 系统架构 📊 高速数据采集系统 │
├─ ADC 接口 (200 MHz LVDS)
├─ DDR3 内存 (800 MHz)
├─ 以太网接口 (125 MHz RGMII)
├─ 系统控制 (100 MHz)
└─ UART 调试 (50 MHz)
7.1.2 完整约束文件 # ========================================
# 高速数据采集系统时钟约束
# ========================================
# ------------------
# 1. 输入时钟
# ------------------
# 系统主时钟:100MHz 差分输入
create_clock -period 10.000 -name clk_sys_p [get_ports sys_clk_p]
set_property IOSTANDARD LVDS [get_ports sys_clk_p]
set_property IOSTANDARD LVDS [get_ports sys_clk_n]
# ADC 数据时钟:200MHz LVDS
create_clock -period 5.000 -name clk_adc [get_ports adc_clk_p]
set_property IOSTANDARD LVDS [get_ports adc_clk_p]
set_property IOSTANDARD LVDS [get_ports adc_clk_n]
# 以太网接收时钟:125MHz
create_clock -period 8.000 -name clk_eth_rx [get_ports eth_rxc]
# ------------------
# 2. PLL 输出时钟
# ------------------
# PLL 实例:sys_pll
# 输入:100MHz -> VCO:1000MHz
# 核心时钟:200MHz
create_generated_clock -name clk_core \
-source [get_pins sys_pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins sys_pll_inst/CLKOUT0]
# DDR 时钟:400MHz
create_generated_clock -name clk_ddr \
-source [get_pins sys_pll_inst/CLKIN1] \
-multiply_by 4 \
[get_pins sys_pll_inst/CLKOUT1]
# DDR 参考时钟:200MHz
create_generated_clock -name clk_ddr_ref \
-source [get_pins sys_pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins sys_pll_inst/CLKOUT2]
# 以太网发送时钟:125MHz
create_generated_clock -name clk_eth_tx \
-source [get_pins sys_pll_inst/CLKIN1] \
-multiply_by 5 \
-divide_by 4 \
[get_pins sys_pll_inst/CLKOUT3]
# UART 时钟:50MHz
create_generated_clock -name clk_uart \
-source [get_pins sys_pll_inst/CLKIN1] \
-divide_by 2 \
[get_pins sys_pll_inst/CLKOUT4]
# ------------------
# 3. 虚拟时钟
# ------------------
# ADC 输出虚拟时钟 (用于输入延迟约束)
create_clock -period 5.000 -name virt_adc_out
# 以太网 PHY 虚拟时钟
create_clock -period 8.000 -name virt_eth_phy
# ------------------
# 4. 时钟组
# ------------------
# 异步时钟组
set_clock_groups -asynchronous \
-group [get_clocks {clk_sys_p clk_core}] \
-group [get_clocks {clk_adc virt_adc_out}] \
-group [get_clocks {clk_ddr clk_ddr_ref}] \
-group [get_clocks {clk_eth_rx clk_eth_tx virt_eth_phy}] \
-group [get_clocks clk_uart]
# ------------------
# 5. I/O 延迟约束
# ------------------
# ADC 数据输入 (LVDS,200MHz)
set_input_delay -clock virt_adc_out -max 1.5 [get_ports adc_data_p[*]]
set_input_delay -clock virt_adc_out -min 0.5 [get_ports adc_data_p[*]]
# 以太网 RGMII 接口
# RX 数据 (125MHz,DDR)
set_input_delay -clock clk_eth_rx -max 1.2 \
-clock_fall [get_ports eth_rxd[*]]
set_input_delay -clock clk_eth_rx -min -0.8 \
-clock_fall [get_ports eth_rxd[*]]
# TX 数据 (125MHz,DDR)
set_output_delay -clock clk_eth_tx -max 1.0 \
-clock_fall [get_ports eth_txd[*]]
set_output_delay -clock clk_eth_tx -min -0.5 \
-clock_fall [get_ports eth_txd[*]]
# ------------------
# 6. 时钟不确定性
# ------------------
# 系统时钟不确定性 (考虑晶振抖动)
set_clock_uncertainty 0.2 [get_clocks clk_sys_p]
# ADC 时钟不确定性
set_clock_uncertainty 0.15 [get_clocks clk_adc]
# ------------------
# 7. 时序例外
# ------------------
# 复位路径 false path
set_false_path -from [get_ports sys_rst_n]
# 异步 CDC 路径
set_max_delay 5.0 -from [get_clocks clk_adc] -to [get_clocks clk_core]
set_max_delay 5.0 -from [get_clocks clk_core] -to [get_clocks clk_eth_tx]
7.2 案例 2:视频处理系统
7.2.1 系统架构 📺 1080 p 视频处理系统 │
├─ HDMI 输入 (148.5 MHz 像素时钟)
├─ 视频处理 (200 MHz)
├─ DDR3 帧缓存 (800 MHz)
├─ HDMI 输出 (148.5 MHz 像素时钟)
└─ 配置接口 (100 MHz)
7.2.2 完整约束文件 # ========================================
# 1080p 视频处理系统时钟约束
# ========================================
# ------------------
# 1. 输入时钟
# ------------------
# 配置时钟:100MHz
create_clock -period 10.000 -name clk_cfg [get_ports cfg_clk]
# HDMI 输入像素时钟:148.5MHz(1080p@60Hz)
create_clock -period 6.734 -name clk_hdmi_rx [get_ports hdmi_rx_clk]
# ------------------
# 2. PLL 输出时钟
# ------------------
# 视频处理 PLL
# 输入:100MHz -> VCO:1200MHz
# 视频处理时钟:200MHz
create_generated_clock -name clk_video \
-source [get_pins video_pll_inst/CLKIN1] \
-multiply_by 2 \
[get_pins video_pll_inst/CLKOUT0]
# DDR 时钟:400MHz
create_generated_clock -name clk_ddr_video \
-source [get_pins video_pll_inst/CLKIN1] \
-multiply_by 4 \
[get_pins video_pll_inst/CLKOUT1]
# HDMI 输出 PLL
# 输入:100MHz -> 输出:148.5MHz
create_generated_clock -name clk_hdmi_tx \
-source [get_pins hdmi_pll_inst/CLKIN1] \
-multiply_by 297 \
-divide_by 200 \
[get_pins hdmi_pll_inst/CLKOUT0]
# HDMI 串行时钟:742.5MHz(5 倍像素时钟)
create_generated_clock -name clk_hdmi_serial \
-source [get_pins hdmi_pll_inst/CLKIN1] \
-multiply_by 1485 \
-divide_by 200 \
[get_pins hdmi_pll_inst/CLKOUT1]
# ------------------
# 3. 衍生时钟
# ------------------
# HDMI 输入恢复时钟 (从 TMDS 解串器)
create_generated_clock -name clk_hdmi_rx_recovered \
-source [get_pins hdmi_rx_inst/clk_in] \
[get_pins hdmi_rx_inst/clk_out]
# ------------------
# 4. 时钟组
# ------------------
# 异步时钟组
set_clock_groups -asynchronous \
-group [get_clocks clk_cfg] \
-group [get_clocks {clk_hdmi_rx clk_hdmi_rx_recovered}] \
-group [get_clocks {clk_video clk_ddr_video}] \
-group [get_clocks {clk_hdmi_tx clk_hdmi_serial}]
# ------------------
# 5. I/O 约束
# ------------------
# HDMI 输入数据
set_input_delay -clock clk_hdmi_rx -max 1.5 [get_ports hdmi_rx_data[*]]
set_input_delay -clock clk_hdmi_rx -min 0.5 [get_ports hdmi_rx_data[*]]
# HDMI 输出数据
set_output_delay -clock clk_hdmi_tx -max 1.0 [get_ports hdmi_tx_data[*]]
set_output_delay -clock clk_hdmi_tx -min -0.5 [get_ports hdmi_tx_data[*]]
# ------------------
# 6. 多周期路径
# ------------------
# 视频处理到 DDR 写入 (允许 2 个周期)
set_multicycle_path 2 -setup \
-from [get_clocks clk_video] \
-to [get_clocks clk_ddr_video]
set_multicycle_path 1 -hold \
-from [get_clocks clk_video] \
-to [get_clocks clk_ddr_video]
7.3 案例 3:通信基站时钟系统
7.3.1 系统架构 📡 通信基站 FPGA 时钟系统 │
├─ 基准时钟:10MHz OCXO
├─ 射频采样:245.76MHz
├─ 基带处理:122.88MHz
├─ 光纤接口:156.25MHz
└─ 控制总线:100MHz
7.3.2 完整约束文件 # ========================================
# 通信基站时钟约束
# ========================================
# ------------------
# 1. 基准时钟
# ------------------
# 高精度基准时钟:10MHz OCXO
create_clock -period 100.000 -name clk_ref_10m [get_ports ref_clk]
# ------------------
# 2. 射频 PLL
# ------------------
# RF PLL:10MHz -> 2457.6MHz VCO
# 射频采样时钟:245.76MHz
create_generated_clock -name clk_rf_sample \
-source [get_pins rf_pll_inst/CLKIN1] \
-multiply_by 24576 \
-divide_by 1000 \
[get_pins rf_pll_inst/CLKOUT0]
# 基带处理时钟:122.88MHz
create_generated_clock -name clk_baseband \
-source [get_pins rf_pll_inst/CLKIN1] \
-multiply_by 12288 \
-divide_by 1000 \
[get_pins rf_pll_inst/CLKOUT1]
# ------------------
# 3. 通信接口 PLL
# ------------------
# 光纤接口时钟:156.25MHz(10G Ethernet)
create_generated_clock -name clk_fiber \
-source [get_pins comm_pll_inst/CLKIN1] \
-multiply_by 15625 \
-divide_by 1000 \
[get_pins comm_pll_inst/CLKOUT0]
# 控制总线时钟:100MHz
create_generated_clock -name clk_ctrl \
-source [get_pins comm_pll_inst/CLKIN1] \
-multiply_by 10 \
[get_pins comm_pll_inst/CLKOUT1]
# ------------------
# 4. 时钟域划分
# ------------------
# 射频域 (同步)
set_clock_groups -name rf_domain -asynchronous \
-group [get_clocks {clk_rf_sample clk_baseband}]
# 通信域 (同步)
set_clock_groups -name comm_domain -asynchronous \
-group [get_clocks {clk_fiber clk_ctrl}]
# 域间异步
set_clock_groups -asynchronous \
-group [get_clocks {clk_rf_sample clk_baseband}] \
-group [get_clocks {clk_fiber clk_ctrl}]
# ------------------
# 5. 时钟质量约束
# ------------------
# OCXO 高精度,极低抖动
set_clock_uncertainty 0.01 [get_clocks clk_ref_10m]
# 射频时钟严格要求
set_clock_uncertainty 0.05 [get_clocks clk_rf_sample]
set_clock_uncertainty 0.05 [get_clocks clk_baseband]
# ------------------
# 6. CDC 约束
# ------------------
# 射频域到通信域
set_max_delay 4.0 \
-from [get_clocks clk_baseband] \
-to [get_clocks clk_ctrl] \
-datapath_only
# 通信域到射频域
set_max_delay 4.0 \
-from [get_clocks clk_ctrl] \
-to [get_clocks clk_baseband] \
-datapath_only
7.4 时钟约束最佳实践
7.4.1 约束文件组织
7.4.2 命名规范 # ✅ 好的命名
clk_sys_100m # 系统时钟 100MHz
clk_pll_200m # PLL 输出 200MHz
clk_eth_rx_125m # 以太网接收 125MHz
clk_ddr_ref # DDR 参考时钟
virt_adc_clk # 虚拟时钟 (ADC)
# ❌ 不好的命名
clk1, clk2, clk3 # 无意义
clock_a, clock_b # 不明确
my_clk # 太泛化
7.4.3 检查清单 # 1. 检查所有时钟是否定义
report_clocks
# 2. 检查未约束的路径
check_timing -verbose
# 3. 检查时钟交互
report_clock_interaction
# 4. 检查 CDC 路径
report_cdc
# 5. 时序分析
report_timing_summary
# 6. 检查时钟网络
report_clock_networks
总结 通过本文的学习,我们系统地掌握了 FPGA 时钟约束的完整知识体系。让我们回顾一下核心要点:
核心知识回顾
1️⃣ 时钟约束基础
主时钟 (Primary Clock) : 使用 create_clock 定义外部输入时钟
衍生时钟 (Generated Clock) : 使用 create_generated_clock 定义内部派生时钟
虚拟时钟 (Virtual Clock) : 用于 I/O 接口时序约束的参考时钟
✅ 指导综合优化:帮助综合器选择合适的实现方式
✅ 指导布局布线:优化逻辑放置和布线资源分配
✅ 静态时序分析:验证设计是否满足时序要求
2️⃣ create_clock 命令精要 create_clock -period <周期> -name <时钟名> [get_ports <端口>]
-period: 时钟周期 (ns),必需参数
-name: 时钟名称,强烈建议指定
-waveform: 自定义波形,控制占空比和相位
get_ports: 指定时钟输入端口
标准时钟约束
差分时钟约束 (只约束 P 端)
自定义占空比和相位
多时钟系统
3️⃣ create_generated_clock 命令精要 create_generated_clock -name <名称> \
-source <源引脚> \
[-multiply_by <倍频>] [-divide_by <分频>] \
<目标引脚>
-source: 源时钟引脚位置,必需参数
-multiply_by: 倍频系数
-divide_by: 分频系数
-edges: 边沿选择,实现复杂分频
-edge_shift: 边沿偏移,实现相位调整
PLL/MMCM 输出时钟
时钟分频器 (偶数/奇数分频)
时钟选择器输出
级联时钟系统
4️⃣ 高级技巧
实现非整数分频 (1.5 分频、2.5 分频等)
精确控制时钟波形
实现相位调整 (90 度、180 度、270 度)
自定义占空比
-asynchronous: 异步时钟组
-physically_exclusive: 物理互斥时钟
-logically_exclusive: 逻辑互斥时钟
5️⃣ 最佳实践 1. 输入时钟定义
2. 衍生时钟定义
3. 虚拟时钟定义
4. 时钟组定义
5. I/O 延迟约束
6. 时钟不确定性
7. 时序例外
使用有意义的名称 (如 clk_sys_100m)
包含频率信息
体现时钟来源和用途
report_clocks # 检查时钟定义
check_timing -verbose # 检查未约束路径
report_clock_interaction # 检查时钟关系
report_timing_summary # 时序分析
关键要点总结
所有外部时钟都要约束
PLL/MMCM 输出时钟要约束
差分时钟只约束 P 端
异步时钟域要声明时钟组
时钟名称要清晰明确
约束完成后要验证
不要遗漏任何时钟
不要在 N 端创建差分时钟
不要过度使用异步声明
不要使用无意义的时钟名
不要忽略时钟不确定性
不要跳过验证步骤
学习路径建议
从简单的单时钟系统开始
掌握 create_clock 基本用法
理解时序分析基本概念
学会使用 report_clocks 检查
学习 PLL/MMCM 时钟约束
掌握 create_generated_clock
理解时钟组的应用
处理多时钟域系统
掌握边沿选择和偏移
学习虚拟时钟应用
处理复杂 CDC 问题
优化时序收敛
常见问题速查
检查约束是否正确 (周期、源引脚等)
查看时序报告找出关键路径
考虑优化 RTL 代码或插入流水线
检查是否有未约束的时钟
如果时钟来自不同源且无相位关系→异步
如果是 PLL 输出的同步时钟→不是异步
如果有数据交互且需要时序分析→不要声明异步
外部器件时钟不进入 FPGA 时
需要独立定义 I/O 时序时
源同步接口约束时
Q4: 分频时钟一定要用 create_generated_clock 吗?
是的,所有内部产生的时钟都应该约束
即使是简单的 2 分频也要约束
这样工具才能正确分析时序
参考资料
官方文档
Xilinx 官方文档
Intel/Altera 官方文档
技术论坛 相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online