☰
Tessent Shell设计内省与编辑:DFT网表查询修改实战笔记
2026/10/3 1:37:15 网站建设 项目流程

先用一句话交代背景:Tessent Shell 是西门子 EDA(原 Mentor)Tessent 工具套件里的交互式命令环境,而第三章 Design Introspection and Editing(设计内省与编辑)讲的是在这个环境里如何查看、分析、修改已经读入内存的设计。听起来简单,但这一章恰恰是我当初从写 RTL 转到做 DFT 时卡得最久的地方。你翻开手册前几十页时候觉得全是在讲命令格式,等真正拿到一个几百万门的网表,想弄清楚某个时钟域里有哪些扫描触发器、想把某条 net 断开插一个观测点的时候,才发现自己对“内省”和“编辑”的理解远远不够。

这篇就是我边啃手册边在项目里折腾出来的实践笔记,范围控制在第三章的前半部分,所以标题里写了“未完待续”。内容适合两类人:一是刚接触 Tessent、准备用 Shell 做设计检查或 DFT 规则预检的工程师;二是用了很久脚本但一直靠复制粘贴、没系统捋过对象模型的老手。看完你至少能搞清楚 Tessent Shell 里对象是怎么组织的、查询命令怎么用才高效、编辑命令改的到底是什么,以及最关键的——改完之后怎么把结果安全写回。

1. 在啃第三章之前,先理解 Tessent Shell 到底是个什么东西

1.1 它是命令解释器,不是普通的 Tcl

很多从 Synopsys Design Compiler 转过来的人,第一反应是“Tessent Shell 跟 DC 差不多吧”,然后就直接写 get_cells、get_ports。大方向没错,但 Tessent Shell 的定位完全不同。DC 的命令环境核心是逻辑综合,而 Tessent Shell 的核心是设计读取后的分析、DFT 规则检查、pattern 生成和仿真。它内部维护的不仅有网表连接关系,还有大量跟可测试性相关的属性,比如某个单元是不是扫描单元、某个端口是否可控制、某个时钟是否可关断。

更准确地说,Tessent Shell 是一个基于 Tcl 语法风格构建的专用命令环境。它不支持你在纯 Tcl 里玩的所有花活,但保留了变量、循环、条件判断这些基本能力,同时内置了几百条 EDA 专用命令。命令按功能分成几大类:读入和写出(read_/write_)、链接与层次管理(link_design、current_design)、查询与报告(get_/report_)、设计编辑(add_/remove_/change_)、约束与属性(set_)等等。

我自己一开始犯的错就是拿它当普通 Tcl 解释器用,使劲折腾列表操作和字符串处理,结果发现很多标准 Tcl 命令根本不存在或者行为不同。后来想明白了:Tessent Shell 的底层其实是一个设计对象数据库,Tcl 只是外壳,重点永远在“怎么操作设计对象”,而不是“怎么操作字符串”。

1.2 为什么要单独设一章讲“内省和编辑”

用户手册里单独把 Design Introspection and Editing 拎出来成章,不是因为作者凑篇幅,而是因为这两个能力是整个 Tessent 使用体验的地基。

先说内省(Introspection)。所谓内省,在 EDA 语境里就是你不需要打开原理图编辑器,就能用命令感知当前设计里的一切:有哪些子模块、每个 pin 叫什么名字、net 怎么连接、端口方向是什么、单元类型属于哪一类、有没有悬空引脚。这种能力在批处理和回归脚本里是不可替代的,因为 GUI 要看图,脚本只能靠查询命令拿信息。

再说编辑(Editing)。你可能会问,网表不是综合完就定死了吗?为什么还要改?实际做 DFT 或者可测试性分析的时候,有三种情况绕不开:一是网表里有部分模块不支持扫描,你要临时把它们隔离或者加旁路逻辑;二是设计里缺少某些测试结构,比如边界观测点、扫描链断点,你需要在内存里补插;三是为了调试某条具体路径,你想临时断开一条 net 看效果,而不是重新跑一遍综合。这些操作如果都要回到 RTL 改代码再重新综合,一轮至少几小时,而直接在 Tessent Shell 里编辑设计,喝杯水的功夫就能试完。

还有个关键点:Tessent Shell 里的编辑,绝大多数操作的是内存中的设计对象,不是磁盘上的原始网表文件。所以你可以大胆试,试完不合适就重新读入,不会污染源文件。但这同时也意味着,你必须明确知道哪些操作会真正写回,否则内存里的修改忘了保存,等于白干。第三章花了不少篇幅讲这些边界的理解,这也是我强烈建议不要跳过这章的原因。

2. 环境准备:一次完整的 Tessent Shell 会话长什么样

2.1 启动、读入设计与链接

这一节算是最基础的,但很多人会在这里栽跟头,所以我按实际顺序走一遍。

