第三章 逻辑综合实战:用Design Compiler把RTL变成门级网表

逻辑综合,说白了就是把我们写的Verilog代码,翻译成芯片厂能用的标准单元门级网表。这一步做不好,后面PR(布局布线)再努力也白搭。我刚开始带项目时,就吃过综合脚本写得太随意的亏——综合出来的面积比预期大了30%,时序还一塌糊涂。从那以后,我对综合脚本的敬畏心就上来了。

今天咱们就聊聊,怎么用Synopsys的Design Compiler(DC)把这件事干漂亮。我会从脚本结构、报告分析到常见坑的解法,一条条捋清楚。

3.1 综合脚本的核心骨架

一个靠谱的综合脚本,其实就三大块:环境设置、约束加载、编译策略。我个人习惯把脚本拆成三个文件,方便复用和调试。

脚本结构建议:

  • setup.tcl:库路径、搜索路径、变量定义
  • constraints.tcl:时钟、输入输出延迟、false path、multicycle path
  • run.tcl:读入设计、链接、编译、输出报告

下面是一个我常用的综合脚本模板,你可以直接拿来改。

# ===== setup.tcl =====
set search_path [list . /home/techlib/gsclib045/synopsys]
set target_library {gsclib045.db}
set link_library [concat "*" $target_library]
set symbol_library {gsclib045.sdb}

# ===== constraints.tcl =====
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_input_delay -max 2.0 -clock clk [all_inputs]
set_output_delay -max 2.5 -clock clk [all_outputs]
set_false_path -from [get_ports rst_n]
set_max_area 0

# ===== run.tcl =====
analyze -format verilog {../rtl/top.v ../rtl/sub_module.v}
elaborate top
link
current_design top
source -echo constraints.tcl
compile_ultra -no_autoungroup
write -format verilog -hierarchy -output ../output/top_synth.v
write_sdc ../output/top_synth.sdc
report_timing > ../reports/timing.rpt
report_area > ../reports/area.rpt
report_power > ../reports/power.rpt

我的小习惯:每次综合前,先跑一遍 check_design 和 report_design(虽然DC里叫check_design)。这能帮你提前发现悬空端口、多驱动、未约束路径等问题。省得综合跑了两小时,最后发现约束写错了。

3.2 综合报告怎么看?

综合跑完,DC会吐出一堆报告。很多新手只看timing和area,其实漏掉了很多关键信息。我一般按这个顺序扫报告:

报告类型 关键字段 我关注什么
report_timing slack、data path、clock path 最差负slack(WNS)和总负slack(TNS)。WNS决定能不能收敛,TNS决定要修多少条路径
report_area combinational、sequential、total cell area 组合逻辑和时序逻辑的比例。如果组合逻辑占比超过70%,说明设计可能偏控制密集型
report_power dynamic、leakage、total power 动态功耗占比是否合理。如果漏电功耗超过30%,可以考虑换低功耗库
report_qor 所有指标汇总 快速扫一眼,看有没有异常值

举个例子,有一次我看到report_timing里WNS是-0.5ns,但TNS只有-2ns。这说明只有少数几条路径有问题,大概率是约束写得太紧或者某条路径的驱动强度不够。我直接定位到那条路径,发现是一个多bit总线的扇出太大,加一级buffer就解决了。

3.3 综合中的常见问题与解法

做综合这么多年,我踩过的坑不少。下面这几个问题,几乎每个项目都会遇到。

3.3.1 时序违例:setup/hold violation

setup violation是最常见的。原因无非是:路径太长、组合逻辑太深、时钟频率太高。我的排查步骤是:

  1. 看路径类型:是reg-to-reg、input-to-reg还是reg-to-output?
  2. 看组合逻辑级数:如果超过20级,考虑插入流水线或重写RTL。
  3. 看cell驱动强度:是不是用了太多小驱动cell?换成X2、X4试试。
  4. 检查约束:clock uncertainty是不是设得太悲观?input/output delay是不是太紧?

我曾经踩过的坑:有一次综合报告显示setup violation严重,我折腾了两天,最后发现是create_clock的period写错了——目标频率是200MHz,我写成了10ns(100MHz)。约束比实际松了一倍,DC当然不会努力去优化。所以,每次综合前,务必double-check时钟周期。

3.3.2 面积爆炸:综合出来的面积比预期大很多

面积超标,通常是因为:

  • compile_ultra用了太多优化:比如 -retime 或 -spg 选项会大量复制逻辑。如果面积敏感,可以改用 compile -map_effort medium。
  • RTL写得太“聪明”:比如用了大量casez、嵌套的三目运算符,DC会展开成很多冗余逻辑。我建议RTL尽量写直白,别炫技。
  • set_max_area设得太小:DC会拼命去压面积,反而可能引入时序问题。我一般设成0,让DC在满足时序的前提下尽量优化面积。

3.3.3 综合结果与PR后不一致

这个问题最头疼。综合时timing是绿的,到了PR(布局布线)后变红了。原因通常是:

  • 线负载模型不准:小工艺下(28nm以下),线延迟占比越来越大。DC用的wire load model是统计模型,跟实际布线差很多。我建议在综合时就用 set_clock_transition 和 set_input_transition 把驱动环境设得更接近真实情况。
  • 综合时没考虑物理信息:如果工艺库支持,可以用DC的拓扑模式(topographical mode),它会读入floorplan信息,估算线延迟更准。

3.4 综合流程全景图

下面这张图,是我自己总结的综合流程。每次带新人,我都会先让他们看这张图,搞清楚每一步的输入输出是什么。

逻辑综合核心流程图 逻辑综合核心流程 RTL代码 (Verilog/VHDL) analyze + elaborate (读入与解析) 约束加载 (SDC + 时钟/IO约束) compile / compile_ultra 输出网表 + SDC + 报告 输入 RTL代码 工艺库 (.db) 约束文件 (.sdc) 输出 门级网表 (.v) SDC约束 (.sdc) 时序/面积/功耗报告 时序不满足? 调整约束或RTL

3.5 避坑指南:综合阶段最容易犯的5个错误

这些错误,我几乎每次评审都能看到。列出来,你对照检查一下自己的脚本。

  1. 忘记set_dont_touch:有些模块(比如PLL、SRAM)是硬宏,综合时不能动。记得加 set_dont_touch [get_cells u_pll]。
  2. 时钟没做generated clock定义:分频时钟、门控时钟如果不定义,DC会当成普通信号处理,时序分析全错。
  3. 异步处理没设false path:跨时钟域的同步器路径,必须设 set_false_path,否则DC会拼命去修那些不存在的时序路径。
  4. 综合策略选错:top-down vs bottom-up。小设计用top-down简单;大芯片(百万门以上)建议用bottom-up,分模块综合,最后再整合。
  5. 忽略report_qor里的congestion信息:如果综合报告显示congestion严重(比如超过5%),PR阶段大概率会绕线不通。这时候要考虑修改RTL结构或调整floorplan。

我的一个实用技巧:每次综合完,除了看timing和area,我还会跑一下 report_clock_gating。如果门控时钟的覆盖率低于80%,说明功耗优化没做到位。这时候可以检查RTL里是否漏写了clock gating,或者DC的clock gating选项没开。

3.6 小结

逻辑综合这件事,说难不难,说简单也不简单。脚本写得好,后面PR能省一半的力。我建议你每次综合完,都花10分钟把报告里的关键指标记下来,形成一份“综合日志”。时间长了,你就能摸清DC的脾气,知道什么样的约束会出什么样的结果。