在数字芯片设计中,时序弧(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_edge、falling_edge - 异步置位/复位:
preset、clear - 建立/保持时间:
setup_rising、hold_falling等 - 恢复/移除时间:
recovery_rising、removal_falling - 非时序约束:
non_seq_setup_rising、non_seq_hold_falling - 不变化约束:
nochange_high_high等四种组合
⚠️ 注意:这些弧在 Java 或 Go 等编程语言中虽无直接对应,但理解其抽象模型有助于你设计更高效的硬件描述语言(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)在解析时可能存在兼容性问题,因此命名应清晰且避免歧义。
以下列出六种常见的多重时序弧场景,后续将逐一说明配置方法:
- 单个相关引脚 vs bundle 组中多个成员
- 多个相关引脚 vs bundle 组中多个成员
- 单个相关引脚 vs bus 组中多个比特
- 多个相关引脚 vs bus 组中多个比特
- bus 组中多个比特 vs 相关 bus 引脚
- 内部引脚 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 */| 起点 | 终点 | 名称 |
| A | B | A_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 */| 起点 | 终点 | 名称 |
| A | C | A_C |
| B | C | B_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";
}
}| 起点 | 终点 | 名称 |
| G | Q0 | G_Q0 |
| G | Q1 | G_Q1 |
| G | Q2 | G_Q2 |
| G | Q3 | G_Q3 |
| 起点 | 终点 | 名称 |
| G0 | Q0 | G_Q0 |
| G1 | Q1 | G_Q1 |
| G2 | Q2 | G_Q2 |
| G3 | Q3 | G_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";
}
}| 起点 | 终点 | 名称 |
| G | Q0 | G_Q0 |
| H | Q0 | H_Q0 |
| G | Q1 | G_Q1 |
| H | Q1 | H_Q1 |
| G | Q2 | G_Q2 |
| H | Q2 | H_Q2 |
| G | Q3 | G_Q3 |
| H | Q3 | H_Q3 |
| 起点 | 终点 | 名称 |
| G0 | Q0 | G_Q0 |
| H | Q0 | H_Q0 |
| G1 | Q1 | G_Q1 |
| H | Q1 | H_Q1 |
| G2 | Q2 | G_Q2 |
| H | Q2 | H_Q2 |
| G3 | Q3 | G_Q3 |
| H | Q3 | H_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";
}
}
}| 起点 | 终点 | 名称 |
| B | X[0] | B_X0 |
| B | X[1] | B_X1 |
| B | X[2] | B_X2 |
| B | X[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";
}
}
}| 起点 | 终点 | 名称 |
| B | X[0] | B_X0 |
| C | X[0] | C_X0 |
| B | X[1] | B_X1 |
| C | X[1] | C_X1 |
| B | X[2] | B_X2 |
| C | X[2] | C_X2 |
| B | X[3] | B_X3 |
| C | X[3] | C_X3 |
| 起点 | 终点 | 名称 |
| B[0] | X[0] | B_X0 |
| C | X[0] | C_X0 |
| B[1] | X[1] | B_X1 |
| C | X[1] | C_X1 |
| B[2] | X[2] | B_X2 |
| C | X[2] | C_X2 |
| B[3] | X[3] | B_X3 |
| C | X[3] | C_X3 |
[AFFILIATE_SLOT_2]
总结:掌握时序弧,驾驭数字设计
时序弧是静态时序分析的基石,也是 Library Compiler 建模的核心。本文从组合逻辑与时序逻辑弧的分类出发,深入介绍了两种建模方法,并通过六种实战场景详细演示了 timing 组的命名与配置技巧。无论你使用 Python 编写自动化脚本,还是用 C++ 进行性能建模,理解这些底层概念都能让你在设计、验证和优化中更加游刃有余。下一期我们将进一步探讨约束类时序弧的进阶用法,敬请期待!
浙公网安备 33010602011771号