第三章 SDC约束编写实战:创建时钟、生成时钟、输入输出延迟约束、伪路径与多周期路径

各位好,我是老李。今天咱们聊聊SDC约束。说实话,我刚入行那会儿,觉得SDC就是随便写写,反正工具能跑通就行。结果呢?有一次项目都快tape out了,时序分析报告里一堆violation,查了半天才发现是生成时钟的约束写错了。从那以后,我对SDC再也不敢马虎了。

SDC约束,说白了就是告诉工具你的芯片想怎么跑。你约束得越精准,工具给你的结果就越靠谱。咱们今天就把创建时钟、生成时钟、输入输出延迟、伪路径和多周期路径这几个核心点掰开揉碎讲清楚。

SDC约束实战 创建时钟 create_clock 生成时钟 generate_clock IO延迟约束 set_input/output_delay 例外路径 伪路径/多周期 四大核心约束:时钟定义 → 生成关系 → IO边界 → 例外处理

3.1 创建时钟:芯片的脉搏

时钟是芯片的脉搏。没有时钟,整个设计就是一堆死电路。create_clock这个命令,就是告诉工具你的时钟长什么样。

最基本的写法是这样的:

create_clock -name clk_sys -period 10 [get_ports clk_in]

这条命令创建了一个叫clk_sys的时钟,周期10ns,也就是100MHz。挂在clk_in这个端口上。

我个人习惯,创建时钟时一定要加-name。为什么?因为工具会自动生成一个名字,有时候会跟你的信号名混在一起,查起来特别费劲。你想想看,一个设计里几十个时钟,如果都叫clk_1、clk_2,后期分析时序报告时头都大了。

如果时钟有抖动,或者有占空比要求,可以这样写:

create_clock -name clk_sys -period 10 -waveform {0 5} [get_ports clk_in]

-waveform {0 5}表示上升沿在0ns,下降沿在5ns。占空比50%。

小技巧: 如果时钟是差分对,比如clk_p和clk_n,只需要在clk_p上创建时钟。工具会自动识别差分对关系。

3.2 生成时钟:分频与倍频的学问

生成时钟,说白了就是从一个已有的时钟派生出来的时钟。比如PLL出来的时钟,或者通过分频器分出来的时钟。

我曾经在一个项目中,工程师直接用create_clock去定义分频时钟。结果呢?工具把分频时钟和源时钟当成两个完全独立的时钟来分析,时序路径全乱了。正确的做法是用create_generated_clock。

create_generated_clock -name clk_div2 -source [get_ports clk_in] -divide_by 2 [get_pins divider/Q]

这条命令告诉工具:clk_div2是从clk_in分频2倍得到的,挂在divider模块的Q端上。

生成时钟有几个关键参数:

  • -source:源时钟的端口或引脚。注意,不是源时钟的名字,是物理位置。
  • -divide_by / -multiply_by:分频或倍频系数。
  • -edges:如果分频关系比较复杂,可以用边沿列表来定义。

举个例子,如果有一个时钟是源时钟的3分频,但占空比不是50%,可以这样写:

create_generated_clock -name clk_div3 -source [get_ports clk_in] -edges {1 3 5} [get_pins divider/Q]

-edges {1 3 5}表示:源时钟的第1个上升沿对应生成时钟的上升沿,第3个上升沿对应下降沿,第5个上升沿对应下一个上升沿。说白了就是每3个源时钟周期出一个生成时钟周期。

注意: 生成时钟的-source一定要指定到源时钟的物理位置,而不是时钟名字。我见过有人写成-source [get_clocks clk_in],这是错的!get_clocks返回的是时钟对象,不是物理位置。

3.3 输入输出延迟约束:芯片与外界的约定

芯片不是孤立存在的。它要跟外面的器件通信,比如DDR颗粒、ADC、DAC等。输入输出延迟约束,就是告诉工具芯片外部的时间关系。

输入延迟的写法:

set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]

这条命令表示:data_in这个端口的数据,相对于clk_sys的时钟沿,最晚2.5ns到达芯片引脚。

输出延迟的写法:

set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]

表示:data_out的数据,在时钟沿之后3.0ns内必须被外部器件采样到。

我个人习惯,输入输出延迟一定要分-max和-min。为什么?因为-max对应setup检查,-min对应hold检查。如果你只写一个值,工具会默认max和min相同。但实际电路中,PVT变化会导致延迟有波动,所以最好分开写。

参数 含义 对应检查
-max 最大延迟(最晚到达/最早需要) setup(建立时间)
-min 最小延迟(最早到达/最晚需要) hold(保持时间)
-clock_fall 使用时钟下降沿 双边沿采样
-add_delay 对同一个端口添加多个延迟约束 多时钟域
核心要点: 输入延迟 = 外部器件Tco + PCB走线延迟。输出延迟 = 外部器件Tsu + PCB走线延迟。这个公式一定要记住。

