做了这么多年可测试性设计和芯片调试,我越来越觉得IJTAG和ICL是绕不开的一关。尤其是当你面对一堆来自不同IP供应商的测试仪器,每个都有自己的一套访问方式,光是理清扫描链的连接关系就能让人头皮发麻。IEEE 1687标准里的ICL(Instrument Connectivity Language)就是用来描述这种“谁跟谁连、怎么连、数据怎么走”的结构化语言。Tessent作为业界用得比较多的DFT工具集,对IJTAG和ICL有相当完整的支持,从网表提取到协议生成再到仿真验证都能串起来。这篇东西我会按照“先搞清楚概念、再拆语法、然后落地跑流程、最后排坑”的顺序,把我实际用Tessent处理ICL时积累的经验完整写出来,给正在被IJTAG网络搞得焦头烂额的DFT和验证工程师一些参考。
1. 先搞清楚一个核心问题:Tessent、IJTAG和ICL到底是什么关系
1.1 一次芯片调试经历引出的话题
前几年我接手过一颗SoC的DFT工作,芯片里集成了七八个不同来源的IP,每个IP都有自己的测试仪器和调试寄存器。最初的设计里,每个IP的扫描链都是单独拉到芯片顶层,导致顶层端口爆炸,测试pattern数量也翻了好几倍。更麻烦的是,验证团队想通过TAP控制器访问某个内部IP的analog trim寄存器,需要在几千行的网表里手工追信号路径,折腾了两天还没搞定。
后来我们把整个可测试性架构改成基于IJTAG的网络,用ICL把每个IP内部的仪器连接关系描述清楚,再把ICL交给Tessent去生成访问路径和测试pattern,问题一下就顺畅了。这个过程让我深刻体会到一件事:IJTAG解决的是“怎么把芯片内部所有测试仪器统一管起来”的问题,而ICL就是这个管理体系里的“接线图”。没有ICL,工具根本不知道你的扫描链是怎么绕的,后面的所有自动化都无从谈起。
1.2 IEEE 1687解决的核心矛盾
在IJTAG出现之前,芯片测试领域其实挺分裂的。JTAG(IEEE 1149.1)提供了标准的TAP控制器和边界扫描架构,但边界扫描链上的数据寄存器都是固定功能,想挂一个新的测试仪器进去,往往要改硬件结构或者绕线,灵活性很差。不同的IP供应商对“如何访问内部寄存器”也有各自的私有方案,系统级集成时就需要写大量的胶水逻辑和适配文档。
IEEE 1687-2014标准提出了一种新的思路:把芯片内部所有需要测试和调试的仪器(Instrument)组织成一个层次化的扫描网络,通过一个标准化的接口来访问。这个网络的拓扑结构可以很灵活,TAP控制器不需要知道每个仪器的细节,只需要知道怎么通过扫描网络把数据送进去。而描述这个网络拓扑结构和仪器接口的,就是ICL;描述如何产生访问时序的,则是PDL(Procedural Description Language)。前者管“长什么样”,后者管“怎么操作”,两者合在一起,才构成了一套完整的可测试性描述体系。
1.3 ICL在这个体系中的角色
很多人第一次看到ICL会把它和Verilog、VHDL混为一谈,实际上两者的定位完全不同。Verilog是给综合器和仿真器看的,用来最终实现硬件;ICL是给测试工具和验证工具看的,用来描述测试仪器的连接关系。ICL不需要被综合成门级网表,也不会占用芯片面积,它的价值在于“信息传递”。
在Tessent的流程里,ICL扮演了三个关键角色。第一,它是IJTAG网络插入的依据,Tessent根据ICL里的连接关系自动生成扫描多路选择器和寄存器访问逻辑;第二,它是测试pattern生成的输入,Tessent结合ICL和PDL生成对特定仪器的访问序列;第三,它是验证环境的参考,验证团队根据ICL构建UVM/VMM验证组件,检查仿真波形是否符合预期。换句话说,ICL是贯穿DFT设计、测试生成、仿真验证三端的“通用语言”,这个定位决定了它的语法风格必须足够精确、无歧义,同时又要简洁到工具能高效解析。后面我会详细拆解它的核心语法和元素。
2. ICL的关键概念与核心语法拆解
2.1 ICL不是你想的那种“硬件描述语言”
先澄清一个常见的误解。ICL的全称虽然是“语言”,但它的语法风格和传统的RTL语言差别很大。ICL更像是一种结构化的配置描述文件,用“模块-端口-连接”三个层次来描述扫描网络。你没有always块,没有assign语句,没有时序逻辑,只有对“静态结构”的描述。
一个完整的ICL文件通常从ICL file_version = "1.0";开始,然后定义若干个module。每个module代表一个可复用的扫描仪器或者一个层次的扫描网络,模块内部通过端口声明和内部连接语句描述数据流。我用一个最小化的例子来展示基本骨架,看起来像这样:
ICL file_version = "1.0"; module debug_reg { port scan_in; port scan_out; port scan_en; port scan_sel; ScanRegister core_reg { ScanInPort = scan_in; ScanOutPort = scan_out; ScanEnPort = scan_en; ScanSelPort = scan_sel; Length = 8; } }这个模块描述了一个8位可扫描寄存器,它对外暴露四个端口,内部用一个ScanRegister原语把输入端和输出端关联起来。这个写法跟Verilog里的模块实例化有点像,但语义完全不同:ScanRegister不是真正的硬件单元,而是一个“逻辑容器”,告诉工具这里有一个可访问的8位寄存器。真正的物理实现在网表里,工具会根据ICL的描述自动去匹配或生成。
提示:不同Tessent版本对ICL语法细节的支持略有差异,但
ScanRegister、ScanMux这些核心原语的写法基本稳定。具体字段以你安装版本的ICL参考手册为准。
2.2 核心元素:端口、ScanRegister、ScanMux、ScanSegment
ICL定义了一组标准原语来描述扫描网络的基本构件,我用一个表格来呈现最常用的几个,方便对照理解。
| 原语名称 | 作用 | 关键字段 |
|---|---|---|
ScanRegister | 描述一个可扫描的移位寄存器,是最常见的仪器访问端点 | ScanInPort、ScanOutPort、ScanEnPort、ScanSelPort、Length |
ScanMux | 描述扫描数据的路径选择,支持多路输入选择一路输出 | ScanInPort、ScanOutPort、SelectPort |
ScanSegment | 描述一个分段扫描链,可以配置为旁路模式或正常模式 | ScanInPort、ScanOutPort、ScanEnPort、Length |
Hierarchy | 描述模块之间的层次引用,将子模块端口映射到父模块端口 | InstanceName、ScanInPort、ScanOutPort等 |
DataRegister | 描述一个不具备移位功能的并行寄存器,通常用于并行数据捕获 | DataInPort、DataOutPort、ClockPort、Length |
这么多原语里,ScanMux是最容易出问题的。它的作用相当于一个可配置的开关,根据SelectPort的值决定扫描数据走哪条路径。举一个实际场景:一条扫描链上有两个仪器,但同一时刻你只想访问其中一个。这时候就可以用ScanMux把两个仪器的输出选择到单一的上游输入端口上。下面是一段示意写法:
ScanMux select_instrument { ScanInPort = inst_a_out; ScanOutPort = top_scan_in; SelectPort = access_sel; }这段描述的意思是当access_sel为某一种逻辑值时,top_scan_in接收inst_a_out的数据,否则接收另一路。具体哪一路对应哪个值,在ICL里通常通过SelectValue字段进一步指定。这个细节很关键,后面我会单独拿出一节讲它在实际工程中踩过的坑。
2.3 层次化描述与连接关系
真实芯片里的扫描网络很少是平面的,绝大多数情况下都是层次化的:顶层有TAP和扫描网络控制器,中间是各个子模块的扫描网络,底层才是具体的仪器寄存器。ICL通过Hierarchy原语支持这种层次化描述。
这里举一个两级层次结构的示意:
module top_network { ieditpi TAP; port scan_in; port scan_out; port scan_en; port scan_sel; Hierarchy sub_inst { InstanceName = debug_reg; ScanInPort = scan_in; ScanOutPort = scan_out; } } module debug_reg { port scan_in; port scan_out; port scan_en; port scan_sel; ScanRegister reg0 { ScanInPort = scan_in; ScanOutPort = scan_out; ScanEnPort = scan_en; ScanSelPort = scan_sel; Length = 8; } }top_network通过Hierarchy引用debug_reg模块,并把自身的scan_in和scan_out映射到子模块的对应端口上。工具解析这个文件时,会构建出一棵“仪器树”,从顶层到叶子节点一目了然。这种层次化描述的好处是:每个子模块的ICL可以独立维护,IP供应商交付IP时附带自己的ICL,集成方只需要做端口映射即可,不需要关心IP内部的具体连接细节。
2.4 多个数据源时的选择与映射关系
实际芯片里,一个测试仪器往往不仅有一条扫描输入,可能还有多路并行数据输入。这种情况下,除了ScanRegister,还需要描述数据输入与扫描寄存器的映射关系。ICL支持通过字段引用的方式将扫描寄存器的某些位与外部数据端口进行绑定。
一个常见的DataRegister使用场景是这样的:某个IP内部有一个配置寄存器,硬件上以并行方式从总线加载数据,测试时需要把串行扫描链上的数据并行写入这个寄存器。那么在ICL里,除了定义扫描路径,还需要通过DataInPort和DataOutPort描述并行通道。Tessent在做pattern生成时,会根据这些描述自动构造正确的访问序列:先扫描移位到目标长度,再触发并行加载。常见写法可以这样理解:
DataRegister cfg_reg { DataInPort = data_in_bus; DataOutPort = data_out_bus; ClockPort = load_clk; EnablePort = load_en; Length = 16; }这段描述并不产生硬件,它是一个信息模型。Tessent读进这段ICL后,会在网表里寻找匹配的物理寄存器,或者在网表不存在时根据约束自动插入。理解了这个点,你就明白了为什么ICL里很多字段看起来像RTL端口,但并不是真正连线——它们是给工具用的“信息标签”。
3. 实践前的准备:Tessent环境搭建与工具链认识
3.1 获取Tessent安装包与License配置
说到Tessent,很多第一次接触的朋友第一反应是“去哪搞安装包”。Tessent是商业EDA工具,正常渠道是通过西门子EDA官网申请评估版或者由公司购买License后获取安装包。我个人建议不要用来路不明的绿色版、破解版,这类版本往往文件不完整,缺少ICL校验器或标准单元库适配文件,跑流程时容易报一些莫名其妙的问题,排查半天最后发现是工具本身有问题,非常浪费时间。
安装Tessent本身并不复杂,但有几个点值得注意。主程序安装完之后,首先要配置LM_LICENSE_FILE或MGLS_LICENSE_FILE环境变量指向License服务器;其次要把Tessent的bin目录加入PATH。如果你们公司有多个版本的Tessent共存,建议在启动脚本里用别名区分,比如tt15指向15.x,tt17指向17.x,避免版本切换时误调用了老版本的可执行文件。
我个人还会额外做一件事:安装完成后,先跑一遍Tessent自带的示例回归脚本。Tessent的安装目录下通常会带一些IJTAG相关的demo,跑通它们可以确认环境没问题,同时也能快速熟悉工具的命令风格。这一步花不了多少时间,但对后续实操帮助很大。
3.2 Tessent IJTAG相关的几大工具模块
Tessent不是单一工具,它是一个工具家族。围绕IJTAG和ICL,主要涉及下面几个模块,我用表格列出来:
| 工具模块 | 主要用途 | 与ICL的关系 |
|---|---|---|
| Tessent Shell | 统一的操作入口环境,支持脚本化执行各类DFT命令 | 很多ICL检查和生成命令都在Shell里运行 |
| Tessent IJTAG | IJTAG网络插入工具,负责生成扫描网络结构 | 根据ICL描述自动插入硬件逻辑 |
| Tessent Scan/ATPG | 扫描测试和自动测试向量生成 | 需要ICL提供扫描寄存器与仪器信息 |
| Tessent Diagnosis | 失效诊断分析 | 通过ICL映射物理失效位置 |
| Tessent Safety | 功能安全相关DFT分析和插入 | 借助ICL分析安全机制的可测试性 |
我在实际工作中,使用频率最高的是Tessent Shell和Tessent IJTAG。Tessent Shell类似于Synopsys的DC Shell,提供一个Tcl解释环境,你可以在这里读入设计网表、读入ICL、执行检查命令。Tessent IJTAG则是在设计后端阶段把IJTAG网络真正“长”到网表里的工具,它会根据ICL描述自动插入扫描多路选择器、旁路逻辑和TAP接口控制逻辑。
3.3 从无到有:三种获取ICL的途径
获取ICL有几种途径,适用场景不同,我分别说一下。第一种是Tessent自动生成。如果你已经完成了IJTAG网表插入,Tessent会根据网表连接自动导出对应的ICL文件,这是最省力也最不容易出错的方式,指令的大致形式类似write_icl。第二种是手工编写。适用于IP供应商交付的场景,他们不需要跑完整的Tessent流程,只需要把自家仪器的连接关系用ICL描述清楚,随IP一起交付给集成方。第三种是转换生成。有些内部历史项目使用自研的描述格式,可以通过Tcl脚本转换后再交给Tessent,这种方式比较小众,但碰上老设计时往往能救命。
三种方式里,我最推荐第一种。自动生成不仅省力,还能保证ICL和网表的一致性,避免手工编写时出现“文件里写的和电路实际连的根本不是一回事”的尴尬。
4. 从规范到实践:手把手走一遍ICL的生成-检查-集成流程
4.1 用Tessent自动提取ICL(推荐路线)
当你已经有一个完整的IJTAG网表,想让Tessent帮你导出ICL时,流程大概是这样的。首先在Tessent Shell里读入设计网表和库文件,然后指定顶层的TAP接口位置,执行ICL导出指令。不同版本指令名有差异,我见过的大致形式是write_icl -output icl/design_top.icl。
执行完导出后,Tessent会生成一个或多个ICL文件。你需要确认文件完整性,一个大的SoC顶层ICL通常包含了所有子模块的层次引用,同时会生成一个被引用的底层模块ICL。这些文件是配套的,缺一不可。一个比较实用的检查方法是:用文本工具打开导出的ICL,快速浏览顶层模块的Hierarchy引用数量和底层模块定义是否对应得上。如果发现“引用了一个模块,但整个文件里找不到该模块定义”,就要回到网表里确认是不是有子模块没有被完整读取。
导出的ICL还有一个好处:它通常会附带很多工具自动生成的内部节点命名。这些命名虽然看起来很长,但包含了信号路径信息,排错时非常有用。我之前有一次遇到扫描链不匹配的问题,就是靠ICL里自动生成的ScanSegment名称定位到了第三层子模块里的一个旁路控制信号。
4.2 手工编写ICL并做语法检查
如果你在写IP级别的ICL,或者需要快速搭一个验证模型,手工编写是不可避免的。这种情况下,我建议遵循几个原则。第一个原则是命名规范统一。端口名、模块名全部小写下划线,避免大小写混用带来的工具解析问题。第二个原则是一个模块只描述一个仪器或一个功能子网络,不要为了省事把多个不相干的仪器塞进同一个module里,否则后续pdL编写和维护会非常痛苦。第三个原则是所有端口必须有明确方向,ICL里虽然没有显式的input/output关键字,但通过端口在连接语句中的使用位置可以推断方向,所以写的时候要格外留意。
编写完成后,一定要做语法检查。Tessent Shell里通常提供类似read_icl的指令来解析ICL文件,如果语法有误,工具会报告解析错误和行号。这里有一个我实际遇到的教训:刚开始手写ICL时,我在ScanRegister里漏写了ScanEnPort字段,工具没有报错,但生成的pattern里对应的寄存器始终无法正确加载数据,调试了很久,最后逐行对照参考ICL才发现问题。所以语法检查通过了不代表逻辑正确,一定要结合仿真或pattern结果去反推ICL的正确性。
4.3 ICL与PDL的协同工作
ICL描述的是结构,PDL描述的是行为。Tessent的IJTAG流程中,两者通常是成对出现的。PDL定义了一组高层操作,比如iWrite、iRead,工具会根据PDL里的操作步骤和ICL里的结构信息生成底层扫描序列。如果你的ICL里某个寄存器长度写错了,PDL操作照样能“无语法错误”地通过编译,但生成的pattern在仿真时一定会挂。从这个角度看,ICL是PDL正确性的前提。
我一般会在写完ICL后,紧接着针对每个可访问仪器写一段简短的PDL自检程序,覆盖最基础的写-读回操作。以8位寄存器为例,先写入0xA5,再读出来比对。Tessent跑完自检程序后生成的pattern如果与预期不符,基本可以断定ICL里的寄存器长度或者端口映射有问题。这个方法帮我抓到过好几个“看起来没问题”的ICL错误。
4.4 集成到Tessent测试流程中
ICL准备好之后,下一步就是把它集成到正式的Tessent测试流程中。这一步要做的事包括:读入顶层ICL、把各子模块的ICL通过层次引用串起来、验证所有仪器在TAP端口上是否可访问、生成最终的测试pattern。Tessent会基于ICL信息构建一张“访问路径表”,每个仪器在TAP级别对应的扫描路径长度、旁路寄存器长度都会自动计算。
集成阶段最需要留意的是“可访问性”。哪怕ICL语法完全正确,拓扑连接也成立,仍然可能出现某个仪器从TAP端不可达的情况。最常见的原因是ScanMux的选择信号写反了,导致TAP发出的地址码无法选中目标仪器。为了快速定位这类问题,Tessent提供了层次化报告功能,可以打印出每个仪器的扫描路径长度和经过的ScanMux选择条件。我建议大家在这个阶段多花点时间看报告,而不是等到pattern仿真失败了再回头查。
注意:在做IJTAG网络集成时,ICL文件应视为设计交付物的一部分,纳入版本管理。我在项目里会把ICL和网表放在同一目录,并加上与RTL版本对应的tag,确保后续诊断、验证用的ICL始终与流片版本一致。
5. 实战中的高频踩坑与排查技巧
5.1 ICL引用路径问题
使用Tessent做ICL层次化解析时,最常见的一类错误是引用路径写错。ICL里的层次路径习惯用点号分隔,比如top.sub_inst.reg0表示顶层模块下sub_inst实例里的reg0。如果你在某个Hierarchy里指定的InstanceName与子模块名不一致,或者路径层级少写了一层,工具就会报“cannot resolve ICL reference”之类的错误。
我的排查思路很直接:先从报错信息里提取引用的模块名,再到ICL文件里搜索该模块定义是否存在;如果存在,检查大小写和空格是否完全一致。有一点容易被忽略:有些工具对路径分隔符的支持存在差异,同样的路径在ICL文件里可以用点号,但在Tcl命令行里可能要用斜杠。这时候不要死磕格式,多试试工具内置的报告命令,让工具告诉你它期望的路径格式。
5.2 ScanMux选择信号接反
这个坑我记忆犹新。当时一个IP里有两条扫描路径,通过一个ScanMux根据access_sel选择。ICL里我把SelectPort描述为当access_sel=1时选择inst_a_out,但实际网表里access_sel=1时选的是另一路。结果就是Tessent生成的pattern访问inst_a时,实际操作的是另一个仪器,而仿真波形看起来又“像是正常的”——因为扫描链长度一致,数据移入移出都能对上,只是内容完全错位。
排查这类问题,最有效的办法是检查Tessent的访问路径报告,里面会明确列出每个仪器被选中时的ScanMux条件。跟网表的实际逻辑对比一下,立刻就能发现反没反。如果没有报告可查,可以在PDL里对两个仪器分别写入不同的特征数据,再读出来比较,也能很快暴露问题。关键是要养成“先查选择条件,再查数据路径”的习惯。
5.3 工具版本差异带来的兼容问题
Tessent版本升级后,ICL解析规则有可能发生变化。最常见的是对某些字段的默认值处理、对非法写法的容忍度等。我之前从Tessent 2021迁移到2023版本时,老工程里的ICL文件在新版本下跑出大量warning,原因是新版本对端口的unconnected状态要求显式声明,而老版本允许省略。
遇到这类问题,不要急着改ICL,先查两个版本的Release Notes里与IJTAG相关的变更条目。大多数情况下,工具会提供兼容模式或者自动迁移脚本。如果实在没有,再手工补字段。另外一个好习惯是:在项目目录里记录下每个ICL文件生成时使用的Tessent版本号,排错时能节省大量时间。
5.4 手写ICL的语法细节错误
手写ICL时,有几个细节特别容易出错。第一个是分号,ICL里几乎每个字段赋值和原语定义结束都需要分号,漏一个分号解析器报错的位置可能跟你预期的差很远。第二个是Length字段的单位,有些工具里是按bit计,有些场景下可能按byte计,默认值也可能不同。第三个是端口方向性,同一个端口如果在不同原语里被重复使用,容易出现方向冲突。
我自己的习惯是:写完ICL后用文本编辑器的语法高亮功能,配合Tessent的解析器做双重检查。写之前先把所有端口列出来,规划好哪些是输入、哪些是输出、哪些是双向,然后再动手写原语,能避免大部分低级错误。
5.5 排查思路速查表
根据我踩过的坑,整理了一个速查表,遇到问题时可以按这个顺序排查:
| 现象 | 优先排查方向 | 关键检查点 |
|---|---|---|
| 工具报“cannot resolve reference” | 模块名、路径、大小写 | InstanceName与模块定义是否一致 |
| pattern仿真数据错位 | ScanMux选择逻辑 | 选择信号值与实际网表逻辑是否一致 |
| 寄存器无法正确加载 | ScanRegister端口映射 | ScanEnPort、ScanSelPort是否声明完整 |
| 扫描链长度不符 | 寄存器长度参数 | Length是否与网表实际位宽一致 |
| ICL解析大量警告 | 工具版本兼容性 | 新版工具是否有新增必填字段 |
| 端口方向冲突 | 端口复用情况 | 同一端口是否被不同原语重复声明 |
我做了这么多年DFT,说句实在话,ICL这个语言本身并不难,语法元素就那么几个,半天就能看完。真正的难点在于“把结构描述准确”和“让工具与网表保持一致”。每次遇到诡异的问题,最后查出来基本都是对连接关系的理解出了偏差,而不是语法不会写。所以我的习惯是:ICL写完后永远不急着往下走,先针对每个可访问仪器写一个最简写读回自检,跑完再继续。这个小习惯帮我省下的排查时间,远比写自检本身花掉的时间多得多。另外也提醒一句,Tessent版本升级时,一定要把老工程的ICL完整回归一遍,工具升级带来的隐形变化,往往比功能更新本身更值得警惕。希望这篇从规范到实践的总结,能帮你少走我走过的那些弯路。