逻辑综合进阶:面积、时序、功耗的三角博弈

综合做完基础约束,能跑出网表了,对吧?但那个网表能不能用,差得远呢。我见过太多新手,跑完综合一看没报错,就兴冲冲往后端走,结果布局布线后时序一团糟,面积大得离谱,功耗也压不住。

说白了,逻辑综合不是「跑通就行」,而是要在面积、时序、功耗这三个维度上找到平衡点。这三者就像跷跷板,你压了一头,另一头就翘起来。今天我们就聊聊怎么在 OpenROAD 里做这个平衡。

核心观点:综合阶段对芯片最终 PPA(Performance, Power, Area)的影响超过 60%。布局布线能优化的空间其实有限,根子在综合。

面积优化:别让芯片「虚胖」

面积直接决定成本。晶圆厂按片卖,你芯片面积大,每片切出来的 die 就少,成本自然高。我做过一个项目,综合时没管面积,结果后端发现芯片比预期大了 15%,最后只能砍功能——教训深刻。

OpenROAD 的 yosys 里,面积优化主要靠这几招:

  • 资源共享(Resource Sharing):多个运算共用同一个硬件模块。比如两个加法器,如果不同时使用,综合器可以合并成一个。
  • 操作数重排序(Operator Reordering):改变运算顺序,减少中间结果的位宽。
  • 常量传播(Constant Propagation):把编译期能算出来的值直接算好,省掉硬件。

在 yosys 脚本里,我习惯这样写:

# 开启资源共享
synth -top top_module -flatten -shared

# 或者更细粒度控制
proc; opt; techmap; opt
share -fast_mode

我的经验:资源共享不是越多越好。共享太狠,控制逻辑变复杂,反而可能增大面积。我一般先跑一版不加共享的,看看 baseline,再加共享看效果。对比着来。

时序优化:让芯片跑得快

时序优化,说白了就是让信号在时钟周期内能稳定到达目的地。你想想看,如果路径太长,数据还没到,下一个时钟沿就来了——那就出错了。

OpenROAD 里时序优化主要靠:

  • 逻辑层级压缩(Logic Level Reduction):把多级逻辑合并成更少的级数。比如把 A+B+C+D 的四级加法,改成 (A+B)+(C+D) 的两级。
  • 关键路径重定时(Retiming):在寄存器之间移动逻辑,平衡路径延迟。
  • 单元尺寸调整(Cell Sizing):把关键路径上的小驱动单元换成大驱动单元。

在 OpenROAD 里,时序约束是核心。我一般这样写:

# 设置时钟周期为 10ns
create_clock -name clk -period 10 [get_ports clk]

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

# 设置 false path(异步路径)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

注意:set_false_path 别乱用。我曾经有个同事,把一条其实有约束的路径设成了 false path,结果芯片回来跑不到目标频率,查了三天才发现。false path 只用于真正的异步跨时钟域路径。

功耗优化:让芯片「省着吃」

功耗现在越来越重要。手机芯片、IoT 芯片,功耗高了根本没法用。综合阶段的功耗优化,主要分动态功耗和静态功耗。

功耗类型 来源 综合优化手段
动态功耗 信号翻转时充放电 门控时钟(Clock Gating)、操作数隔离(Operand Isolation)
静态功耗 漏电流 多阈值单元(Multi-Vt)、电源门控(Power Gating)

OpenROAD 里,yosys 本身对功耗优化支持有限,但我们可以通过 RTL 编码来辅助。比如:

// 手动插入门控时钟
always @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    data_q <= '0;
  else if (en)
    data_q <= data_d;
end

这样写,综合器就能识别出 en 信号,自动插入门控时钟单元。比让综合器自己去猜要靠谱得多。

我的习惯:在 RTL 里就把门控时钟写清楚。虽然综合器也能自动推断,但手动写出来的控制更精确,面积也更小。我做过对比,手动门控比自动推断能省 10-15% 的功耗。

综合报告解读:别只看数字

综合跑完,会生成一堆报告。很多人只看「有没有违规」,其实报告里信息量很大。

OpenROAD 里,我主要看这几份报告:

  • 时序报告(timing.rpt):看最差路径的 slack,以及路径的组成。
  • 面积报告(area.rpt):看各模块的面积占比,找「大块头」。
  • 功耗报告(power.rpt):看动态和静态功耗的占比。

举个例子,时序报告里如果看到:

Startpoint: reg_a
Endpoint:   reg_b
Path Group: clk
Path Type:  max

  Level   Delay   Description
  ----------------------------------------
    0     0.00    clock network delay
    1     0.35    reg_a/Q (DFF)
    2     1.20    U1/A (AND2)
    3     1.45    U2/A (OR2)
    4     0.80    reg_b/D (DFF)
  ----------------------------------------
  Total:  3.80 ns  (slack: -0.30 ns)

slack 是负的,说明这条路径超了。看路径组成,AND2 和 OR2 各占了不少延迟。这时候我会考虑:能不能把 AND2+OR2 合并成一个 AOI 复合门?或者把逻辑挪到寄存器前面去?

解读技巧:不要只看最差路径。我习惯看 top 10 最差路径,如果它们都集中在同一个模块,那问题大概率出在那个模块的 RTL 设计上。如果分散在各处,可能是约束太紧或者时钟周期设得太短。

知识体系总览

下面这张图,是我自己总结的综合进阶知识框架。每次做项目前,我都会对照着过一遍:

逻辑综合进阶知识体系 逻辑综合进阶:三大优化维度 逻辑综合 面积优化 时序优化 功耗优化 资源共享 常量传播 逻辑层级压缩 重定时 门控时钟 多阈值单元 三者相互制约:优化时序可能增大面积 降低功耗可能牺牲性能

实战中的权衡策略

说了这么多,到底怎么选?我个人的做法是:

  1. 先定时序目标:芯片能不能跑,时序是第一位的。先满足时序,再看面积和功耗。
  2. 再压面积:时序满足后,用资源共享和常量传播压面积。每次改完都重新检查时序。
  3. 最后调功耗:面积和时序都差不多了,再上功耗优化。门控时钟、多阈值单元,一步步加。

避坑指南:不要一次性把所有优化选项全打开。我曾经这么干过,结果综合跑了 8 小时,出来的网表时序反而更差了。优化选项之间有冲突,要一个一个试,对比着来。