综合(Synthesis)基础:RTL到网表的转换

综合,说白了就是把我们写的 RTL 代码(Verilog/VHDL)翻译成门级网表的过程。你想想看,我们写的是行为级的描述——"如果时钟上升沿来了,就把数据打一拍",但芯片里实际跑的是一个个标准单元:与门、或门、触发器、多路选择器。综合工具就是干这个翻译活的。

我个人习惯把综合分成三个步骤来看:转换(Translation)→ 逻辑优化(Logic Optimization)→ 映射(Mapping)。这三个步骤,每一步都有坑。

综合工具的工作原理:DC 与 Genus

业界主流的综合工具就两家:Synopsys 的 Design Compiler(DC)和 Cadence 的 Genus。我两个都用过,说实话核心思想是一样的,只是命令和脚本风格不同。

DC 的流程大概是这样的:

read_verilog {top.v sub.v}    # 读入RTL
link                          # 链接所有模块
source constraints.tcl        # 读入约束
compile_ultra                 # 执行综合
write -f ddc -o top.ddc      # 输出网表

Genus 的流程类似:

read_hdl -v2001 {top.v sub.v}
elaborate top
read_sdc constraints.sdc
syn_generic
syn_map
syn_opt
write_hdl -mapped > top.vg

嗯,这里要注意一个关键点:综合工具不是万能的。我在项目中遇到过一位同事,写了一段很"优雅"的代码,用了三层嵌套的 for 循环生成逻辑。结果综合出来面积爆炸,时序也完全跑不过。为什么?因为综合工具对某些写法天生不友好。

核心原则:综合工具擅长处理"结构清晰"的代码。你写的是"硬件",不是"软件"。用 always 块描述状态机、用 assign 描述组合逻辑、用实例化描述层次结构——这才是综合工具喜欢的。

综合约束:没有约束就没有质量

约束是什么?说白了就是告诉综合工具:"你要给我做到什么程度"。没有约束的综合,就像没有目标的旅行——走到哪算哪,结果往往不是你想要的。

最基本的约束包括:

  • 时钟约束:定义时钟周期、占空比、抖动
  • 输入延迟:信号从片外到达芯片输入端口的时间
  • 输出延迟:信号从芯片输出端口到片外寄存器的路径延迟
  • 伪路径:不需要时序检查的路径(比如跨时钟域的同步器)
  • 多周期路径:允许超过一个时钟周期完成的数据路径

我给大家看一个典型的约束脚本片段:

create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.2 [get_clocks clk]
set_input_delay -max 3.0 -clock clk [get_ports data_in]
set_output_delay -max 2.5 -clock clk [get_ports data_out]
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_multicycle_path -setup 2 -from [get_pins reg_a/Q] -to [get_pins reg_b/D]

我的经验:约束不要"拍脑袋"定。我曾经接手一个项目,前一个人把输入延迟设成了 5ns,但实际片外器件只需要 2ns。结果综合工具为了满足这个过紧的约束,拼命优化输入路径,导致其他路径的资源被挤占。最后整个芯片的时序都很难收敛。

优化策略:面积、速度、功耗的三角博弈

综合优化说白了就是在面积(Area)速度(Timing)功耗(Power)之间找平衡。这三者就像三角形的三个顶点——你不可能同时做到最好。

优化目标 常用策略 副作用
速度优先 逻辑复制、寄存器平衡、高驱动强度单元 面积增大、功耗上升
面积优先 资源共享、逻辑合并、低驱动强度单元 时序变差、扇出增大
功耗优先 门控时钟、操作数隔离、低功耗单元库 面积略增、时序可能受影响

DC 里有个很实用的命令叫 compile_ultra,它默认会做很多优化。但如果你不加约束,它可能会"过度优化"。比如逻辑复制——为了减少扇出,工具会把一个逻辑复制成多份。这能改善时序,但面积会翻倍。

我记得有一次做 AI 加速器芯片,数据通路特别宽(512位)。综合工具为了满足时序,把加法器复制了 4 份。我一看面积报告,好家伙,光加法器就占了芯片面积的 15%。后来我加了 set_max_area 0 约束,让工具在满足时序的前提下尽量压缩面积,最终面积降到了 8%。

避坑指南:我曾经在综合时忘记设置 set_clock_transition,结果工具默认用了 0.1ns 的时钟转换时间。实际芯片上时钟树的转换时间可能是 0.3ns 甚至更大。这导致综合结果过于乐观,后仿真时时序全崩了。记住:综合时的假设一定要和后端实现一致

综合后的检查:别急着往下走

综合跑完了,网表出来了,是不是就可以交给后端了?别急,先做几件事:

  1. 检查时序报告:看 setup 和 hold 有没有违例。如果有,先分析是约束问题还是代码问题。
  2. 检查面积报告:和预期对比,如果偏差超过 20%,要查原因。
  3. 检查功耗报告:特别是动态功耗,看看哪些模块是"电老虎"。
  4. 形式验证:用 Formality 或 LEC 对比 RTL 和网表的功能一致性。这一步不能省!

我见过最惨的一次,是同事改了 RTL 代码但忘了重新综合,直接把旧网表交出去了。流片回来功能全错。嗯,从那以后我养成了习惯:每次综合完,一定跑一遍形式验证。

总结一下:综合不是"一键生成"那么简单。它需要你理解工具的工作原理、会写合理的约束、懂得权衡优化策略。更重要的是——综合结果一定要验证。别问我为什么强调这个,问就是吃过亏。