第三章 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_a 到 clk_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都会回头检查一下约束,看看有没有遗漏或者过时的内容。嗯,养成这个习惯,能省很多麻烦。
好,今天的课就到这里。下一讲咱们聊聊时钟树综合,这可是后端工程师的看家本领。到时候见!