第二讲:SDC约束入门——文件结构、时钟定义与时钟组

大家好,欢迎来到时序约束实战的第二讲。

上一讲我们聊了STA的基本概念。今天,咱们直接上手干活——学SDC。

SDC,全称Synopsys Design Constraints。说白了,就是告诉工具:你的芯片要在什么频率下工作,哪些路径要特别照顾。我刚开始学的时候,觉得这玩意儿不就是写几行命令吗?后来踩了坑才发现,SDC写得好不好,直接决定了芯片能不能跑起来。

一、SDC文件长什么样?

SDC文件本质上是一个文本文件,里面是一行一行的Tcl命令。工具(比如Design Compiler、PrimeTime)会逐行读取并执行。

一个典型的SDC文件结构是这样的:

# ============================================
# 1. 时钟定义
# ============================================
create_clock -name clk_sys -period 10 [get_ports clk]
create_clock -name clk_uart -period 83.33 [get_ports uart_clk]

# ============================================
# 2. 时钟特性(抖动、延迟等)
# ============================================
set_clock_uncertainty -setup 0.2 [get_clocks clk_sys]
set_clock_latency -source 0.5 [get_clocks clk_sys]

# ============================================
# 3. 输入输出延迟
# ============================================
set_input_delay -clock clk_sys -max 2.0 [get_ports data_in]
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]

# ============================================
# 4. 时序例外
# ============================================
set_false_path -from [get_clocks clk_uart] -to [get_clocks clk_sys]
set_multicycle_path -setup 2 -from [get_pins reg_a/Q] -to [get_pins reg_b/D]

# ============================================
# 5. 设计约束
# ============================================
set_max_fanout 8 [current_design]
set_max_transition 0.5 [current_design]

嗯,结构很清晰对吧?从上到下,先定义时钟,再约束IO,最后处理例外路径。我个人习惯把SDC分成这五大块,每块之间用注释隔开。这样后期查问题,一眼就能定位。

小技巧: 我建议每个SDC文件开头都加一个版本注释,比如 # Rev 1.2 - 2024-03-15 - Added UART clock group。团队协作时,这能省去很多沟通成本。

二、时钟定义——create_clock 是核心

时钟是时序分析的基准。没有时钟,STA就没法做。create_clock命令就是用来告诉工具:哪个端口是时钟,频率是多少。

基本语法:

create_clock -name <时钟名> -period <周期(ns)> [get_ports <端口名>]

举个例子:

create_clock -name clk_core -period 5.0 [get_ports clk]

这条命令的意思是:端口clk是一个时钟,名字叫clk_core,周期5ns(对应200MHz)。

你可能会问:为什么还要给时钟起个名字?直接用端口名不行吗?

行,但不好。我在项目中遇到过这样的情况:同一个端口在不同模式下频率不同。比如芯片测试模式用50MHz,正常工作用200MHz。这时候你就要定义两个时钟:

create_clock -name clk_func -period 5.0 [get_ports clk]
create_clock -name clk_test -period 20.0 [get_ports clk] -add

注意那个 -add 参数。不加的话,第二个create_clock会覆盖第一个。加了,工具就知道这个端口上有两个时钟,分别用于不同场景。

时钟的波形定义

默认情况下,时钟的上升沿在0ns,下降沿在周期的一半。但有些时钟不是50%占空比,比如DDR接口的时钟。这时候要用 -waveform 参数:

# 占空比 60%:高电平持续 3ns,低电平持续 2ns(周期5ns)
create_clock -name clk_ddr -period 5.0 -waveform {0 3.0} [get_ports ddr_clk]

-waveform 后面跟两个值:上升沿时间和下降沿时间。上面这个例子,上升沿在0ns,下降沿在3ns,所以高电平是3ns,低电平是2ns。

重点: 时钟定义是SDC的基石。时钟定义错了,后面所有约束都是白搭。我见过有人把周期写反了,200MHz写成了20ns(应该是5ns),结果综合出来的电路根本跑不到目标频率。

三、时钟组——clock_group 解决跨时钟域问题

一个芯片里通常有多个时钟。比如CPU核跑1GHz,UART跑12MHz,USB跑480MHz。这些时钟之间如果没有任何关系,工具默认会分析它们之间的所有路径。

但问题是:跨时钟域的路径,真的需要分析吗?

不一定。如果两个时钟域之间用了同步器(比如两级触发器),那这些路径就是异步路径,不需要做setup/hold检查。如果不告诉工具,工具会傻乎乎地去分析,结果报出一堆假违例。

这时候就要用 set_clock_groups 命令:

set_clock_groups -asynchronous -group [get_clocks clk_core] -group [get_clocks clk_uart]

这条命令告诉工具:clk_core和clk_uart是异步时钟组,它们之间的路径不需要做时序分析。

还有一种情况是互斥时钟。比如芯片有两种工作模式,模式A用clk_a,模式B用clk_b,但两个时钟不会同时存在:

set_clock_groups -exclusive -group [get_clocks clk_a] -group [get_clocks clk_b]

这样工具就知道:分析模式A时只看clk_a的路径,分析模式B时只看clk_b的路径,不会把两个时钟混在一起分析。

注意: 我曾经犯过一个错误——把异步时钟组设成了互斥组。结果工具把两个时钟之间的路径全忽略了,但实际上这些路径是有同步器的,需要做CDC检查。后来花了两天时间才排查出来。所以,一定要搞清楚你的设计里哪些是异步路径,哪些是互斥路径。

四、知识体系结构图

下面这张图总结了SDC约束的核心逻辑:

SDC约束核心知识体系 SDC 约束文件 时钟定义 (create_clock) 时钟组 (clock_group) IO约束与例外 周期与占空比 多时钟 (-add) 异步组 互斥组 输入/输出延迟 时钟抖动/延迟设置 生成时钟 (gen_clk) CDC路径处理 多周期路径 核心原则:先定义时钟 → 再分组 → 最后约束IO和例外

五、实战建议与避坑指南

讲完理论,分享几个我实际项目中的经验:

  1. 时钟命名要有规范。 我习惯用 clk_模块名 的格式,比如 clk_core、clk_ddr、clk_ahb。这样在report_timing里一眼就能看出是哪个时钟。
  2. 时钟组不要乱设。 我曾经把两个同源时钟设成了异步组,结果工具忽略了所有跨时钟域路径,导致流片后功能异常。记住:同源时钟(比如同一个PLL分出来的)一般不需要设异步组。
  3. 用report_clock检查。 写完SDC后,先跑一下 report_clock 看看时钟定义对不对。我每次都会检查:周期对不对?有没有漏掉时钟?有没有多余的时钟?
  4. 版本管理。 SDC文件一定要纳入版本控制。我见过有人直接改服务器上的SDC,结果改错了回退不了。用Git管理,每次修改都有记录。
一句话总结: SDC约束的核心就三件事——定义时钟、分组时钟、约束路径。时钟定义是基础,时钟组解决跨时钟域问题,IO约束和例外处理特殊路径。把这三点搞明白,SDC就算入门了。

好了,这一讲就到这里。下一讲我们会深入 create_generated_clock——生成时钟的定义,这是处理PLL分频、倍频时钟的关键。到时候见。


可可精品资料库