时序约束基础:什么是时序约束,SDC文件结构

各位好。咱们正式开始第2章。

上一章聊了LINCS项目里约束激增的麻烦。但在动手排查之前,你总得知道「约束」到底是什么东西。我个人觉得,很多工程师被激增搞到头大,根本原因不是工具报错多,而是对约束本身的理解还停留在「抄上一个项目的」水平。

这一章,我们把时序约束和SDC文件掰开揉碎。嗯,都是基础,但基础不牢,后面排查起来会非常痛苦。

什么是时序约束

说白了,时序约束就是你告诉EDA工具的一句话:「我这个设计要在多快的时钟下跑,哪些路径可以慢一点,哪些路径一点都不能慢。」

我有个比喻:你想象一下,你给一张芯片画好了所有走线的地图。时序约束就是那个限速牌——哪个路段必须跑300MHz,哪个路段可以慢悠悠走,哪个交叉口必须确保信号1ns内到达。没有这些限速牌,工具就像没有导航的司机,要么乱开,要么超速撞车。

时序约束的目标有三个:

  1. 保证芯片能在目标频率下稳定工作
  2. 告诉工具哪些路径是「假的」,不用管
  3. 让工具知道每条路径的时间预算,从而优化面积和功耗

你想想看,如果什么都不约束,工具默认所有路径都要在最短时间内走完。结果就是面积巨大、功耗爆炸、布线绕成麻花。后来我发现,很多人只会加一个create_clock就跑STA,然后报了一堆违例也不知道怎么调。其实就是没搞懂约束的真正作用。

避坑指南

我曾经在一个SoC项目中接手过别人的约束文件。打开一看,光时钟就定义了80多个,大部分还是无脑复制粘贴。后来发现,其中20多个时钟根本不存在于RTL中。这种冗余约束不仅拖慢运行时间,还会让工具多出大量假违例。嗯,从那时起,我养成了一个习惯:每写一句约束,都要问自己一句「这个约束到底在管哪条路径?」

SDC文件的结构

SDC是Synopsys Design Constraints的缩写。它是个文本文件,后缀一般是.sdc。你别以为它是Synopsys私有的东西——现在所有主流EDA工具都认它。

SDC的内容其实挺有规律的。我习惯把它分成几大块:

约束类别主要命令作用
时钟定义create_clock, create_generated_clock定义时钟周期、波形、源点
时钟组set_clock_groups告诉工具哪些时钟是异步的,不用分析
输入/输出延迟set_input_delay, set_output_delay定义端口到外部器件的时序窗口
异常路径set_false_path, set_multicycle_path跳过或放宽某些路径的分析
驱动/负载set_drive, set_load模拟端口的外部驱动能力和负载
线载模型(较少用)set_wire_load_model早期工艺使用,现在很少见了

你看,其实就这么几类。我见过最乱的约束文件,就是把这些命令全部混在一起写,时钟定义、例外路径、IO延迟全堆在一堆,根本分不清哪句管什么。我个人习惯是按照上面这个表格顺序来组织SDC,每一类之间加注释分隔。

举个例子,一份结构清晰的SDC开头应该是这样的:

# ========================================
# Clock Definitions
# ========================================
create_clock -name sys_clk -period 10.000 [get_ports sys_clk]
create_generated_clock -name div_clk -divide_by 2 \
    -source [get_ports sys_clk] [get_pins u_div/clk_out]

# ========================================
# Clock Groups
# ========================================
set_clock_groups -asynchronous \
    -group {sys_clk} \
    -group {div_clk}

# ========================================
# Input and Output Delays
# ========================================
set_input_delay -clock sys_clk -max 2.000 [get_ports data_in*]
set_output_delay -clock sys_clk -min 0.500 [get_ports data_out*]

# ========================================
# False Paths and Multicycle Paths
# ========================================
set_false_path -from [get_ports rst_n]
set_multicycle_path -setup 2 -from [get_clocks div_clk] -to [get_clocks sys_clk]

# ========================================
# Drive and Load
# ========================================
set_drive 1.0 [get_ports data_in*]
set_load 0.5 [get_ports data_out*]

你看,用注释隔开,结构一目了然。我每次接手新项目的SDC,第一件事就是先用这种格式帮它重构一遍。通常重构完,就能发现很多冗余或矛盾的约束。

常见误区

