第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约束文件体系结构 SDC约束文件 时钟约束 I/O约束 时序例外 create_clock create_generated_clock set_clock_latency/uncertainty set_input_delay set_output_delay set_load/transition/drive set_false_path set_multicycle_path set_max/min_delay 操作对象:all_inputs / all_outputs / get_ports / get_cells ... 时序分析引擎 (STA / PrimeTime / Tempus) 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约束文件就是你对工具说的话。话说得越准确,工具干得就越漂亮。这一章我把最基础的语法、对象集合和常用命令讲清楚了。你自己动手写几遍,就明白里面的门道了。