☰
Tessent Shell设计内省与编辑:DFT网表操作实战指南
2026/10/3 1:11:54 网站建设 项目流程

Tessent Shell用户手册翻到第三章的时候,我停下来多看了几眼。前面两章基本是环境、启动、基础命令这些热身内容,到了"Design Introspection and Editing"这里,才真正开始碰核心的东西。这章讲的是两件大事:怎么把一个设计对象里的信息"看透",以及怎么在工具内部直接"改设计"。说大白话就是,Tessent Shell不只是一个跑命令的壳,它更像是一个DFT流程里的“总控台”,比netlist编辑器灵活,又比文本脚本直观,搞定它的内省和编辑能力,整个Tessent流程才算真正掌握。

这篇笔记我会从设计数据模型讲起,把命名、查询、属性访问、连接修改这些内容拆开揉碎,再补一些在真实项目里才会遇到的坑。不管你是刚接触Tessent Shell的新手,还是已经跑过几条scan chain的老手,这篇内容都能帮你在翻手册的时候少走点弯路。

1. 花一整章讲"看"和"改",Tessent Shell的底气在哪

1.1 它不是Linux shell的兄弟,而是DFT工作台

很多人第一次听到"Tessent Shell"这个名字,会下意识拿它和bash、csh做类比。确实,Tessent Shell底层是基于Tcl的交互式命令行环境,支持变量、循环、条件判断、proc定义,看起来像个脚本壳。但它的核心不是通用计算,而是面向Tessent工具链的统一设计操作平台。

在DFT流程里,你经常要做的事情是:读入综合后的门级网表,确认顶层端口和时钟,手动插一条scan chain,检查某个EDT通道的连接关系,或者把某个测试点信号从A点挪到B点。这些操作如果用文本编辑器改网表,既容易出错又不可追溯;如果用专门的结构化设计工具,代价又太大。Tessent Shell正好填了这个空档——它在工具内部直接建立设计与命令的映射关系,让工程师可以用Tcl语句操作设计对象。

Tessent Shell的操作风格沿袭了门级EDA工具的一贯思路:先读设计,再把设计拆解成可寻址的对象集合,通过命令对对象做查询、遍历和修改。第三章之所以把"Design Introspection"和"Editing"放在一起,是因为这两个能力在实操中本来就是咬合在一起的——你得先看清楚网表长什么样,才知道要改哪里;改完了之后,又得靠introspection去验证改的结果是否符合预期。

1.2 Introspection与Editing为什么必须放在同一章

我最初翻手册的时候也想过,为什么不干脆把"设计查询"和"设计修改"分成两个独立章节?多跑两个项目之后我才意识到,这两者在Tessent Shell里的边界几乎是模糊的。很多查询命令本身就带有"状态改变"的副作用,比如某些get_*命令会触发内部缓存更新;反过来,几乎所有编辑命令在执行完后,都会自动做一次一致性检查并报告内部数据库状态。

这种"看"和"改"深度耦合理念,本质上是为DFT特有的"增量修改"场景设计的。在功能仿真里,你可以把一个完整RTL代码从头跑一遍;但DFT流程往往是在一个已经基本冻结的门级网上做局部改动,scan chain重建、observability cell插入、时钟mux替换,每一样都是精细手术。Tessent Shell通过统一的introspection和editing机制,保证了这类改动在同一个建模空间内完成,而不是"外部脚本改文本网表"这种粗放方式。

这一点是读第三章之前必须先建立的观念:Tessent Shell里的设计,不是一个个孤立的状态列表,而是一个带类型定义、属性绑定、连接关系索引的数据库。Introspection是对这个数据库的读取操作,Editing是对这个数据库的事务性写入操作。两者配合,才能在不离开工具的前提下完成验证闭环。

2. 先把"家底"摸清楚:设计对象模型与命名规范

2.1 设计读入之后,工具眼里的网表是什么

