4、DC综合实战:时序约束与综合策略

好,咱们今天来聊聊DC综合里最核心的部分——约束与策略。说实话,很多工程师觉得DC就是个跑脚本的工具,但真正决定芯片能不能用的,恰恰是这些约束写得好不好。我见过太多项目,因为一条set_input_delay写错,导致后端修时序修到崩溃。

4.1 时序约束:芯片的“交通规则”

时序约束说白了,就是告诉工具你的芯片要跑多快,信号什么时候来,什么时候走。没有约束,DC就像没地图的司机,乱开一气。

4.1.1 create_clock:定义你的时钟

这是最基础的约束。我习惯先问自己几个问题:时钟频率多少?占空比多少?有没有分频或倍频?

# 创建一个50MHz的主时钟,占空比50%
create_clock -name clk -period 20 [get_ports clk]

# 创建一个分频时钟,基于主时钟
create_generated_clock -name clk_div2 -source [get_ports clk] \
    -divide_by 2 [get_pins U_FF/Q]

我个人习惯:主时钟的命名要清晰,比如clk_50M、clk_100M。别用clk1、clk2这种,三个月后你自己都看不懂。

4.1.2 set_input_delay / set_output_delay:定义边界

这个坑我踩过。有一次项目,我忘了设input_delay,结果DC把输入路径优化得特别激进,后端一跑,发现根本收不住。为什么呢?因为工具默认输入延迟为0,它以为信号一开机就到了。

# 假设外部器件输出延迟为2ns,时钟周期10ns
set_input_delay -clock clk -max 2 [get_ports data_in]
set_input_delay -clock clk -min 0.5 [get_ports data_in]

# 输出延迟:外部器件需要3ns的建立时间
set_output_delay -clock clk -max 3 [get_ports data_out]

注意:max和min都要设。max对应setup检查,min对应hold检查。只设max不设min,hold时序可能会出问题。

4.2 面积与功耗约束:平衡的艺术

你想想看,时序、面积、功耗,这三者就像跷跷板。压了时序,面积就涨;省了功耗,时序可能崩。我一般会先满足时序,再优化面积和功耗。

4.2.1 面积约束

DC默认会尽量优化面积,但你可以给它一个目标。不过说实话,面积约束更多是“别超过这个数”,而不是“必须达到这个数”。

# 设置面积目标为10000 um²
set_max_area 10000

嗯,这里要注意:如果你设得太紧,DC可能会牺牲时序来换面积。我建议先跑一版不加面积约束的,看看自然面积是多少,再往下压10%-20%。

4.2.2 功耗约束

功耗现在越来越重要了。尤其是移动芯片,功耗高了直接废掉。DC支持多种功耗优化方式,最常用的是动态功耗和漏电功耗的权衡。

# 设置动态功耗目标
set_max_dynamic_power 0.5

# 设置漏电功耗目标
set_max_leakage_power 0.01

# 或者用更高级的:多阈值电压库选择
set_operating_conditions -library slow -analysis_type on_chip_variation

我在项目中遇到过:用高阈值电压(HVT)单元可以大幅降低漏电,但时序会变差。所以我会先让DC用标准阈值(SVT)跑一遍,再把关键路径上的单元换成HVT,非关键路径用低阈值(LVT)来提速。这叫“混合阈值设计”。

4.3 综合策略:Top-Down vs Bottom-Up

这是个老生常谈的问题。我两种都用过,各有优劣。

4.3.1 Top-Down(自顶向下)

把整个芯片当成一个模块来综合。优点是简单,工具能看到全局,优化效果好。缺点是——如果芯片太大,DC会跑不动,或者跑几天几夜。

# Top-Down综合,直接读顶层
read_verilog top.v
current_design top
link
uniquify
compile_ultra

我曾经用Top-Down综合一个500万门的芯片,跑了整整48小时。结果中间报了个错,全白费。从那以后,超过200万门的项目我坚决用Bottom-Up。

4.3.2 Bottom-Up(自底向上)

先综合每个子模块,再拼起来。优点是快,可以并行跑,每个模块独立优化。缺点是——模块间的接口时序容易出问题,需要额外花时间做“接口约束”。

# 先综合子模块A
read_verilog sub_a.v
current_design sub_a
create_clock -name clk -period 10 [get_ports clk]
set_input_delay -clock clk 2 [get_ports data_in_a]
compile_ultra
write -f ddc -hierarchy -output sub_a.ddc

# 再综合子模块B,类似操作
# 最后在顶层把DDC文件读进来,只做连接和顶层逻辑
read_ddc sub_a.ddc
read_ddc sub_b.ddc
current_design top
link
compile_ultra -top

我个人习惯:对于新项目,先用Bottom-Up跑一版,看看每个模块的时序余量。如果某个模块特别紧张,再单独优化它。等所有模块都收敛了,再用Top-Down做一次全局优化——但只跑增量综合,不从头跑。

4.4 分析综合报告:读懂DC的“体检报告”

综合跑完,你得会看报告。不然跑得再快也没用。

4.4.1 面积报告

面积报告告诉你用了多少门,多少内存,多少物理单元。

report_area -hierarchy -physical

我一般关注三个数:组合逻辑面积、时序逻辑面积、总物理面积。如果组合逻辑占比太高,说明你的设计可能太“组合”了,可以考虑加流水线。

4.4.2 时序报告

这是最重要的报告。DC会列出所有违反setup和hold的路径。

report_timing -delay_type max -nworst 10 -significant_digits 3

看时序报告时,我习惯先看WNS(最差负余量)和TNS(总负余量)。如果WNS是负的,说明有路径没满足setup。如果TNS很大,说明很多路径都有问题。

指标 含义 我的经验值
WNS 最差路径的余量 应 > 0,最好 > 0.1ns
TNS 所有违例路径的余量总和 应 < 1ns,否则问题较多
FEP 违例路径数量 应 < 100,否则需重新约束

如果WNS是负的但很小(比如-0.05ns),别慌。有时候是库的精度问题,或者你可以稍微放松一下约束。但如果WNS超过-0.5ns,那就要认真查了。

4.4.3 功耗报告

功耗报告会告诉你动态功耗和静态功耗各占多少。

report_power -hierarchy -analysis_effort high

我一般先看动态功耗占比。如果超过70%,说明你的设计翻转率太高,可以考虑加门控时钟或降低电压。如果静态功耗占比高,说明用了太多低阈值单元,可以换成高阈值的。

4.5 实战避坑指南

最后,分享几个我踩过的坑:

  • 时钟没定义好:有一次我忘了定义生成时钟,结果DC把所有路径都按同一个时钟分析,hold时序全崩了。后来花了三天才查出来。
  • 输入延迟设反了:max和min搞反,导致setup和hold检查错位。嗯,这个错误我犯过两次,后来我写了个脚本自动检查。
  • 面积约束设太紧:DC为了省面积,把关键路径上的buffer都优化掉了,结果时序变差。我后来学乖了,先不加面积约束跑一版,看看自然面积。
  • Bottom-Up接口没对齐:两个模块的接口时序约束不一致,导致顶层综合时出现大量违例。解决办法是统一写一个接口约束文件,所有模块共用。

好了,这一章的内容就到这里。下一章我们会讲形式验证和逻辑等价性检查,那是确保你综合前后功能一致的关键一步。记得把今天的约束脚本练熟,这是基本功。