逻辑综合基础:从RTL到门级网表的第一次握手

各位同学,今天咱们聊聊逻辑综合。这是后端流程的第一步,也是很多人容易轻视的一步。

我记得刚入行那会儿,总觉得综合就是跑个脚本的事。直到有一次,我综合出来的网表面积比预期大了30%,后端布局死活塞不进那片区域……嗯,从那以后我才明白,综合这一步要是没打好基础,后面全是坑。

综合的概念与目标

什么叫逻辑综合?说白了,就是把我们写的RTL代码(Verilog/VHDL),翻译成由标准单元库里的门级电路组成的网表。

你想想看,RTL里写的是 assign c = a & b;,综合工具要把它变成具体的与非门、或非门、反相器。这个过程,就是综合。

综合的目标其实就三个:

  • 功能正确:综合后的网表,行为必须和RTL完全一致。这是底线。
  • 时序达标:所有路径的延迟,都要满足你设定的时钟频率。跑不到目标频率,芯片就是废的。
  • 面积和功耗可控:不能为了时序拼命堆大驱动单元,也不能为了省面积把时序搞崩。这是个平衡的艺术。

我在项目中遇到过一种情况:RTL仿真全过,综合后功能也正确,但流片回来芯片就是跑不到目标频率。查到最后,发现是综合时约束没写对,工具把关键路径优化错了方向。所以,综合不是简单的「翻译」,它是一次深度优化。

标准单元库(Liberty)解读

综合工具用什么来「翻译」RTL?答案是标准单元库。这个库通常以Liberty格式(.lib)提供。

我建议你拿到一个.lib文件后,先别急着跑综合。打开看看,里面信息量很大。

Liberty文件里有什么?

字段含义我的经验
cell单元名称,比如INVX1、NAND2X2名字里的数字代表驱动强度,X1最弱,X8最强
pin输入输出引脚每个引脚都有电容值,这个值直接影响前级驱动
timing时序弧信息包含cell rise/fall、transition等,是STA的基础
power功耗信息内部功耗、开关功耗,低功耗设计必看
area单元面积单位通常是um²,综合时工具会尽量选面积小的

举个例子,一个INVX1的.lib片段长这样:

cell (INVX1) {
  area : 0.784;
  pin (A) {
    direction : input;
    capacitance : 0.002;
  }
  pin (Y) {
    direction : output;
    function : "(!A)";
    timing () {
      related_pin : "A";
      cell_rise (delay_template_7x7) {
        values ( \
          "0.012, 0.015, 0.019, 0.024, 0.030, 0.037, 0.045", \
          "0.014, 0.017, 0.021, 0.026, 0.032, 0.039, 0.047", \
          ...
        );
      }
    }
  }
}

你看,这个反相器的面积是0.784,输入引脚电容0.002pF。输出延迟是一个7x7的查找表,横轴是输入转换时间,纵轴是输出负载电容。综合工具就是查这个表,算出每条路径的延迟。

重要提醒:同一个功能的不同驱动强度单元,延迟和面积差异很大。比如INVX1面积小但驱动弱,INVX8面积大但驱动强。综合工具会在时序和面积之间做权衡。

怎么看懂时序弧?

每个单元内部,从输入到输出有多条路径,每条路径都有一条时序弧。比如一个二输入与非门,从A到Z有一条,从B到Z也有一条。

每条时序弧包含:

  • cell_rise/cell_fall:单元本身的上升/下降延迟
  • rise_transition/fall_transition:输出信号的转换时间

我刚开始看.lib的时候,总觉得这些表格密密麻麻的。后来发现一个规律:输入转换时间越慢,输出负载越大,延迟就越大。这个规律在所有单元上都成立。

小技巧:如果你发现某条路径时序紧张,可以试试换用驱动强度更大的单元。但要注意,驱动强度大了,面积和功耗也会涨。我曾经为了修一条hold violation,把整条路径的单元都换大了一号,结果面积涨了5%……得不偿失。

综合约束(SDC)编写入门

SDC(Synopsys Design Constraints)是综合和时序分析的标准约束格式。没有约束,综合工具就不知道你要优化成什么样。

