3. 逻辑综合实践:DC 工具使用、脚本编写与报告分析
逻辑综合,说白了就是把 RTL 代码翻译成门级网表。DC(Design Compiler)是 Synopsys 家的老牌工具,我用了快十年了。刚开始接触时觉得它就是个黑盒子,后来踩了不少坑才明白——工具是死的,脚本是活的,关键看你怎么用。
3.1 DC 工具的基本工作流程
DC 的工作流程其实不复杂,核心就三步:读入设计、约束设计、综合优化。我习惯把它画成一张流程图,方便新人理解。
3.2 综合脚本编写——从入门到实战
写 DC 脚本,我建议你从一开始就养成好习惯。别图省事把所有东西塞到一个文件里,后面改起来会疯掉。
3.2.1 脚本结构划分
我个人习惯把脚本拆成三部分:
| 脚本文件 | 内容 | 说明 |
|---|---|---|
| setup.tcl | 库路径、搜索路径、变量定义 | 环境配置,基本不动 |
| constraints.tcl | 时钟、IO、时序例外 | 每次项目重点修改 |
| run.tcl | 读入、综合、报告、输出 | 主流程控制 |
这样做的好处是——你想想看,如果项目换了工艺库,只需要改 setup.tcl;如果时序不满足,改 constraints.tcl 就行。互不干扰。
3.2.2 一个完整的综合脚本示例
下面这个脚本是我在项目中常用的模板,去掉了一些敏感信息,核心逻辑都在。
# ============================================
# setup.tcl — 环境配置
# ============================================
set TARGET_LIBRARY "/path/to/typical.db"
set LINK_LIBRARY "* $TARGET_LIBRARY /path/to/io.db"
set SYMBOL_LIBRARY "/path/to/symbol.sdb"
set SEARCH_PATH [list . /path/to/rtl /path/to/db]
set_app_var target_library $TARGET_LIBRARY
set_app_var link_library $LINK_LIBRARY
set_app_var symbol_library $SYMBOL_LIBRARY
set_app_var search_path $SEARCH_PATH
# ============================================
# constraints.tcl — 时序与设计约束
# ============================================
create_clock -name clk -period 10.0 [get_ports clk]
set_clock_uncertainty -setup 0.3 [get_clocks clk]
set_clock_uncertainty -hold 0.1 [get_clocks clk]
set_input_delay -max 2.0 -clock clk [get_ports data_in*]
set_output_delay -max 2.5 -clock clk [get_ports data_out*]
set_max_area 0
set_max_fanout 20 [current_design]
# ============================================
# run.tcl — 主流程
# ============================================
source -echo setup.tcl
source -echo constraints.tcl
read_verilog [list top.v sub_module_a.v sub_module_b.v]
current_design top
link
uniquify
check_design > reports/check_design.rpt
compile_ultra -gate_clock -no_autoungroup
report_timing > reports/timing.rpt
report_area > reports/area.rpt
report_power > reports/power.rpt
report_qor > reports/qor.rpt
write -format ddc -hierarchy -output outputs/top.ddc
write -format verilog -hierarchy -output outputs/top.v
write_sdc outputs/top.sdc
check_design,看看有没有悬空端口、未连接线网。这些小问题在综合前修掉,比在网表里折腾省事多了。
3.3 综合报告分析——看懂工具在说什么
报告是 DC 给你的反馈。看不懂报告,综合就等于白做。我见过不少新人,跑完综合只看 timing 有没有 violation,其他报告一眼不看——这是大忌。
3.3.1 时序报告怎么看
时序报告里最重要的几个字段:
- Startpoint / Endpoint:路径的起点和终点,告诉你哪条路径出问题
- slack:余量。正数表示满足,负数表示 violation
- data arrival time:数据实际到达时间
- data required time:数据要求到达时间
举个例子,假设你看到这样一段报告:
Startpoint: reg_a_reg/CK (clock edge)
Endpoint: reg_b_reg/D (setup)
Slack: -0.45 ns
Path Group: clk
Path Type: max
Point Incr Path
------------------------------------------------------------
clock clk (rise edge) 0.00 0.00
clock network delay (propagated) 0.80 0.80
reg_a_reg/CK (DFF) 0.00 0.80 r
reg_a_reg/Q (DFF) 0.35 1.15 r
net_123 (wire) 0.12 1.27
U_AND2/Z (AND2) 0.28 1.55 r
net_456 (wire) 0.15 1.70
reg_b_reg/D (DFF) 0.00 1.70 r
------------------------------------------------------------
data arrival time 1.70
clock clk (rise edge) 10.00 10.00
clock network delay (propagated) 0.80 10.80
clock uncertainty -0.30 10.50
reg_b_reg/CK (DFF) 0.00 10.50 r
library setup time -0.35 10.15
------------------------------------------------------------
data required time 10.15
------------------------------------------------------------
data required time 10.15
data arrival time 1.70
------------------------------------------------------------
slack (MET) 8.45
这个例子 slack 是正的,说明时序满足。但如果 slack 是负的,比如 -0.45,那就得想办法优化了。
3.3.2 面积报告怎么看
面积报告告诉你芯片用了多少资源。我一般关注这几个指标:
| 指标 | 含义 | 我的经验值 |
|---|---|---|
| Combinational area | 组合逻辑面积 | 通常占总面积 40%-60% |
| Sequential area | 时序逻辑面积(寄存器等) | 通常占 30%-50% |
| Total cell area | 所有标准单元总面积 | 不能超过预算 |
| Utilization | 利用率(综合阶段不直接给出) | 后端会算,综合时留 20% 余量 |
3.3.3 功耗报告怎么看
功耗报告分三部分:动态功耗、静态功耗、总功耗。我习惯先看动态功耗,因为它占了芯片功耗的大头。
Power Group Internal Switching Leakage Total %
------------------------------------------------------------
clock 0.85 mW 0.12 mW 0.02 mW 0.99 mW 35%
register 0.45 mW 0.30 mW 0.01 mW 0.76 mW 27%
combinational 0.30 mW 0.25 mW 0.01 mW 0.56 mW 20%
sequential 0.15 mW 0.10 mW 0.01 mW 0.26 mW 9%
memory 0.15 mW 0.08 mW 0.02 mW 0.25 mW 9%
------------------------------------------------------------
Total 1.90 mW 0.85 mW 0.07 mW 2.82 mW 100%
看到 clock 占了 35% 的功耗,别惊讶。时钟树是芯片里翻转最频繁的网络,功耗高是正常的。如果觉得太高,可以考虑门控时钟(clock gating)。
3.4 常见问题与避坑指南
做综合这么多年,我踩过的坑不少。挑几个典型的说说:
- 读入设计时忘记 uniquify:如果同一个模块被例化了多次,不加 uniquify 会导致综合时共享一个模块,后面修 timing 会非常痛苦。
- 约束写得太松或太紧:太松,综合出来的网表性能差;太紧,工具会过度优化,面积暴涨。我一般先给一个合理的约束,看结果再微调。
- 忽略 check_design 的 warning:有些 warning 是良性的,但有些会直接导致综合结果错误。比如未连接的端口,工具可能会把它优化掉,导致功能不对。
- 综合后不检查形式验证:综合可能会改变 RTL 的行为,尤其是用了 compile_ultra 的某些选项。我每次综合完都会跑一下 formality,确保网表和 RTL 等价。
3.5 综合策略选择
DC 提供了多种综合策略,我常用的就两种:
| 策略 | 命令 | 适用场景 | 特点 |
|---|---|---|---|
| 标准综合 | compile |
小模块、快速迭代 | 速度快,优化力度一般 |
| 超大规模综合 | compile_ultra |
大芯片、高性能要求 | 优化力度强,支持门控时钟、retiming |
我个人建议,除非是特别小的模块,否则直接用 compile_ultra。它比 compile 多做了很多优化,比如自动插入门控时钟、逻辑重定时等。虽然运行时间会长一些,但结果通常更好。
compile_ultra 跑得太慢,可以试试 compile_ultra -no_autoungroup。这个选项会禁止工具自动打平层次结构,能显著减少运行时间,但可能会牺牲一点优化效果。我一般先用这个选项跑一版,看看时序大概什么水平,最后再跑一版全优化的。
好了,关于 DC 综合实践,核心内容就这些。脚本结构、约束写法、报告分析、常见避坑——这几个点你掌握了,大部分项目都能应付。剩下的就是在实际项目中慢慢积累经验了。