我通常这样启动 Tessent Shell:

tessent -shell

也有老版本或者特定安装方式支持直接敲 tessent,然后进到交互界面,提示符会变成类似tessent_shell>。进去之后第一件事不是急着读网表,而是先确认工作路径和库文件。Tessent Shell 本身不认识你项目里的工艺库路径,你要么在启动前设置好环境变量,要么在脚本里先告诉它。

读入设计这一步,最常用的是 read_verilog,也可以根据需求用 read_design 读入其他格式或者之前保存的二进制设计状态。举例:

set search_path [list . /home/project/rtl /home/project/syn/netlist] set target_library "slow_vdd1v0.db" read_verilog /home/project/syn/netlist/chip_top.v

这里有个细节,很多人只读网表不读库,后面查询单元类型、检查单元属性时候全部扑空。Tessent Shell 对设计的理解分成两层:网表告诉你单元怎么连,库告诉你单元是什么。所以 read_verilog 之后,通常还要读入对应的综合库或者 PDK 库,然后执行链接。

链接命令在不同版本里写法不太一样,常见的有 link_design 或者 link:

link_design

链接过程相当于把所有单元引用和库里的定义对上号。完成之后,你可以用 list_designs 看看内存里有哪些设计,用 current_design 切换当前操作对象:

list_designs current_design chip_top

如果网表有层次,读入后默认 current_design 可能是顶层,也可能是一个包壳(wrapper),记得用 list_designs 确认。我踩过一个坑:连续读入两个网表没注意,current_design 一直停在第一个设计上,后面所有查询全是空结果,排查了半天才发现对象搞错了。

2.2 用 help 和 schema 摸清一套命令

Tessent Shell 里我最常用的两个“手册外挂”,一个是 help,一个是描述对象类型的 schema 命令。

help get_cells help -verbose get_cells

第一条会告诉你 get_cells 的用法摘要,第二条会展开完整的参数说明和默认值。遇到不确定的命令,我基本都是先 help 再试。比翻 PDF 快很多,而且永远是当前版本的第一手信息。

schema 命令则更底层。你可以把它理解成数据库的表结构定义,展示某个对象类型有哪些属性、哪些属性是用户可设置的、哪些是只读的:

schema cell

这个命令对我帮助最大的是写 filter 的时候。比如我想过滤“所有非扫描的 D 触发器”,如果不知道属性名,就只能瞎猜。先敲 schema cell,看到可用的属性列表,再挑出 related to scan 的那几个属性,filter 一次就能写对。整个过程十分钟不到,比自己翻手册快得多。

提示:Tessent Shell 不同小版本之间命令可能会有细微差异。如果你在脚本里用了某个选项,另一台机器上报错 unknown option,第一时间 help 确认,不要想当然。

3. Design Introspection 核心:先学会“看”设计的每个角落

3.1 对象体系与集合(collection)机制

Tessent Shell 里所有设计信息都以对象的形式存在,常见对象类型包括:design(设计)、cell(实例/单元)、port(端口)、pin(引脚)、net(连线)、hierarchy(层次路径)、library(工艺库)、pattern(测试图形)等。

对象之间有关联关系,比如 cell 有 type 属性,port 属于某个 design,pin 属于某个 cell,net 连接多个 pin。理解这些关系,是你写查询命令的基础。

Tessent Shell 里查询命令返回的并不是普通字符串列表,而是一个“集合”(collection)。这个集合你可以直接 feed 给其他命令,也可以用专门命令遍历。常见写法:

set all_cells [get_cells -hier] puts [sizeof_collection $all_cells] foreach_in_collection c $all_cells { puts [get_object_name $c] }

很多人第一次写脚本时不理解为什么要引入 collection 这个概念,直接想把它当成 list 处理,结果 foreach 了半天全是地址串。记住:凡是从 get_* 和 create_* 返回的东西,先当集合处理,用 get_object_name 拿名字,用 sizeof_collection 拿数量,用 foreach_in_collection 遍历。这三个命令就能覆盖八成需求。

3.2 高频查询命令与实战写法

第三章里讲了一堆查询命令,我用下来真正高频的其实就那么几个:get_cells、get_pins、get_ports、get_nets,外加报告类命令 report_design、report_cells、report_ports。下面列几个我实际项目里反复用的模式:

查某个层次下的所有实例:

get_cells -hier -filter "is_hierarchical == true"

查所有扫描触发器,常见写法是通过单元类型名字匹配,或者用对象属性做过滤。比如:

get_cells -hier -filter "@ref_name =~ *SDFF*" get_cells -hier -filter "is_scan_cell == true"

注意一个细节:属性名的写法在不同版本里可能不同,有的用 is_scan_cell,有的用 is_scan_flop。写之前先查 schema cell,别凭记忆硬编。

