3. 逻辑综合实战:编写综合脚本、分析综合报告、解决常见的综合问题
逻辑综合,说白了就是把 RTL 代码翻译成门级网表的过程。很多刚入行的朋友觉得这步就是跑个脚本完事,其实不然。我见过太多项目因为综合脚本里一个约束写错,导致后端跑了好几个月才发现时序收敛不了,最后不得不推倒重来。
今天我们就来聊聊综合实战中的三个核心环节:脚本怎么写、报告怎么看、问题怎么修。
3.1 综合脚本的骨架与血肉
一个标准的综合脚本,通常包含以下几个部分。我个人习惯把它们分成四块:
| 模块 | 内容 | 说明 |
|---|---|---|
| 环境设置 | search_path, target_library, link_library, synthetic_library | 告诉工具去哪找库文件 |
| 读入设计 | analyze + elaborate 或 read_verilog / read_vhdl | 把 RTL 代码加载进来 |
| 约束施加 | create_clock, set_input_delay, set_output_delay, set_max_fanout 等 | 告诉工具你的设计目标 |
| 编译与输出 | compile_ultra, write -f verilog, write_sdc, write_sdf | 跑综合并保存结果 |
下面是一个我常用的综合脚本模板,你可以直接拿来改:
# ============================================
# 综合脚本模板 (DC Synthesis Script)
# ============================================
# 1. 库设置
set search_path ". /path/to/liberty /path/to/db"
set target_library "tcbn28hpcplusc35l.db"
set link_library "* $target_library dw_foundation.sldb"
set synthetic_library "dw_foundation.sldb"
# 2. 读入设计
analyze -format verilog {top.v sub_module_a.v sub_module_b.v}
elaborate top
# 3. 时钟约束
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_clock_transition 0.15 [get_clocks clk]
# 4. 输入输出延迟
set_input_delay 2.5 -clock clk [all_inputs]
set_output_delay 2.5 -clock clk [all_outputs]
# 5. 设计规则约束
set_max_fanout 20 [current_design]
set_max_transition 0.5 [current_design]
set_max_capacitance 0.3 [current_design]
# 6. 编译
compile_ultra -gate_clock -no_autoungroup
# 7. 报告与输出
report_timing > reports/timing.rpt
report_area > reports/area.rpt
report_power > reports/power.rpt
write -f verilog -output outputs/top_netlist.v
write_sdc -output outputs/top_constraints.sdc
set_host_options -max_cores 8,让 DC 用多核并行编译。尤其是大模块,能省不少时间。不过别贪心,设太多核反而会因为内存争抢变慢。
3.2 综合报告:别只看时序
综合跑完后,工具会吐出一堆报告。很多新手只盯着 timing report 看,其实另外两份报告同样重要。
3.2.1 时序报告怎么看
时序报告的核心是看 slack。正 slack 说明时序有余量,负 slack 说明有违例。但这里有个坑:
- Setup slack 负了:说明路径太长,需要优化逻辑深度或换用更快的 cell。
- Hold slack 负了:说明路径太短,需要插入 buffer 或 delay cell。
- Transition / Capacitance 违例:说明驱动能力不够,需要 upsizing cell。
我曾经在一个项目中,setup slack 全是正的,但 hold slack 在几个关键路径上负了 0.2ns。当时没在意,结果流片回来芯片在低温下频繁出错。嗯,从那以后我再也不敢忽略 hold 检查了。
3.2.2 面积报告里的猫腻
面积报告会告诉你设计用了多少 combinational cells、sequential cells、以及总 cell 面积。但要注意:
- Combinational area 占比过高:说明逻辑太复杂,可以考虑 retiming 或重新划分模块。
- Sequential area 占比过高:说明寄存器太多,可能是流水线级数过深。
- Net area 很大:说明布线资源紧张,后端可能会遇到 routing congestion。
3.2.3 功耗报告里的门道
功耗报告分三部分:动态功耗、静态功耗、内部功耗。我个人习惯先看 动态功耗,因为它通常占大头。如果动态功耗超标,可以尝试:
- 降低 clock gating 的使能信号切换率
- 使用低功耗 cell(如 HVT cell)
- 优化数据路径的翻转率
3.3 常见综合问题与解决方案
下面是我在实际项目中遇到最多的几个问题,以及对应的解决思路。
3.3.1 时序违例:setup 修不动
现象: 关键路径上的 slack 是负的,而且怎么优化都修不干净。
排查步骤:
- 先看路径报告,找到最长的逻辑级数(logic level)。
- 如果 logic level > 20,说明 RTL 代码写得太深,需要手动插入流水线。
- 如果 logic level 在 10-15 之间,可以尝试
compile_ultra -retime让工具自动做 retiming。 - 如果还是不行,检查是不是 fanout 太大导致 cell 延迟过高。
set_clock_uncertainty 设得太保守。把 uncertainty 从 0.5ns 降到 0.3ns 后,slack 就变正了。当然,这需要你对自己的时钟树质量有信心。
3.3.2 面积爆炸:综合出来的网表比预期大 30%
原因: 通常是 RTL 代码中使用了大量的 casez 或 casex,或者有未优化的 for 循环被综合成了并行逻辑。
解决方案:
- 检查 RTL 中是否有
full_case或parallel_case的误用。 - 使用
set_flatten true让工具尝试优化共享逻辑。 - 如果面积还是大,考虑把大模块拆分成多个小模块,分别综合后再拼接。
3.3.3 功耗超标:动态功耗比预期高 50%
原因: 通常是 clock gating 没有生效,或者数据路径的翻转率过高。
排查方法:
- 在功耗报告中查看 clock gating 覆盖率。如果低于 80%,说明很多寄存器没有被门控。
- 检查 RTL 中是否写了
always @(posedge clk) if (enable) ...这种手动门控,工具可能无法识别。 - 使用
set_clock_gating_style指定门控风格,并开启compile_ultra -gate_clock。
3.4 综合流程全景图
下面这张图总结了逻辑综合的完整流程,以及每个阶段的关键产出物:
3.5 综合脚本的调试技巧
写综合脚本就像写软件,总会遇到 bug。这里分享几个我常用的调试方法:
- 分段执行:不要一次性跑完整个脚本。先跑库设置和读入设计,确认无误后再加约束,最后再跑编译。这样出问题时能快速定位。
- 使用 check_timing:在编译前跑一下
check_timing,它会告诉你哪些路径没有被约束。我见过有人漏了异步时钟域的约束,结果综合出来的网表在后端根本没法用。 - 保存中间状态:在关键步骤后使用
write -f ddc保存设计状态。这样如果后面跑崩了,不用从头再来。
if { [file exists "outputs/top.ddc"] } { read_ddc outputs/top.ddc } 的判断。这样第二次跑脚本时,如果网表已经存在,就直接跳过读入和编译,只重新生成报告。省时又省力。
3.6 综合后的检查清单
综合跑完后,别急着往后端交接。先过一遍下面的检查清单:
| 检查项 | 检查方法 | 合格标准 |
|---|---|---|
| 时序检查 | report_timing -max_paths 100 | 所有路径 slack ≥ 0 |
| 设计规则检查 | report_constraint -all_violators | 无 transition / capacitance 违例 |
| 面积检查 | report_area | 总面积 ≤ 预算的 110% |
| 功耗检查 | report_power | 总功耗 ≤ 预算的 105% |
| 网表完整性 | check_design | 无 dangling wire / unconnected pin |
| 约束一致性 | compare_sdc | 输出 SDC 与输入 SDC 一致 |
嗯,以上就是逻辑综合实战的核心内容。脚本怎么写、报告怎么看、问题怎么修,这三块你掌握了,综合这关基本就稳了。记住,综合不是一次性的工作,它需要你反复迭代、不断调优。别怕麻烦,多跑几次,你自然就能找到感觉。