第3章 SDC约束文件入门
说实话,看一个数字IC工程师是不是老手,我一般先看他的SDC写得怎么样。你想想,RTL写得再漂亮,综合工具看不懂时序要求,最后流片回来就是一堆废片。SDC(Synopsys Design Constraints)就是工具和设计师之间的对话语言,你得说清楚、说准确。
这一章,咱们就从SDC最核心的东西说起——语法、对象集合和常用命令。别小看这些基础,我见过不少项目,就因为最基础的语法写错,导致整个约束文件失效,流片回来才发现。
3.1 SDC语法基础
SDC是基于Tcl(Tool Command Language)的。说白了,SDC文件本身就是一个Tcl脚本。每条命令就是一个Tcl命令,只不过在综合或时序分析工具里,这些命令被扩展了。
SDC命令的基本格式是这样的:
command_name [option1] [option2] ... [argument]
每个命令都有固定的选项和参数。比如:
create_clock -name clk_sys -period 10.0 [get_ports clk]
这条命令的意思是:创建一个名为clk_sys的时钟,周期是10ns,驱动端口是clk。
我个人习惯把SDC按功能分成几大类:时钟约束、I/O约束、时序例外、以及一些辅助命令。这样写出来的SDC文件结构清晰,后期维护也方便。
3.1.1 语法要点
这里有几个容易踩坑的地方,我提醒一下:
- 大小写敏感:SDC是大小写敏感的。create_clock和Create_Clock是两个完全不同的东西。
- 对象引用:对设计里的对象操作时,要用get_ports、get_cells、get_nets这些命令来获取。
- 转义字符:如果信号名里带了特殊字符,比如中括号、斜杠,需要用花括号括起来或用反斜杠转义。
- 注释:用#开头的行是注释,Tcl里也支持C风格的多行注释,但SDC里最好只用#。
小技巧:我一般会在每个时钟约束前加一段注释,写明时钟的来源、用途和预期频率。这样三个月后回来看,还能记得当时为什么这么设。
3.2 SDC对象集合
在SDC里,所有设计元素都通过“对象集合”来访问。你想想,一个芯片里有成千上万个单元,你怎么一次性地把所有的输入端口都约束上?这时候就需要对象集合命令了。
常用的对象集合命令有这些:
| 命令 | 返回对象 | 适用场景 |
|---|---|---|
| all_inputs | 所有输入端口 | 设置输入延迟约束 |
| all_outputs | 所有输出端口 | 设置输出延迟约束 |
| all_registers | 所有寄存器 | 时钟特性分析 |
| all_clocks | 所有已定义时钟 | 检查时钟定义 |
| get_ports | 指定端口 | 精确选择端口 |
| get_cells | 指定单元 | 精确选择单元 |
| get_nets | 指定线网 | 精确选择线网 |
| get_pins | 指定引脚 | 精确选择引脚 |
3.2.1 实际用法
这些集合命令可以配合通配符使用:
# 选择所有以data_开头的输入端口
all_inputs -filter {name =~ data_*}
# 选择所有名为clk的端口
get_ports clk
# 选择所有寄存器
all_registers -clock clk_sys
我在一个项目里遇到过这样的问题:设计师用了all_inputs,但这个命令返回的是所有输入端口,包括时钟输入。如果时钟端口也被加上了输入延迟约束,那时钟的时序分析就全乱了。所以,我建议对时钟端口单独处理,不要和普通数据端口混在一起。
注意:all_inputs不包括芯片内部的输入端口,只包括顶层输入端口。如果你做的是模块级的约束,要用get_ports加上通配符。
3.3 常用SDC命令概览
SDC的命令有好几十条,但常用的其实就那么十来条。我来分类介绍一下:
3.3.1 时钟约束命令
这是约束文件的核心,没有时钟一切都免谈。
- create_clock:定义主时钟。指定时钟名称、周期、波形和驱动端口。
- create_generated_clock:定义衍生时钟,比如PLL输出时钟。
- set_clock_latency:设置时钟网络延迟。
- set_clock_uncertainty:设置时钟抖动和不确定性。
- set_clock_gating_check:设置时钟门控检查。
3.3.2 I/O约束命令
这些命令告诉工具,芯片外面是什么样的环境。
- set_input_delay:设置输入端口相对于时钟的延迟。
- set_output_delay:设置输出端口相对于时钟的延迟。
- set_input_transition:设置输入端口的过渡时间。
- set_load:设置输出端口的负载电容。
- set_drive:设置输入驱动强度。
3.3.3 时序例外命令
这些命令用来处理特殊路径,比如跨时钟域路径:
- set_false_path:禁止路径分析。
- set_multicycle_path:设置为多周期路径。
- set_max_delay:设置最大延迟。
- set_min_delay:设置最小延迟。
3.3.4 环境与模式命令
- set_operating_conditions:设置工艺角。
- set_wire_load_model:设置线负载模型。
- set_driving_cell:用具体的标准单元来驱动输入。
我曾经有个项目,所有I/O约束都写对了,但就是时序过不了。最后发现是set_driving_cell设错了——用了大驱动能力的单元去驱动片外的小负载,导致工具以为信号上升很快,但实际上芯片管脚上根本没有那么强的驱动。
建议:写约束文件时,先写时钟约束,再写I/O约束,最后再处理时序例外。这个顺序效率最高,因为时钟约束是其他所有约束的基础。
上面我提到了不少SDC命令,它们之间不是孤立的。为了让概念更清晰,我画了一张关系图,把SDC的体系结构串起来:
你看这张图,SDC的体系其实很清晰:上层是三大约束主体,中间是所有命令依赖的对象集合,底层是时序分析工具。你把上层的约束写对了,工具就能准确地跑STA。写错了?那工具要么分析过头、要么漏掉关键路径。
3.4 实践案例
最后,我拿一个实际项目里的约束片段来说明:
# 时钟约束
create_clock -name clk_sys -period 10.0 [get_ports sys_clk]
create_clock -name clk_mem -period 12.0 [get_ports mem_clk]
# 衍生时钟
create_generated_clock -name clk_core -source [get_ports sys_clk] \
-divide_by 2 [get_pins u_pll/clk_out]
# 输入延迟约束(不对clk_sys施加)
set_input_delay -clock clk_sys -max 5.0 [all_inputs -filter {name != sys_clk}]
# 输出延迟约束
set_output_delay -clock clk_sys -min 1.0 [all_outputs]
# 跨时钟域例外
set_false_path -from [get_clocks clk_sys] -to [get_clocks clk_mem]
# 多周期路径
set_multicycle_path -setup 2 -from [get_pins u_core/reg_a/CK] \
-to [get_pins u_core/reg_b/D]
这个例子涵盖了最常用的几个命令。我建议你拿到一个新项目时,先把SDC文件按这个结构写出来,然后再根据具体需求调整。
警示:有些工程师喜欢把all_inputs和all_outputs当作“万能药”,不加区分地全部施加同样的约束。这样做有时能通过时序分析,但芯片的工作状态可能会和你的约束假设完全不同。测试用例里能过,实际电路就崩了。
说白了,SDC约束文件就是你对工具说的话。话说得越准确,工具干得就越漂亮。这一章我把最基础的语法、对象集合和常用命令讲清楚了。你自己动手写几遍,就明白里面的门道了。