查端口方向为 output 的顶层输出:

get_ports -filter "direction == out"

查某条 net 连接了哪些 pin:

get_nets clk get_pins -of_objects [get_nets clk]

提示:在 Tessent Shell 里,直接 get_cells 不带 -hier 时只查当前 design 的直属层次,不会递归到下层模块。看全部单元必须加 -hier。这个跟 DC 的 all_cells 逻辑类似,但新人不注意就漏了一片。

当你理解了对象模型,会发现查询命令本身很简单,真正体现内省能力的是你组合它们的方式。比如查“某个时钟域里所有扫描单元的驱动引脚”,连续用两三条 get 命令就能串起来,这在脚本化回归里是真正的利器。

3.3 从“能查”到“会问”:用 filter 写出精确查询

get 命令如果只是无脑加 -hier,会返回一大堆对象,真正有用的是会写 filter。filter 的语法大致是:

<属性> <操作符> <值>

操作符包括 ==、!=、=~(正则或者说通配符匹配)、!~(不匹配),以及逻辑组合 &&(与)、||(或)、!(非)。举几个我在项目里验证过的例子:

找出所有不是顶层端口的 input pin:

get_pins -hier -filter "direction == in && is_port_pin == false"

找所有 clock pin 并且连接到特定 net 名模式的单元:

get_pins -hier -filter "@net_name =~ *clk* && direction == in"

去掉没有输出连接的悬空单元,需要结合查询对象而不是纯属性过滤时,可以先拿到集合再二次过滤:

set dangling_cells [get_cells -hier -filter "is_unconnected == true"]

写 filter 最容易翻车的三个点:

  • 属性名拼错了,工具直接报 attribute not found;
  • 值的大小写和网表命名不一致,比如命名里是 SDFF 而你写 sdff,匹配不上;
  • 把对象属性当成函数用,试图在 filter 里调命令,这是不行的。

我自己的习惯是,拿不准属性名就先 schema cell,拿不准值就先 get_cells 抓一个对象看它的属性。宁可多花两分钟查,也别因为 filter 写错导致结果集不对,后面带着脏数据做编辑,那真是灾难。

4. Design Editing 核心:改网表不是改 RTL

4.1 编辑命令家族:add/remove/change/create/delete

设计编辑这块,命令看起来就是一组动词,但一定要清楚它们改的是内存中的对象模型,而不是文本文件。常见的编辑命令包括:

  • add_ports / add_pins / add_cells:新增端口、引脚或单元;
  • remove_cells / remove_nets / remove_ports:删除对象,同时更新连接关系;
  • change_cell / change_port_type / change_object_name:修改对象类型或名字;
  • create_cell / create_net / create_port:以更底层的方式创建对象;
  • disconnect_pin / connect_pin:断开或建立连接,这是网表编辑里最常用的两个;
  • write_design / save_design:把内存中的设计写回文件。

需要特别强调的是,这些命令不是文本替换,它们会维护连接关系的一致性。你删除一个 cell,与其相连的 net 会自动处理引用关系;你连接一个 pin,工具会帮你检查是否存在多驱动冲突。这也是为什么要在 Tessent Shell 里做编辑而不是用 Perl 脚本直接改网表文本。

不过也别高估工具的自动纠错能力,它不会阻止你做不合逻辑的事,比如把一个 input 端口改成 output 类型而不处理连接,后面检查时候就会报错。所以编辑完之后必须做一致性验证,这个后面单独说。

4.2 一个具体编辑流程示例

我拿之前做过的一个真实场景举例:某个子模块的输出信号,我想在它顶层的输出端口处插入一个 buffer 单元作为可测试性观测点,方便后面做 debug 的时候观察信号活动。先看原始设计:

current_design chip_top get_ports test_obs

然后我打算在顶层输出端口前放一个 buffer。思路分四步:断开原端口与内部逻辑的连接,创建 buffer 单元,把内部逻辑连到 buffer 的输入,再把 buffer 的输出连到原端口。

具体命令大概是:

# 找到驱动该端口的net set obs_net [get_nets -of_objects [get_ports test_obs]] # 断开该net与端口之间的连接 disconnect_pin [get_ports test_obs] # 创建buffer单元 create_cell obs_buf [get_lib_cells slow_vdd1v0/BUFX8] # 连接buffer输入到原来的net connect_pin [get_pins obs_buf/A] -to $obs_net # 连接buffer输出到端口 connect_pin [get_ports test_obs] -to [get_pins obs_buf/Y]

这里有个细节我必须多说一句:connect_pin 的语法在不同版本之间差异比较大,有的版本是 connect_pin [get_pins obs_buf/A] [get_nets obs_net],有的是 -to 形式。所以你实际操作时,第一件事还是 help connect_pin。我上面给的是常见写法,但别直接复制就上,先看版本。

