3. SDC语法入门:命令格式、对象、选项、注释

SDC,全称Synopsys Design Constraints,是时序约束的标准语言。说白了,它就是告诉工具你的芯片设计应该跑多快、哪些路径是关键路径、哪些输入输出要特殊处理。我刚入行那会儿,总觉得SDC是随便写写就行,后来有一次因为忘了约束一条跨时钟域路径,流片回来的芯片在高温下直接罢工——嗯,从那以后我对待SDC比看老婆的脸色还认真。

3.1 SDC命令的基本格式

一个SDC命令,通常长这样:

命令名 对象 选项

举个例子:

set_input_delay -clock CLK -max 2.0 [get_ports DATA_IN]

这里面:set_input_delay是命令名,[get_ports DATA_IN]是对象,-clock CLK -max 2.0是选项。我个人习惯把选项放在前面,对象放在最后,这样一眼就能看到核心信息。

核心要点:

  • 命令名:告诉工具要干什么
  • 对象:告诉工具作用于谁
  • 选项:告诉工具具体怎么做

你可能会问,为什么顺序这么重要?其实顺序本身不重要,但保持一致的风格能减少低级错误。我在项目中见过有人把对象和选项写反了,结果工具报错,查了半天才发现是顺序问题。

3.2 对象:命令的目标

SDC里最常见的对象有四种:端口(ports)、引脚(pins)、时钟(clocks)和单元(cells)。获取它们需要用get_*系列命令。

对象类型获取命令示例
端口get_portsget_ports DATA_IN
引脚get_pinsget_pins U1/Q
时钟get_clocksget_clocks CLK
单元get_cellsget_cells U1

通配符的使用

有时候你想一次性选中很多对象,通配符就派上用场了。比如:

get_ports DATA_*

这条命令会选中所有以DATA_开头的端口。我曾经在调试一个复杂的SoC项目时,用通配符一次约束了200多个端口,省了至少半天的人工活。

注意:通配符不要滥用。我见过有人写get_ports *,结果把电源和地端口也选进去了,导致约束文件异常庞大,运行时间暴增。

集合操作

SDC还支持对对象集合做加减操作:

# 取两个集合的并集
all_clocks = [get_clocks CLK1] + [get_clocks CLK2]

# 从集合中排除某些对象
except_ports = [all_inputs] - [get_ports RST_N]

嗯,这里要注意:加减操作用的是方括号和圆括号的组合,不要搞混了。我刚开始用的时候,经常把括号类型写错,导致工具报语法错误。

让我用一个SVG图来展示SDC命令的整体结构,帮你快速建立概念框架:

SDC命令结构 SDC命令 命令名 对象 选项 注释 set_input_delay create_clock set_max_delay get_ports get_pins get_clocks -clock -max/-min -rise/-fall # 单行注释 /* 多行 */ 实战口诀 命令选功能 → 对象选目标 → 选项定细节 → 注释保可读

3.3 选项:功能的微调

选项是SDC的精华所在。同一个命令,配上不同的选项,效果天差地别。常用的选项类型有:

  • 时钟相关:-clock指定关联时钟,-rise/-fall指定边沿
  • 数值相关:-max/-min指定最大/最小值,-setup/-hold指定检查类型
  • 路径相关:-from/-to/-through指定路径起点、终点、中间点
  • 条件相关:-condition指定使能条件

举个例子,约束一条路径的建立时间检查:

set_max_delay -from [get_pins U1/CLK] -to [get_pins U2/D] 3.5

这条命令告诉工具:从U1的CLK引脚到U2的D引脚,最大延迟不能超过3.5ns。我一般习惯把-from和-to都写完整,这样别人看代码时不用猜。

小技巧:选项的缩写形式也可以使用,比如-max可以写成-max,-setup可以写成-setup。但我建议新手不要偷懒,写全称更安全。我见过有人用缩写写-pd结果工具不认识,查了半天才发现是-propagated_clock的缩写。

3.4 注释:代码的可读性保障

SDC支持三种注释方式:

  • 单行注释:用#开头,这是最常见的
  • 内联注释:在命令后面用#补充说明
  • 多行注释:用/* */包裹,适用于大段说明

实际写约束时,我强烈建议每个关键约束都加上注释。比如:

# 输入延迟约束 - 时钟CLK上升沿采样
# 最大值2.0ns,最小值0.5ns
set_input_delay -clock CLK -max 2.0 [get_ports DATA_IN]
set_input_delay -clock CLK -min 0.5 [get_ports DATA_IN]

/* 
 * 以下约束用于异步复位信号RST_N
 * 复位信号不需要和时钟同步
 * 只约束其最大延迟即可
 */
set_max_delay -from [get_ports RST_N] -to [all_registers] 5.0

你可能会觉得加注释麻烦。但想想看,几个月后你回来看这段代码,没有注释的约束文件就像一座迷宫——我亲身经历过这种痛苦。

3.5 常见错误与避坑指南

这里列几个我常踩的坑:

错误类型错误示例正确做法
对象拼写错误get_potrs DATA_IN检查端口名拼写
选项类型不匹配为引脚使用-clock选项确认选项接的对象类型
通配符路径错误get_pins */Q遗漏深层路径用*/*/Q或get_pins -hier *Q
注释符号用错使用//作为注释(C语言风格)使用#或/* */

我曾经在做一个高速接口的约束时,把一个get_ports写成了get_pins,结果工具没报错,因为端口名和引脚名刚好重名了。但这样一来,约束就作用在了错误的对象上,导致时序报告完全不准。查了两天才找到原因——嗯,从那以后我再也不依赖all_ports和all_pins这类模糊命令了。

踩坑提醒:我曾经见过有人把端口名拼写错了,比如DATA_IN写成DATA_INN,工具不会报错,只是提示找不到这个端口。如果不仔细检查,你可能会以为约束已经生效了。所以每次写完SDC,建议用report_ports、report_clocks等命令验证对象是否被正确选中。

总之,SDC语法本身不难,难的是细心和规范。养成写注释、做验证的习惯,比你记住所有命令参数更重要——这是我在无数个加班夜之后悟出来的道理。