3.4 伪路径:那些不需要关心的路径

伪路径,就是那些你明确知道不需要做时序分析的路径。比如跨时钟域的同步器路径、测试模式下的路径、上电复位时的路径。

写法很简单:

set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

这条命令告诉工具:从clk_a到clk_b的所有路径,都不用做时序分析。

也可以指定具体的起点和终点:

set_false_path -from [get_pins sync_inst/D] -to [get_pins sync_inst/Q]

我曾经犯过一个错误:把异步FIFO的读写指针比较路径设成了伪路径。结果呢?功能仿真没问题,但芯片跑起来偶尔会读到错误数据。后来才发现,虽然读写指针是异步的,但比较逻辑本身需要满足一定的时序要求。所以伪路径不是随便设的,一定要确认清楚。

避坑指南: 伪路径设多了,工具会忽略很多路径,可能导致setup/hold违例没有被发现。设少了,工具会花大量时间分析不重要的路径。所以伪路径要「精准打击」,不要「地毯式轰炸」。

3.5 多周期路径:给数据多一个周期的时间

多周期路径,就是那些数据不需要在一个时钟周期内完成传输的路径。比如一些复杂的组合逻辑,或者低频使能信号。

写法:

set_multicycle_path -setup 2 -from [get_pins comp_inst/A] -to [get_pins comp_inst/B]

这条命令表示:从A到B的路径,setup检查时允许使用2个时钟周期。

注意,多周期路径的hold检查默认会跟着变。如果你只设了-setup 2,工具会认为hold检查也推迟一个周期。但实际中,hold检查通常不需要推迟。所以正确的写法是:

set_multicycle_path -setup 2 -from [get_pins comp_inst/A] -to [get_pins comp_inst/B]
set_multicycle_path -hold 1 -from [get_pins comp_inst/A] -to [get_pins comp_inst/B]

这样,setup检查用2个周期,hold检查用1个周期。嗯,这里要注意,hold的数值是相对于setup的。如果-setup 2,那么-hold 1表示hold检查在setup检查的1个周期前。

我建议,写多周期路径时,一定要把setup和hold都写清楚。不要偷懒只写一个。否则工具可能会给你一个意想不到的结果。

个人经验: 多周期路径常用于以下场景:
1. 除法器、乘法器等复杂运算单元
2. 低频使能信号(比如每4个周期才采一次的数据)
3. 跨时钟域的同步器(虽然通常用伪路径,但有时也用多周期)

3.6 实战总结:一个完整的SDC示例

说了这么多,咱们来个完整的例子。假设有一个芯片,输入时钟100MHz,内部有一个PLL产生200MHz和50MHz时钟,还有几个外部接口。

# 1. 创建主时钟
create_clock -name clk_100m -period 10 [get_ports clk_in]

# 2. 生成时钟(PLL输出)
create_generated_clock -name clk_200m -source [get_ports clk_in] -multiply_by 2 [get_pins pll/clk_out1]
create_generated_clock -name clk_50m -source [get_ports clk_in] -divide_by 2 [get_pins pll/clk_out2]

# 3. 输入延迟
set_input_delay -clock clk_100m -max 3.0 [get_ports data_in]
set_input_delay -clock clk_100m -min 1.0 [get_ports data_in]

# 4. 输出延迟
set_output_delay -clock clk_200m -max 2.5 [get_ports data_out]
set_output_delay -clock clk_200m -min 0.5 [get_ports data_out]

# 5. 伪路径(异步FIFO跨时钟域)
set_false_path -from [get_clocks clk_200m] -to [get_clocks clk_50m]
set_false_path -from [get_clocks clk_50m] -to [get_clocks clk_200m]

# 6. 多周期路径(复杂运算)
set_multicycle_path -setup 3 -from [get_pins div_inst/A] -to [get_pins div_inst/B]
set_multicycle_path -hold 2 -from [get_pins div_inst/A] -to [get_pins div_inst/B]

这个例子基本覆盖了咱们今天讲的所有内容。你写SDC时,可以按照这个顺序来:先定义时钟,再定义生成时钟,然后约束IO,最后处理例外路径。

好了,今天的实战内容就到这里。SDC约束这东西,光看文档是学不会的。你得亲手写,亲手跑,亲手看时序报告。遇到问题别慌,先检查时钟定义对不对,再检查IO延迟合不合理,最后看看伪路径和多周期有没有漏掉。按这个思路来,大部分问题都能解决。


公众号:蓝海资料掘金营,微信deep3321