我见过很多新手,SDC就写两三行:create_clock和set_input_delay。结果综合出来的网表,时序一塌糊涂。SDC写得好不好,直接决定了后端能不能顺利收工。

核心SDC命令

下面这几个命令,是每个项目都必须有的:

# 创建时钟,周期10ns,占空比50%
create_clock -name clk -period 10 [get_ports clk]

# 设置输入延迟,相对于时钟上升沿
set_input_delay -max 5 -clock clk [get_ports data_in]

# 设置输出延迟
set_output_delay -max 5 -clock clk [get_ports data_out]

# 设置伪路径(不需要检查时序的路径)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

# 设置多周期路径
set_multicycle_path -setup 2 -from [get_pins reg_a/CK] -to [get_pins reg_b/D]

我来解释一下这几条命令的含义:

  • create_clock:告诉工具时钟长什么样。周期、占空比、时钟源端口。这是所有时序分析的基准。
  • set_input_delay:外部输入信号到达芯片引脚的时间。这个值通常由前级芯片的时序参数决定。设小了,工具会过度优化;设大了,时序可能过不了。
  • set_output_delay:芯片输出信号被下一级采样的时间。同理,需要和后级芯片对齐。
  • set_false_path:有些路径不需要检查时序,比如跨时钟域的同步器路径。告诉工具别管它,能省很多优化资源。
  • set_multicycle_path:有些路径需要多个时钟周期才能稳定。比如一个乘法器,可能需要两个周期才能出结果。这时候就要设多周期路径。

避坑指南:我曾经在一个项目里,忘了设set_false_path。结果综合工具花了大把时间去优化一条跨时钟域的路径,导致其他关键路径的时序反而变差了。所以,该设的约束一定要设全,别偷懒。

SDC编写的常见误区

  1. 时钟定义不完整:只定义了主时钟,忘了生成时钟(generated clock)。比如PLL输出的时钟,必须用create_generated_clock定义。
  2. 输入输出延迟设得太紧:设得太紧,工具会拼命优化IO路径,导致内部路径资源被挤占。我建议留10%-20%的余量。
  3. 忘了设时序例外:异步复位、跨时钟域路径、测试模式路径,这些都要设false_path或disable_timing。
  4. 时钟组没设:多个异步时钟之间,要用set_clock_groups -asynchronous告诉工具它们之间不需要检查时序。

知识体系总览

下面这张图,是我自己总结的逻辑综合知识框架。你可以把它当作学习路线图:

逻辑综合知识体系 综合的概念与目标 • RTL → 门级网表 • 功能正确 • 时序达标 • 面积/功耗可控 • 综合 = 翻译 + 优化 标准单元库 (Liberty) • cell: 单元名称/面积 • pin: 引脚电容 • timing: 时序弧查找表 • power: 内部/开关功耗 • 驱动强度: X1~X8 • 延迟 = f(输入转换, 负载) • 面积 vs 时序 权衡 综合约束 (SDC) • create_clock • set_input/output_delay • set_false_path • set_multicycle_path • set_clock_groups • 约束要完整 • 留10%~20%余量 依赖 指导 提供参数 综合输出:门级网表 (.v) + 时序报告 (.rpt) 输入:RTL + Liberty库 + SDC约束 → 综合工具 (DC/Genus/Yosys) ↓ 下一阶段:布局规划 (Floorplan) ↓

这张图把逻辑综合拆成了三个核心模块:综合概念、标准单元库、SDC约束。三者缺一不可。概念告诉你「为什么」,库告诉你「用什么」,约束告诉你「做到什么程度」。

我个人习惯是,拿到一个新工艺库,先花半天时间把.lib文件从头到尾看一遍。看看有哪些单元类型,驱动强度范围是多少,典型延迟值大概在什么量级。这样后面写SDC的时候,心里就有底了。

好了,逻辑综合的基础就讲到这里。下一节我们会深入综合工具的具体操作,包括如何读入设计、如何设置综合策略、如何分析综合报告。到时候我会拿一个真实项目案例来演示。


公众号:蓝海资料掘金营,微信deep3321