在数字芯片设计中,时序弧(Timing Arc)是连接逻辑单元内部行为的桥梁。无论是用 Verilog 描述组合逻辑,还是用 SystemVerilog 构建时序电路,理解时序弧的建模与约束都至关重要。本文将深入剖析时序弧的核心概念、分类及 Library Compiler 中的建模技巧,帮助你在 C++ 级性能分析或 Python 脚本自动化中也能融会贯通。

时序弧:数字电路的时间地图

时序弧定义了单元内部从一个节点到另一个节点的延迟或约束关系,与网表互连信息共同构成静态时序分析(STA)的路径基础。每一条时序弧都有明确的起点(Startpoint)和终点(Endpoint),其中起点可以是输入引脚、输出引脚或双向引脚,终点则始终是输出或双向引脚。唯一的例外是约束类时序弧,比如两个输入引脚之间的建立时间(Setup)或保持时间(Hold)检查。

关键区分:时序弧的起止点不同于时序路径的起止点——前者描述单元内部关系,后者描述跨单元的完整路径。例如,一个与门(AND gate)拥有两条从输入到输出的延迟类时序弧,如图1所示。

图1: 与门的A→C和B→C时序弧示意

组合逻辑 vs 时序逻辑:两类核心弧

时序弧必须区分组合逻辑类型(Combinational)和时序逻辑类型(Sequential),因为它们在设计综合与优化中的角色截然不同。Design Compiler 使用组合逻辑弧计算物理延迟并进行路径追踪,而时序逻辑弧则用于定义优化约束(Optimization Constraints)。

组合逻辑弧timing_type 包括:

  • combinational(通用组合延迟)
  • combinational_rise / combinational_fall(上升/下降沿)
  • three_state_disable / three_state_enable 系列(三态控制)

时序逻辑弧则覆盖更复杂的场景:

  • 边沿敏感延迟:rising_edgefalling_edge
  • 异步置位/复位:presetclear
  • 建立/保持时间:setup_risinghold_falling
  • 恢复/移除时间:recovery_risingremoval_falling
  • 非时序约束:non_seq_setup_risingnon_seq_hold_falling
  • 不变化约束:nochange_high_high 等四种组合

⚠️ 注意:这些弧在 JavaGo 等编程语言中虽无直接对应,但理解其抽象模型有助于你设计更高效的硬件描述语言(HDL)代码。

建模方法:从简单到精确

以图2所示的两个反相器单元为例,我们可以用两种方式建模其时序信息。

图2: 两个反相器组成的单元

模型A(简单):定义两条弧——从输入引脚 A 到输出 Y,以及从 A 到 Z。这是最直接的建模方式,适用于快速原型验证。

模型B(精确):第一条弧仍为 A→Y,第二条弧改为从 Y→Z。这种建模方式能反映输出 Y 上的负载对 Z 延迟的影响,更贴近实际物理行为。输出引脚到输出引脚的弧既可用于组合逻辑,也可用于时序逻辑单元。

图3: 两种建模方法对比

[AFFILIATE_SLOT_1]

定义 Timing 组:语法与命名规则

timing 组是 Library Compiler 中描述时序弧的核心结构,通常定义在 pin 组(或 bundle 组)内。其基本语法如下:

library (lib_name) {
  cell (cell_name) {
    pin (pin_name) {
      timing () {
        ... timing description ...
      }
    }
  }
}

命名的重要性:当同一对引脚之间存在多条时序弧时,必须为每条弧指定唯一名称。Library Compiler 允许定义多条弧,但其他工具(如 PrimeTime)在解析时可能存在兼容性问题,因此命名应清晰且避免歧义。

以下列出六种常见的多重时序弧场景,后续将逐一说明配置方法:

  1. 单个相关引脚 vs bundle 组中多个成员
  2. 多个相关引脚 vs bundle 组中多个成员
  3. 单个相关引脚 vs bus 组中多个比特
  4. 多个相关引脚 vs bus 组中多个比特
  5. bus 组中多个比特 vs 相关 bus 引脚
  6. 内部引脚 vs 终点 bus 组中所有比特

实战:从单引脚到多引脚建模

1️⃣ 单引脚 vs 单相关引脚

最简单的场景,在 timing 组中直接指定名称即可:

cell (my_inverter) {
  ...
  pin (A) {
    direction : input;
    capacitance : 1;
  }
  pin (B) {
    direction : output;
    function : "A'";
    timing (A_B) {
      related_pin : "A";
      ...
    } /* end timing() */
  } /* end pin B */
} /* end cell */