手册第三章开头部分会介绍Tessent Shell中设计数据的组织方式。如果你在模拟器或者综合工具里接触过网表,理解起来会很快:设计里大致有cell(器件单元)、port(顶层端口)、pin(端口引脚)、net(连线)这几类基础对象。值得注意的是,Tessent Shell还会额外维护一些测试领域特有的对象类型,比如scan_cell、scan_group、edt_channel、test_point等。这些对象不是网表里原生存在的,而是经过Tessent工具识别或插入后产生的逻辑抽象。

比如一条scan chain在网表层面看来,只是若干个DFF首尾相连、加上对应的scan_en/scan_in/scan_out信号。但在Tessent Shell的对象模型里,它会抽象成一个scan_group或scan_path对象,你可以直接对这个对象做整体报告、属性修改、甚至重排顺序,不需要一个个去摸cell。这种高层抽象是Tessent Shell比直接操作门级网表高效的关键,也是Introspection能力的主要价值所在。

要看到设计里都有哪些对象,最基本的命令是:

set all_cells [get_cells -hier -filter "is_scan_cell == true"] puts "Total scan cells: [sizeof_collection $all_cells]"

这里-hier表示遍历层次,-filter按照对象的属性做筛选。执行完之后all_cells是一个collection对象,不是普通的Tcl list,所以要用sizeof_collection来取数量。这是一个新手几乎必踩的坑,后面我会专门讲。

2.2 层次化路径与命名解析的底层规则

看门级网表,最头疼的就是层次化路径。Tessent Shell延续了EDA工具的经典命名风格,用/作为层间分隔符。比如一个top/block1/u_fft/fft_core/gen_fft[0].u_fft_reg这样的名字,代表从顶层一路trace到某个寄存器。在Tessent Shell内部,对象的名字本质上是一串路径信息,但它在显示和匹配的时候有自己的一套规则。

第一个规则是相对路径与绝对路径。如果你在current_instance是顶层时用get_cells u_fft_reg,它只会找当前层次下的直接子单元;如果要用绝对路径,必须在前面加/,例如get_cells /top/block1/u_fft/fft_core/gen_fft[0]/u_fft_reg。注意,Tessent Shell对/的处理很严格——多一个少一个都可能导致匹配失败。

第二个规则是名字里的特殊字符。门级网表里经常会出现[0]、\这类字符。如果你直接用get_cells gen_fft[0].u_fft_reg,工具可能会把[0]解释成数组下标或者通配符语法。规避的办法有两个:要么给名字加花括号做精确匹配,要么用-regexp时做合理的转义。我个人习惯是,遇到总线单元名字时优先用get_cells -filter "full_name =~ *gen_fft*"这种模糊匹配,避免跟特殊字符较劲。

第三个规则是集合的上下文绑定。Tessent Shell里很多命令的操作对象集合是有"当前范围"概念的,比如all_inputs、all_outputs、all_registers这类内置集合,会随current_instance变化而不同。做层次化分析时,务必确认当前的instance指向,不然你查到的端口列表可能根本不是你脑子里想的那个模块。

current_instance /top/block1 set pins_in_block1 [get_pins -hier -filter "direction == in"] current_instance / ;# 用完之后记得切回来

这个习惯不养成,脚本跑到一半报出一堆莫名其妙的空集合,排除问题会花掉大量时间。

3. Design Introspection命令体系:从查名字到查约束

3.1 get_* 系列命令与过滤条件

Tessent Shell的Introspection命令按对象类型可以列出一长串:get_cells、get_pins、get_ports、get_nets、get_clocks、get_properties等等。光记住命令不够,还得掌握它们的统一选项风格。

几乎所有get_*命令都支持以下常用选项:

选项作用适用场景
-hier递归遍历所有子层次全设计范围查找对象
-filter按属性条件过滤精准定位特定属性对象
-regexp使用正则表达式匹配名字对复杂名字做模糊查询
-quiet匹配不到时报错但不中断独立脚本容错
-of_objects从某个已有集合反查关联对象依据pin找net等反向追踪

举个综合一点的例子:我想查一下设计中所有带scan_en功能的时钟引脚,并且只关心在测试模式下被启用的。

