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 的直接成员,不会自动下钻到子层。
二、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 的另一个重要选项,它按“属于哪个对象”来筛选引脚,比用通配符路径更稳健,尤其适合在层次化设计中批量处理。
六、层次遍历的检查清单
在实际项目中,建议按以下顺序检查层次化查询是否正确:
- 用
current_design确认当前设计名,避免在错误层次上操作。 - 用
get_designs确认目标子模块确实存在。 - 用
get_cells -hierarchical确认实例路径名,注意斜杠分隔。 - 用
sizeof_collection检查返回数量,空集合立即排查。 - 用
get_object_name打印实际对象名,与预期路径比对。 - 约束设置完成后,用
report_clock、report_timing验证约束是否生效。
把这套检查流程固化到脚本中,可以显著减少因对象层次混淆导致的约束错误。Design Compiler 的层次管理并不复杂,核心就是一句话:先明确 current_design,再决定是否下钻,最后用对象名验证结果。