第二章:约束文件入门——SDC文件结构、时钟约束与IO约束
大家好,我是你们的时序约束讲师。今天咱们聊聊约束文件的基础——SDC。说实话,我见过不少工程师,写RTL代码一把好手,但一碰到SDC就头大。其实这东西没那么玄乎,说白了就是告诉工具:你的芯片想跑多快、数据什么时候来、什么时候走。
我个人习惯把SDC文件当成芯片的「交通规则」。没有它,综合工具和STA工具就像没交警的路口——乱成一锅粥。好,咱们直接进入正题。
2.1 SDC文件结构:别把它当黑盒子
SDC(Synopsys Design Constraints)是行业标准格式。你想想看,不管是Synopsys的PrimeTime,还是Cadence的Tempus,甚至开源工具,都认这个格式。为什么?因为它简单、清晰。
一个典型的SDC文件,结构大致是这样的:
# 时钟定义
create_clock -name clk_sys -period 10 [get_ports clk]
create_generated_clock -name clk_div2 -source [get_ports clk] -divide_by 2 [get_pins u_div/clk_out]
# IO约束
set_input_delay -clock clk_sys -max 5 [get_ports data_in]
set_output_delay -clock clk_sys -min 2 [get_ports data_out]
# 时序例外(后面章节会细讲)
set_false_path -from [get_clocks clk_sys] -to [get_clocks clk_div2]
嗯,这里要注意:SDC是顺序执行的。后面的约束可能会覆盖前面的。我在项目中遇到过有人把set_input_delay写在create_clock前面,结果工具报了一堆warning——因为时钟还没定义呢。
2.2 create_clock:一切时序的起点
时钟是时序分析的「心跳」。没有时钟,STA根本没法干活。create_clock就是告诉工具:嘿,这里有个时钟,周期多少,从哪个端口进来。
基本语法:
create_clock -name <时钟名> -period <周期(ns)> [get_ports <端口名>]
举个例子:
create_clock -name clk_100m -period 10 [get_ports clk_in]
这表示:定义一个叫clk_100m的时钟,周期10ns(也就是100MHz),从端口clk_in进入芯片。
你可能会问:为什么还要给时钟起名字?直接用端口名不行吗?说实话,在简单设计中确实可以。但一旦涉及多时钟域、时钟复用,名字就很重要了。我习惯用「时钟名_频率」的命名方式,比如clk_100m、clk_50m,一目了然。
2.3 create_generated_clock:派生时钟的「身份证」
实际芯片中,很少有只有一个时钟的情况。PLL分频、门控时钟、倍频……这些派生时钟怎么定义?用create_generated_clock。
基本语法:
create_generated_clock -name <时钟名> -source <源时钟> -divide_by <分频系数> [get_pins <输出引脚>]
举个例子:
create_generated_clock -name clk_50m -source [get_ports clk_in] -divide_by 2 [get_pins u_div/clk_out]
这表示:从clk_in输入100MHz时钟,经过分频器u_div,在clk_out引脚上输出50MHz时钟,命名为clk_50m。
这里有个关键点:-source指定的是源时钟的端口或引脚,而不是源时钟的名字。我刚开始做的时候也搞混过,后来养成了习惯——先看原理图,找到时钟路径,再写约束。
除了-divide_by,还有-multiply_by(倍频)、-edges(边沿指定)等选项。比如:
# 2倍频
create_generated_clock -name clk_200m -source [get_ports clk_in] -multiply_by 2 [get_pins u_pll/clk_out]
# 指定上升沿和下降沿(用于DDR等)
create_generated_clock -name clk_ddr -source [get_ports clk_in] -edges {1 3 5} [get_pins u_ddr/clk_out]
2.4 set_input_delay / set_output_delay:芯片与外界的「握手协议」
芯片不是孤岛。它要和外部器件通信——比如FPGA、DDR颗粒、ADC/DAC。外部器件的数据什么时候来?芯片的数据什么时候该出去?这就是set_input_delay和set_output_delay要干的活。
2.4.1 set_input_delay:数据到达芯片的时间
基本语法:
set_input_delay -clock <时钟名> -max <最大延迟(ns)> -min <最小延迟(ns)> [get_ports <输入端口>]
举个例子:
set_input_delay -clock clk_100m -max 5 -min 2 [get_ports data_in]
这表示:数据data_in相对于时钟clk_100m的上升沿,最大延迟5ns,最小延迟2ns。换句话说,数据在时钟沿之后2ns到5ns之间到达芯片引脚。
你可能会问:这个值怎么来的?答案是——看外部器件的datasheet。比如外部器件是FPGA,它输出数据相对于时钟的延迟是Tco(Clock-to-Output),再加上PCB走线延迟,就是input delay。
2.4.2 set_output_delay:芯片数据被外部采样的时间
基本语法:
set_output_delay -clock <时钟名> -max <最大延迟(ns)> -min <最小延迟(ns)> [get_ports <输出端口>]
举个例子:
set_output_delay -clock clk_100m -max 4 -min 1 [get_ports data_out]
这表示:外部器件在时钟沿之后1ns到4ns之间需要采样到数据。也就是说,芯片内部的数据必须在时钟沿之前(setup)和之后(hold)满足这个窗口。
注意:output delay是「外部器件的需求」,不是「芯片内部的实际延迟」。工具会根据这个需求,反推出芯片内部需要满足的时序。
2.5 知识体系总览
说了这么多,咱们用一张图来总结本章的核心逻辑:
2.6 本章小结
好,咱们捋一捋今天的内容:
- SDC文件结构:顺序执行,时钟定义在前,IO约束在后。注释写清楚版本和用途。
- create_clock:定义主时钟,只能用在端口上。周期、名字、端口三要素。
- create_generated_clock:定义派生时钟,指定源时钟和分频/倍频关系。
- set_input_delay:告诉工具数据从外部到达芯片的时间窗口。
- set_output_delay:告诉工具外部器件需要什么时间窗口来采样数据。
说实话,这些基础约束看起来简单,但实际项目中80%的时序问题都出在这里。我见过有人把input delay和output delay搞反了,结果综合出来的电路根本不能用。所以我的建议是:每写一条约束,都问自己一句——「这个值合理吗?有没有datasheet支撑?」
下一章咱们会深入时序例外的世界——set_false_path、set_multicycle_path这些「大杀器」。不过在那之前,先把今天的基础打牢。记住:基础不牢,地动山摇。