set scan_en_pins [get_pins -hier -filter "full_name =~ *scan_en*"] set test_mode_pins [get_pins -hier -filter "name == test_mode"] puts "Scan enable pins: [sizeof_collection $scan_en_pins]" puts "Test mode pins: [sizeof_collection $test_mode_pins]"

-filter支持的操作符类似Tcl表达式的扩展,常见的==、!=、=~、>、<都能用,属性名和值之间用空格隔开。有一点要特别注意,属性名到底是name还是full_name,在不同对象类型上结果完全不一样。name通常只取叶子节点名字,full_name则包含完整层次路径。你要是用name去匹配一个路径字段,大概率什么都查不到。

3.2 属性读取与Tcl变量的联动

Introspection不仅仅是"查对象名单",更重要的是查对象绑定的属性。Tessent Shell里几乎所有对象都带有一组属性,比如cell有ref_name、is_scan_cell、is_black_box,pin有direction、is_clock、is_scan_enable,net有is_scan_net、is_clock_net等等。

读取属性用得最多的命令是get_property:

set test_cells [get_cells -hier -filter "is_scan_cell == true"] foreach cell $test_cells { set library_cell [get_property $cell ref_name] if {[string match "*EDT*" $library_cell]} { puts "EDT-related scan cell: $cell ($library_cell)" } }

注意,get_property返回的结果可能是字符串、整型、布尔值,也可能是一个collection。比如查某个pin连接的net,get_property $pin net返回的经常是一个net对象集合,需要再用get_property去取这个net的full_name。如果直接用puts打印,你只会看到像net_42这种内部ID,而不是可读的网表名字。这时候要手动追一层:

set my_net [get_property $my_pin net] set net_name [get_property $my_net full_name] puts "Pin $my_pin is driven by net: $net_name"

这个"属性套属性"的写法是第三章里非常高频的玩法,本质上对应着对象数据库里的关系索引——pin与net的关系、cell与pin的隶属关系、instance与hierarchy的包含关系,都是靠层层取属性实现的。

3.3 设计健康度检查与报告生成

Introspection不光是给脚本服务,也承担设计质量检查的功能。Tessent Shell里很多命令本身就对设计做了静态检查,比如读取时钟树时会检查时钟定义是否完整,读取scan chain时会把链断掉的地方打出来。这些检查结果通常会在命令执行时直接输出到终端或log文件里。

想要系统性地报告设计状态,可以用report_*系列命令。例如:

report_clocks -verbose report_scan_cells report_scan_groups -verbose report_design_rules

这类命令在第三章节里可能不会花太多篇幅,但你真正做项目时离不开它们。我的经验是,每次跑完一轮大的编辑操作之后,至少做一次report_design_rules,它会发现断链、悬空引脚、多个驱动源等基本问题。DFT流程和功能流程不一样,功能综合后的网表只要是闭合的,一般不会有大问题;但DFT编辑是"推荐扫描链重连"、"插入观察点"这类动作,任何一个环节失败,都会直接打断后续的ATPG准备。

这里还要提一句,report_*输出到屏幕上内容很多,跑脚本时最好用Tcl的redirect命令把输出保存到文件,方便后续比对:

redirect -file ./report_before_edit.txt { report_scan_groups -verbose report_design_rules }

不要直接让几万行report刷在你的终端里,滚动窗口既刷屏又难回溯。规范化保存log,是所有可回归脚本的第一步。

4. Design Editing上手:改连接、改约束、改属性的正确姿势

4.1 为什么DFT流程需要"改"设计

有人会觉得:综合工具都帮我把scan chain stitch好了,还需要手工改吗?真做过几个项目,你会发现需要手工干预的地方非常多。典型的场景包括:

  • apb/axi测试接口复用时:需要把测试相关的控制信号从原来的功能通路切到Tessent定义的测试通路上。
  • 时钟不可控问题:某个子模块的时钟在测试模式下仍由PLL输出,但DFT约束要求测试时钟必须完全可控,这时候需要在测试模式下用mux把时钟源切换掉。
  • 异步复位信号的测试隔离:有些异步复位网络在上电时会毛刺,测试时要切断它,改接到固定的test_mode ? 1'b1 : 功能值上。
  • 扫描链顺序优化:为了布局布线拥塞,经常要调整链上cell的排列顺序。在网表层面改连接,比重新综合再插入扫描便宜得多。

