FPGA 时钟约束完全攻略
概述
时钟约束是 FPGA 设计中最核心、最重要的约束类型,可以说是整个时序约束体系的基石。正确的时钟约束是时序收敛的前提,所有其他约束 (I/O 延迟、时序例外等) 都建立在时钟约束之上。
为什么时钟约束如此重要?
在 FPGA 设计中,时钟信号驱动着整个数字系统的运行。如果没有正确的时钟约束,会导致:
- 综合工具无法正确优化设计
FPGA 时钟约束核心概念,涵盖主时钟、衍生时钟及虚拟时钟的定义与约束方法。深入解析 create_clock 和 create_generated_clock 命令参数,包括频率换算、占空比调整、相位偏移及边沿选择技巧。提供 PLL/MMCM、分频器、时钟选择器等场景的实战案例,并介绍时钟组(Clock Groups)配置与时序检查方法,帮助设计者实现时序收敛。
时钟约束是 FPGA 设计中最核心、最重要的约束类型,可以说是整个时序约束体系的基石。正确的时钟约束是时序收敛的前提,所有其他约束 (I/O 延迟、时序例外等) 都建立在时钟约束之上。
为什么时钟约束如此重要?
在 FPGA 设计中,时钟信号驱动着整个数字系统的运行。如果没有正确的时钟约束,会导致:
本文将帮助您:
时钟约束不仅仅是告诉工具时钟频率那么简单,它在 FPGA 设计流程中扮演着多重关键角色。
综合器需要知道时钟频率才能进行合理的优化:
没有时钟约束的情况:
// 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 内完成运算,会:
布局布线器根据时钟约束来放置逻辑单元和规划布线:
# 高速时钟约束
create_clock -period 2.000 -name clk_500m [get_ports clk_in]
布局布线器会:
静态时序分析 (STA) 是验证设计时序正确性的关键步骤:
时序检查的基本原理:
发起触发器 (Launch FF) → 组合逻辑 → 捕获触发器 (Capture FF)
↓ ↓
时钟边沿 T0 时钟边沿 T1
Setup 时间检查:
数据必须在时钟边沿前稳定 ≥ Setup 时间
Tclk ≥ Tco + Tlogic + Trouting + Tsetup
Hold 时间检查:
数据必须在时钟边沿后保持 ≥ Hold 时间
Tco + Tlogic + Trouting ≥ Thold
没有时钟约束,这些检查都无法进行!
在多时钟系统中,时钟约束定义了时钟之间的关系:
# 定义两个独立时钟
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]
这告诉工具:
FPGA 设计中的时钟可以分为三大类,每类都有对应的约束方法:
定义: 直接从 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]
定义: 由 FPGA 内部逻辑产生的时钟,来源于主时钟或其他衍生时钟
特点:
约束方法:
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]
定义: 不直接驱动 FPGA 内部逻辑的时钟,主要用于 I/O 时序约束
特点:
约束方法:
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[*]]
时钟约束是静态时序分析 (STA) 的基础,两者密不可分。
基于时钟的路径分类:
📊 时序路径类型 │
├─ 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[*]]
时钟不确定性 (Clock Uncertainty) 包括时钟抖动 (Jitter) 和时钟偏斜 (Skew):
时钟抖动 (Jitter):
# 晶振抖动:±50ps
set_clock_uncertainty 0.1 [get_clocks clk_sys]
时钟偏斜 (Skew):
时钟树延迟差异导致不同寄存器的时钟到达时间不同,工具会自动计算,但可以手动指定额外的裕量。
同步时钟域:
# 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 \
-period <period_value> \
[-name <clock_name>] \
[-waveform <edge_list>] \
[-add] \
[get_ports <port_name>]
1. -period(时钟周期)
# 单位:纳秒 (ns)
create_clock -period 10.000 -name clk_100m [get_ports clk]
# 100MHz = 1000MHz / 100 = 10ns 周期
频率与周期换算公式:
周期 (ns) = 1000 / 频率 (MHz)
频率 (MHz) = 1000 / 周期 (ns)
2. -name(时钟名称)
# ✅ 推荐:使用有意义的名称
create_clock -period 10.000 -name clk_sys_100m [get_ports sys_clk]
# ❌ 不推荐:使用默认名称或无意义名称
create_clock -period 10.000 [get_ports sys_clk]
# 默认使用端口名
命名建议:
3. -waveform(波形定义)
# 默认波形: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
波形格式: {上升沿时间 下降沿时间}
4. -add(添加模式)
# 同一端口定义多个时钟 (用于时钟切换场景)
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]
最常见的时钟约束方式:
# 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]
| 频率 | 周期 (ns) | 约束命令 |
|---|---|---|
| 10MHz | 100.000 | create_clock -period 100.000 |
| 25MHz | 40.000 | create_clock -period 40.000 |
| 50MHz | 20.000 | create_clock -period 20.000 |
| 100MHz | 10.000 | create_clock -period 10.000 |
| 125MHz | 8.000 | create_clock -period 8.000 |
| 150MHz | 6.667 | create_clock -period 6.667 |
| 200MHz | 5.000 | create_clock -period 5.000 |
| 250MHz | 4.000 | create_clock -period 4.000 |
| 300MHz | 3.333 | create_clock -period 3.333 |
| 400MHz | 2.500 | create_clock -period 2.500 |
| 500MHz | 2.000 | create_clock -period 2.000 |
25% 占空比:
create_clock -period 10.000 \
-waveform {0 2.5} \
-name clk_25duty \
[get_ports clk]
# 波形:
# _____|‾‾|_______|‾‾|_______
# 0 2.5 10 12.5 20
75% 占空比:
create_clock -period 10.000 \
-waveform {0 7.5} \
-name clk_75duty \
[get_ports clk]
# 波形:
# _____|‾‾‾‾‾‾|___|‾‾‾‾‾‾|___
# 0 7.5 10 17.5 20
90 度相移 (延迟 1/4 周期):
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
180 度相移 (反相):
create_clock -period 10.000 \
-waveform {5.0 10.0} \
-name clk_180deg \
[get_ports clk]
# 波形:
# ‾‾‾‾‾|_____|‾‾‾‾‾|_____
# 0 5 10 15 20
⚠️ 重要原则:只约束 P 端,不约束 N 端!
# ✅ 正确:只约束 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]
LVDS_25:
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]
DIFF_SSTL15:
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]
# 系统时钟: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]
应用场景:时钟模式切换
# 正常模式: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]
低速时钟 (≤50MHz):
| 频率 | 周期 (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 | 通用系统时钟 |
中速时钟 (50-200MHz):
| 频率 | 周期 (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 |
高速时钟 (>200MHz):
| 频率 | 周期 (ns) | 应用场景 |
|---|---|---|
| 250MHz | 4.000 | DDR3-2000 |
| 300MHz | 3.333 | DDR3-2400 |
| 400MHz | 2.500 | DDR4-3200 |
| 500MHz | 2.000 | 高速 SerDes |
公式:
周期 (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]
占空比定义:
占空比 = 高电平时间 / 周期 × 100%
常见占空比设置:
20% 占空比:
# 周期 10ns,高电平 2ns
create_clock -period 10.000 -waveform {0 2.0} -name clk [get_ports clk]
30% 占空比:
# 周期 10ns,高电平 3ns
create_clock -period 10.000 -waveform {0 3.0} -name clk [get_ports clk]
40% 占空比:
# 周期 10ns,高电平 4ns
create_clock -period 10.000 -waveform {0 4.0} -name clk [get_ports clk]
60% 占空比:
# 周期 10ns,高电平 6ns
create_clock -period 10.000 -waveform {0 6.0} -name clk [get_ports clk]
相位与时间的关系:
时间偏移 = 周期 × (相位角度 / 360°)
常见相位偏移:
45 度相移:
# 周期 10ns,45 度 = 10 × (45/360) = 1.25ns
create_clock -period 10.000 -waveform {1.25 6.25} -name clk [get_ports clk]
90 度相移:
# 周期 10ns,90 度 = 10 × (90/360) = 2.5ns
create_clock -period 10.000 -waveform {2.5 7.5} -name clk [get_ports clk]
180 度相移:
# 周期 10ns,180 度 = 10 × (180/360) = 5ns
create_clock -period 10.000 -waveform {5.0 10.0} -name clk [get_ports clk]
270 度相移:
# 周期 10ns,270 度 = 10 × (270/360) = 7.5ns
create_clock -period 10.000 -waveform {7.5 12.5} -name clk [get_ports clk]
错误 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]
错误 2:差分时钟两端都约束
# ❌ 错误: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]
错误 3:波形定义错误
# ❌ 错误:下降沿时间小于上升沿时间
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]
错误 4:忘记约束时钟
# ❌ 错误:只约束了部分时钟
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]
错误 5:时钟名称冲突
# ❌ 错误:使用相同的时钟名称
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]
检查命令:
# 查看所有时钟
report_clocks
# 检查时钟网络
report_clock_networks
# 检查未约束的时钟
check_timing -verbose
report_clocks 输出示例:
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
警告 1:no_clock
[Timing 38-313] There are no user specified timing constraints.
解决: 添加时钟约束
警告 2:unconstrained_internal_endpoints
[Timing 38-316] Clock 'clk_sys' has unconstrained internal endpoints.
解决: 检查是否有未约束的衍生时钟
警告 3:multiple_clock
[Timing 38-304] Multiple clocks are defined on port 'clk_in'.
解决: 使用 -add 参数并声明时钟组关系
步骤 1: 运行实现
opt_design place_design route_design
步骤 2: 时序分析
# 生成时序报告
report_timing_summary -file timing_summary.rpt
# 查看最差路径
report_timing -max_paths 10 -nworst 1 -file worst_timing.rpt
# 检查时钟偏斜
report_clock_utilization
✅ 必检项目:
时钟名称清晰且无冲突
# 查看所有时钟名称
get_clocks
异步时钟已声明
# 检查时钟组
report_clock_groups
差分时钟只约束 P 端
# 检查是否有 N 端的时钟约束
report_clocks | grep "_n"
时钟周期正确
# 验证公式:周期 (ns) = 1000 / 频率 (MHz)
所有外部时钟都已约束
# 检查命令
report_clocks
check_timing -verbose
衍生时钟 (Generated Clock) 是指由主时钟经过 FPGA 内部逻辑处理后产生的时钟信号。
📌 关键特征:
| 特征 | 说明 |
|---|---|
| 来源 | 由主时钟 (Primary Clock) 派生而来 |
| 产生方式 | 通过 PLL、MMCM、分频器、选择器等逻辑产生 |
| 频率关系 | 与源时钟存在确定的频率关系 (倍频/分频) |
| 相位关系 | 可能存在相位偏移 |
| 约束方式 | 使用 create_generated_clock 命令 |
❌ 不约束衍生时钟的后果:
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. 优化时钟树结构
场景分类:
📊 衍生时钟产生方式 │
├─ 1️⃣ PLL/MMCM 输出
│ ├─ 倍频时钟 (如 100MHz -> 200MHz)
│ ├─ 分频时钟 (如 100MHz -> 50MHz)
│ └─ 相移时钟 (如 90 度相移)
├─ 2️⃣ 时钟分频器
│ ├─ 计数器分频 (偶数/奇数分频)
│ └─ 触发器分频
├─ 3️⃣ 时钟选择器
│ ├─ BUFGMUX 输出
│ └─ 多路选择器输出
└─ 4️⃣ 组合逻辑
├─ 门控时钟 (Clock Gating)
└─ 时钟反相
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> # 目标引脚 (必需)
1. -name(时钟名称)
# 命名规范建议
create_generated_clock -name clk_pll_200m ...
# ✅ 清晰表明来源和频率
create_generated_clock -name clk_div2 ...
# ✅ 表明分频关系
create_generated_clock -name clk1 ...
# ❌ 名称不明确
2. -source(源时钟引脚)
# 必须指向源时钟所在的引脚
# 对于 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]
3. -divide_by(分频系数)
# 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
4. -multiply_by(倍频系数)
# 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
5. -master_clock(主时钟指定)
# 当源引脚有多个时钟时,需要明确指定主时钟
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]
典型 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]
# 输入时钟
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]
📌 MMCM 约束要点:
1. 反馈时钟通常不需要约束 (工具自动处理)
2. multiply_by 和 divide_by 要与 IP 配置一致
3. 相移时钟可以不指定 multiply/divide(自动继承)
4. 输出使能信号不影响时钟约束
2 分频电路:
// 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]
4 分频电路:
// 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]
3 分频电路 (占空比非 50%):
// 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]
# 工具会自动计算占空比
5 分频电路:
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]
RTL 代码:
// 使用 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]
RTL 代码:
// 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]
⚠️ 警告:门控时钟不推荐使用!
// 不推荐的门控时钟写法
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
RTL 代码:
// 时钟反相
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]
错误 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]
错误 2:分频系数与实际不符
# 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]
错误 3:忘记约束 PLL 输出时钟
# ❌ 只约束了输入,忘记约束输出
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]
错误 4:多时钟选择器未声明互斥
# ❌ 只创建了衍生时钟,未声明互斥关系
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]
检查衍生时钟是否正确创建:
# 查看所有时钟
report_clocks
# 检查特定时钟
report_clocks -name clk_pll_out
# 查看时钟树
report_clock_networks
检查时钟关系:
# 查看时钟间的关系
report_clock_interaction
# 检查时钟组
report_clock_groups
验证时钟约束完整性:
# 检查未约束的时钟
check_timing -verbose
# 查看时钟覆盖率
report_clock_utilization
-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...)
方法 1:使用上升沿
# 源时钟: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 分频:选择第 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
# 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
1.5 分频示例:
# 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 分频示例:
# 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
-edge_shift 参数用于对选定的边沿进行时间偏移,实现相位调整。
语法格式:
create_generated_clock -name <name> \
-source <source_pin> \
-edges {edge1 edge2 edge3} \
-edge_shift {shift1 shift2 shift3} \
<target_pin>
📌 偏移值单位:纳秒 (ns)
90 度相移 (延迟 1/4 周期):
# 源时钟: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 度相移 (反相):
# 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 度相移:
# 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]
占空比调整:
# 调整占空比为 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
示例: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
示例:100MHz -> 150MHz
# 使用边沿选择实现×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
示例: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 指向第二级的输出
示例:PLL1 输出作为 PLL2 输入
# 输入时钟: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]
# 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]
20% 占空比示例:
# 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]
DDR 接口时钟 (90 度相移):
# 数据时钟: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]
7:1 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]
像素时钟和行同步时钟:
# 像素时钟: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]
虚拟时钟 (Virtual Clock) 是一种不直接驱动 FPGA 内部逻辑的时钟,主要用于定义 I/O 接口的时序约束。
📌 关键特征:
| 特征 | 说明 |
|---|---|
| 物理存在 | 不存在于 FPGA 内部,仅用于约束 |
| 主要用途 | 定义输入/输出延迟的参考时钟 |
| 创建方式 | 使用 create_clock 但不指定目标对象 |
| 典型场景 | 板级接口时序约束 |
应用场景:
📊 虚拟时钟使用场景 │
├─ 1️⃣ 源同步接口
│ └─ 数据和时钟来自同一源,但时钟不进入 FPGA
├─ 2️⃣ 系统同步接口
│ └─ 外部器件使用不同频率时钟,但与 FPGA 同步
├─ 3️⃣ 中心对齐接口
│ └─ 数据在时钟中心采样
└─ 4️⃣ 复杂 I/O 时序
└─ 需要独立定义输入输出时序
基本语法:
# 创建虚拟时钟 (不指定目标对象)
create_clock -period <period> -name <virtual_clock_name>
示例 1:源同步接口
# 外部器件使用 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[*]]
示例 2:不同频率的外部时钟
# 外部 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[*]]
场景:DDR3 内存接口
# 虚拟时钟: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[*]]
场景:LVDS 接口
# 虚拟时钟:外部发送端时钟 (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]
时钟组用于定义时钟之间的关系:
| 关系类型 | 命令 | 说明 |
|---|---|---|
| 异步时钟 | -asynchronous | 时钟之间完全独立,无相位关系 |
| 物理互斥 | -physically_exclusive | 同一时刻只有一个时钟有效 |
| 逻辑互斥 | -logically_exclusive | 逻辑上互斥,但可能同时存在 |
语法:
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]
# 效果:工具不会分析这些时钟域之间的时序路径
示例:PLL 输出时钟
# 输入时钟
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]
应用场景:时钟选择器
# 输入时钟
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]
应用场景:条件时钟路径
# 时钟 A 和时钟 B 在逻辑上互斥,但可能物理上同时存在
set_clock_groups -logically_exclusive \
-group [get_clocks clk_a] \
-group [get_clocks clk_b]
示例:完整的多时钟域系统
# ========== 输入时钟 ==========
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]
跨时钟域 (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]
# 查看所有时钟
report_clocks
# 查看时钟树
report_clock_networks
# 检查未约束的时钟
check_timing -verbose
# 查看时钟组定义
report_clock_groups
# 查看时钟交互关系
report_clock_interaction
# 检查时钟域交叉
report_cdc
问题 1:虚拟时钟未使用
# 检查命令
report_clocks -name virt_clk_100m
# 如果显示"No paths",说明虚拟时钟未被引用
# 解决:检查 set_input_delay/set_output_delay 是否正确使用
问题 2:时钟组冲突
# 错误示例:同一时钟在多个互斥组中
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]
问题 3:过度使用异步声明
# ❌ 错误: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 输出时钟是同步的,不应声明为异步
# 只有在确实没有数据交互时才声明异步
系统组成:
📊 高速数据采集系统 │
├─ ADC 接口 (200MHz LVDS)
├─ DDR3 内存 (800MHz)
├─ 以太网接口 (125MHz RGMII)
├─ 系统控制 (100MHz)
└─ UART 调试 (50MHz)
# ========================================
# 高速数据采集系统时钟约束
# ========================================
# ------------------
# 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]
系统组成:
📺 1080p 视频处理系统 │
├─ HDMI 输入 (148.5MHz 像素时钟)
├─ 视频处理 (200MHz)
├─ DDR3 帧缓存 (800MHz)
├─ HDMI 输出 (148.5MHz 像素时钟)
└─ 配置接口 (100MHz)
# ========================================
# 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]
系统组成:
📡 通信基站 FPGA 时钟系统 │
├─ 基准时钟:10MHz OCXO
├─ 射频采样:245.76MHz
├─ 基带处理:122.88MHz
├─ 光纤接口:156.25MHz
└─ 控制总线:100MHz
# ========================================
# 通信基站时钟约束
# ========================================
# ------------------
# 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
推荐结构:
# ========================================
# 项目名称 - 时钟约束文件
# 创建日期:YYYY-MM-DD
# ========================================
# ------------------
# 1. 输入时钟定义
# ------------------
# 所有外部输入时钟
# ------------------
# 2. 衍生时钟定义
# ------------------
# PLL/MMCM 输出时钟
# 分频器时钟
# 时钟选择器输出
# ------------------
# 3. 虚拟时钟定义
# ------------------
# I/O 接口参考时钟
# ------------------
# 4. 时钟组定义
# ------------------
# 异步时钟组
# 互斥时钟组
# ------------------
# 5. I/O 延迟约束
# ------------------
# 输入延迟
# 输出延迟
# ------------------
# 6. 时钟不确定性
# ------------------
# 各时钟的抖动/偏斜
# ------------------
# 7. 时序例外
# ------------------
# False path
# Multicycle path
# Max/Min delay
时钟命名建议:
# ✅ 好的命名
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 # 太泛化
约束完成后必查项:
# 1. 检查所有时钟是否定义
report_clocks
# 2. 检查未约束的路径
check_timing -verbose
# 3. 检查时钟交互
report_clock_interaction
# 4. 检查 CDC 路径
report_cdc
# 5. 时序分析
report_timing_summary
# 6. 检查时钟网络
report_clock_networks
通过本文的学习,我们系统地掌握了 FPGA 时钟约束的完整知识体系。让我们回顾一下核心要点:
时钟分类:
create_clock 定义外部输入时钟create_generated_clock 定义内部派生时钟时钟约束的三大作用:
基本语法:
create_clock -period <周期> -name <时钟名> [get_ports <端口>]
关键参数:
-period: 时钟周期 (ns),必需参数-name: 时钟名称,强烈建议指定-waveform: 自定义波形,控制占空比和相位get_ports: 指定时钟输入端口常见应用:
基本语法:
create_generated_clock -name <名称> \
-source <源引脚> \
[-multiply_by <倍频>] [-divide_by <分频>] \
<目标引脚>
关键参数:
-source: 源时钟引脚位置,必需参数-multiply_by: 倍频系数-divide_by: 分频系数-edges: 边沿选择,实现复杂分频-edge_shift: 边沿偏移,实现相位调整典型应用:
边沿选择 (-edges):
边沿偏移 (-edge_shift):
时钟组 (Clock Groups):
-asynchronous: 异步时钟组-physically_exclusive: 物理互斥时钟-logically_exclusive: 逻辑互斥时钟约束文件组织:
1. 输入时钟定义
2. 衍生时钟定义
3. 虚拟时钟定义
4. 时钟组定义
5. I/O 延迟约束
6. 时钟不确定性
7. 时序例外
命名规范:
clk_sys_100m)验证检查:
report_clocks # 检查时钟定义
check_timing -verbose # 检查未约束路径
report_clock_interaction # 检查时钟关系
report_timing_summary # 时序分析
✅ 必须做的:
❌ 不要做的:
初学者:
create_clock 基本用法report_clocks 检查进阶者:
create_generated_clock高级应用:
Q1: 时钟约束后时序仍然不满足怎么办?
Q2: 如何判断两个时钟是否需要声明为异步?
Q3: 虚拟时钟什么时候使用?
Q4: 分频时钟一定要用 create_generated_clock 吗?

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online