4、逻辑综合入门:RTL到网表、综合流程、DC工具基本使用、时序约束(SDC)初探

逻辑综合,说白了就是把我们写的 RTL 代码(硬件描述语言)翻译成门级网表的过程。你想想看,我们写的是行为级的描述,比如“always @(posedge clk)”,但芯片里实际跑的是一个个标准单元——与门、或门、触发器、MUX 这些。综合就是做这个翻译工作。

我刚入行那会儿,总觉得综合就是点一下按钮的事。后来有一次,我写的 RTL 综合出来面积超标了 30%,老板把我叫去喝茶……嗯,从那以后我才真正开始认真研究综合的每一个步骤。

4.1 综合的本质:RTL → 网表

综合工具(比如 Synopsys 的 Design Compiler,简称 DC)会做三件事:

  1. 翻译:把 Verilog/VHDL 解析成工具内部的通用布尔表达式。
  2. 优化:根据你给的约束(面积、时序、功耗),对布尔表达式做逻辑化简、资源共享、路径重定时等操作。
  3. 映射:把优化后的逻辑映射到目标工艺库里的标准单元上。

核心概念:综合不是“翻译”,而是“翻译 + 优化 + 映射”三位一体的过程。缺了约束,综合出来的网表可能根本不能用。

我见过很多新手,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 综合后的检查清单

综合跑完,别急着高兴。我一般会检查这几项:

  1. 时序报告:看 setup 和 hold 有没有 violation。如果有,先看是不是约束设错了。
  2. 面积报告:看总面积是否在预算内。如果超标,检查是不是有冗余逻辑。
  3. 功耗报告:看动态功耗和静态功耗。
  4. 网表质量:检查有没有悬空引脚、驱动强度不足、扇出过大等问题。
  5. 一致性检查:用 formality 或 LEC 做形式验证,确保综合后的网表功能与 RTL 一致。

这里我特别想强调第 5 点。很多人觉得综合工具不会改功能,所以跳过形式验证。但我在项目中遇到过两次:一次是 DC 把某个 case 语句优化成了优先级编码器,导致功能出错;另一次是 compile_ultra 自动插了 clock gating,但 gating 逻辑有 bug。从那以后,形式验证我一次都没落下过。

4.6 知识结构图:逻辑综合全景

下面这张图把逻辑综合的核心流程和关键节点串起来了。你可以把它当作一张“地图”,每次做综合时对照着走一遍。

逻辑综合核心流程 逻辑综合核心流程 RTL 代码 analyze + elaborate GTECH 网表 SDC 时序约束 compile / compile_ultra 门级网表 时序报告 面积报告 功耗报告 关键输入 • 工艺库 (.db) • 线负载模型 • 操作条件 (PVT)

这张图里,我特意把 SDC 约束放在了 GTECH 网表旁边。为什么?因为约束在综合过程中是同时起作用的,不是先有网表再加约束。很多人搞反了这个顺序,结果综合出来的网表跟约束不匹配。

4.7 小结:综合入门的关键点

逻辑综合入门,说白了就三件事:

  • 理解流程:RTL → GTECH → 优化 → 门级网表,每一步都有它的意义。
  • 会用工具:DC 脚本不复杂,但参数设置很关键。尤其是 compile_ultra 的选项,比如 -retime、-gate_clock,用好了事半功倍。
  • 写好约束:SDC 是综合的灵魂。时钟定义、输入输出延迟、时序例外,这三样写对了,综合就成功了一半。

我记得刚学综合那会儿,总觉得 SDC 是给后端用的,前端不用管。后来被 mentor 骂了一顿才明白:综合阶段的约束质量,直接决定了后端时序收敛的难度。你综合时留的余量越大,后端跑起来就越轻松。