这些操作如果做在纯文本网表上,改起来又慢又容易漏。Tessent Shell的Editing能力,让你在对设计做了充分Introspection之后,直接对具体对象发出修改指令。

4.2 连接修改:从点操作到网操作

连接修改是Editing最核心的内容。手册里常见的命令是change_connection、disconnect_net、connect_net、create_net、remove_net这类。它们做的事很简单:把某个pin从旧net上摘下来,然后挂到另一个net上。

以最常见的需求为例——把一个叫func_scan_en的信号改接到一个测试模式的mux输出上:

current_instance /top set mux_out_pin [get_pins example_mux/Y] set target_pin [get_pins dft_top_func_scan_en_reg/D] disconnect_net -pin $target_pin connect_net -pin $mux_out_pin -pin $target_pin

这里有三点值得注意。

第一,disconnect_net执行之后,目标pin会变成floating状态,如果在这中间工具报warning,不要慌,这是正常的中间态。关键是尽快完成下一句connect_net,不要让设计长时间处于不完整状态。

第二,connect_net的语义在不同Tessent版本上有细微差异。有的版本要求你直接指定net对象,有的版本允许传一个pin对象让它自动创建或复用net。用pin对象的写法在多数版本都能工作,也最直观。

第三,绑定的方向性。如果你真的是在做网表编辑而不是纯粹改扫描链顺序,务必确认source pin和sink pin的方向。把一个驱动pin接到另一个驱动pin上,工具会报"multiple driver"错误;把sink pin接到sink pin,则会得到floating源。这类问题,靠report_design_rules能抓到,但在编辑的时候养成"先查方向再连接"的习惯,能省很多事。

方向检查的小技巧:

set pin_dir [get_property $target_pin direction] if {$pin_dir != "in"} { error "Expected target pin direction=in, got $pin_dir" }

4.3 属性与约束的修改边界

连接修改是物理层面的,属性修改则是逻辑层面的。Tessent Shell里可以用set_property修改对象属性,比如把一个cell标记成dont_touch、把某个pin标记为is_clock、设置某个net的测试属性。这类改动不像连接修改那样改变网表拓扑,但它们会影响后续Tessent工具对设计的理解和处理。

典型场景是手动指定扫描链类型。Tessent在自动识别scan cell时,偶尔会因为跨时钟域或特殊cell类型判断错误。你可以这样修正:

set scan_cells [get_cells -hier -filter "is_scan_cell == true"] foreach cell $scan_cells { set ref [get_property $cell ref_name] if {[string match "*SDFF*" $ref]} { set_property $cell scan_type "muxed_dff" } }

注意,不要随意修改工具内部维护而你没有明确理解的对象属性。比如is_scan_cell,这个属性通常是由analyze_scan_cells这种命令根据库信息推断出来的,如果你手工覆盖,后面的ATPG可能会做出错误的假设。修改属性前,先确认这个属性是"用户可写"还是"工具自动维护",否则你改完后面跑出的结果会完全对不上。

手册里一般会标注属性的读写属性归属,别跳过那些属性表格。否则出了问题,排查方向往往错得离谱。

5. 把"看"和"改"串成流程:一个可回归的DFT准备脚本

5.1 脚本骨架与执行策略

Introspection和Editing单独用都简单,难的是把它们串成一个流程。项目一复杂,你可能要经历"读入设计 -> 多层实例多次切换 -> 内部属性检查 -> 连接修改 -> 同步修改时钟约束 -> 重新报告"这样一环扣一环的流程。这时候脚本的组织方式决定了调试效率。

我习惯把脚本拆成三个阶段。

阶段一是"环境与设计加载",只做read_design和必要的基本配置;阶段二是"结构化内省",把设计关键信息写到外部文件或Tcl变量里;阶段三才是"编辑+验证"。这样设计的最大好处是,当第三阶段报错时,你可以随时重新source前两阶段,不需要重新读一遍设计。

