4. 时钟定义与治理
时序收敛的第一道门槛不是“跑得快”,而是“时钟描述得对”。在 Lattice FPGA 中,时钟约束一旦定义错误,后续所有建立/保持分析都会建立在错误的时间基准上:要么过度约束导致工具徒劳优化,要么约束不足导致上板才暴露问题。本节围绕主时钟、生成时钟、虚拟时钟、时钟组、时钟不确定性五类基本对象,以及 Lattice PLL/DLL 配置如何改变这些对象,梳理一套可落地的时钟治理方法。
一、五类时钟对象各自解决什么问题
主时钟(primary clock)是时序分析的绝对时间原点,通常对应外部晶振或时钟源进入 FPGA 的引脚。它必须显式声明周期、波形(上升/下降沿位置),例如 100 MHz、50% 占空比、上升沿在 0 ns、下降沿在 5 ns。主时钟不依赖任何其他时钟,是约束树的根。
生成时钟(generated clock)由主时钟经 PLL、DLL、时钟分频/倍频、时钟使能或门控逻辑派生而来。它必须声明“源时钟 + 派生关系”,而不是重新写一个独立周期。这样工具才能正确推导两个时钟之间的相位与频率关系,进而判断跨时钟路径是同步还是异步。
虚拟时钟(virtual clock)没有对应的物理引脚,用于描述“FPGA 外部器件”的时钟特性,典型场景是输入/输出接口的 IO 时序分析:外部 ADC 用 50 MHz 采样,FPGA 内部用 100 MHz 处理,此时需要一个虚拟时钟代表 ADC 的采样时钟,配合 input/output delay 才能正确约束接口路径。
时钟组(clock group)用于声明两个时钟域之间是异步关系。默认情况下,工具会分析所有时钟之间的路径;若两个时钟实际无相位关系,应通过 set_clock_groups -asynchronous 明确切断,避免工具对不可能同时满足的路径做无意义优化,同时减少运行时间。
时钟不确定性(clock uncertainty)描述时钟沿相对理想位置的抖动与偏差,包括输入抖动、PLL 相位误差、时钟树偏斜等。它直接叠加到建立/保持时间预算中,是“留多少余量”的关键参数。
二、Lattice PLL/DLL 配置如何改变时钟约束
Lattice 器件的 PLL(如 ECP5 的 EHXPLLL、MachXO3 的 PLL)和 DLL 是生成时钟的主要来源,其配置参数直接决定生成时钟的周期、相位和抖动特性。以 ECP5 的 EHXPLLL 为例,关键参数包括:
- CLKI 输入频率:必须与主时钟约束一致,否则 PLL 锁定失败或工具报频率不匹配。
- CLKOP 输出频率:由 CLKI 乘以倍频系数、除以分频系数得到,例如 100 MHz 输入、CLKOP 200 MHz 输出,则生成时钟周期为 5 ns。
- CLKOS 相移:可配置 0°、45°、90°、135° 等,用于源同步接口的采样点调整。相移会改变生成时钟的边沿位置,约束中必须体现。
- CLKOK / CLKOS2 等辅助输出:每个输出都是独立的生成时钟,需分别约束。
DLL 主要用于延迟锁定,常见于需要精确相位对齐的接口场景。DLL 本身不改变频率,但会引入固定的相位偏移,约束中应通过生成时钟的相位参数或不确定性来体现。
三、时钟不确定性:留多少余量才合理
时钟不确定性不是“随便填一个数”,而应基于器件手册和实际时钟源特性计算。以 Lattice ECP5 为例,典型构成如下:
| 来源 | 典型值 | 说明 |
|---|---|---|
| 输入时钟抖动 | ±50 ps | 外部晶振或时钟源 datasheet 给出 |
| PLL 相位误差 | ±80 ps | PLL 锁定后的相位不确定度 |
| 时钟树偏斜 | ±100 ps | 全局时钟网络到各触发器的偏斜 |
| 合计(示例) | ±230 ps | 作为 set_clock_uncertainty 的输入 |
实际约束中,可对主时钟和生成时钟分别设置不确定性。例如主时钟 100 MHz 周期 10 ns,若不确定性设为 0.23 ns,则有效建立时间预算从 10 ns 缩减为 9.77 ns。这个值过大会导致工具过度优化、面积和功耗上升;过小则余量不足,上板易出现偶发失败。
四、时钟组与异步域隔离
当设计中存在多个独立时钟域时,必须明确哪些是同步、哪些是异步。同步时钟(如 PLL 输出的 200 MHz 与 100 MHz,同源)应保留分析;异步时钟(如外部 ADC 的 50 MHz 与 FPGA 内部 100 MHz,无相位关系)应通过 set_clock_groups -asynchronous 切断。切断后,跨域路径不再做建立/保持分析,但设计者仍需在 RTL 中插入同步器(如双触发器、异步 FIFO),否则亚稳态风险依然存在。
五、可执行的约束检查清单
以下清单可直接用于 Lattice 项目约束评审:
- 每个外部时钟输入引脚是否都有 create_clock,且周期、波形与原理图一致?
- 每个 PLL/DLL 输出是否都有 create_generated_clock,且 -source 指向正确的输入时钟?
- 生成时钟的相位、倍频/分频比是否与 PLL 配置界面一致?
- 外部接口是否创建了虚拟时钟,并配合 set_input_delay / set_output_delay?
- 异步时钟域之间是否用 set_clock_groups -asynchronous 隔离?
- set_clock_uncertainty 是否基于器件手册和时钟源特性计算,而非默认值?
- 约束后是否运行 report_clock_networks 和 report_clocks 确认时钟树完整?
# Lattice ECP5 约束示例(片段)
create_clock -name clk_in -period 10.0 [get_ports clk_in]
create_generated_clock -name clk_200 -source [get_ports clk_in] \
-divide_by 1 -multiply_by 2 [get_pins pll_inst/CLKOP]
create_generated_clock -name clk_50 -source [get_ports clk_in] \
-divide_by 2 [get_pins pll_inst/CLKOS]
create_clock -name virt_adc -period 20.0
set_clock_groups -asynchronous -group {clk_in clk_200 clk_50} -group {virt_adc}
set_clock_uncertainty -setup 0.23 [get_clocks clk_in]
set_clock_uncertainty -setup 0.18 [get_clocks clk_200]
完成上述约束后,应通过 report_clocks 确认每个时钟的周期、源、生成关系是否正确,再运行静态时序分析。若发现生成时钟未正确派生,优先检查 PLL 输出引脚名称是否与约束中的 get_pins 匹配,以及 -source 是否指向了正确的输入时钟对象。时钟治理的本质是让约束树与物理时钟树一一对应,任何“多写一个时钟”或“少写一个生成时钟”都会在时序报告中留下隐患。