3. 设计对象与层次管理

在 Design Compiler 中,所有综合操作都围绕一组“设计对象”展开。理解这些对象的层次关系,是写出可靠约束脚本、正确遍历网表、精准定位时序路径的前提。很多初学者在写 get_* 命令时反复报 “nothing matched”,根源往往不是命令拼写错误,而是没有分清当前设计(current_design)与对象层次之间的从属关系。

一、六类核心对象的层次关系

Design Compiler 的设计对象可以按“容器—成员”的包含关系来理解。最上层是 design,它代表一个模块或一个完整的设计单元,对应 Verilog 中的一个 module。每个 design 内部包含若干 cell(实例)、net(连线)、port(端口)和 pin(引脚)。其中 cell 是实例化的子模块或工艺库单元,net 是连接各引脚的信号线,port 是 design 与外界交互的边界,pin 则是 cell 或 design 上的具体连接点。

关键点在于:port 和 pin 是同一类连接点在两个不同层次上的称呼。从 design 外部看,它是 port;从 design 内部看,它是 pin。同样,一个 cell 的引脚在父层看是 pin,在子层看则对应子 design 的 port。这种“同一对象、两种视角”的关系,是层次化设计遍历时最容易混淆的地方。

current_design 则是一个全局状态变量,它决定了 get_* 命令默认在哪个 design 范围内搜索。所有不带 -hierarchical 的查询都只作用于 current_design 的直接成员,不会自动下钻到子层。

Design Compiler 设计对象层次关系 current_design 全局状态:搜索起点 design: top_module port(边界连接点):clk, rst_n, data_in[7:0], data_out[7:0] cell(实例) U1: sub_ctrl U2: sub_datapath U3: NAND2X1(库单元) 每个 cell 有对应 pin net(连线) n1: U1/out → U2/in n2: U2/out → data_out clk_net: clk → 各触发器 net 连接 pin 与 port pin(引脚):cell 或 design 上的连接点 U1/A, U1/Y, U2/clk, U3/A1 —— 父层视角为 pin,子层视角为 port 子层 design: sub_ctrl / sub_datapath 子层内部同样有 cell、net、port、pin,形成递归层次 ← 搜索范围由 current_design 决定

二、get_* 命令族的使用逻辑

Design Compiler 提供了一组以 get_ 开头的查询命令,用于按名称、类型、属性等条件筛选设计对象。常用的包括:

  • get_designs:查询设计单元,常用于切换 current_design 前的确认。
  • get_cells:查询实例,支持 -hierarchical 下钻。
  • get_nets:查询连线,常用于检查跨模块信号连接。
  • get_ports:查询当前设计的端口,用于设置输入延迟、输出负载等约束。
  • get_pins:查询引脚,是时序约束和路径分析中最常用的对象。
  • get_clocks、get_libs 等:分别查询时钟和库对象。

这些命令的通用语法是 get_xxx [patterns] [options]。patterns 支持通配符 * 和 ?,options 中最关键的是 -hierarchical(简写 -h),它让查询从 current_design 开始递归遍历所有子层。另一个常用选项是 -filter,可以按属性过滤,例如 get_cells -filter "is_hierarchical == true" 只返回层次化实例。

需要特别注意的是:get_* 命令返回的是对象集合(collection),不是字符串列表。集合可以直接传给其他命令,也可以用 sizeof_collection 统计数量,用 foreach_in_collection 遍历。如果直接对集合做字符串操作,往往得不到预期结果。

三、层次化设计的遍历与查询实践

假设当前设计为 top_module,内部实例化了两个子模块 U1/sub_ctrl 和 U2/sub_datapath,同时还有一些库单元。下面通过一组示例命令展示遍历与查询的典型用法。

# 1. 确认当前设计
current_design top_module
get_designs top_module

# 2. 查询当前层所有实例(不下钻)
get_cells

# 3. 递归查询所有层次中的实例
get_cells -hierarchical *

# 4. 只查询层次化子模块实例
get_cells -hierarchical -filter "is_hierarchical == true"

