4. 综合中的时序优化:逻辑综合流程、综合策略、面积与速度的权衡

逻辑综合,说白了就是把 RTL 代码翻译成门级网表。这一步做得好不好,直接决定了芯片能不能跑起来、能跑多快、功耗多少、面积多大。我见过不少团队,RTL 写得挺漂亮,结果综合策略没选对,最后时序收敛成了噩梦。

嗯,咱们今天就把综合这件事掰开揉碎,看看里面到底有哪些门道。

4.1 逻辑综合的标准流程

综合不是一键点下去就完事的。它分三步走:

  1. 转译(Translation):把 HDL 代码读进来,转成 GTECH 网表。这一步不优化,只做语法解析和结构映射。
  2. 逻辑优化(Logic Optimization):对布尔表达式做化简、资源共享、重定时等操作。说白了就是让电路更聪明。
  3. 工艺映射(Technology Mapping):把优化后的逻辑映射到目标工艺库的标准单元上。这一步决定了最终的面积和延迟。

核心观点:综合的瓶颈往往不在工具,而在你给的约束和策略。工具只是听话的执行者。

我在一个 28nm 的项目里,就因为转译阶段没处理好三态总线,导致后续优化全乱套。后来我养成了一个习惯:综合前先跑一遍 lint 检查,把语法层面的坑先填平。

4.2 综合策略:到底该选哪种?

综合工具(比如 Synopsys DC、Cadence Genus)通常提供几种策略。我个人把它们分成三类:

策略 特点 适用场景
Top-Down 整个设计一次性综合,工具全局优化 中小规模设计(< 500K 门)
Bottom-Up 先综合子模块,再拼顶层 大规模设计,或模块间有独立时序要求
Incremental 在已有网表上做局部优化 ECO 阶段或小范围修改

你想想看,Top-Down 虽然省事,但一旦设计规模大了,工具跑不动,内存爆掉,那就尴尬了。我建议:超过 200 万门的设计,老老实实用 Bottom-Up。每个子模块单独设约束,最后顶层只做连线优化。

小技巧:Bottom-Up 策略里,给每个子模块留 5%-10% 的时序余量。这样顶层拼起来才不会崩。我曾经因为没留余量,顶层时序差了 200ps,最后不得不重跑整个综合流程。

4.3 面积与速度的权衡:鱼和熊掌怎么兼得?

这是综合里最经典的话题。面积小、速度快,理论上不可能同时做到。但实际项目中,我们往往需要找到一个平衡点。

影响面积和速度的因素主要有这几个:

  • 逻辑深度:组合逻辑级数越深,延迟越大,但面积可能更小(因为用了更少的门)。
  • 驱动强度:大驱动单元能跑得快,但面积大、功耗高。
  • 资源共享:复用运算单元能省面积,但可能引入多路选择器,增加延迟。
  • 重定时:移动寄存器位置来平衡路径延迟,可能增加寄存器数量。

我记得有个项目,数据通路里用了大量的乘法器。为了省面积,我让工具做了资源共享,结果关键路径上多了两级 MUX,时序直接崩了。后来我改成部分共享,只对非关键路径做优化,才把时序收回来。

避坑指南:不要盲目追求面积最小。面积省了 10%,时序差了 20%,得不偿失。我一般先跑一次面积优先,再跑一次速度优先,然后取中间值作为 baseline。

4.4 综合中的时序优化技巧

这里我分享几个实战中常用的方法:

4.4.1 设置合理的约束

约束是综合的指挥棒。常见的约束包括:

  • 时钟周期:目标频率的 90%-95% 作为约束,留点余量。
  • 输入/输出延迟:根据外部接口时序来设,别乱猜。
  • 虚假路径:告诉工具哪些路径不用优化,能省不少力气。
  • 多周期路径:对于跨时钟域或慢速逻辑,明确声明多周期约束。

4.4.2 使用 compile_ultra 的进阶选项

Synopsys DC 的 compile_ultra 命令有很多开关。我常用的几个:

# 开启面积优化与速度优化的平衡
compile_ultra -area_high_effort_script

# 开启寄存器重定时
compile_ultra -retime

# 开启自动门控时钟
compile_ultra -gate_clock

# 指定扫描链插入
compile_ultra -scan

注意,-retime 选项虽然能优化时序,但会增加综合时间。我一般只在最后冲刺阶段才开。

4.4.3 分组优化与边界优化

对于 Bottom-Up 策略,子模块的边界优化很关键。工具默认会在模块边界插入 buffer,但有时候会过度插入。我建议:

  • 对顶层模块设置 dont_touch 属性,防止工具乱改。
  • 对子模块的输出端口设置合理的驱动强度。
  • 使用 set_boundary_optimization 命令控制边界优化力度。

4.5 综合后的检查清单

综合跑完后,别急着往下走。先检查这几项:

  1. 时序报告:看 setup 和 hold 是否满足。重点关注最差路径。
  2. 面积报告:对比预估面积,看是否超标。
  3. 功耗报告:动态功耗和静态功耗是否在预算内。
  4. 扇出/扇入:高扇出节点可能导致时序恶化。
  5. 未映射单元:检查是否有未被工艺库映射的单元。

经验之谈:我每次综合完都会跑一遍形式验证(Formal Verification),确保网表和 RTL 功能一致。这一步不能省,否则后面 debug 会哭。

4.6 综合策略选择流程图

下面这张图总结了综合策略的选择逻辑,你可以对照自己的项目来选:

综合策略选择流程图 综合策略选择流程 开始综合 设计规模 > 200万门? 是 否 Bottom-Up 策略 子模块独立约束 + 顶层连线优化 Top-Down 策略 一次性综合,全局优化 时序收敛? 否 是 调整约束/策略 综合完成 说明:时序不收敛时,优先检查约束是否合理,再考虑切换策略或开启进阶优化选项。

4.7 面积与速度的权衡实战案例

我拿一个实际项目来说。一个 32 位加法器树,用三种不同策略综合,结果如下:

策略 面积 (μm²) 延迟 (ns) 功耗 (mW)
面积优先 1200 2.8 15
速度优先 1800 1.9 22
平衡策略 1500 2.2 18

你看,面积优先比速度优先省了 33% 的面积,但延迟多了 47%。而平衡策略只比速度优先多了 15% 的延迟,面积却省了 16%。

我个人习惯:先跑一次面积优先,看看最差路径在哪里。如果关键路径的 slack 为正,那就用面积优先。如果 slack 为负,再切换到平衡策略,逐步收紧约束。

实用建议:在综合脚本里,用 group_path 命令把关键路径和非关键路径分开优化。关键路径用速度优先,非关键路径用面积优先。这样既能保证时序,又能控制面积。

好了,综合中的时序优化就聊到这里。记住一句话:综合不是终点,而是时序收敛的起点。后面我们还会讲到布局布线阶段的优化,那才是真正的硬仗。