时序库(liberty)详解:单元延时、查找表、时序弧

各位,咱们今天来聊聊 liberty 库。说实话,这玩意儿是 STA 的“字典”。没有它,你连门延时都算不出来。我刚入行那会儿,以为 liberty 就是一堆表,后来被坑过一次才明白——里面藏着芯片速度的命脉。

什么是 Liberty 库

Liberty(通常叫 .lib)是 Synopsys 定义的标准时序库格式。它描述了一个标准单元库里每个单元的:

  • 输入引脚到输出引脚的传播延时
  • 时序约束(setup / hold / recovery / removal)
  • 功耗、面积、电容等参数

我个人习惯把 liberty 文件叫作“单元的身份证”。每个工艺节点、每个电压温度(PVT)组合都会生成一份独立的 .lib。你想想看,一个 7nm 库可能有几千个单元,每个单元有十几条时序弧,光解析就得花点功夫。

核心思路:延时 = 函数(输入转换时间, 输出负载电容)。Liberty 用查找表(LUT)存这个函数。

查找表(LUT)的结构

先看一段真实的 .lib 片段。别怕,我拆开讲。

cell (AND2X1) {
  pin (A) {
    direction : input;
    capacitance : 0.0032;
    timing () {
      related_pin : "Y";
      timing_sense : positive_unate;
      cell_rise (delay_template_7x7) {
        index_1 ("0.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.0");  // 输入转换时间 (ns)
        index_2 ("0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0");  // 输出负载电容 (pF)
        values ( \
          "0.012, 0.018, 0.025, 0.040, 0.065, 0.110, 0.200", \
          "0.015, 0.020, 0.030, 0.045, 0.070, 0.120, 0.210", \
          ...  // 共7x7=49个值
        );
      }
    }
  }
}

一个查找表由两个索引(index_1, index_2)和一个二维数组组成。索引是固定的采样点,实际工作时要靠线性插值算出非采样点的延时。

我曾经遇到一个问题:某库的 index_1 只给了3个点,导致高频下延时误差大。嗯,后来我跟库厂商吵了一架,让他们加了密度。

提醒:查表时注意边界值。如果输入转换时间超出索引范围,工具会用最靠近的端点值推算,这往往不准确。

延时模型的计算

一个典型的两输入与非门,它的cell_rise和cell_fall分别对应输出上升沿和下降沿的传播延时。每个查找表的名字指向一个模板(template),模板定义了索引轴的范围和单位。

模板名索引1(输入转换)索引2(输出负载)单位
delay_template_5x50.01, 0.1, 0.5, 1.0, 2.00.01, 0.05, 0.1, 0.5, 1.0ns / pF
delay_template_7x70.01, 0.05, 0.1, 0.2, 0.5, 1.0, 2.00.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1.0ns / pF

你可能会问:为什么不直接用公式?因为现代工艺下延时跟输入转换和负载的关系非常非线性,查表加插值才是最精确的办法。说白了,这是用存储空间换精度。

查找表结构示意图 NLDM 查找表二维网格示意 输入转换 (ns) 0.01 0.1 0.5 1.0 2.0 输出负载 (pF) 1.0 0.5 0.1 0.01 0.030 0.045 0.065 0.024 查表 → 线性插值 💡 实际工作方式 STA工具根据输入transition和输出load,找到最近四个采样点进行双线性插值。 索引点越密,延时精度越高,但库文件体积也越大。

时序弧详解

除了传播延时,liberty 里还有一堆“弧”(arc)。每条弧描述的是两个引脚之间的时序关系。我把它分为两类:

  1. 组合弧:从输入到输出的直通延时,比如 AND 门的 A→Y。
  2. 约束弧:定义时序检查,包括 setup、hold、recovery、removal、minimum pulse width 等。

看一个 setup 弧的例子:

pin (CK) {
  timing () {
    related_pin : "D";
    timing_type : setup_rising;
    rise_constraint (constraint_template_5x5) { 
      index_1 ("0.01, 0.1, 0.5, 1.0, 2.0");  // CK 的转换时间
      index_2 ("0.01, 0.1, 0.5, 1.0, 2.0");  // D 的转换时间
      values ( ... );
    }
  }
}

这里 timing_type 指定了弧类型。常见的类型有:

timing_type含义用于
setup_rising上升沿触发的建立时间D 相对 CK 上升沿
hold_falling下降沿触发的保持时间D 相对 CK 下降沿
recovery_rising复位信号的恢复时间复位相对时钟
removal_falling复位信号的移除时间复位相对时钟
min_pulse_width_high高电平最小脉宽时钟、复位
曾经踩坑:有一次我漏掉了某个 flip-flop 的 recovery 检查,结果复位释放后数据采样不稳定。后来被迫改了版。所以务必检查库里的所有约束弧是否覆盖完整。

你可能会问,为什么 setup 和 hold 也要用查找表?因为信号转换时间越慢,需要预留的余量越大。举个例子,如果 D 的转换时间很长(比如 2ns),setup 时间可能比 0.1ns 转换时要多出好几倍。这就是 LUT 存在的意义。

个人习惯总结

  • 新项目拿到 liberty 后,我会先跑一遍 report_lib 看是否有缺失的模板或奇怪的索引范围。
  • 对于高频模块(>1GHz),我建议要求库厂商提供 7x7 以上的 LUT,否则延时误差会吃掉时序裕量。
  • 注意 timing_sense:positive_unate(同向变化)、negative_unate(反向变化)、non_unate(未知)。这影响你判断路径是否可能同时翻转。
调试小技巧:在 Primetime 里用 report_timing -nosplit -transition_time -capacitance 看每个 pin 的 actual transition 和 load,然后手动查 .lib 验证插值结果。我曾经用这个方法抓到一个库的错误。

好了,关于 liberty 库的核心就这些。下次你再看到那些密密麻麻的表,心里要清楚:它们就是芯片性能的“天气预报”——依赖它,才能知道你的设计能不能按时到达终点。