4. SDC 约束编写实战:时钟定义、输入输出延迟、虚假路径与多周期路径约束
SDC 约束,说白了就是告诉工具你的芯片想怎么跑。我见过不少新手,一上来就急着写约束,结果跑出来的时序报告根本没法看。其实约束写得好不好,直接决定了你的芯片能不能按时收敛。
这一章,咱们就聊聊 SDC 约束里最核心的几个部分:时钟怎么定义、输入输出延迟怎么给、哪些路径该设 false path、哪些路径该设 multicycle。这些都是我平时做项目天天要打交道的东西。
4.1 时钟定义——芯片的“心跳”
时钟是时序分析的基准。你想想看,没有时钟,工具根本不知道信号什么时候该到、什么时候该走。所以写 SDC 的第一步,就是把时钟定义清楚。
4.1.1 主时钟(create_clock)
主时钟通常来自芯片的输入引脚,或者 PLL 的输出。我个人习惯,先确认时钟源是什么,再写约束。
# 定义一个 100MHz 的主时钟,占空比 50%
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这里有几个关键参数:
- -name:时钟名字,方便后面引用
- -period:周期,单位 ns
- [get_ports]:时钟源端口
-jitter 参数。不过我更推荐在时序分析时统一设置 uncertainty,后面会讲到。
4.1.2 生成时钟(create_generated_clock)
芯片内部经常有分频、倍频或门控时钟。这些时钟不能直接用 create_clock,得用 create_generated_clock 来定义它们和主时钟的关系。
# 定义一个 2 分频时钟
create_generated_clock -name clk_div2 -source [get_ports clk_in] \
-divide_by 2 [get_pins u_div/clk_out]
我在项目中遇到过一个问题:有人把分频时钟直接当主时钟定义,结果工具把两个时钟当成异步的,该分析的路径没分析到,不该分析的反而分析了。嗯,这个坑踩过一次就记住了。
4.1.3 时钟分组与不确定性
不同时钟域之间的路径,需要明确告诉工具它们是同步还是异步的。同步时钟域用 set_clock_groups -physically_exclusive 或 -logically_exclusive,异步的用 -asynchronous。
# 异步时钟分组
set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b]
时钟不确定性(uncertainty)也很关键。它包含了时钟抖动、PLL 偏差等因素。我一般这样设:
# 设置时钟不确定性
set_clock_uncertainty -setup 0.2 [get_clocks clk_sys]
set_clock_uncertainty -hold 0.05 [get_clocks clk_sys]
4.2 输入输出延迟——芯片与外界的“握手”
芯片不是孤立存在的。它要和外部器件通信,比如 DDR 颗粒、ADC、DAC 等。这时候就需要定义输入输出延迟,告诉工具信号从芯片引脚到内部寄存器,或者从内部寄存器到芯片引脚,花了多少时间。
4.2.1 输入延迟(set_input_delay)
输入延迟指的是外部信号从发送端到芯片输入引脚的时间。这个时间包括外部器件的输出延迟、PCB 走线延迟等。
# 定义输入延迟,相对于时钟的上升沿
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
set_input_delay -clock clk_sys -min 1.0 [get_ports data_in]
这里 -max 和 -min 分别对应最坏和最好情况。我习惯先看外部器件的 datasheet,把它的输出延迟范围找出来,再加上 PCB 走线的估计值。
4.2.2 输出延迟(set_output_delay)
输出延迟是芯片输出信号到外部器件接收端的时间。它包含了 PCB 走线延迟和外部器件的建立/保持时间要求。
# 定义输出延迟
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
set_output_delay -clock clk_sys -min 0.5 [get_ports data_out]
4.3 虚假路径(False Path)——别让工具白费力气
芯片里有些路径,工具根本不需要分析。比如跨时钟域的同步器路径、复位信号路径、测试模式下的路径等。这些路径如果不去掉,工具会花大量时间优化它们,反而耽误了真正关键的路径。
4.3.1 什么时候设 False Path
- 跨时钟域的同步器路径:已经用两级或三级触发器同步了,不需要 STA 分析
- 复位信号路径:复位信号通常异步,不需要时序约束
- 测试模式下的路径:比如 scan 模式下的路径,只在测试时用
- 静态配置信号:芯片上电后就不变的信号
# 设置跨时钟域路径为 false path
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# 设置复位路径为 false path
set_false_path -to [get_pins *rst_n_reg/D]
4.4 多周期路径(Multicycle Path)——给慢信号多一点时间
有些路径不需要在一个时钟周期内完成。比如从慢速外设读取数据,或者某些计算路径需要多个周期才能稳定。这时候就要用多周期约束,告诉工具放宽检查条件。
4.4.1 什么时候用 Multicycle
- 慢速外设接口:比如 SPI、I2C 等,数据可能多个周期才变化一次
- 长计算路径:比如乘法器、除法器,可能需要多个周期才能出结果
- 使能信号控制的路径:数据只在使能信号有效时才变化
# 设置 setup 检查为 2 个周期
set_multicycle_path -setup 2 -from [get_pins u_mul/a_reg/CK] -to [get_pins u_mul/b_reg/D]
# 对应的 hold 检查也要调整
set_multicycle_path -hold 1 -from [get_pins u_mul/a_reg/CK] -to [get_pins u_mul/b_reg/D]
这里有个容易搞混的地方:setup 设成 2,hold 要设成 1。为什么?因为 hold 检查默认比 setup 早一个周期。如果你只设 setup 不设 hold,工具会认为 hold 也要放宽,结果可能把 hold 检查放得太松。
4.5 知识结构总览
下面这张图把本章的核心知识点串起来了。你可以把它当成一个检查清单,写 SDC 的时候对照着看。
4.6 实战案例:一个简单的 SDC 文件
说了这么多,咱们来写一个完整的 SDC 文件。假设有一个芯片,包含一个 100MHz 的主时钟、一个 50MHz 的分频时钟、一个 SPI 接口和一个乘法器模块。
# ============================================
# 时钟定义
# ============================================
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
create_generated_clock -name clk_spi -source [get_ports clk_in] \
-divide_by 2 [get_pins u_div/clk_out]
# ============================================
# 时钟分组
# ============================================
set_clock_groups -asynchronous -group [get_clocks clk_sys] \
-group [get_clocks clk_spi]
# ============================================
# 时钟不确定性
# ============================================
set_clock_uncertainty -setup 0.2 [get_clocks clk_sys]
set_clock_uncertainty -hold 0.05 [get_clocks clk_sys]
# ============================================
# 输入输出延迟
# ============================================
# SPI 输入
set_input_delay -clock clk_spi -max 5.0 [get_ports spi_mosi]
set_input_delay -clock clk_spi -min 2.0 [get_ports spi_mosi]
# SPI 输出
set_output_delay -clock clk_spi -max 4.0 [get_ports spi_miso]
set_output_delay -clock clk_spi -min 1.0 [get_ports spi_miso]
# ============================================
# 虚假路径
# ============================================
# 跨时钟域路径
set_false_path -from [get_clocks clk_sys] -to [get_clocks clk_spi]
set_false_path -from [get_clocks clk_spi] -to [get_clocks clk_sys]
# 复位路径
set_false_path -to [get_pins *rst_n_reg/D]
# ============================================
# 多周期路径
# ============================================
# 乘法器路径,需要 2 个周期
set_multicycle_path -setup 2 -from [get_pins u_mul/a_reg/CK] \
-to [get_pins u_mul/result_reg/D]
set_multicycle_path -hold 1 -from [get_pins u_mul/a_reg/CK] \
-to [get_pins u_mul/result_reg/D]
report_timing 和 report_clock,确认约束生效了再往下走。