# Stage 1: 环境与设计 set_workspace ./tmp_run read_design -netlist ../data/top_netlist.v -top top \ -library ../lib/techlib.tessent # Stage 2: 内省与检查 source ./procs/inspect_design.tcl inspect_design -output ./report/design_snapshot.txt # Stage 3: 编辑和验证 set_property [get_cells top/dft_wrapper] is_black_box false change_connection ... source ./procs/validate_edits.tcl

注意,在长时间运行的批处理脚本里,每一段关键操作后面都应该加一句puts "=== Completed: ... ==="这样的日志输出。Tessent Shell不是调试器,你没有逐行断点可踩,唯一的定位依据就是log。日志不打清楚,实际项目里会很难受。

5.2 与乒乓操作和断点保存的配合

Tessent Shell对设计修改的支持虽然灵活,但毕竟不是增量式版本管理工具。一个常见的失误是:脚本一口气做了几十处修改,做到第35处时发现前面的第12处修改引入了一个后续无法忽略的问题。这时候你回滚的代价就很大了,除非你提前做了保存。

所以我在做长期编辑任务时,会刻意采取分段保存策略。比如每完成一个逻辑功能块的修改,就执行:

write_design -format ddc -output ./checkpoints/design_after_mux_edits.ddc

这样如果后续出错,你不需要从头开始读原始网表,而是直接从最近一个checkpoint继续。配合write_design的还有对应的读回命令read_design。Tessent Shell的read/write体系支持ddc、verilog、def等格式,但checkpoint最好选工具原生格式,因为只有原生格式才能无失真地保留所有Tessent扩展属性。如果你导出Verilog再读回来,那些scan_group、edt_channel的抽象信息很可能会丢。

5.3 从脚本报错倒推设计问题的经验

脚本报错是家常便饭,但很多报错其实非常典型。比如Cannot find object这种,往往不是命令写错了,而是current_instance指向不对。又比如Collection is empty,不一定是设计本身的问题,很可能你在-filter里写了一个不存在的属性名,工具直接返回空集合,连warning都不打。

我在排查这类问题时有一套固定的"三查"套路。

先查上下文。看报错那一行前面最近一次current_instance是什么,大部分失败都能在这个层面解决。再查对象类型。确定你要操作的是cell、pin还是net,用对应类型的get_*命令去确认对象是否存在,顺便看一下它的full_name是不是和你预期的一致。最后查属性绑定。如果你用了-filter,把filter里的属性名单独拿出来跑一遍,比如get_property [get_cells xxx] 你的属性名,看能否正确返回值。

大多数脚本问题都能在第三步解决。如果你走到第三步还是查不出来,那就要怀疑设计数据的完整性了,比如是否漏读库文件、是否有module mismatch。这种问题一般会在log早期阶段出现,往回翻log往往能找到线索。

6. 内省与编辑的常见坑位排查实录

6.1 名字解析失败:大小写、逃逸与路径三连坑

名字解析问题是introspection中最频繁的败点。第一个坑是大小写。门级网表里的命名通常保留大小写,但Tessent Shell在默认情况下对名字是敏感还是不敏感,实际上和具体版本、设置有关系。我遇到过在某个版本里get_cells TOP能查到顶层,但get_cells top查不到的情况。所以写脚本时,名字大小写一律以get_property $obj full_name输出为准,不要凭记忆敲。

第二个坑是总线名的方括号。综合工具生成的名字,经常是data_reg[0]、data_reg[31]这种形式。如果直接用get_cells data_reg[0],Tessent Shell有可能会把[0]当作Tcl列表下标或者正则表达式的字符组来解析,结果完全不是你想要的。稳妥的写法是用花括号做精确匹配:

set cell [get_cells {top/data_reg[0]}]

第三个坑是路径前缀。Tessent Shell里有些命令在解析路径时,会自动在对象名前加current_instance前缀,有些则不会。如果你的脚本切换过current_instance,同样的对象名在不同阶段可能解析到不同对象。要彻底规避这个问题,最可靠的做法是在关键操作前显式、完整地写出绝对路径,或者先在脚本里把对象抓成collection变量,后续操作都用变量引用,不要反复用名字去解析。