执行完之后怎么确认改对了?再用查询命令:

get_pins -of_objects [get_nets obs_net] get_pins -of_objects [get_ports test_obs]

如果第一行的输出里出现了 obs_buf/A,第二行出现了 obs_buf/Y,说明连接成功。如果你还想确认驱动方向没错,可以 report_pins obs_buf/A 看看它的 direction 和 connected net。

这个流程本身不复杂,但它是后面所有复杂编辑的基础。等你需要在一个百兆级网表里批量插入测试结构时,思路也是一样的,只是换成了循环和批量操作。

4.3 写回与一致性检查

编辑只存在内存里,如果不写回,关掉 Shell 就全没了。Tessent 提供了几个写回命令,最常见的:

write_design -verilog -output /path/to/output.v

也有 save_design 这类保存整个会话的形式,适合后续接着调试。写回之后,我强烈建议重新开一个干净的 Shell,读回这个输出的网表,再跑一遍基本检查和 Lint,确认没有悬空引脚、没有未连接端口、没有单元类型缺失。

一致性检查怎么做?我常用的几个命令:

report_design -check check_design report_cells -unconnected

你要理解这步的价值。在项目里,编辑后的网表很可能直接作为后续 DFT 流程的输入,如果输出网表有隐性错误,后面的 scan insertion、ATPG 全都会崩,到时候回溯问题会非常痛苦。所以宁可多花五分钟做检查,也别把脏网表往下面传。

注意:写回路径和文件名尽量别用中文和特殊字符,Tessent Shell 在某些版本下对非常规字符支持不好,容易写出路径异常。这是小问题,但真碰上了很浪费时间。

5. 常见问题与排查速查(个人踩坑记录)

5.1 典型报错与解决办法

这一节我给出一张自己在实际使用中反复遇到的“报错-原因-解法”表格,不一定覆盖所有情况,但都是高频问题。

报错现象常见原因解决办法
command not found当前 Shell 没加载对应子系统;命令拼写不对先用 help 查当前版本是否支持;确认是否进入了正确的子环境
cannot find object xxx对象名写错或层次路径引用不对用 get_cells -hier -filter "name =~xxx" 先搜名字;确认 current_design 正确
no design in memory / design not linked没读入网表或没执行链接先 read_verilog,再 link_design,然后 list_designs 确认
attribute not found属性名拼写错误;对象类型不对用 schema <object_type> 查可用属性;确认查询对象是否属于该类型
collection is emptyfilter 条件过严;-hier 没加;网表中确实无该对象逐步放宽过滤条件;去掉 filter 先看全集;核对对象名大小写

除了表格里这些,还有一个非常隐蔽的坑:Tessent Shell 的交互历史和日志默认不会保存,你调试半天终于跑通了一串命令,结果忘了记录,后面想复用还得重新敲。我建议一进 Shell 就开启日志:

log_on /path/to/session.log

之后你会感谢自己这个习惯。

5.2 关于“未完待续”的实用建议

既然标题里写了“未完待续”,最后这部分我就多说几句怎么继续啃第三章以及后续章节。

Tessent 官方手册是出了名的厚,第三章本身就有大量命令和示例,如果对着 PDF 一页页翻,效率极低。我的做法是:先把目录和命令索引扫一遍,标记出跟当前任务相关的命令,然后直接在 Shell 里用 help 和 schema 做即时验证。手册更像字典,需要时查,而不是从头读到尾。

另外,我强烈建议给自己建一个常用脚本片段库。比如查询扫描单元、插入 buffer、断开 net、写回网表,这些操作都是高频动作,整理成带参数的过程(proc)脚本,下次需要时直接调用,能省出大量重复写命令的时间。我在实际项目里就是这么做的,脚本库里大概存了二十多个常用 proc,覆盖了网表检查、结构修改、报告输出这几类。

还有一点很重要:版本差异。Tessent Shell 的命令和属性在 2021、2022、2023 这些版本之间有过不少调整,网上很多教程是基于旧版本写的,用法可能已经变了。任何命令以你自己环境里的 help 输出为准,别人的博客和示例只能当线索,不能当权威。

最后再分享一个小经验。我最早啃第三章的时候,总觉得 Design Introspection 和 Design Editing 是两个独立主题,后来才悟到它们是同一件事的两面:你修改设计之前,必须能精确地查看到你想要修改的位置;而修改之后,又必须通过查询来验证修改结果。真正的高手写出来的脚本,往往就是“查询—编辑—再查询”的交替循环。把这个循环练熟了,后面 Tessent 的 DFT 规则检查和 pattern 生成章节,你会觉得顺畅很多。Tessent Shell 这套工具,上手不难,但用得好不好,很大程度取决于你对“设计对象”的理解有多深,而第三章恰恰就是帮你建立这个理解的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询