2. 时序约束概念:SDC约束、时钟定义、输入输出延迟
说到时序约束,很多刚入行的朋友会觉得这就是一堆烦人的命令。其实不然。时序约束说白了,就是告诉工具你的芯片要跑多快,数据从哪里来,要到哪里去。我刚开始做项目时,也曾觉得约束差不多就行,结果有一次综合后的时序报告惨不忍睹...从那天起,我才真正开始认真对待每一行约束。
核心观点:时序约束是连接设计意图与工具实现的桥梁。没有它,工具就像盲人摸象.
2.1 什么是SDC?
SDC是Synopsys Design Constraints的缩写。它是业界标准约束格式,几乎所有数字芯片设计工具都支持。你想想看,无论是Synopsys的DC、PrimeTime,还是Cadence的Genus、Tempus,底层拿到的都是同一套约束语言。
SDC文件本质上是一个Tcl脚本。每一行都是一条命令。工具按顺序执行这些命令,构建出时序分析的“游戏规则”。我个人习惯把SDC文件分为三大块:
- 时钟定义——告诉工具时钟长什么样
- 输入输出延迟——告诉工具芯片边界的数据怎么走
- 异常路径——告诉工具哪些路径不用查
这章我们先聊前两块。异常路径后面有专门章节讲。
2.2 时钟定义——时序分析的“心跳”
时钟是时序分析的基准。没有时钟,就没有建立时间和保持时间的概念。
最常见的时钟定义命令是create_clock。来看一个实际例子:
# 定义一个100MHz的主时钟,占空比50%
create_clock -name clk_sys -period 10.000 \
-waveform {0.000 5.000} [get_ports clk_in]
这里有几个关键参数:
| 参数 | 含义 | 常见坑点 |
|---|---|---|
-period |
时钟周期,单位ns | 别忘了单位是ns。100MHz就是10ns |
-waveform |
上升沿和下降沿的位置 | 默认就是50%占空比,但不能省略 |
[get_ports] |
时钟源端口 | 必须指定物理端口,不能是内部节点 |
注意:时钟定义必须写在SDC文件最前面。因为后面的输入输出延迟、异常路径都依赖时钟信息。我曾经见过有人把create_clock写在文件末尾,结果前面所有约束都被工具忽略——查了整整半天才找到原因。
除了主时钟,还有生成时钟。比如PLL分频出来的时钟:
# 定义一个由clk_sys二分频得到的时钟
create_generated_clock -name clk_div2 \
-source [get_ports clk_in] \
-divide_by 2 \
[get_pins u_pll/clk_out]
这里要注意-source指定的是源时钟的物理节点,不是时钟名。很多新手把时钟名写进去,工具直接报错。我见过最离谱的是有人写了clk_sys——那是时钟名,不是pin或port,工具当然不认识。
2.3 输入延迟——数据进芯片的“第一关”
数据从片外进入芯片,肯定有延迟。这个延迟来自PCB走线、片外器件的输出延迟等等。set_input_delay就是告诉工具这个延迟有多大。
举个例子:假设片外器件在时钟上升沿后2ns才输出数据,那输入延迟就是2ns。
# 设置输入延迟,相对时钟clk_sys的上升沿
set_input_delay -clock clk_sys -max 2.500 [get_ports data_in*]
set_input_delay -clock clk_sys -min 1.000 [get_ports data_in*]
这里为什么要分-max和-min?
- -max:最大延迟,用于建立时间分析。数据来得越晚,留给内部逻辑的时间越少。
- -min:最小延迟,用于保持时间分析。数据来得越早,越容易和上一拍数据冲突。
小技巧:在做输入延迟约束时,我建议先拿到片外器件的datasheet,找到它的t_co(时钟到输出延迟)参数。输入延迟就等于t_co_max和t_co_min。不要自己瞎猜,数据手册说了算。
2.4 输出延迟——数据出芯片的“最后一关”
输出延迟的思路和输入延迟是对称的。set_output_delay告诉工具,数据从芯片内部到达片外器件,还有多少时间预算留给芯片内部。
# 设置输出延迟,相对时钟clk_sys的上升沿
set_output_delay -clock clk_sys -max 4.000 [get_ports data_out*]
set_output_delay -clock clk_sys -min 1.500 [get_ports data_out*]
输出延迟的物理意义是:片外器件需要数据在时钟沿之前建立好。假设片外器件的建立时间要求是4ns,那输出延迟就是4ns。同样,保持时间对应-min。
我自己总结了一个小技巧:
- 输出延迟 = 片外器件的建立时间要求(对
-max来说) - 输出延迟 = 片外器件的保持时间要求(对
-min来说)
这样记就不会搞反了。
2.5 知识体系总览
下面这张图概括了时序约束的核心逻辑。画的是数据从片外进来,经过内部逻辑,再到片外的完整路径。你看一眼就能明白约束之间是怎么配合的。
2.6 避坑指南:我踩过的几个典型坑
坑1:忘记加-clock参数
很多人写set_input_delay时,漏掉了-clock。工具不会报错,但会默认使用所有时钟,结果分析结果完全不对。我记得有一次帮同事查问题,他折腾了两天没搞定,我一看——就是少了-clock。
坑2:max和min搞反
这其实是对概念理解不透。我建议你记住一句话:数据来得越晚,setup越紧张;数据来得越早,hold越紧张。所以-max对应晚到,-min对应早到。
坑3:生成时钟的-source指向了时钟名
-source必须指向物理pin或port。你可以用get_pins或者get_ports,但不要用get_clocks。这是新手最容易犯的错。
实用建议:每写完一条约束,立刻跑一遍check_timing或者report_timing看有没有unclocked register。如果发现大量没有时钟的寄存器,那八成是时钟定义出了问题。别等到综合跑完才检查。
2.7 总结
时序约束说白了就是三件事:
- 定义时钟——定下节奏
- 定义输入延迟——数据什么时候进来
- 定义输出延迟——数据什么时候出去
这三件事做好了,后面的STA分析就有个靠谱的基础。我见过太多项目因为约束写错,导致最后一版芯片频率上不去,或者功能不稳定。花点时间把约束写对,是最划算的投资。