4. Synopsys Design Compiler (DC) 实战:从启动到报告生成
DC 是数字后端综合的起点。说白了,就是把 RTL 代码变成门级网表。我见过不少新人,脚本写得挺顺,但一遇到实际项目就卡壳。今天咱们就手把手过一遍 DC 的完整流程。
核心目标: 把 RTL 代码 + 工艺库 + 约束 → 综合出满足时序/面积/功耗的门级网表。
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 轮:先跑通,再调约束,最后优化面积和功耗。每轮都看报告,确保没有遗漏。