2. SDC约束基础:语法、常用命令与文件组织
SDC,全称Synopsys Design Constraints,是FPGA时序约束的行业标准格式。说白了,它就是一份告诉工具“你的设计要在什么条件下工作”的说明书。
我刚入行那会儿,觉得SDC就是个麻烦事。后来有一次,一个跑得好好的设计,换了个器件型号就崩了。查了三天,最后发现是约束文件里少写了一条时钟定义。从那以后,我再也不敢小看SDC了。
2.1 SDC语法基础
SDC的语法其实很简单。它基于Tcl(Tool Command Language),所以每条命令都是一个Tcl函数调用。基本格式就两种:
# 带参数的命令
命令名 -选项1 值1 -选项2 值2 对象
# 不带参数的命令
命令名 对象
举个例子:
create_clock -name sys_clk -period 10.000 [get_ports clk_in]
这条命令的意思是:创建一个名为sys_clk的时钟,周期10ns(对应100MHz),源端口是clk_in。
#开头。我习惯在每个约束块前面加一段注释,说明这个约束是干什么的。半年后你自己回来看,会感谢这个习惯的。
2.2 常用命令概览
SDC命令很多,但日常用的也就那么十来条。我把它们分成三类:
| 类别 | 命令 | 作用 |
|---|---|---|
| 时钟约束 | create_clock |
定义主时钟 |
| 时钟约束 | create_generated_clock |
定义衍生时钟(PLL输出等) |
| 时钟约束 | set_clock_uncertainty |
设置时钟抖动和裕量 |
| I/O约束 | set_input_delay |
定义输入信号相对于时钟的延迟 |
| I/O约束 | set_output_delay |
定义输出信号相对于时钟的延迟 |
| I/O约束 | set_input_transition |
设置输入信号的转换时间 |
| I/O约束 | set_output_load |
设置输出端口的负载电容 |
| 例外约束 | set_false_path |
标记不需要时序分析的路径 |
| 例外约束 | set_multicycle_path |
定义跨多个时钟周期的路径 |
| 例外约束 | set_max_delay / set_min_delay |
自定义路径延迟上限/下限 |
2.2.1 create_clock — 一切约束的起点
没有时钟,时序分析就无从谈起。create_clock是最基础的命令。它的核心参数就几个:
create_clock -name <时钟名> -period <周期(ns)> \
[ -waveform {上升沿 下降沿} ] \
[ -add ] [get_ports <端口名>]
注意-waveform参数。默认情况下,时钟的上升沿在0ns,下降沿在period/2。如果你的时钟是占空比50%的方波,可以省略这个参数。但如果是非50%占空比,或者有相位偏移,就必须显式指定。
-waveform,结果工具默认认为上升沿在0ns。整个设计的时序分析全错了。后来花了整整一天才找到原因。所以,只要时钟波形不是标准的50%占空比方波,就老老实实写-waveform。
2.2.2 set_input_delay / set_output_delay — 连接片外世界
这两个命令是FPGA与外部器件通信的关键。它们告诉工具:数据相对于时钟,在芯片引脚上是什么时候有效的。
# 输入延迟:外部器件发送数据到FPGA引脚的时间
set_input_delay -clock clk -max 2.5 [get_ports data_in]
set_input_delay -clock clk -min 1.0 [get_ports data_in]
# 输出延迟:FPGA输出数据到外部器件被采样的时间
set_output_delay -clock clk -max 3.0 [get_ports data_out]
set_output_delay -clock clk -min 0.5 [get_ports data_out]
这里-max和-min分别对应最坏情况和最好情况。工具会用-max检查建立时间,用-min检查保持时间。
set_input_delay和set_output_delay的方向。我教你一个记忆方法:站在FPGA内部看引脚。set_input_delay:数据从外部进来,已经走了一段路才到FPGA引脚,所以是“延迟”。set_output_delay:数据从FPGA出去,还要走一段路才能被外部器件采到,所以也是“延迟”。说白了,这两个值描述的都是“FPGA引脚之外”的延迟。
2.2.3 set_false_path / set_multicycle_path — 处理特殊路径
不是所有路径都需要在一个时钟周期内完成。比如跨时钟域的同步器路径、复位信号路径,这些就可以标记为false_path。
# 跨时钟域路径,不需要时序分析
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# 复位信号,不需要时序分析
set_false_path -to [get_pins *rst_n_reg*]
而set_multicycle_path用于那些需要多个周期才能完成的路径。比如一个计数器,每4个时钟周期才更新一次输出:
set_multicycle_path -setup 4 -from [get_pins cnt_reg*] -to [get_pins out_reg*]
set_multicycle_path -hold 3 -from [get_pins cnt_reg*] -to [get_pins out_reg*]
set_multicycle_path时,-hold的值通常是-setup减1。这是很多新手容易忽略的。我曾经见过一个设计,只写了-setup 4没写-hold,结果保持时间检查全错了。
2.3 SDC文件的组织与结构
一个完整的SDC文件,不是把命令随便堆在一起就行。我见过最乱的SDC文件,2000多行,没有注释,没有分组,改一个参数要找半天。
我个人习惯把SDC文件分成几个逻辑块:
- 时钟定义块 — 所有
create_clock和create_generated_clock - 时钟特性块 —
set_clock_uncertainty、set_clock_latency等 - I/O约束块 — 所有
set_input_delay、set_output_delay等 - 例外约束块 —
set_false_path、set_multicycle_path等 - 其他约束块 —
set_max_fanout、set_max_transition等
每个块之间用空行和注释分隔。举个例子:
# ============================================================
# 1. 时钟定义
# ============================================================
# 主时钟:100MHz,来自板级晶振
create_clock -name sys_clk -period 10.000 [get_ports clk_in]
# PLL输出:200MHz,0度相移
create_generated_clock -name pll_clk_200m \
-source [get_ports clk_in] \
-multiply_by 20 -divide_by 1 \
[get_pins pll_inst/clk_out]
# PLL输出:100MHz,90度相移
create_generated_clock -name pll_clk_100m_90 \
-source [get_ports clk_in] \
-multiply_by 10 -divide_by 1 \
-phase 90 \
[get_pins pll_inst/clk_out_90]
# ============================================================
# 2. 时钟特性
# ============================================================
set_clock_uncertainty -setup 0.200 [get_clocks sys_clk]
set_clock_uncertainty -hold 0.050 [get_clocks sys_clk]
# ============================================================
# 3. I/O约束
# ============================================================
# 输入:ADC数据,相对于sys_clk
set_input_delay -clock sys_clk -max 2.0 [get_ports adc_data*]
set_input_delay -clock sys_clk -min 0.5 [get_ports adc_data*]
# 输出:DAC数据,相对于pll_clk_200m
set_output_delay -clock pll_clk_200m -max 1.5 [get_ports dac_data*]
set_output_delay -clock pll_clk_200m -min 0.3 [get_ports dac_data*]
# ============================================================
# 4. 例外约束
# ============================================================
# 跨时钟域:sys_clk -> pll_clk_200m
set_false_path -from [get_clocks sys_clk] -to [get_clocks pll_clk_200m]
# 复位路径
set_false_path -to [get_pins *rst_n_reg*]
你想想看,这样组织之后,是不是一目了然?改时钟频率,去第一个块;调I/O时序,去第三个块。维护成本大大降低。
2.4 SDC知识体系总览
为了让你对SDC有一个整体的认识,我画了一张图:
这张图把SDC的核心内容串起来了。从顶层文件,到三大类约束,再到具体命令,最后落到文件组织和最佳实践。你每次写SDC的时候,可以对照这张图,看看自己有没有遗漏什么。