4、逻辑综合入门:RTL到网表、综合流程、DC工具基本使用、时序约束(SDC)初探
逻辑综合,说白了就是把我们写的 RTL 代码(硬件描述语言)翻译成门级网表的过程。你想想看,我们写的是行为级的描述,比如“always @(posedge clk)”,但芯片里实际跑的是一个个标准单元——与门、或门、触发器、MUX 这些。综合就是做这个翻译工作。
我刚入行那会儿,总觉得综合就是点一下按钮的事。后来有一次,我写的 RTL 综合出来面积超标了 30%,老板把我叫去喝茶……嗯,从那以后我才真正开始认真研究综合的每一个步骤。
4.1 综合的本质:RTL → 网表
综合工具(比如 Synopsys 的 Design Compiler,简称 DC)会做三件事:
- 翻译:把 Verilog/VHDL 解析成工具内部的通用布尔表达式。
- 优化:根据你给的约束(面积、时序、功耗),对布尔表达式做逻辑化简、资源共享、路径重定时等操作。
- 映射:把优化后的逻辑映射到目标工艺库里的标准单元上。
核心概念:综合不是“翻译”,而是“翻译 + 优化 + 映射”三位一体的过程。缺了约束,综合出来的网表可能根本不能用。
我见过很多新手,RTL 写完了直接跑综合,不加任何时序约束。结果呢?综合出来的网表频率只能跑到 50MHz,而设计要求是 200MHz。这就是典型的“只翻译不优化”。
4.2 综合流程:从 RTL 到网表的五步走
一个标准的综合流程,我习惯把它分成五个阶段。每个阶段都有它的坑,我一个个说。
| 阶段 | 输入 | 输出 | 关键动作 |
|---|---|---|---|
| ① 准备 | RTL 代码、工艺库 | 设置好环境 | 设置 search_path、target_library、link_library |
| ② 读入 | RTL 文件 | GTECH 网表 | analyze + elaborate 或 read_verilog |
| ③ 约束 | 设计规格 | SDC 约束文件 | 定义时钟、输入输出延迟、时序例外 |
| ④ 综合 | GTECH + 约束 | 优化后网表 | compile 或 compile_ultra |
| ⑤ 输出 | 优化后网表 | 门级网表 + 报告 | write -f verilog、report_timing、report_area |
这里我要特别强调一下第②步。analyze 和 elaborate 是分开的,analyze 只做语法检查,elaborate 才真正构建出 GTECH(通用技术)网表。我曾经遇到过一个问题:RTL 里有一个模块实例化了两次,但参数传递写错了,analyze 没报错,elaborate 才报出来。所以,千万不要只看 analyze 通过就觉得万事大吉。
4.3 DC 工具基本使用:命令行与脚本
DC 有两种运行方式:交互式(dc_shell-t)和批处理式(dc_shell -f script.tcl)。
我个人习惯用批处理脚本,因为可重复、可版本管理。下面是一个最简的 DC 综合脚本:
# 综合脚本:syn.tcl
set target_library "typical.db"
set link_library "* typical.db"
set search_path ". ./rtl ./lib"
analyze -format verilog {top.v sub_module.v}
elaborate top
# 时钟约束
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty 0.2 [get_clocks clk]
set_input_delay 2.0 -clock clk [all_inputs]
set_output_delay 2.0 -clock clk [all_outputs]
# 综合
compile_ultra
# 输出
write -f verilog -output mapped/top_mapped.v
write_sdc -output mapped/top_mapped.sdc
report_timing > reports/timing.rpt
report_area > reports/area.rpt
小技巧:compile_ultra 比 compile 优化力度更大,尤其适合高性能设计。但要注意,它可能会增加运行时间。如果设计不大,用 compile 就够了。
运行命令很简单:
dc_shell -f syn.tcl | tee syn.log
tee 命令会把屏幕输出同时写到日志文件里,方便排查问题。我每次跑综合都会看日志里的 warning,尤其是“Unresolved reference”和“Inferred latch”这类,基本上一看一个准。
4.4 时序约束(SDC)初探
SDC(Synopsys Design Constraints)是综合和时序分析的核心。没有约束,工具不知道你要多快。
最基本的 SDC 命令就这几个:
- create_clock:定义时钟,指定周期和波形。
- set_clock_uncertainty:留出时钟抖动和裕量。
- set_input_delay / set_output_delay:告诉工具外部路径的延迟。
- set_max_delay / set_min_delay:定义路径的上下限。
- set_false_path:告诉工具某些路径不需要检查时序。
举个例子,一个 100MHz 的时钟,周期是 10ns。但你不能真的设成 10ns 就完事了,你得留点余量:
create_clock -name clk -period 10 [get_ports clk]
set_clock_uncertainty -setup 0.3 [get_clocks clk]
set_clock_uncertainty -hold 0.1 [get_clocks clk]
为什么?因为芯片制造出来,时钟会有抖动(jitter),温度电压也会变化。你设 10ns,实际可能只有 9.5ns 的可用时间。我曾经在一个项目里,就是因为 uncertainty 设得太小,流片回来芯片在高温下时序失效,最后只能降频使用。教训深刻。
避坑指南:我曾经犯过一个低级错误——把 set_input_delay 设成了负值,结果综合出来的网表面积奇大无比。后来才发现,负的 input delay 意味着外部数据在时钟沿之前就到达了,工具会拼命优化 setup,导致面积爆炸。所以,input_delay 和 output_delay 一定要根据实际接口时序来设,不要乱填。
4.5 综合后的检查清单
综合跑完,别急着高兴。我一般会检查这几项:
- 时序报告:看 setup 和 hold 有没有 violation。如果有,先看是不是约束设错了。
- 面积报告:看总面积是否在预算内。如果超标,检查是不是有冗余逻辑。
- 功耗报告:看动态功耗和静态功耗。
- 网表质量:检查有没有悬空引脚、驱动强度不足、扇出过大等问题。
- 一致性检查:用 formality 或 LEC 做形式验证,确保综合后的网表功能与 RTL 一致。
这里我特别想强调第 5 点。很多人觉得综合工具不会改功能,所以跳过形式验证。但我在项目中遇到过两次:一次是 DC 把某个 case 语句优化成了优先级编码器,导致功能出错;另一次是 compile_ultra 自动插了 clock gating,但 gating 逻辑有 bug。从那以后,形式验证我一次都没落下过。
4.6 知识结构图:逻辑综合全景
下面这张图把逻辑综合的核心流程和关键节点串起来了。你可以把它当作一张“地图”,每次做综合时对照着走一遍。
这张图里,我特意把 SDC 约束放在了 GTECH 网表旁边。为什么?因为约束在综合过程中是同时起作用的,不是先有网表再加约束。很多人搞反了这个顺序,结果综合出来的网表跟约束不匹配。
4.7 小结:综合入门的关键点
逻辑综合入门,说白了就三件事:
- 理解流程:RTL → GTECH → 优化 → 门级网表,每一步都有它的意义。
- 会用工具:DC 脚本不复杂,但参数设置很关键。尤其是 compile_ultra 的选项,比如 -retime、-gate_clock,用好了事半功倍。
- 写好约束:SDC 是综合的灵魂。时钟定义、输入输出延迟、时序例外,这三样写对了,综合就成功了一半。
我记得刚学综合那会儿,总觉得 SDC 是给后端用的,前端不用管。后来被 mentor 骂了一顿才明白:综合阶段的约束质量,直接决定了后端时序收敛的难度。你综合时留的余量越大,后端跑起来就越轻松。