3. 逻辑综合实践:DC 工具使用、脚本编写与报告分析

逻辑综合,说白了就是把 RTL 代码翻译成门级网表。DC(Design Compiler)是 Synopsys 家的老牌工具,我用了快十年了。刚开始接触时觉得它就是个黑盒子,后来踩了不少坑才明白——工具是死的,脚本是活的,关键看你怎么用。

3.1 DC 工具的基本工作流程

DC 的工作流程其实不复杂,核心就三步:读入设计、约束设计、综合优化。我习惯把它画成一张流程图,方便新人理解。

DC 逻辑综合基本流程 读入设计 (read) 约束设计 (constrain) 综合优化 (compile) 报告分析 (report) 输出网表 (write) 迭代优化 关键说明 • 读入:支持 Verilog、VHDL、 SystemVerilog • 约束:时钟、IO、功耗、面积 • 综合:compile_ultra 最常用 • 报告:timing、area、power • 输出:ddc、verilog、sdc 💡 经验:每次迭代只改一个 约束参数,方便定位问题

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,那就得想办法优化了。

⚠️ 注意:我曾经遇到过一个项目,所有路径 slack 都是正的,但芯片流片回来就是跑不到目标频率。后来发现是 clock network delay 设得太乐观了。记住,综合时的 clock network delay 只是估算,后端布局布线后会有变化。综合阶段留 10%-20% 的余量比较稳妥。

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)。

🔑 核心观点:综合报告不是只看有没有 violation,而是要理解每个数字背后的含义。时序、面积、功耗三者是 trade-off 的关系——你压时序,面积可能变大;你省功耗,时序可能变差。好的综合脚本就是在三者之间找到平衡点。

3.4 常见问题与避坑指南

做综合这么多年,我踩过的坑不少。挑几个典型的说说:

  • 读入设计时忘记 uniquify:如果同一个模块被例化了多次,不加 uniquify 会导致综合时共享一个模块,后面修 timing 会非常痛苦。
  • 约束写得太松或太紧:太松,综合出来的网表性能差;太紧,工具会过度优化,面积暴涨。我一般先给一个合理的约束,看结果再微调。
  • 忽略 check_design 的 warning:有些 warning 是良性的,但有些会直接导致综合结果错误。比如未连接的端口,工具可能会把它优化掉,导致功能不对。
  • 综合后不检查形式验证:综合可能会改变 RTL 的行为,尤其是用了 compile_ultra 的某些选项。我每次综合完都会跑一下 formality,确保网表和 RTL 等价。
⚠️ 我曾经踩过的坑:有一次做低功耗项目,用了 power switch 和 retention register。综合时没注意 retention 的约束,结果综合出来的网表在 sleep 模式下数据全丢了。从那以后,我每次综合前都会仔细检查 retention 相关的约束,并在综合后做功能仿真验证。

3.5 综合策略选择

DC 提供了多种综合策略,我常用的就两种:

策略 命令 适用场景 特点
标准综合 compile 小模块、快速迭代 速度快,优化力度一般
超大规模综合 compile_ultra 大芯片、高性能要求 优化力度强,支持门控时钟、retiming

我个人建议,除非是特别小的模块,否则直接用 compile_ultra。它比 compile 多做了很多优化,比如自动插入门控时钟、逻辑重定时等。虽然运行时间会长一些,但结果通常更好。

💡 小技巧:如果 compile_ultra 跑得太慢,可以试试 compile_ultra -no_autoungroup。这个选项会禁止工具自动打平层次结构,能显著减少运行时间,但可能会牺牲一点优化效果。我一般先用这个选项跑一版,看看时序大概什么水平,最后再跑一版全优化的。

好了,关于 DC 综合实践,核心内容就这些。脚本结构、约束写法、报告分析、常见避坑——这几个点你掌握了,大部分项目都能应付。剩下的就是在实际项目中慢慢积累经验了。