3. 时钟约束工程
在时序收敛流程中,时钟约束是所有分析计算的起点。如果时钟定义不完整或相互冲突,后续的建立/保持时间检查、跨时钟域分析、时钟偏斜估算都会失去意义。本节围绕 create_clock 与 create_generated_clock 两个核心 SDC 命令,讨论主时钟、衍生时钟、时钟使能、分频与切换场景的约束方法,并给出避免遗漏与冲突的工程实践。
主时钟:时序分析的绝对基准
主时钟是进入 FPGA 的原始时钟信号,通常来自外部晶振、时钟芯片或板上其他器件的输出。在 SDC 中,主时钟通过 create_clock 定义,它同时确定了时钟周期、占空比、上升沿与下降沿位置,以及该时钟所驱动的源节点。
# 50 MHz 输入时钟,接在 clk_50m 端口
create_clock -name sys_clk -period 20.000 \
-waveform {0.000 10.000} [get_ports clk_50m]
# 100 MHz 差分参考时钟,接在专用时钟引脚
create_clock -name ref_clk -period 10.000 \
-waveform {0.000 5.000} [get_ports ref_clk_p]
这里 -period 的单位由 set_units 决定,工程中通常统一为 ns。-waveform 给出上升沿和下降沿时刻,默认是 {0 period/2}。对于 DDR 源同步接口,占空比可能不是 50%,必须显式写出波形,否则时序模型会偏离实际。
主时钟定义中最容易出问题的是源节点选择。如果时钟从输入端口进入后先经过 IBUF、PLL 或时钟网络,create_clock 应作用在端口上,而不是内部节点。Quartus 的 Timing Analyzer 会自动把时钟传播到其驱动的寄存器时钟引脚,但前提是源节点确实位于时钟路径的起点。若把主时钟错误地定义在 PLL 输出上,PLL 输入路径就会缺少约束,形成隐性未约束路径。
report_clocks,检查每个时钟的 Source 字段是否指向预期的端口或引脚。若 Source 显示为内部节点,说明约束位置需要调整。
衍生时钟:PLL、分频器与时钟使能
FPGA 内部大量时钟并非直接来自外部,而是由 PLL、MMCM、分频计数器或时钟使能逻辑产生。这类时钟必须用 create_generated_clock 描述其与主时钟的相位、倍频/分频关系,否则工具无法建立正确的时钟到达时间模型。
以 PLL 为例,输入 50 MHz,输出 100 MHz 和 25 MHz 两路时钟:
create_clock -name sys_clk -period 20.000 [get_ports clk_50m]
# PLL 输出 100 MHz,与输入同相
create_generated_clock -name pll_clk_100m \
-source [get_ports clk_50m] \
-divide_by 1 -multiply_by 2 \
[get_pins pll_inst|altpll_component|auto_generated|pll1|clk[0]]
# PLL 输出 25 MHz,与输入同相
create_generated_clock -name pll_clk_25m \
-source [get_ports clk_50m] \
-divide_by 2 \
[get_pins pll_inst|altpll_component|auto_generated|pll1|clk[1]]
关键参数是 -source,它指向衍生时钟的参考时钟源。对于 PLL,-source 应指向 PLL 的输入时钟端口或输入引脚,而不是 PLL 输出。若 -source 写错,工具会认为衍生时钟与主时钟无关,跨时钟路径将被当作异步处理,可能掩盖真实的时序问题。
对于用计数器实现的分频时钟,例如 50 MHz 经 5 分频得到 10 MHz:
create_generated_clock -name div_clk_10m \
-source [get_ports clk_50m] \
-divide_by 5 \
[get_pins div_cnt_reg[0]/Q]
这里源节点是分频计数器的输出寄存器 Q 端。注意 -divide_by 5 表示输出周期是源时钟周期的 5 倍,即 100 ns,对应 10 MHz。若分频比不是整数,或占空比有特殊要求,应改用 -edges 显式指定边沿序列。
时钟使能:不是时钟,但影响时序
时钟使能(Clock Enable)常被误认为需要单独创建时钟。实际上,CE 信号并不改变时钟频率,它只是控制寄存器是否在某个时钟沿更新。因此不需要为 CE 创建 create_generated_clock,但必须确保 CE 路径本身满足时序要求。
典型场景是低速逻辑用高频时钟加使能实现。例如系统时钟 100 MHz,某模块每 10 个周期工作一次,等效 10 MHz 行为:
# 不创建 10 MHz 衍生时钟,只约束 CE 路径
# CE 由计数器产生,需满足 100 MHz 下的建立/保持
set_max_delay -from [get_pins ce_cnt_reg[*]/Q] \
-to [get_pins data_reg[*]/CE] 8.000
如果错误地为 CE 创建了一个 10 MHz 衍生时钟,工具会认为数据寄存器工作在 10 MHz,从而放松时序要求,导致实际在 100 MHz 下无法收敛。这是时钟约束中常见的“过度约束放松”错误。
时钟切换:多路选择与冲突避免
时钟切换电路(Clock Mux)允许系统在多个时钟源之间动态选择。SDC 中必须为每个输入时钟分别定义,并对切换后的输出节点做正确处理。
create_clock -name clk_a -period 10.000 [get_ports clk_a]
create_clock -name clk_b -period 20.000 [get_ports clk_b]
# 切换输出节点:不能同时属于两个时钟
# 方法一:为输出创建独立衍生时钟,指定默认源
create_generated_clock -name clk_mux_out \
-source [get_ports clk_a] \
-divide_by 1 \
[get_pins clk_mux_inst|out]
# 方法二:使用 set_clock_groups 声明互斥
set_clock_groups -logically_exclusive \
-group {clk_a} -group {clk_b}
时钟切换的核心矛盾是:同一个物理节点在不同时刻可能由不同时钟驱动。若不加处理,工具会认为该节点同时受两个时钟约束,产生冲突。工程上通常采用两种策略:一是为切换输出创建独立的衍生时钟,并指定一个默认源;二是用 set_clock_groups -logically_exclusive 声明两个时钟不会同时有效,从而避免跨时钟路径被错误分析。
需要注意的是,-logically_exclusive 表示两个时钟在逻辑上互斥,工具会切断它们之间的时序路径。如果实际电路中两个时钟可能同时存在(例如毛刺或切换瞬间),应改用 -physically_exclusive 或增加切换同步逻辑,否则会遗漏真实的亚稳态风险。
约束遗漏与冲突的检查方法
完成时钟约束后,必须进行系统性检查。以下是在 Quartus Timing Analyzer 中常用的验证命令:
report_clocks:列出所有已定义时钟及其源节点、周期、波形,检查是否有遗漏或重复定义。report_clock_transfers:显示时钟之间的传递关系,确认跨时钟路径是否被正确识别为同步或异步。check_timing:报告未约束的时钟、未约束的路径、冲突的时钟定义等问题。report_sdc:回读 SDC 约束,确认命令顺序和优先级符合预期。
常见的冲突包括:同一节点被两个 create_clock 定义;衍生时钟的 -source 指向了另一个衍生时钟而非主时钟;时钟使能被误建为衍生时钟;时钟切换输出未做互斥声明。这些问题在 check_timing 中通常会有明确提示,应在布局布线前解决。
工程实践中的参数与判断依据
以下是一份可直接参考的时钟约束检查表,用于在 Quartus 工程中逐项确认。表中参数为示例值,实际工程应根据器件手册和板级时钟方案填写。
| 检查项 | 示例参数 | 判断依据 | 常见问题 |
|---|---|---|---|
| 主时钟周期 | 20.000 ns | 与板级晶振频率一致 | 周期写错导致全链路时序偏差 |
| 主时钟源节点 | get_ports clk_50m | report_clocks 中 Source 为端口 | 定义在内部节点,输入路径未约束 |
| PLL 衍生时钟 | ×2 / ÷2 | 与 PLL 配置的倍频/分频比一致 | -source 指向 PLL 输出而非输入 |
| 分频时钟 | ÷5 → 10 MHz | 输出周期 = 源周期 × 5 | 分频比与实际计数器不符 |
| 时钟使能 | 不建衍生时钟 | CE 路径用 set_max_delay 约束 | 误建衍生时钟导致时序放松 |
| 时钟切换 | logically_exclusive | 两时钟不会同时有效 | 未声明互斥,路径冲突 |
| 未约束路径 | check_timing 报告为空 | 所有时钟路径均有约束覆盖 | 遗漏衍生时钟或跨时钟组 |
时钟约束的质量直接决定时序分析的可信度。一个完整的约束集应当做到:每个主时钟有明确的源节点和周期;每个衍生时钟有正确的 -source 和分频/倍频关系;时钟使能不误建为时钟;时钟切换有互斥声明;check_timing 报告无未约束路径。在此基础上,后续的时序例外、多周期路径和跨时钟域处理才有可靠的基准。