3. 时序约束基础与Lattice工具链
时序约束不是“给工具看的备注”,而是把设计意图翻译成静态时序分析(STA)能够计算的数学条件。Lattice 的 Radiant 与 Diamond 都采用业界通用的 SDC(Synopsys Design Constraints)作为约束描述语言,工具在综合后的映射、布局布线以及最终时序验证阶段反复读取这些约束,用来判断每条时序路径是否满足建立时间与保持时间要求。约束写错,STA 就会在错误的假设下给出“通过”的假象;约束缺失,工具只能按默认条件分析,往往掩盖真实问题。因此,理解 SDC 的语法结构、时钟定义方式、I/O 延迟建模以及时序例外的适用边界,是后续定位复杂时序问题的前提。
一、SDC 的基本语法与执行模型
SDC 由一条条命令组成,每条命令以命令名开头,后跟对象、参数和选项。它本质上是一种 Tcl 脚本,因此支持变量、循环和条件判断,但工程中更常见的是直接书写约束命令。SDC 的执行顺序很重要:后执行的约束会覆盖或补充先前的约束,尤其是时钟定义、时钟组和时序例外之间存在明确的优先级关系。通常的书写顺序是:先定义时钟,再定义时钟特性(不确定性、延迟、转换时间),然后描述输入输出延迟,最后书写时序例外。
SDC 中大量使用“对象查询”命令来指定约束作用对象,例如 get_ports、get_clocks、get_cells、get_pins、get_nets。这些命令返回对象集合,后续命令再对该集合施加约束。对象查询写错是约束失效最常见的原因之一:如果 get_ports 匹配不到任何对象,多数工具只会给出警告而非报错,约束被静默忽略,STA 结果自然不可信。
二、时钟定义:一切时序分析的基准
时钟是 STA 的基准。没有时钟定义,工具无法计算任何建立/保持关系。最基本的时钟定义命令是 create_clock:
create_clock -name sys_clk -period 10.000 [get_ports clk_in]
create_clock -name sys_clk -period 10.000 -waveform {0 5} [get_ports clk_in]
第一条命令定义了一个周期 10 ns、占空比 50% 的时钟,上升沿在 0 ns,下降沿在 5 ns。第二条显式指定了波形,效果相同。周期 10 ns 对应 100 MHz,这是 Lattice 中低端 FPGA 常见的系统时钟频率。对于由 PLL 或 DLL 生成的内部时钟,通常不需要再对输出端口写 create_clock,而是用 create_generated_clock 描述它与源时钟的相位、分频、倍频关系:
create_generated_clock -name pll_clk -source [get_pins pll_inst/CLKI] \
-divide_by 2 -multiply_by 5 [get_pins pll_inst/CLKO]
这条约束表示 PLL 输出时钟相对源时钟做了 5 倍频、2 分频,即频率为源时钟的 2.5 倍。如果只写 create_clock 而不写 create_generated_clock,工具会把 PLL 输出当作独立时钟,无法正确分析源时钟与生成时钟之间的相位关系,跨时钟域路径的时序结果就会失真。
时钟定义之后,通常还要补充时钟不确定性(set_clock_uncertainty)和时钟延迟(set_clock_latency)。不确定性用于为时钟抖动和相位误差留出余量,例如 set_clock_uncertainty 0.150 [get_clocks sys_clk] 表示在建立和保持分析中各扣除 150 ps 的余量。时钟延迟则描述时钟源到寄存器时钟引脚之间的插入延迟,在布局布线前用于估算,布局布线后工具会用实际时钟树延迟替代。
三、输入输出延迟:把外部世界纳入分析
FPGA 的时序分析不能只看芯片内部。数据从外部器件进入 FPGA 输入端口,或从 FPGA 输出端口送往外部器件,这段片外路径的延迟必须通过 set_input_delay 和 set_output_delay 建模。它们描述的是“相对于某个时钟沿,数据到达/离开端口的时间”。
set_input_delay -clock sys_clk -max 3.000 [get_ports data_in*]
set_input_delay -clock sys_clk -min 1.000 [get_ports data_in*]
set_output_delay -clock sys_clk -max 4.000 [get_ports data_out*]
set_output_delay -clock sys_clk -min 1.500 [get_ports data_out*]
这里 -max 用于建立时间分析,-min 用于保持时间分析。输入延迟的数值来自外部器件的 Tco(时钟到输出有效时间)加上 PCB 走线延迟;输出延迟的数值来自外部器件的建立/保持要求加上 PCB 走线延迟。如果外部器件手册给出 Tco 最大 2.5 ns、PCB 延迟 0.5 ns,则输入延迟最大值取 3.0 ns 是合理的。这些数值必须与外部器件的时序参数一致,否则 STA 会在错误的边界条件下判断。
输入输出延迟的参照时钟必须与数据实际随动的时钟一致。如果输入数据由外部时钟同步,而该时钟与 FPGA 内部时钟同源但存在相位差,就需要用 create_clock 或 create_generated_clock 先建立这个外部时钟,再以其为参照写输入延迟。
四、时序例外:放宽或切断不真实的路径
并非所有路径都需要按最严格条件分析。异步跨时钟域路径、伪路径、多周期路径需要用时序例外命令处理,否则工具会按最坏情况分析,导致无法收敛或掩盖真实问题。
set_false_path 用于告诉工具某条路径不需要做时序分析,典型场景是两个异步时钟域之间的数据传递,或者复位信号从复位源到各寄存器的路径:
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_false_path -from [get_ports rst_n]
set_multicycle_path 用于允许某条路径在多个时钟周期内完成,常见于慢速逻辑或分频使能路径:
set_multicycle_path 2 -setup -from [get_cells slow_reg*] -to [get_cells capture_reg*]
set_multicycle_path 1 -hold -from [get_cells slow_reg*] -to [get_cells capture_reg*]
注意多周期路径必须同时设置建立和保持,且保持通常比建立少一个周期,否则保持分析会过于宽松或过于严格。时序例外的优先级高于普通时序约束,但 set_false_path 和 set_multicycle_path 之间也存在覆盖关系,书写顺序和对象范围需要仔细核对。
五、Radiant 与 Diamond 中的约束加载与检查
在 Radiant 中,SDC 约束通常放在工程的 constraint 目录下,以 .sdc 文件形式存在,并在综合或布局布线阶段通过工程设置指定。Radiant 的 Timing Analyzer 会读取这些约束并生成时序报告。在 Diamond 中,约束同样以 .sdc 文件管理,通过 Spreadsheet View 或直接编辑文件加载,Timing Analysis 工具会给出路径级的建立/保持余量。
约束加载后必须检查三件事:第一,工具是否报告了“未匹配对象”的警告,这通常意味着 get_ports 或 get_cells 写错;第二,时钟是否被正确识别,可以在时序报告的时钟列表中确认周期、波形和生成关系;第三,输入输出延迟和时序例外是否作用到了预期路径,可以通过路径报告中的“约束来源”字段回溯。只有这三项都确认无误,后续的时序问题定位才有可靠基础。
get_ports、get_cells、get_pins)如果匹配不到任何对象,工具通常只给出警告。建议在每次修改约束后,先查看工具日志中的警告条目,确认没有“未匹配对象”的提示,再进入时序报告分析。
set_false_path 和 set_multicycle_path 会改变 STA 的分析范围。如果例外范围写得过大,可能把真实需要分析的路径也排除在外,导致时序违例被掩盖。书写例外时,应尽量用 -from 和 -to 精确限定对象,避免使用过于宽泛的 get_cells * 或 get_clocks *。
总结来看,SDC 约束的核心逻辑是:先建立时钟基准,再描述外部接口的时序边界,最后用例外命令处理不按常规分析的路径。Radiant 和 Diamond 都提供了约束加载和时序报告机制,但工具不会替设计者判断约束是否合理。约束写完只是第一步,确认对象匹配、时钟识别和路径作用范围这三项检查通过,才能让后续的时序问题定位建立在可信的数据之上。