6.2 改动没生效:数据库一致性问题是真凶

比"找不到对象"更隐蔽的,是"命令跑完了,但后面的工具不认这些改动"。常见情况是你用connect_net连了一条线,report_design_rules也检查通过,但跑到insert_scan或者build_atpg时,工具仍然按旧连接去分析。

这时候问题往往不在连接本身,而在于属性缓存和层次上下文的关联。Tessent Shell内部对设计是有缓存机制的,某些大范围报告类命令会在内存里缓存netlist的拓扑信息。如果你在报告命令之后做了编辑,但没有触发对应的缓存失效,后续分析就可能读到旧数据。

这类问题的演示方式简单粗暴:执行完一系列编辑之后,调用一次update_design -full或者reset_design,让工具强制刷新内部数据库。不同的Tessent版本对这个命令的命名方式会有差异,但基本思想一样。如果找不到,就跑一条无关紧要的重量级report命令,通常也会触发刷新。

经验法则:不要在一条"只读报告"和另一条"只读报告"之间直接做修改,否则报告的间隔里很容易混入过期缓存。也不要在循环体内频繁做全量刷新,那会大幅拖慢大型设计的运行效率。批量编辑 -> 一次刷新 -> 集中验证,这是最优节奏。

6.3 改坏了怎么办:备份策略与增量编辑

最后一个坑,也是最致命的,是改了设计之后发现改不回去。Tessent Shell的undo支持非常有限,不像文本编辑器那样能不断Ctrl+Z。所以永远不要在一个不可恢复的原始设计上直接做大量修改。

我个人的做法是三层保险。

第一层是文件层面备份。启动脚本时第一件事就是write_design -format ddc -output ./bk/original.ddc,保留一个纯原始状态。第二层是checkpoint备份,前面提到过,每完成一个功能性修改就保存一个checkpoint。第三层是snapshot式的属性记录——用introspection命令把关键属性导出成文本文件:

set fp [open ./bk/original_attributes.txt w] foreach cell [get_cells -hier -filter "is_scan_cell == true"] { puts $fp "$cell [get_property $cell scan_type]" } close $fp

这样的文本snapshot,即使工具崩溃打不开ddc,你也能通过重新读入网表、再执行一段属性恢复脚本,把关键信息复原。某种程度上,这份文本snapshot比ddc更有价值,因为它可读、可diff、可审计。

6.4 编辑后验证:不只看report,还要看时间序

最后想额外提醒一点。Tessent Shell的report_design_rules和report_scan_groups能抓结构性问题,但抓不了时序相关性。DFT编辑,尤其是替换时钟mux、插入测试点这类操作,往往会引入新的组合路径或新的约束冲突。在做完结构验证之后,务必再跑一遍完整的时序约束检查(比如report_constraints -verbose或工具自带的DRC),确保改动没有破坏原有的时序边界。

我见过有人把一条scan_en直接从功能引脚改接到内部测试寄存器输出,结构上完全正确,report_design_rules干干净净,但那条路径的驱动强度不够,到布局布线阶段变成了一个时序违约点。这类问题在逻辑编辑阶段看不见,但它确实是"编辑后验证"需要考虑的一环。Tessent Shell本身提供的内省能力已经很强了,但跨工具的数据流验证永远不能省。

回到第三章标题里的"Design Introspection and Editing",它其实是在传达一个理念:真正高效的DFT设计维护,不是你抓着网表文件一遍遍翻,而是建立起"结构可以查询、对象可以操作、结果可以验证"的闭环。Tessent Shell把这个闭环做进了命令层,工程师要做的,就是学会用它的语法去描述脑子里那张设计图。

以我自己的使用体会收个尾:刚上手时别急着追求掌握所有get_*命令,先把get_cells、get_pins、get_nets、get_property和current_instance这一套组合玩熟,再慢慢往外扩。第三章作为"未完待续"的手册章节,我猜后面还会有更深入的场景化案例,比如和insert_scan、edt_*命令体系的联动。真到那一步,前面这些内省和编辑的基础就会成为你能往前走的底气。

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

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

立即咨询