第三章 SDC约束入门:时钟定义、输入延迟、输出延迟、伪路径、多周期路径

各位同学,欢迎来到时序收敛实战的第三讲。今天咱们聊聊SDC约束。说实话,很多新手觉得写约束就是抄模板,其实不然。我见过太多项目,因为几条约束写错了,导致后端跑出来的结果惨不忍睹。SDC约束,说白了就是告诉工具:你的芯片到底想怎么工作。

3.1 时钟定义——时序分析的基石

时钟是时序分析的“心跳”。没有时钟,STA根本没法玩。我习惯把时钟定义比作给芯片装一个“节拍器”。

最基本的时钟定义用 create_clock 命令。举个例子:

create_clock -name clk_sys -period 10.0 [get_ports clk]

这条命令的意思是:在端口 clk 上创建一个名为 clk_sys 的时钟,周期是10ns,也就是100MHz。

嗯,这里要注意几个坑:

  • 时钟源点:一定要指定对端口或网络。我曾经见过有人把时钟定义在内部节点上,结果工具报了一堆奇怪的违例。
  • 时钟组:如果芯片有多个时钟,记得用 -group 选项分组。不同组的时钟默认是异步的,工具不会去分析它们之间的路径。
  • 虚拟时钟:有时候输入输出端口没有真实的时钟引脚,这时候需要定义一个虚拟时钟。比如:
create_clock -name vclk -period 5.0

虚拟时钟不绑定任何端口,只用来做输入输出延迟的参考。我个人习惯在项目一开始就把所有时钟定义好,包括虚拟时钟,免得后面忘了。

小技巧:时钟的 -waveform 选项可以指定占空比。默认是50%,如果你的时钟不是50%占空比,记得手动设置。比如上升沿在2ns,下降沿在7ns:-waveform {2 7}

3.2 输入延迟——告诉工具数据什么时候来

输入延迟描述的是:数据相对于时钟边沿,到底什么时候到达芯片的输入引脚。说白了,就是外部器件把数据送过来需要花多少时间。

命令格式:

set_input_delay -clock clk_sys -max 2.0 [get_ports data_in]
set_input_delay -clock clk_sys -min 0.5 [get_ports data_in]

这里 -max 表示最大延迟(setup检查用),-min 表示最小延迟(hold检查用)。

你想想看,如果外部器件的数据在时钟上升沿之后2ns才稳定,那你的内部寄存器就必须等这2ns过去才能采样。否则采到的数据可能是错的。

我在项目中遇到过一个问题:输入延迟设得太乐观,结果流片回来发现数据采错了。后来一查,是外部器件的输出延迟比我们预估的大了0.5ns。从那以后,我每次设输入延迟都会留一点余量。

避坑指南:输入延迟的值不能乱填。它应该等于外部器件的 clock-to-output 延迟加上PCB走线延迟。我曾经见过有人直接把数据手册上的最大值抄过来,结果忽略了PCB走线,导致时序分析不准确。

3.3 输出延迟——告诉工具数据什么时候被取走

输出延迟和输入延迟是对称的。它描述的是:芯片内部的数据输出到引脚后,外部器件需要多少时间才能稳定采样。

命令格式:

set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
set_output_delay -clock clk_sys -min 1.0 [get_ports data_out]

这里 -max 对应setup检查,意思是外部器件在时钟边沿之后3ns内必须收到数据。-min 对应hold检查,意思是数据至少要保持1ns不变。

说白了,输出延迟就是外部器件的setup和hold时间要求。你想想看,如果外部器件要求数据在时钟边沿前2ns就稳定,那你的内部逻辑就必须提前把数据准备好。

我个人习惯把输出延迟和输入延迟放在同一个约束文件里,方便统一管理。而且我会在注释里写明每个延迟值的来源,比如“来自数据手册第X页”。这样过几个月回头看,自己还能看懂。

3.4 伪路径——让工具别管那些不重要的路径

