4. Synopsys Design Compiler (DC) 实战:从启动到报告生成

DC 是数字后端综合的起点。说白了,就是把 RTL 代码变成门级网表。我见过不少新人,脚本写得挺顺,但一遇到实际项目就卡壳。今天咱们就手把手过一遍 DC 的完整流程。

核心目标: 把 RTL 代码 + 工艺库 + 约束 → 综合出满足时序/面积/功耗的门级网表。
DC 综合核心流程 启动 DC 读取设计 设置约束 选择编译策略 运行综合 生成报告 输出网表 💡 实际项目中,这 6 步通常写在一个 .tcl 脚本里,一键运行。 我习惯把约束和编译策略单独拆成文件,方便复用。

4.1 启动 DC

启动 DC 有两种方式:图形界面和命令行。我个人习惯用命令行,跑脚本效率高。图形界面适合初学调试。

# 命令行启动(推荐)
dc_shell -f run.tcl | tee dc.log

# 图形界面启动
design_vision
我的习惯: 启动时加 -f 直接跑脚本,用 tee 保存日志。这样出了问题能回头查,不用重跑。

4.2 读取设计

读取设计就是把 RTL 代码和库文件加载进来。DC 支持 Verilog、VHDL、SystemVerilog。

# 设置搜索路径
set search_path  ". /home/techlib/gsclib045"

# 设置目标库和链接库
set target_library  "gsclib045.db"
set link_library    "* $target_library"

# 读取 RTL 文件
analyze -format verilog {top.v cpu.v alu.v}
elaborate top

# 或者一步到位
read_verilog {top.v cpu.v alu.v}
link

这里有个坑。我曾经遇到过 analyze 和 elaborate 分开用,结果模块名写错了,DC 报了一堆未定义的错。后来我改用 read_verilog 一步到位,省心不少。

注意: link_library 里的 * 表示先搜索设计中已定义的模块,再找库。顺序错了,综合出来的网表可能不对。

4.3 设置约束

约束是综合的灵魂。没有约束,DC 就不知道往哪个方向优化。我见过有人随便写个时钟周期就跑,结果流片回来时序全崩。

核心约束包括:

约束类型 命令示例 说明
时钟 create_clock -period 10 [get_ports clk] 定义时钟周期和端口
输入延迟 set_input_delay -max 5 [get_ports data_in] -clock clk 外部到芯片的延迟
输出延迟 set_output_delay -max 6 [get_ports data_out] -clock clk 芯片到外部的延迟
伪路径 set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] 跨时钟域,不检查时序
多周期路径 set_multicycle_path 2 -setup -from [get_pins reg_a/Q] -to [get_pins reg_b/D] 信号需要多个时钟周期
# 一个完整的约束示例
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty 0.5 [get_clocks clk]
set_input_delay  -max 4 [get_ports {din[*]}] -clock clk
set_output_delay -max 5 [get_ports {dout[*]}] -clock clk
set_false_path -from [get_clocks clk] -to [get_clocks test_clk]
关键点: 约束要写实。太紧,面积和功耗爆炸;太松,流片回来跑不动。我一般先给 10% 的余量,后期再收紧。

4.4 选择编译策略

DC 提供两种编译策略:Top-down 和 Bottom-up。选哪个,看项目规模。

策略 适用场景 优点 缺点
Top-down 中小规模(< 50万门) 全局优化好,脚本简单 编译时间长,内存占用大
Bottom-up 大规模(> 100万门) 可并行,内存可控 需要写接口约束,容易出问题

我做过一个 300 万门的芯片,Top-down 跑了 12 小时还没完。后来改成 Bottom-up,分 4 个模块并行,3 小时搞定。嗯,这里要注意:Bottom-up 的接口约束一定要写对,不然顶层合起来时序全乱。

# Top-down 策略(推荐新手用)
compile_ultra -gate_clock -no_autoungroup

# Bottom-up 策略(先合模块,再合顶层)
# 模块级
current_design sub_module
compile_ultra -gate_clock
write -hierarchy -output sub_module.db

# 顶层
current_design top
read_db {sub_module.db ...}
link
compile_ultra -gate_clock -incremental
我的建议: 第一次跑用 Top-down,看看 DC 能不能吃下。如果内存爆了,再切 Bottom-up。别一上来就搞复杂。

4.5 生成报告

综合完不看报告,等于白做。报告能告诉你时序、面积、功耗、约束覆盖情况。我每次必看 4 份报告:

# 时序报告
report_timing -delay_type max -nworst 10 > timing.rpt

# 面积报告
report_area -hierarchy > area.rpt

# 功耗报告
report_power -hierarchy > power.rpt

# 约束检查
report_constraint -all_violators > constraint.rpt

你想想看,如果 report_constraint 里一堆 violation,那综合出来的网表就是废的。我曾经有一次忘了设 set_clock_uncertainty,结果时序报告全绿,流片回来直接翻车。从那以后,我每次必查约束覆盖。

避坑指南: 看时序报告时,别只看 slack。还要看 data path 和 clock path 的延迟分布。有时候 slack 是负的,但数据路径很短,可能是时钟偏斜太大。

4.6 输出网表

综合完,输出网表和约束文件,给后端布局布线用。

# 输出门级网表
write -format verilog -hierarchy -output top_synth.v

# 输出 SDC 约束
write_sdc top_synth.sdc

# 输出 SDF 延迟信息(可选)
write_sdf top_synth.sdf
总结: DC 综合不是一锤子买卖。我通常要迭代 3-5 轮:先跑通,再调约束,最后优化面积和功耗。每轮都看报告,确保没有遗漏。