数字芯片后端基础操作入门教程 - 第3节
一、逻辑综合到底是什么?
逻辑综合,说白了就是把 RTL 代码翻译成门级网表的过程。你写的是 always @(posedge clk) q <= d;,综合工具会把它变成一堆标准单元——触发器、与非门、或非门,然后连起来。
我刚开始接触这行时,觉得综合就是个“翻译器”。后来踩了坑才明白,它更像一个“优化器”。同样的 RTL,不同的约束能综合出完全不同的电路。面积、速度、功耗,全看你怎么“调教”它。
综合的三个阶段
DC 做综合,其实分三步走:
- 转换(Translation):把 RTL 读进来,转成 GTECH 格式的通用网表。这一步不优化,就是直译。
- 逻辑优化(Logic Optimization):开始动刀了。冗余逻辑去掉、共用逻辑合并、路径重定时……说白了就是让电路更“聪明”。
- 映射(Mapping):把优化后的电路映射到目标工艺库里的标准单元。这一步决定了最终用哪些门、多大驱动。
我见过不少新手,跑完综合发现面积爆炸,回头一看——映射阶段没给面积约束,工具默认往快了推。嗯,这里要注意。
二、DC 的基本使用流程
DC 怎么跑?我习惯用 Tcl 脚本驱动。图形界面当然也有,但脚本可重复、可版本管理,项目里没人点 GUI 的。
启动 DC
dc_shell -f run.tcl | tee dc.log
加 tee 是为了把屏幕输出同时写进日志。我吃过亏——跑了一晚上综合,第二天发现 log 没存,报错信息全丢了。从那以后,tee 成了肌肉记忆。
读入设计与链接库
这是综合的第一步,也是最容易出问题的一步。你需要告诉 DC 三件事:
- 你的设计文件在哪(RTL、网表、IP)
- 目标工艺库是什么(比如台积电 28nm 的
tcbn28hpcplusbwp12t30p140ss_ccs.db) - 链接库有哪些(综合库、IO 库、Memory Compiler 生成的库)
典型脚本长这样:
# 设置库路径
set target_library "tcbn28hpcplusbwp12t30p140ss_ccs.db"
set link_library "* $target_library dw_foundation.sldb"
# 设置搜索路径
set search_path ". /home/techlib/tsmc28nm /home/design/rtl"
# 读入设计
analyze -format verilog {top.v sub1.v sub2.v}
elaborate top
current_design top
link
link_library 里的 * 不能省。它表示“先搜已经读入的设计”,没有它,DC 会找不到顶层模块。
我见过一个同事,link_library 漏了 *,结果 DC 报了一堆 unresolved reference。他查了两小时,最后发现是少了个星号。这种坑,踩一次就记住了。
综合策略:两种路线
DC 提供两种综合策略,我根据项目阶段选:
| 策略 | 命令 | 适用场景 | 特点 |
|---|---|---|---|
| Top-down | compile_ultra |
小模块、单次综合 | 全局优化好,但内存消耗大 |
| Bottom-up | 分模块 compile 再整合 |
大规模芯片、多时钟域 | 可控性强,适合团队并行 |
我个人习惯:芯片规模小于 50 万门时用 top-down,简单粗暴。超过 100 万门,老老实实 bottom-up,不然 DC 能把服务器内存吃光。
三、约束怎么写?
约束是综合的灵魂。没有约束,DC 不知道你要快还是要省。我刚开始做综合时,随便写了几个时钟周期就跑了,结果后仿时序一塌糊涂。
时钟约束
时钟是时序的基准。最基本的写法:
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_clock_transition -rise 0.1 [get_clocks clk]
-period 10 表示周期 10ns,也就是 100MHz。uncertainty 是时钟抖动 + 裕量,我一般设周期的 2%~5%。
输入输出延迟
芯片不是孤岛。输入信号从片外进来,输出信号要送到片外,你得告诉 DC 外部延迟:
set_input_delay -max 2.5 [get_ports data_in] -clock clk
set_output_delay -max 3.0 [get_ports data_out] -clock clk
这些值怎么定?看芯片规格书里的接口时序图。我曾经偷懒随便填了个 1ns,结果流片回来接口对不上——嗯,那次加班加得挺惨。
面积与功耗约束
时序优先,面积和功耗次之。但如果你不设,DC 会往死里优化时序:
set_max_area 0
set_max_dynamic_power 0.5
set_max_leakage_power 0.1
set_max_area 0 的意思是“面积越小越好”。注意,这只是一个指导方向,DC 不会真的追求零面积。
四、综合脚本的完整骨架
把上面这些串起来,一个完整的综合脚本大概长这样:
# ============================================
# DC 综合脚本 - top.syn.tcl
# ============================================
# 1. 库设置
set target_library "tcbn28hpcplusbwp12t30p140ss_ccs.db"
set link_library "* $target_library dw_foundation.sldb"
set symbol_library "tcbn28hpcplusbwp12t30p140ss_ccs.sdb"
set search_path ". /home/techlib/tsmc28nm /home/design/rtl"
# 2. 读入设计
analyze -format verilog {top.v sub1.v sub2.v}
elaborate top
current_design top
link
# 3. 时钟约束
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_clock_transition -rise 0.1 [get_clocks clk]
# 4. IO 约束
set_input_delay -max 2.5 [all_inputs] -clock clk
set_output_delay -max 3.0 [all_outputs] -clock clk
# 5. 面积/功耗约束
set_max_area 0
set_max_dynamic_power 0.5
# 6. 综合
compile_ultra
# 7. 保存结果
write -format verilog -hierarchy -output ./output/top_syn.v
write_sdc -output ./output/top_syn.sdc
write_sdf -version 2.1 -output ./output/top_syn.sdf
# 8. 生成报告
report_timing > ./output/timing.rpt
report_area > ./output/area.rpt
report_power > ./output/power.rpt
top_syn.v— 综合后的门级网表top_syn.sdc— 时序约束文件(给后端用)top_syn.sdf— 标准延迟格式文件(给后仿用)timing.rpt / area.rpt / power.rpt— 报告
五、常见问题与避坑
问题1:综合后时序不满足
先看 critical path 在哪。是组合逻辑太长?还是扇出太大?我一般先调 compile_ultra -retime,让 DC 自动重定时。还不行,就回去改 RTL。
问题2:面积爆炸
检查是不是 set_max_area 没设。或者约束太紧,DC 用了大量大驱动单元。试试 compile_ultra -no_autoungroup,关掉自动合并层次。
问题3:DC 跑不动
内存不够?试试 bottom-up 策略。或者加 set_host_options -max_cores 8 开多核。我遇到过 200 万门的设计,单核跑了 12 小时,开 8 核后 3 小时搞定。
逻辑综合是后端流程的起点。网表质量好不好,直接决定后面布局布线顺不顺。我个人觉得,花时间把约束写清楚、把策略选对,比后面反复迭代省时得多。嗯,今天就先聊到这。