伪路径(False Path)是时序约束里最常用的“减负”手段。芯片里有些路径根本不需要时序分析,比如跨时钟域的同步器路径、测试模式下的路径、复位路径等。

命令格式:

set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_false_path -from [get_pins rst_n_reg/C] -to [get_pins rst_n_reg/Q]

第一条命令告诉工具:从 clk_aclk_b 的所有路径都不用分析。第二条命令告诉工具:复位寄存器的输出到输入也不用分析。

嗯,这里要特别小心。伪路径设多了,可能会漏掉真正的时序问题。我见过一个案例:有人把整个模块的输入都设成了伪路径,结果那个模块的功能完全没被验证,流片回来直接废了。

我的经验:伪路径一定要有明确的理由。比如跨时钟域用了双触发器同步,那确实不需要分析。但如果只是“我觉得这条路径不重要”,那最好再想想。我曾经因为一条伪路径设错了,导致芯片在高温下工作不稳定,后来花了整整两周才定位到问题。

3.5 多周期路径——给慢速路径多一点时间

多周期路径(Multicycle Path)用于那些不需要在一个时钟周期内完成计算的路径。比如一个乘法器,可能需要两个周期才能算出结果。

命令格式:

set_multicycle_path -setup 2 -from [get_pins mul_reg/C] -to [get_pins acc_reg/D]
set_multicycle_path -hold 1 -from [get_pins mul_reg/C] -to [get_pins acc_reg/D]

这里 -setup 2 表示数据需要2个时钟周期才能到达,所以setup检查放宽到第2个周期。-hold 1 表示hold检查的默认值是setup值减1,也就是第1个周期。

你想想看,如果不设多周期路径,工具会默认所有路径都是单周期。对于慢速路径,工具会拼命优化,浪费大量面积和功耗。设了多周期路径后,工具就知道“这条路可以慢一点”,反而能做出更好的结果。

我在项目中遇到过一个问题:设了多周期路径后,忘了设hold的 -hold 选项。结果hold检查还是按单周期来,导致工具报了一堆hold违例。后来查资料才发现,set_multicycle_path 的默认行为是只改setup,hold需要单独指定。

小技巧:多周期路径的 -start-end 选项可以指定是从发起时钟还是捕获时钟开始计算周期数。默认是从捕获时钟算。我个人习惯显式指定,避免混淆。

3.6 综合实战——一个完整的SDC示例

好了,理论讲完了,咱们看一个完整的例子。假设有一个芯片,包含一个100MHz的系统时钟、一个50MHz的慢速时钟,以及一些输入输出端口。

# 时钟定义
create_clock -name clk_fast -period 10.0 [get_ports clk_fast]
create_clock -name clk_slow -period 20.0 [get_ports clk_slow]

# 虚拟时钟
create_clock -name vclk -period 10.0

# 输入延迟
set_input_delay -clock vclk -max 2.0 [get_ports data_in]
set_input_delay -clock vclk -min 0.5 [get_ports data_in]

# 输出延迟
set_output_delay -clock vclk -max 3.0 [get_ports data_out]
set_output_delay -clock vclk -min 1.0 [get_ports data_out]

# 伪路径:跨时钟域
set_false_path -from [get_clocks clk_fast] -to [get_clocks clk_slow]
set_false_path -from [get_clocks clk_slow] -to [get_clocks clk_fast]

# 多周期路径:慢速模块
set_multicycle_path -setup 2 -from [get_pins slow_mod/*/C] -to [get_pins slow_mod/*/D]
set_multicycle_path -hold 1 -from [get_pins slow_mod/*/C] -to [get_pins slow_mod/*/D]

这个例子涵盖了今天讲的所有内容。你把它保存下来,以后写约束的时候可以直接参考。

最后说一句:SDC约束不是写一次就完事的。随着设计迭代,约束也要不断更新。我每次跑完STA都会回头检查一下约束,看看有没有遗漏或者过时的内容。嗯,养成这个习惯,能省很多麻烦。

好,今天的课就到这里。下一讲咱们聊聊时钟树综合,这可是后端工程师的看家本领。到时候见!