第二章 逻辑综合:DC/Genus工具实战,掌握时序约束与面积优化的平衡艺术

逻辑综合,说白了就是把RTL代码翻译成门级网表。这一步做得好不好,直接决定了芯片能不能跑起来、能跑多快、要花多少钱。

我刚开始做综合时,总觉得这是工具的活儿,跟我没啥关系。直到有一次,我写了个挺复杂的模块,DC跑出来面积大得吓人,时序还一塌糊涂。后来才发现,是代码风格和约束写法都有问题。从那以后,我再也不敢小看综合这一步了。

2.1 综合工具的核心流程

不管是Synopsys的DC还是Cadence的Genus,核心流程其实差不多。你想想看,工具要做的无非就是三件事:读入设计、施加约束、优化输出。

综合三阶段:

  • 转换(Translation):把RTL代码读入工具内部数据库,生成GTECH网表
  • 逻辑优化(Logic Optimization):对布尔逻辑进行化简、重写、资源共享
  • 映射(Mapping):把优化后的逻辑映射到目标工艺库的标准单元上

我个人习惯把综合脚本分成三个文件:setup.tcl(环境配置)、constraints.tcl(时序约束)、run.tcl(综合流程)。这样改起来方便,也容易复用。

DC/Genus 综合核心流程 RTL 代码 工艺库 (.db/.lib) 时序约束 (.sdc) 阶段一:转换(Translation) read_verilog / read_vhdl → GTECH 网表 阶段二:逻辑优化(Logic Optimization) compile_ultra / set_structure → 布尔化简、资源共享 阶段三:映射(Mapping) compile_ultra -gate_clock → 标准单元映射

2.2 时序约束:别让工具猜你的心思

工具不会读心术。你不给它约束,它就按最保守的方式做,结果往往不是你想要的。

我在项目中遇到过最典型的问题:一个模块综合出来时序报告全是violation,但仿真明明没问题。后来发现是约束文件里忘了设input delay,工具默认输入信号在时钟沿才到,这谁受得了?

2.2.1 核心约束命令

约束类型 命令示例 说明
时钟定义 create_clock -period 10 [get_ports clk] 周期10ns,对应100MHz
输入延迟 set_input_delay -max 3 [get_ports data_in] 数据在时钟沿后3ns内到达
输出延迟 set_output_delay -max 4 [get_ports data_out] 数据在时钟沿前4ns必须稳定
伪路径 set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] 跨时钟域路径不做时序检查
多周期路径 set_multicycle_path 2 -setup -from [get_pins reg_a/CK] 数据需要两个时钟周期才能稳定

我的小技巧:约束文件里,时钟定义一定要放在最前面。因为input delay、output delay都依赖时钟做参考。我曾经见过有人把约束写反了顺序,结果工具报了一堆莫名其妙的warning。

2.2.2 时序报告怎么看

综合跑完后,第一件事就是看时序报告。别急着看violation,先确认时钟定义对不对。

*******************************************************************************
Report : timing
        -path_type full
        -delay_type max
        -max_paths 10
*******************************************************************************
  Startpoint: data_reg[0]/CK
  Endpoint:   result_reg[7]/D
  Path Group: clk
  Path Type:  max

  Point                                    Incr       Path
  ---------------------------------------------------------------
  clock clk (rise edge)                    0.00       0.00
  clock network delay (propagated)         0.50       0.50
  data_reg[0]/CK (DFF)                    0.00       0.50 r
  data_reg[0]/Q (DFF)                     0.35       0.85 r
  U1/A (AND2)                             0.12       0.97 r
  U1/Z (AND2)                             0.18       1.15 r
  result_reg[7]/D (DFF)                   0.00       1.15 r
  data arrival time                                   1.15

  clock clk (rise edge)                   10.00      10.00
  clock network delay (propagated)         0.50      10.50
  result_reg[7]/CK (DFF)                             10.50 r
  library setup time                      -0.20      10.30
  data required time                                 10.30
  ---------------------------------------------------------------
  data required time                                 10.30
  data arrival time                                  1.15
  ---------------------------------------------------------------
  slack (MET)                                         9.15
*******************************************************************************

你看这个报告,slack是9.15ns,说明时序余量很充裕。但如果slack是负的,比如-0.5ns,那就得想办法优化了。

注意:slack为正不代表设计没问题。我曾经遇到一个设计,综合报告全是绿色,但流片回来就是跑不到目标频率。后来发现是约束里忘了设clock uncertainty,工具把理想时钟当成了真实时钟。所以,clock uncertainty一定要加,通常设周期5%~10%。

2.3 面积优化:别让芯片变成奢侈品

面积就是钱。同样的功能,面积越小,成本越低。但面积和时序往往是矛盾的——你越想跑得快,就得用更大的cell,面积自然就上去了。

2.3.1 面积优化的常用手段

  • 资源共享:多个运算共用同一个硬件模块,比如两个加法器合并成一个
  • 操作数重排:把关键路径上的操作提前,减少逻辑级数
  • 门控时钟:不用的寄存器关掉时钟,既省面积又省功耗
  • 工艺库选择:用低驱动强度的cell代替高驱动强度的cell

我建议你在综合脚本里加上 set_ultra_optimization -area(DC)或 set_db area_weight 1.0(Genus),让工具在优化时多考虑面积因素。

2.4 时序与面积的平衡艺术

说白了,这就是个trade-off。你不可能既要马儿跑得快,又要马儿不吃草。

我个人习惯的做法是:

  1. 先跑一版不加任何面积优化,看看时序能不能收敛
  2. 如果时序有violation,先优化代码和约束,而不是急着调工具参数
  3. 时序收敛后,再打开面积优化选项,逐步收紧面积目标
  4. 每次只改一个参数,对比前后结果,记录变化

平衡策略总结:

场景 优先目标 推荐做法
高频设计(>500MHz) 时序优先 使用高驱动cell,关闭面积优化
低成本设计 面积优先 开启资源共享,使用低驱动cell
一般设计(100~500MHz) 平衡 先满足时序,再逐步收紧面积

2.5 避坑指南

做综合这些年,我踩过的坑不少。挑几个典型的说说:

  • 忘记设wire load model:工具会默认用理想连线,综合结果跟实际差很远。我吃过这个亏,后来每次都在脚本里显式指定。
  • 约束过紧:有些人为了保险,把约束设得特别严。结果工具为了满足时序,用了大量大cell,面积爆炸。其实留10%~15%的余量就够了。
  • 忽略transition time:综合报告里时序都过了,但后仿就是出问题。后来发现是某个信号的transition time太大,导致下一级cell延迟剧增。记得加 set_max_transition。
  • 综合后不看log:工具在log里会报很多warning,比如unresolved reference、inferred latch。我建议你养成习惯,每次综合完先扫一遍log,别等流片回来才发现问题。

一个实用脚本片段:

# 设置时钟
create_clock -period 10 [get_ports clk]
set_clock_uncertainty 0.5 [get_clocks clk]

# 设置输入输出延迟
set_input_delay -max 3 [all_inputs] -clock clk
set_output_delay -max 4 [all_outputs] -clock clk

# 设置transition和load
set_max_transition 0.5 [current_design]
set_max_capacitance 0.3 [current_design]

# 综合策略
compile_ultra -no_autoungroup -retime

嗯,综合这一步,说难不难,说简单也不简单。关键是要理解工具在做什么,而不是盲目跑脚本。你想想看,工具只是按你的约束和代码来干活,真正决定芯片质量的,还是你写的代码和约束的质量。