写这篇笔记之前,我一直在犹豫要不要把SDC单独拎出来写。因为市面上讲SDC的文章太多了,但大多数不是停留在命令列表的罗列,就是直接丢一份“万能模板”让新手去抄。真正把约束背后那个“为什么”讲清楚的,少之又少。尤其是综合阶段的SDC,和PR阶段给的约束虽然语法一样,但语义和侧重点有微妙差别——综合阶段约束写得对不对,直接决定了后面PR能不能顺利收时序。
这篇笔记就基于我自己在数模混合芯片和数字芯片流程里积累的经验,把综合阶段最常用到的SDC约束命令、参数选择依据、以及实战中的坑全部梳理一遍。这篇文章不追求覆盖SDC的全部语法,而是聚焦最常见、最核心的那一部分,目标是让你看完之后能独立写出一份像样的综合约束文件,并且在遇到时序问题时知道从哪下手。
1. 综合阶段与SDC到底是怎么协作的
1.1 SDC在综合流程中的角色定位
逻辑综合做的是三件事:把RTL转成门级网表、完成逻辑映射、基于时序目标做优化。综合工具的优化引擎是“目标驱动”的,目标就是SDC定义的约束。没有约束,综合器不知道时钟长什么样、不知道输入信号什么时候到、不知道输出信号什么时候必须有效,它就只能瞎猜,结果往往是一份时序崩坏的门级网表。
我经常用一句话跟新人解释SDC的作用:SDC就是设计者写给综合工具的“需求说明书”,而且是用工具能听得懂的Tcl语法写的。时序、面积、DRC规则全部写在这里面,综合器据此理解设计的意图,决定在关键路径上花多大代价做优化。
综合阶段的SDC还有一个特殊角色——它是贯穿全流程的“计时基准”。综合结束之后,形式验证要看它,时序签核要参照它,后端PR也要基于同一份(或扩展后的)SDC来规划布局布线。所以综合阶段的SDC写得有没有逻辑、对象引用是否正确,影响的不只是这一版网表的QoR,而是后续整个流程的稳定性。
1.2 读入SDC的时机与综合工具的基本流程
拿Synopsys Design Compiler来说,一份标准的综合脚本流程通常是这样的:
- 读入lib库文件和RTL设计文件(analyze、elaborate)
- 设置环境变量、链接设计(link)
- 读入SDC约束文件
- 执行综合优化(compile或compile_ultra)
- 导出网表、SDC、报告
DC要求在综合前完成时钟约束的设置,因为时钟是timing计算的骨架。输入输出延迟、时序例外、DRC约束都是在这副骨架之上补充的。顺序上没有硬性规定,但经验上先定义时钟、再设例外、最后做环境约束和DRC,思路最清晰,排查问题也最快。
Cadence Genus的流程略有差异:读入设计后同样通过read_sdc读入约束,大部分SDC命令直接兼容,只是个别命令的行为细节和DC不完全一致。这块差异我后面在常见问题里专门写。
2. 时钟约束:整份SDC的地基
2.1 创建主时钟的完整语法与参数依据
时钟约束是SDC里优先级最高的部分。综合工具在分析时序时,所有路径的起点和终点都以时钟边沿为参照。主时钟一般定义在芯片的输入引脚上,语法如下:
create_clock -name clk_main -period 10 -waveform {0 5} [get_ports clk_main]-period的单位是ns,表示时钟周期;-waveform指定上升沿和下降沿的绝对时间点。占空比50%、周期10ns的时钟,waveform写{0 5}即可。如果系统时钟是DDR接口那种双沿采样时钟,或者有非50%占空比的特殊时钟,就需要根据实际波形调整waveform参数。
这里有一个新手容易忽略的点:-name定义的名字和[get_ports]引用的物理端口名可以不同。SDC内部以-name定义的时钟名为准,后续所有引用都必须用它。端口名和时钟名不一致时,工具不会报错,但后续set_clock_groups、set_clock_uncertainty等命令如果混用了端口名,会静默失败——结果就是约束没生效,时序分析结果完全不正常。
2.2 时钟不确定度、延迟与转换时间的设置逻辑
set_clock_uncertainty模拟的是真实时钟的不理想性,包含时钟抖动(jitter)和时钟偏斜(skew)的预算。综合阶段没有时钟树,skew完全是预估的,所以这个值一般要在标准单元库的jitter基础上加一部分裕量。
set_clock_uncertainty -setup 0.2 [get_clocks clk_main] set_clock_uncertainty -hold 0.05 [get_clocks clk_main]我自己的经验是:高频模块(比如CPU核心、DDR控制器)的setup uncertainty至少要留0.2ns以上,hold uncertainty可以留小一点,因为综合阶段hold违例可以通过PR阶段修时钟树消除,但setup问题在PR阶段修复成本极高。所以综合阶段宁可setup约束紧一点,也不要松。
set_clock_latency描述时钟从时钟源到寄存器时钟引脚的传播延迟。综合阶段这个值分两部分:-source表示时钟源到芯片引脚的延迟,-network表示芯片引脚到寄存器端的延迟。因为综合阶段没有时钟树,工具不知道clock network实际延迟,默认会认为是ideal network。如果你想模拟真实的时钟树延迟,可以设置network latency:
set_clock_latency -source 0.5 [get_clocks clk_main] set_clock_latency 0.5 [get_clocks clk_main]set_clock_transition设置时钟信号的翻转时间,约束得越紧,工具在时钟路径上需要驱动的cell就越大,功耗越高。实际项目中常用工艺库里的典型翻转时间,例如0.1ns-0.3ns之间。
2.3 生成时钟与异步时钟域的处理
芯片里经常有分频、倍频、相位调整后的派生时钟,必须用create_generated_clock定义,不能用create_clock手工再造一个主时钟出来。
create_generated_clock -name clk_div2 -divide_by 2 -source [get_ports clk_main] [get_pins u_div/Q]-source指定的是源时钟的端口或引脚,[get_pins u_div/Q]是分频器输出引脚。工具会从源时钟开始推导这个生成时钟的边沿关系,自动计算它的周期和相位。
跨时钟域的信号必须显式告诉工具怎么处理。最常用的是异步时钟分组:
set_clock_groups -asynchronous -group [get_clocks clk_main] -group [get_clocks clk_uart]这个命令的语义是:clk_main和clk_uart之间所有跨时钟域路径都不做时序检查。很多人以为set_clock_groups会自动把两组时钟之间的路径设掉,实际确实如此,它内部等价于自动加false path且效率更高。但注意:set_clock_groups要求时钟之间不能有高速安全的同步器逻辑漏掉——如果两个时钟域之间有功能相关的数据交互,必须在RTL里已经用同步器或异步FIFO处理过,否则设了异步时钟组等于把设计错误也一起屏蔽掉了。
2.4 时钟约束的优先级与隐患
时钟约束里的一个隐藏优先级陷阱:如果同一个时钟引脚上同时存在create_clock和create_generated_clock,工具默认区分主时钟和生成时钟。如果生成时钟定义错误导致周期性异常,工具不会报错,但后续所有基于该时钟的时序路径分析结果都会失真。所以写完SDC之后,第一件事永远是跑report_clock -skew和report_generated_clock,确认时钟关系符合设计预期。
另一个隐患是set_clock_groups与set_false_path混用。在综合脚本里,我见过不少工程师在set_clock_groups之外又额外对相同路径加set_false_path,导致约束文件冗余,后续排查问题时分不清哪条约束真正生效。我的建议是:异步时钟域用set_clock_groups,功能性的例外才用set_false_path,两者不要重复。
3. 时序例外约束:伪路径与多周期路径
3.1 时序例外是Constraint的“特判”分支
时序例外的本质是在常规时序检查之外,针对特定路径给出特判规则。综合工具默认对所有寄存器到寄存器、引脚到寄存器的路径按单周期约束做setup和hold检查。但真实设计中,有一部分路径根本不需要单周期满足,或者压根不需要检查。如果不加例外,工具会把这些无效路径也纳入时序分析,导致优化资源被浪费在根本不该优化的路径上。
比如异步FIFO的读指针到写指针的跨时钟域路径,它们的时序关系本身就是不确定的,工具如果死磕这些路径的setup,就会在白费力气的同时把面积、功耗做得很差。合理设置例外之后,综合工具可以把精力集中在真正有约束价值的路径上。
3.2 伪路径的典型场景与设置方法
set_false_path是使用频率最高的时序例外,表示这条路径不需要做任何时序检查。典型场景有几类:
第一类是异步复位信号,复位信号释放的时序不需要像数据路径那样严格:
set_false_path -from [get_ports rst_n]第二类是测试相关的逻辑,如scan_enable、mbist_enable这类信号:
set_false_path -from [get_ports scan_enable]第三类是从一个时钟域到另一个异步时钟域的路径。如果RTL中已经用两级同步器处理过,这里就可以设为false path。
使用set_false_path时有个容易踩的坑:路径如果加得过于宽泛,会掩盖真正的时序问题。比如对某个模块的所有输出统一加false path,结果这个模块内部的锁存器时序有问题,工具也不会报。所以加例外之前,一定要确认路径本身的功能确实不需要做时序检查。
3.3 多周期路径的设置与计算
多周期路径(multicycle path)表示信号在多个时钟周期之后才会被采样。典型场景是慢速外设接口或者低功耗设计的流水线写入:
set_multicycle_path 2 -setup -from [get_clock clk_main] -to [get_pins u_fifo/wr_en]这条命令的意思是数据要经过2个周期才是有效采样点。注意它的一个关键隐含行为:设为2个周期setup之后,工具会自动把hold检查点也顺延为1个周期(即第二个周期的边沿),这点和很多新手的理解不一样——hold检查点并不会自动从0挪到1。如果要在hold检查时也顺延,需要单独加一条命令:
set_multicycle_path 1 -hold -from [get_clock clk_main] -to [get_pins u_fifo/wr_en]这里面的1指相对默认hold检查点的偏移。hold检查默认在setup检查点前一个时钟沿,把-hold设为1后会顺延一个周期,正好和setup的2周期对齐。
多周期路径设置时还有个常见误区:误把所有跨时钟域的路径都设成了multicycle path。异步时钟域之间的相位关系不是整数倍关系,多周期路径语义上要求两个时钟有确定且对齐的边沿关系,所以异步时钟域不能直接这样设,而应该用false path或异步时钟分组。
3.4 其他必要的时序例外
set_max_delay和set_min_delay在组合逻辑路径约束里用得多。比如输入端口到第一级寄存器的纯组合逻辑路径,如果未定义时钟,可以通过set_max_delay直接限制路径延迟:
set_max_delay 5.0 -from [get_ports din_valid] -to [get_pins u_reg/D]set_case_analysis用于指定静态恒定的信号值。如果某个信号在正常工作模式下固定为0或固定为1,例如芯片的测试模式选择信号、BIST使能信号,设定后工具会按恒定的值进行分析,避免把不会发生的翻转路径纳入时序计算:
set_case_analysis 0 [get_ports test_mode]这个命令还有一个重要的用途:多路选择器的选择信号。比如CPU的cache miss信号,如果正常工作下大部分时间保持恒定的逻辑值,通过set_case_analysis排除掉一部分优化路径,可以让综合工具更聚焦于真正需要时序收敛的关键路径。
4. 输入输出延迟约束与接口时序预算
4.1 输入延迟的计算方法与实操公式
set_input_delay描述的是外部信号相对于时钟边沿到达芯片输入引脚的时间。很多新人理解不了这个值怎么算,其实核心公式很简单:
输入延迟最大值 = 外部器件输出延迟最大值(Tco max)+ PCB走线延迟最大值
输入延迟最小值 = 外部器件输出延迟最小值(Tco min)+ PCB走线延迟最小值
举个例子,假设外部芯片的Tco max是2.5ns,PCB走线延迟约0.5ns,那么输入延迟最大值就是3.0ns。写下来是:
set_input_delay -max 3.0 -clock clk_main [get_ports datain] set_input_delay -min 0.2 -clock clk_main [get_ports datain]- 注意:set_input_delay的time值跟采样时钟沿有关。默认相对时钟的捕获沿,如果你的接口是双沿采样,需要加-clock_fall选项指定。
这里还有一层关键理解:set_input_delay实际上是给内部逻辑加上了一条“隐性延迟”。工具在做时序计算时,会把这个3ns的延迟当作从时钟边沿到输入引脚的路径延迟来处理。所以输入延迟设置得过大,内部留给组合逻辑的时间就变少,综合优化压力就大;设置得过小,相当于骗工具说信号到得很早,实际上外部信号根本没那么快到,结果就是测出来setup违例。
4.2 输出延迟的计算方法与时序预算公式
set_output_delay描述的是输出信号在时钟边沿之后多久必须到达外部接收器件。计算公式:
输出延迟最大值 = 外部器件建立时间(setup time)+ PCB走线延迟最大值
输出延迟最小值 = 外部器件保持时间(hold time)+ PCB走线延迟最小值
打个比方,外部接收芯片的setup requirement是1.2ns,PCB走线延迟0.3ns,那么输出延迟最大值写1.5ns:
set_output_delay -max 1.5 -clock clk_main [get_ports dataout] set_output_delay -min 0.0 -clock clk_main [get_ports dataout]输出延迟的含义可以理解为:工具在计算时将输出延迟作为外部路径的“债务”扣减,内部可用的时间 = 时钟周期 - 输出延迟。所以输出延迟约束过紧(比如1ns),工具就要在内部实现上耗费更多资源;约束过松(比如5ns),内部时序压力小,但可能做出一个外部接口时序不满足要求的设计。
4.3 环境约束:驱动强度、负载与转换时间
set_driving_cell用来约束输入端口的驱动能力,模拟实际外部驱动单元的驱动强度。如果输入端口的驱动条件设得太强,工具会低估输入端的延迟,导致后续实现不真实;设得太弱则过度悲观。
set_driving_cell -lib_cell INV_X1M -pin A [get_ports datain]这里用标准单元库里的INV_X1M的A引脚来模拟外部驱动,是比较贴近实际的做法。如果不知道怎么选,可以用set_input_transition直接指定输入信号翻转时间,一个典型的经验值是时钟周期的2%-5%,比如10ns时钟就设0.2ns-0.5ns:
set_input_transition 0.2 [get_ports datain]set_load约束输出端口的负载电容,这个值一般根据外部器件的输入电容加PCB寄生电容来估算:
set_load 0.03 [get_ports dataout]单元库中一个标准驱动单元的输出电容通常在0.01pF到0.05pF之间,如果外部负载是3个等效输入引脚加布线寄生的组合,取0.03pF左右是常见的选择。
4.4 IO约束的完整时序预算
实际的IO接口约束需要把tco、tsu、th、PCB延迟、时钟偏斜全部纳入预算。一个完整的接口时序预算表通常是这样的:
| 参数 | 符号 | 数值 | 说明 |
|---|---|---|---|
| 外部器件输出延迟 | Tco | 2.0ns | 数据从外部时钟沿到输出有效 |
| PCB走线延迟大值 | Tpcb_rise | 0.5ns | 信号从外部器件到本芯片引脚 |
| PCB走线延迟小值 | Tpcb_fall | 0.2ns | 同上 |
| 输出延迟计算(对输入) | - | 3.2ns | Tco max + Tpcb max |
| 输出延迟计算(对输入) | - | 0.6ns | Tco min + Tpcb min |
| 外部接收芯片建立时间 | Tsetup | 1.0ns | 输出接口约束依据 |
| 外部接收芯片保持时间 | Thold | 0.3ns | 输出接口约束依据 |
接上电路上跑的实测:这块如果外部连接的芯片或者板级走线的时序参数拿不到准确值,宁可留裕量也别拍脑袋。我见过太多案例是IO约束过于乐观导致流片回来接口采样出错,返工成本远超想象。
5. 设计规则约束、面积与优化策略
5.1 DRC约束的核心指标
综合阶段的DRC约束是让工具在优化时满足物理可实现性的规则。最常用的三项是最大转换时间、最大扇出、最大电容:
set_max_transition 0.5 [current_design] set_max_fanout 20 [current_design] set_max_capacitance 0.2 [current_design]这些约束不是拍脑袋填的,应当参考工艺库、后端PR团队的物理实现能力和功耗目标来定。比如7nm工艺节点,时钟网络的最大转换时间通常要求在0.05ns-0.1ns之间,数据网络可以放宽到0.2ns-0.5ns。扇出限制一般设在20-40之间,具体取决于标准单元库中驱动cell的驱动能力。
DRC约束设定过紧会让工具增加buffer插入,浪费面积和功耗;设定过松会导致信号完整性问题和后端修复成本激增。一个经过验证的做法是:前期先用宽松的DRC约束跑通流程,再看报告中的violation数量,逐步收紧。
5.2 面积约束与give-up策略
综合阶段还可以约束面积上限。在DC中:
set_max_area 0这个写法的含义是告诉工具在满足时序的前提下,尽量把面积优化到最小。它的好处是让综合器把面积作为优化目标之一,而不是只在违例后做修复。如果芯片有硬性面积预算,可以把0替换为具体面积数值,工具会在面积和时序之间找到一个权衡点。
实际项目中,我习惯将set_max_area作为可选项放在SDC里,跑完第一版之后再看时序余量和面积的trade-off,而不是一开始就盲目设一个过紧的面积目标导致时序收敛困难。
5.3 时钟网络的ideal network声明与set_clock_latency的关系
综合阶段内部时钟网络默认按ideal处理。set_ideal_network告诉工具该网络不需要做DRC优化,时钟信号在综合是理想传输:
set_ideal_network [get_ports clk_main]这个命令在DC中其实是隐含执行的——DC默认时钟引脚是ideal network。但在某些设定下,比如对时钟网络做了set_clock_latency后,工具的时钟树优化行为会有变化。如果你希望在综合阶段让工具对时钟网络的cell做尺寸优化,可以移除或部分放宽ideal network的约束。
这里有个实用心得:如果项目对时钟偏斜非常敏感,比如高速接口,建议在综合阶段保留时钟网络ideal,并用set_clock_uncertainty模拟偏斜预算。如果项目时钟频率不高,尝试让工具优化时钟路径也可以,但会增加综合时间,收益未必明显。
6. 实操示例:一份完整的综合SDC脚本
6.1 设计场景与约束目标
为了把前面讲的约束串起来,我构造一个典型的MCU子系统设计场景:主频率100MHz(周期10ns),UART接口时钟25MHz(周期40ns),主时钟和UART时钟为异步关系。设计包含32位ALU、异步FIFO、UART接口逻辑、SPI从机接口。
接口约束需求:
- datain[31:0]:外部芯片驱动,外部Tco为2.0ns,PCB走线延迟0.5ns
- dataout[31:0]:输出至SDRAM控制器,外部建立时间1.5ns
- uart_rx:外部UART芯片驱动,输入延迟相对uart时钟2.0ns
- uart_tx:输出至外部UART芯片,输出延迟相对uart时钟1.0ns
- rst_n为异步复位,正常工作为高电平
6.2 完整SDC脚本与注释解析
下面是我实际项目中抽出来的简化版本,关键命令都加了注释:
# 时钟定义 create_clock -name clk_main -period 10 -waveform {0 5} [get_ports clk_main] create_clock -name clk_uart -period 40 -waveform {0 20} [get_ports clk_uart] # 时钟不确定度与延迟 set_clock_uncertainty -setup 0.2 [get_clocks clk_main] set_clock_uncertainty -hold 0.05 [get_clocks clk_main] set_clock_uncertainty -setup 0.3 [get_clocks clk_uart] set_clock_uncertainty -hold 0.1 [get_clocks clk_uart] set_clock_latency -source 0.3 [get_clocks clk_main] set_clock_latency 0.4 [get_clocks clk_main] set_clock_latency -source 0.3 [get_clocks clk_uart] set_clock_latency 0.4 [get_clocks clk_uart] # 异步时钟域划分 set_clock_groups -asynchronous \ -group [get_clocks clk_main] \ -group [get_clocks clk_uart] # 输入输出延迟 set_input_delay -max 2.5 -clock clk_main [get_ports datain] set_input_delay -min 0.2 -clock clk_main [get_ports datain] set_output_delay -max 1.5 -clock clk_main [get_ports dataout] set_output_delay -min 0.0 -clock clk_main [get_ports dataout] set_input_delay -max 2.0 -clock clk_uart [get_ports uart_rx] set_output_delay -max 1.0 -clock clk_uart [get_ports uart_tx] # 时序例外 set_false_path -from [get_ports rst_n] set_false_path -from [get_ports scan_enable] set_multicycle_path 2 -setup -from [get_clocks clk_main] -to [get_pins u_spi/rxd_load_reg/D] set_multicycle_path 1 -hold -from [get_clocks clk_main] -to [get_pins u_spi/rxd_load_reg/D] # 环境约束 set_input_transition 0.2 [get_ports datain] set_input_transition 0.3 [get_ports uart_rx] set_load 0.02 [get_ports dataout] set_load 0.02 [get_ports uart_tx] # DRC约束 set_max_transition 0.5 [current_design] set_max_fanout 20 [current_design] set_max_capacitance 0.15 [current_design] # 面积优化 set_max_area 06.3 脚本执行与验证
脚本写完之后,跑综合前一定要做check_design和check_timing:
check_design report_clock report_timing -max_paths 20check_timing会列出缺失约束的路径类型。我见过最多的警告是“No clock defined for port xxx”或者“Input arrival time not specified”。这些警告不能忽视,必须逐项确认是设计里真不需要该约束,还是SDC漏写了。
综合完成后重点看两类报告:report_qor看整体时序余量和面积;report_timing -max_paths看最差路径的违例情况和路径明细。如果出现setup违例,先回看SDC里的时钟约束是否过紧(uncertainty太大、时钟延迟太大),再检查例外约束是否误加了范围。
7. 常见问题与排查心得
7.1 时钟没定义导致的时序全面崩溃
一个典型症状:综合后的report_timing一片大红,几乎所有路径都有几十纳秒的违例。这往往是SDC没有定义时钟,工具默认按照无限周期建时钟,后续所有约束都建立在虚假的时间参考上。
排查方法很简单:先跑report_clock看有哪些clock被识别,再跑report_timing -group [get_clocks clk_main]确认分析走了正确时钟域。如果工具报了“clock not defined”相关警告,回SDC检查get_ports的端口名拼写是否正确、端口是否真的存在。
7.2 时钟分组的隐含作用与冗余约束
用set_clock_groups -asynchronous之后,工具自动屏蔽两个时钟域间的所有时序路径。有些人还会额外加set_false_path,这个冗余操作本身不会错,但会造成一种麻烦:当跨时钟域路径数量巨大时,两条约束的内存开销和工作量不同,后续要单独豁免某条跨时钟域路径时,需要同时修改两处,极易漏改。
我的建议是:异步时钟域之间只保留set_clock_groups一种约束方式,功能上的单条豁免用单独的set_false_path,但注意set_false_path要从更具体的目标路径去描述,颗粒度要细。
7.3 输入输出延迟过大或没有留裕量
这是一个隐蔽的坑。输入延迟设得过大,内部可用时间变少,工具只能拼命插buffer或换大驱动cell,面积暴涨、功耗飙升;设得过小,内部时序看起来很漂亮,但流片回来接口对不上。
判断标准是:综合完成后,检查输入路径的time slack是否接近0。如果slack特别好,往往意味着输入延迟设小了。一个实操习惯:把输入延迟在真实计算值基础上多留10%-15%的裕量,把输出延迟也按同样比例留裕,花一点性能代价换取接口的可靠性和后端的可收敛性,这笔交易非常划算。
7.4 DC与Genus的命令差异速查
虽然SDC是标准格式,但两家工具对部分命令的支持和默认行为不完全一致,这里列几个最容易踩的差异:
| 命令/行为 | Design Compiler | Genus |
|---|---|---|
| 读约束命令 | 在dc_shell内直接定义SDC | read_sdc文件或set_sdc命令 |
| set_clock_groups的支持 | 支持,语义严格 | 支持,但对时钟重定义有差异 |
| set_driving_cell默认值 | 有默认warning | 不设则驱动无限强 |
| report_timing输出格式 | 默认简洁,可选详细 | 默认详细,需要额外配置 |
如果项目从前端到后端用的是同一套约束,这份SDC在PR阶段的目标是保持与综合一致;如果使用Genus综合、Innovus做PR,SDC的兼容性整体良好,但set_clock_latency等命令在Innovus里默认会被当作粗略估计,实际时钟树综合时会重新计算。这点务必提醒接手PR的同事,别让综合阶段的clock latency被错误继承到后端。
7.5 关于SDC过约束与欠约束的教训
我踩过最深的一个坑是在某个低功耗项目里,为了保险把所有跨时钟域路径都加了set_multicycle_path。综合报告一切正常,结果流片回来功耗和性能都远低于预期。后来排查发现,多周期路径的误用让工具对着压根不存在的时序要求做了大量无效优化,同时把多余的裕量都转化成了面积浪费。
这个教训说明:宁可先少约束,跑出baseline,再逐步增加约束观察QoR变化,也不要图省事一次加一堆全局例外。SDC的本质是“精确表达设计意图”,而不是“尽量让工具放松约束”。多写一行约束,看似手动能过,实际可能掩盖一个设计层面的架构问题。真正靠谱的做法是每条约束都有人能说清楚它为什么存在、依据是什么。
最后说几句过来人的体会
SDC约束这份东西,看起来是写命令,本质上是把设计时序意图翻译给工具听。我见过很多工程师把大量精力花在调compile策略上,结果综合出来的QoR一直不好,最后发现是SDC里时钟uncertainty的预算根本不合理。约束写得扎实,综合工具才能妙手生花;约束写得稀烂,再高级的compile命令也救不回来。
如果你正在学芯片流程,我的建议是:找一个小规模的模块(比如UART或SPI),手动写一份SDC,跑完整综合,再看report里每条路径的时序计算方法,对照着一行行搞懂每个约束命令的语义。把这份基本功练扎实,后面接触CPU级别的复杂约束才不会心里发虚。
这套笔记我会持续更新,下一篇准备写综合结果分析中的时序报告解读技巧,包括怎么区分setup违例和hold违例的真正来源,以及如何利用path group和clock group快速定位问题模块。欢迎有同类经验的朋友在评论区交流各自的SDC踩坑记录。