# 5. 查询子模块 U1 内部的所有引脚
get_pins U1/*

# 6. 查询所有时钟端口
get_ports -filter "direction == in" clk*

# 7. 统计当前设计中 net 的数量
sizeof_collection [get_nets -hierarchical *]

# 8. 遍历所有层次化 cell 并打印名称
foreach_in_collection c [get_cells -hierarchical -filter "is_hierarchical == true"] {
    puts "Hierarchical cell: [get_object_name $c]"
}

执行第 3 条命令时,如果 top_module 下有两层子模块,返回结果会包含所有层级的实例,名称形如 U1、U2、U1/U1_1、U1/U1_2 等。而第 2 条命令只返回 U1、U2 以及顶层库单元,不会出现带斜杠的路径名。这个差异是判断查询是否下钻的最直接依据。

实用技巧:当 get_* 返回空集合时,先用 sizeof_collection 确认数量,再用 get_object_name 打印实际匹配到的对象名。不要假设命令一定匹配成功,尤其在层次化设计中,路径名拼写错误是常见原因。

四、current_design 的切换与作用域

current_design 决定了所有 get_* 命令的默认搜索根。切换 current_design 的常用方式是 current_design sub_ctrl 或 current_design U1。切换后,get_ports 返回的是子模块的端口,get_pins 返回的是子模块内部的引脚,而不是顶层视角下的引脚。

在编写约束脚本时,通常的做法是:先用 current_design top_module 设置顶层约束,再用 current_design 切换到子模块设置模块级约束,最后切回顶层。如果忘记切回,后续的 get_ports 可能作用在错误的层次上,导致约束丢失或报错。

常见陷阱:在子层执行 get_pins U1/A 时,如果 U1 是当前层的实例,命令有效;但如果 U1 是更深层的实例,必须使用完整路径 U1/U1_1/A 或加 -hierarchical。路径分隔符统一使用斜杠 /,不要用点号。

五、对象查询的典型应用场景

掌握对象层次和 get_* 命令后,可以完成很多实际任务。例如:用 get_ports 批量设置输入延迟;用 get_pins 定位触发器的时钟引脚并设置 set_clock_gating_check;用 get_nets 查找跨时钟域信号并设置 set_false_path;用 get_cells -hierarchical 统计设计规模,判断是否需要调整综合策略。

一个典型的批量约束示例如下:

# 为所有输入端口设置 2ns 输入延迟(相对时钟 clk)
set_input_delay 2.0 -clock clk [remove_from_collection [all_inputs] [get_ports clk]]

# 为所有输出端口设置 1.5ns 输出延迟
set_output_delay 1.5 -clock clk [all_outputs]

# 查找所有触发器并报告其时钟引脚
foreach_in_collection ff [get_cells -hierarchical -filter "is_sequential == true"] {
    set ff_pins [get_pins -of_objects $ff -filter "direction == in"]
    puts "FF [get_object_name $ff] input pins: [get_object_name $ff_pins]"
}

这段脚本中,-of_objects 是 get_pins 的另一个重要选项,它按“属于哪个对象”来筛选引脚,比用通配符路径更稳健,尤其适合在层次化设计中批量处理。

六、层次遍历的检查清单

在实际项目中,建议按以下顺序检查层次化查询是否正确:

  1. 用 current_design 确认当前设计名,避免在错误层次上操作。
  2. 用 get_designs 确认目标子模块确实存在。
  3. 用 get_cells -hierarchical 确认实例路径名,注意斜杠分隔。
  4. 用 sizeof_collection 检查返回数量,空集合立即排查。
  5. 用 get_object_name 打印实际对象名,与预期路径比对。
  6. 约束设置完成后,用 report_clock、report_timing 验证约束是否生效。

把这套检查流程固化到脚本中,可以显著减少因对象层次混淆导致的约束错误。Design Compiler 的层次管理并不复杂,核心就是一句话:先明确 current_design,再决定是否下钻,最后用对象名验证结果。