不要以为SDC文件越长越好。我见过一个设计只有500行RTL代码,SDC却有800行。你以为这样很齐全,其实里面有大量无用的set_false_path。嗯,如果你发现工程里SDC比RTL还长,就要警惕了——大概率是约束激增的早期信号。

SDC文件的核心命令详解

重点说几个最常用的命令。这些是我在日常排查中频繁打交道的。

1. create_clock

这是SDC里最重要的命令。定义了时钟的周期和波形。注意,它定义的是时钟源,而不是时钟树上的内部节点。通常加在port上。

create_clock -name clk_core -period 5.000 [get_ports clk_core_in]

这个命令说明clk_core_in这个端口进来的时钟周期是5ns,也就是200MHz。默认波形是50%占空比,上升沿在0时刻,下降沿在2.5ns时刻。

2. create_generated_clock

分频或倍频后的时钟。它需要指定源时钟和分频关系。

create_generated_clock -name clk_halved \
    -divide_by 2 -source [get_ports clk_core_in] \
    [get_pins u_divider/clk_out]

这里容易搞错的是-source参数。它指向的是源时钟所在的端口或引脚,而不是源时钟的时钟名。我见过有人莫名其妙写-source [get_clocks clk_core],结果工具报错找不到对象——因为get_clocks返回的是时钟对象,但-source需要的是物理节点。

3. set_clock_groups

这个命令能帮工具省下大量STA时间。如果两个时钟是异步的,直接告诉工具它们永远不会同步,路径不用分析。

set_clock_groups -asynchronous \
    -group {clk_a clk_b} \
    -group {clk_c}

意思是clk_a和clk_b属于同一组,它们与clk_c是异步的。不同组之间的路径,工具全部跳过。

4. set_false_path

这个最容易被滥用。正确的用法是:确定这条路径永远不会影响芯片功能——比如测试模式下的路径、复位信号、跨异步时钟域的安全路径。但我见过有人在SDC里写了几百条set_false_path,只为了掩盖STA违例。

我的经验

每一次使用set_false_path,都应该在注释里写清楚理由。比如这样:

# Reset is asynchronous, no need to check timing
set_false_path -from [get_ports rst_n]

如果以后有人来排查约束激增问题,看到注释就能知道这条约束到底是必要的还是多余的。

5. set_multicycle_path

当一条数据路径需要多个时钟周期才能稳定,就用这个命令。典型场景是握手协议或片内SRAM的读操作。

set_multicycle_path -setup 2 -from [get_pins u_controller/data_reg/CK] \
    -to [get_pins u_ram/address]

这里-setup 2表示数据可以在2个时钟周期后到达。注意,工具默认还会同时调整hold检查,所以通常还要配合-hold参数。嗯,这个有点绕,后面专门讲。

SDC的执行顺序

工具读取SDC时,是按行从上往下执行的。写在前面的约束可能会影响后面的约束行为。举个例子:

create_clock -name clk_vco -period 2.000 [get_pins u_pll/clk_vco]
create_clock -name clk_final -period 4.000 [get_ports clk_final_in]

如果先写了pll的时钟,后写了端口的时钟,那么工具在处理后面这条命令时,已经知道前面有个200MHz的时钟存在。如果两条时钟有路径交叉,工具会按后来定义的时钟做分析。

我建议把时钟定义放在最前面。然后是时钟组、IO延迟、异常路径。这样执行顺序最安全。

一个小技巧

可以用report_clock命令来验证SDC里的时钟定义是否和预期一致。我一般在写完约束后,先跑一下report_clock,看看时钟数量、周期、源点有没有问题。这能发现大多数笔误。

本章小结

时序约束就是给工具的一张限速地图。SDC文件是这张地图的载体。它的结构其实很清晰:时钟定义→时钟组→IO延迟→异常路径→驱动/负载。只要按这个顺序组织,后面的排查工作就会轻松很多。

下一节我们会深入时钟约束的具体写法。但在此之前,我建议你把手里项目的SDC文件打开,按照今天的结构重构一遍。相信我,你会发现不少问题的苗头。

时序约束核心逻辑:从设计到STA RTL设计 物理信息(库/网表) SDC时序约束 时钟 | IO延迟 | 例外路径 STA引擎 建立时间/保持时间检查 违例报告 优化迭代 SDC是STA的输入核心 没有约束,STA无法判断好坏 流程:RTL设计 + SDC约束 → STA引擎 → 违例报告 → 优化迭代 → 调整SDC