起点终点名称
ABA_B

2️⃣ 单引脚 vs 多相关引脚

当终点引脚有多个相关引脚时,使用名称列表标识多条弧:

cell (my_and) {
  ...
  pin (A) {
    direction : input;
    capacitance : 1;
  }
  pin (B) {
    direction : input;
    capacitance : 2;
  }
  pin (C) {
    direction : output
    function : "A B";
    timing (A_C, B_C) {
      related_pin : "A B";
      ...
    }/* end timing() */
  }/* end pin B */
}/* end cell */

起点终点名称
ACA_C
BCB_C

3️⃣ Bundle 组 vs 单相关引脚

如果 timing 组位于 bundle 组内,且只有一个相关引脚,Library Compiler 按成员顺序映射名称:

...
bundle (Q){
  members (Q0, Q1, Q2, Q3);
  direction : output;
  function : "IQ";
  timing (G_Q0, G_Q1, G_Q2, G_Q3){
    timing_type : rising_edge;
    related_pin : "G";
  }
}

起点终点名称
GQ0G_Q0
GQ1G_Q1
GQ2G_Q2
GQ3G_Q3

起点终点名称
G0Q0G_Q0
G1Q1G_Q1
G2Q2G_Q2
G3Q3G_Q3

4️⃣ Bundle 组 vs 多相关引脚

类似地,当 bundle 组每个成员都有对应相关引脚时,名称列表按顺序一一对应:

bundle (Q){
  members (Q0, Q1, Q2, Q3);
  direction : output;
  function : "IQ";
  timing (G_Q0, H_Q0, G_Q1, H_Q1, G_Q2, H_Q2, G_Q3, H_Q3){
    timing_type : rising_edge;
    related_pin : "G H";
  }
}

起点终点名称
GQ0G_Q0
HQ0H_Q0
GQ1G_Q1
HQ1H_Q1
GQ2G_Q2
HQ2H_Q2
GQ3G_Q3
HQ3H_Q3

起点终点名称
G0Q0G_Q0
HQ0H_Q0
G1Q1G_Q1
HQ1H_Q1
G2Q2G_Q2
HQ2H_Q2
G3Q3G_Q3
HQ3H_Q3

5️⃣ Bus 组 vs 单相关引脚

Bus 组中的比特共享同一个相关引脚时,名称列表按从 MSB 到 LSB 的顺序映射:

...
bus (X){
  /* assuming MSB is X[0] */
  bus_type : bus4;
  direction : output;
  capacitance : 1;
  pin (X[0:3]){
    function : "B'";
    timing (B_X0, B_X1, B_X2, B_X3){
      related_pin : "B";
    }
  }
}

起点终点名称
BX[0]B_X0
BX[1]B_X1
BX[2]B_X2
BX[3]B_X3

起点终点名称
B[0]X[0]B_X0
B[1]X[1]B_X1
B[2]X[2]B_X2
B[3]X[3]B_X3

6️⃣ Bus 组 vs 多相关引脚

每个比特有独立相关引脚时,名称列表同样按 MSB 到 LSB 顺序:

bus (X){
  /* assuming MSB is X[0] */
  bus_type : bus4;
  direction : output;
  capacitance : 1;
  pin (X[0:3]){
    function : "B'";
    timing (B_X0, C_X0, B_X1, C_X1, B_X2, C_X2, B_X3, C_X3){
      related_pin : "B C";
    }
  }
}

起点终点名称
BX[0]B_X0
CX[0]C_X0
BX[1]B_X1
CX[1]C_X1
BX[2]B_X2
CX[2]C_X2
BX[3]B_X3
CX[3]C_X3

起点终点名称
B[0]X[0]B_X0
CX[0]C_X0
B[1]X[1]B_X1
CX[1]C_X1
B[2]X[2]B_X2
CX[2]C_X2
B[3]X[3]B_X3
CX[3]C_X3

[AFFILIATE_SLOT_2]

总结:掌握时序弧,驾驭数字设计

时序弧是静态时序分析的基石,也是 Library Compiler 建模的核心。本文从组合逻辑与时序逻辑弧的分类出发,深入介绍了两种建模方法,并通过六种实战场景详细演示了 timing 组的命名与配置技巧。无论你使用 Python 编写自动化脚本,还是用 C++ 进行性能建模,理解这些底层概念都能让你在设计、验证和优化中更加游刃有余。下一期我们将进一步探讨约束类时序弧的进阶用法,敬请期待!