☰
Tessent ICL语法详解:SIB、TDR与TAP定义和连接实战
2026/10/3 4:45:09 网站建设 项目流程

做DFT的人看到ICL这几个字母,心情通常比较复杂。我在项目里第一次接触Tessent ICL(Instrument Connectivity Language)时,以为这只是工具自动生成的文件,直到要自己在RTL里插SIB、自定义TDR,还得把不同IP的TAP串起来,才发现ICL要么不看,要看就得比网表还细。这篇博文想解决的就是这个最现实的问题:当你要用Tessent工具链定义SIB、TDR和TAP模块时,ICL语法到底该怎么写,连接关系怎么描述,以及那些工具报错背后到底是什么原因。

这篇文章适合三类人看:刚接手IEEE 1687、1149.1相关项目的DFT工程师;做测试复用IP集成、需要在RTL里挂自定义仪器的验证工程师;以及所有被“port not connected”“TDR_ADDR重复”这类报错折磨过的朋友。我会把ICL的模块声明、端口映射、SIB的TDR选择、TDR的数据位宽、TAP的状态机和指令译码全部拆开讲,配上可直接抄走的模板,最后整理一份排错速查表。写的都是我实际跑过的写法,不一定覆盖Tessent所有版本,但核心思想和套路是通用的。

1. 先搭建ICL的基本盘:module、port与ICL_INSTANCE的关系

1.1 把ICL当成一张片上接线图,而不是RTL代码

很多人第一次看ICL文件会懵,因为它长得既像Verilog,又比Verilog抽象。我更愿意把ICL理解成一张“纯文本接线图”:它不描述逻辑实现,只描述“有什么端口”“端口之间怎么连”“某个例化对应哪个模块”。你可以把module理解成接线盒,port是接线盒上的接口,而ICL_INSTANCE则是告诉你“这里放了一个某某型号的接线盒,它的接口接到了哪里”。

这个定位决定了写ICL时的思维方式。你在RTL里要关心SIB的MUX选通逻辑怎么写,但在ICL里只需要告诉工具:这个SIB模块有哪些输入输出端口,它在实例化时接到了哪条扫描链上,它的TDR地址是多少。工具根据这份描述去做pattern retargeting、生成测试激励、做诊断分析。所以ICL写得准不准,直接影响后面一系列工具链的输出质量。

1.2 一个最小可用的ICL模块长什么样

先看一个最朴素的模块声明,假设我们要描述一个8位的TDR:

ICL { module "MyTDR_8bit" { port { port_in "tdi" { } port_out "tdo" { } input "capture" { } input "shift" { } input "update" { } input "reset" { } input "select" { } } ICL_MODULE_ID "MY_TDR_8BIT"; } }

这里面每一个关键字都有明确含义。ICL是文件顶层,所有模块定义都包在这对大括号里。module后面跟的是模块名,这个名字建议和RTL模块名保持一致。port块里声明模块的对外端口,input和output管的是控制类信号,port_in和port_out则用来表达扫描数据通路上的串行输入输出。ICL_MODULE_ID是这个模块在ICL世界里的唯一身份证,工具之间做匹配时靠它识别。

我之前遇到过一个问题:模块名和ICL_MODULE_ID不一致,RTL里叫“tdr_8bit_top”,ICL_MODULE_ID却写的“MY_TDR_8BIT”,结果Tessent在做网表匹配时死活认不出来。后来统一规定:模块名、ICL_MODULE_ID、RTL实例名尽量用同一套命名,能省掉大量定位问题的精力。

1.3 端口方向的细节:scan类端口和控制类端口别混用

ICL里的端口声明方式比较灵活,但有一个原则必须守住:扫描数据通路端口使用port_in和port_out,而控制信号使用input和output。这不是风格问题,而是语义问题。port_in和port_out描述的是扫描移位路径上的数据流向,工具做链跟踪时会沿着它们走;input和output则用于capture、shift、update这类控制信号,工具在生成时序关系时会用到。

混用的典型后果是:工具把控制端口当成扫描端口去追踪连接,报告里出现一堆莫名其妙的环或者“unexpected port type”。所以写端口声明时先分类,扫描通路归扫描通路,控制信号归控制信号,宁可在注释里写清楚,也不要图省事混在一起。

1.4 ICL文件组织和include机制

一个芯片里ICL模块通常不止一两个,我的习惯是按照仪器类型拆分文件:TAP相关放tap.icl,SIB定义放sib.icl,TDR按IP或功能块分文件,最后用一个顶层文件include进来。Tessent的ICL编译器支持类似C语言的include机制,你可以用相对路径或绝对路径引用其他ICL文件,但要注意路径解析的搜索顺序,这个在大型项目中很容易踩坑。

文件拆分还有一个好处:不同IP团队维护各自的ICL文件,互不干扰。但拆分之后要统一约定命名规则,避免两个模块在联合编译时出现同名冲突。我见过两个IP团队都定义了名叫“sib_inst”的ICL_INSTANCE,合并后工具直接报duplicate instance name,最后只能全局重命名,改得头大。

2. SIB定义实操:用ICL_TDR_SELECT写一个能用的SIB

2.1 SIB的核心作用:在扫描链上做“段开关”

SIB的全称是Segment Insertion Bit,是一种可配置的旁路开关。它挂在扫描路径上,通过一个控制位决定是“让数据进入后面的TDR段”还是“直接旁路过去”。这个机制最大的好处是:不需要访问某段仪器时,扫描链长度不会白白变长,测试时间和诊断数据量都能省下来。

在ICL语法里,SIB的描述方式通常不靠手画状态机,而是使用ICL_TDR_SELECT这类模板。模板内部封装了旁路和选通的行为,你只需要告诉它TDR的地址和对应关系。这也是ICL“高一层描述”的体现:工具知道SIB的标准行为,不需要你重复描述切换逻辑。

2.2 一个最小SIB的ICL定义模板

我在项目里常用的SIB定义模板是这样的:

module "SIB_TOP" { port { port_in "sib_in" { } port_out "sib_out" { } input "select" { } input "shift" { } input "capture" { } input "update" { } input "reset" { } port_out "tdr_select" { } } ICL_MODULE_ID "SIB_TOP"; ICL_TDR_SELECT "sib_select" { TDR_ADDR "0x00"; SELECT_PORT "select"; // 其他控制端口映射 } ICL_INSTANCE "sib_impl" { ICL_MODEL "SIB_TOP"; ICL_PORTS { "sib_in" = "sib_in"; "sib_out" = "sib_out"; "select" = "select"; "shift" = "shift"; "capture" = "capture"; "update" = "update"; "reset" = "reset"; "tdr_select" = "tdr_select"; } } }

这里ICL_TDR_SELECT告诉工具“这是一个TDR选择逻辑”,TDR_ADDR指定了这个SIB对应的地址。当工具需要访问该地址的TDR时,它就知道要让这个SIB进入选通状态。SELECT_PORT则把RTL里的select信号和这个语义绑定,保证ICL描述的“意图”和硬件实现是同一个信号。

2.3 SIB嵌套时的地址分配原则

当设计里有多级SIB嵌套,比如顶层SIB下面又挂了二级SIB,地址分配就变得敏感。ICL对SIB地址的解析依赖TDR_ADDR的唯一性,同一个父SIB下的所有子SIB地址不能重复,但不同父SIB下的子SIB可以重复使用相同地址。听起来和内存地址空间很像,实际上它就是一套“扫描网络地址空间”的编址规则。

我常犯的一个错误是在多级SIB的场景里,只检查了全局地址唯一性,忽略了局部地址空间。比如顶层A下面的SIB1和顶层B下面的SIB1都用了0x01,工具直接报冲突。后来我养成了习惯:先在纸上画出SIB树形结构,逐层标注地址,写进ICL之后再用工具编译验证,基本能杜绝这类低级问题。

2.4 手写SIB需要注意的RTL一致性

一条最重要的提示:ICL里写的SIB行为必须和RTL实现完全一致。工具不会去检查你的RTL,它只会按照ICL的描述生成pattern。如果你的RTL里SIB的实际地址是0x05,但ICL里写的是0x00,工具生成的测试序列在芯片上根本选不到这个SIB,整个测试会失败。

所以每次改完RTL里的SIB地址,我一定同步更新ICL,并且跑一遍一致性检查。Tessent环境下可以用Tessent ICL Compiler的lint功能做基础检查,再结合网表做连接性验证。这个流程虽然多花几分钟,但能避免后续在ATE上抓狂。

3. TDR的ICL定义:数据位宽、控制端口与连接映射

3.1 TDR是“被访问的目标”,不是“要实现的逻辑”

TDR(Test Data Register)是被SIB或TAP选中的测试数据寄存器。它可能是某个传感器的配置寄存器,可能是内建自检的结果锁存器,也可能是任意一个需要被测试访问机制读写的自定义寄存器。ICL里描述TDR时,不需要描述它的内部电路怎么搭,只需要告诉工具三件事:数据通路端口是什么、位宽是多少、它挂在哪个选择逻辑下面。

把TDR理解成一个“接口描述”很有用。你在RTL里怎么实现移位寄存器、怎么产生捕获时钟,工具不关心。ICL关心的是:扫描数据从哪个端口进、哪个端口出、一次shift能有多少位。这决定了pattern的长度和数据的解读方式。

3.2 定义TDR位宽和控制行为的语法要点

TDR的位宽在ICL里通过数据端口的定义间接表达。常见做法是模块里暴露一组位宽为N的扫描数据端口,并用工具可识别的属性或模板声明其行为。参考我项目里的写法:

module "TDR_WIDTH16" { port { port_in "tdi_16" { width 16; } port_out "tdo_16" { width 16; } input "select" { } input "shift" { } input "capture" { } input "update" { } } ICL_MODULE_ID "TDR_WIDTH16"; ICL_MODEL "tdr_model" { ICL_TDR_DATA_WIDTH 16; } }

这里width 16声明了数据端口位宽,ICL_TDR_DATA_WIDTH 16进一步向工具声明这是16位TDR。两处保持一致的目的是让工具在做位宽检查时不需要猜测。如果端口位宽写16,但ICL_TDR_DATA_WIDTH写8,编译时工具会提示不匹配,尽早暴露问题。

3.3 把TDR挂到SIB或TAP上:端口映射与连接声明

一个TDR模块写完后,要被真正“使用”,需要在上层模块中实例化,并把端口连接起来。这是ICL语法的核心操作,类似RTL里的例化与连线。看这个例子:

module "TOP_TDR_BLOCK" { port { port_in "tdi" { } port_out "tdo" { } input "select" { } input "shift" { } input "capture" { } input "update" { } } ICL_MODULE_ID "TOP_TDR_BLOCK"; ICL_INSTANCE "tdr16_inst" { ICL_MODEL "TDR_WIDTH16"; ICL_PORTS { "tdi_16" = "tdi"; "tdo_16" = "tdo"; "select" = "select"; "shift" = "shift"; "capture" = "capture"; "update" = "update"; } } }

在ICL_PORTS块里,左边的名字是子模块(TDR_WIDTH16)的端口名,右边是上层模块的端口名或网络名。工具会把两边连起来,形成完整的数据通路。如果某个端口没有连接,在部分版本里工具只是warning,但如果你在继续做retargeting,就可能出现pin悬空导致的pattern缺失。

3.4 边界扫描TDR和1687自定义TDR的差异

做1149.1兼容的边界扫描设计时,TDR通常指边界扫描寄存器(BSR)这类标准寄存器;而做1687自定义仪器时,TDR更像一个个独立的“测试挂载点”。ICL对两者都支持,但描述方式略有差异。边界扫描TDR往往和指令译码器、Bypass逻辑一起捆绑在TAP内部,因此不需要单独写成模块;而自定义TDR通常是独立模块,需要完整声明端口和连接关系。

我在实际项目中摸索出的经验是:如果TDR是IP供应商交付的,先确认对方提供的是“完整ICL模块”还是“半截子文件”。半截子文件经常缺少顶层连接信息,需要你补充模块实例化部分。补充时一定要参照IP的端口列表,别凭RTL记忆去连,不然端口名对不上又要来回debug。

4. TAP的ICL描述:状态机、指令译码和多TAP层级连接

4.1 TAP是整张ICL网络的“入口”

TAP(Test Access Port)是整个扫描访问机制的门面,IEEE 1149.1标准的TCK、TMS、TDI、TDO都从这里进出。在ICL里,TAP描述的不仅仅是几个引脚,更重要的是它内部状态机如何响应TMS序列、如何把指令寄存器译码成TDR选择信号、以及TDO数据通路在什么时候驱动回扫。

不是所有ICL顶层都要显式写TAP。如果Tessent已经帮你抽象了标准的1149.1 TAP行为,你只需要把TAP当作一个预定义模块去实例化。但在多TAP、桥接、自定义TAP口场景里,就需要你自己定义TAP的ICL描述。

4.2 用ICL_TAP_STATE_MACHINE描述TAP行为

IEEE 1687 ICL标准里对TAP的描述有一类模板叫ICL_TAP_STATE_MACHINE,用来声明TAP的协议状态。实际使用Tessent时,更常见的方式是引用标准TAP模型库,避免重复造轮子。但如果你必须自定义TAP,那么至少要定义TCK、TMS、TDI、TDO以及可选的TRST,并指明状态机类型,例如:

module "CUSTOM_TAP" { port { input "tck" { } input "tms" { } input "tdi" { } output "tdo" { } input "trst_n" { } } ICL_MODULE_ID "CUSTOM_TAP"; ICL_TAP_STATE_MACHINE "tap_state_machine" { ICL_TAP_TYPE "1149.1"; } ICL_INSTANCE "tap_internal" { ICL_MODEL "CUSTOM_TAP_CORE"; ICL_PORTS { "tck" = "tck"; "tms" = "tms"; "tdi" = "tdi"; "tdo" = "tdo"; "trst_n" = "trst_n"; } } }

ICL_TAP_TYPE "1149.1"是在告诉ICL工具,这个TAP遵循1149.1的时序行为。这样一来,Tessent生成pattern时就知道TMS序列应该按哪套规则驱动。如果设计用的不是标准1149.1,而是某种裁剪过的TAP协议,这里需要换成对应的类型,或者干脆在更高层用自定义状态机描述。

4.3 指令寄存器和TDR选择:ICL里的译码逻辑表达

TAP的核心功能之一是把指令寄存器中的值译码成TDR选择信号。ICL里通常通过将指令寄存器与各TDR的TDR_ADDR对应起来表达这种关系。当指令译码输出选中某个地址时,对应的TDR或SIB就被激活。

我们在网表里看到的是一堆译码逻辑,但ICL层面只需要描述“这条指令对应哪个TDR”。Tessent工具在生成pattern时会根据ICL里的地址对应关系,自动产生加载指令、等待状态、搬移数据等一系列时序。所以ICL描述越清晰,工具生成的pattern越可靠。

4.4 多TAP与桥接:主TAP、辅助TAP的层级关系

芯片规模变大之后,单一TAP往往不够用。常见的做法是多个TAP通过桥接逻辑串成网络,或者有一个主TAP控制若干辅助TAP。ICL在描述这种场景时要特别注意层级关系,明确“谁是根节点,谁是子节点”。

举例来说,如果芯片有两个TAP:主TAP通过桥接模块访问辅TAP,那么在主TAP模块里,桥接模块可以看作一个特殊的TDR,通过指令选中;而在辅TAP模块里,它自己又是一套完整的TAP端口。ICL里你需要分别定义两个TAP模块,再用顶层模块把桥接关系连起来。连接时最常犯的错是端口方向搞反,尤其是TDO通路,一不留神就把“从辅TAP读出”写成了“驱动进辅TAP”,后患无穷。

我建议在多TAP设计中,先画一张TAP级连接图,标注好每个TAP的TDI和TDO方向,再动笔写ICL。端口方向这类问题,在纸上推演一遍就能发现,不要靠工具报错来提醒。

5. 实例化与连接:怎么把模块正确地“接线”

5.1 写ICL的推荐顺序:从TAP往下,还是从仪器往上?

写ICL如果毫无章法,很容易写着写着就漏连接。我常用的顺序是“自上而下”:先定义顶层TAP端口,再把TAP实例化到顶层,接着定义它下面的SIB网络,最后把TDR挂到SIB或TAP上。这样每一步的连接对象都是明确的,不会出现“子模块写好了,但不知道往哪里接”的尴尬。

一个例外是当你有大量现成的IP级ICL文件时,自上而下的方式需要反复查看子模块端口名。这时可以改为“自下而上梳理,自上而下连接”:先收集所有子模块的ICL文件和端口列表,建立一个端口速查表,再动笔写顶层。总之,顺序可以灵活,但一定不能跳步,尤其是连接关系,跳一步后面就全是坑。

5.2 端口映射的细节:未连接端口该怎么办

在ICL_PORTS里写端口映射时,最容易忽略“子模块有、但当前层级确实用不到的端口”。某些工具对未映射端口会输出warning,如果你不注意,后续仿真或审查时可能会被误判为悬空。我的习惯是:凡是没有连接的端口,明确留一个注释说明原因,例如“该端口在功能模式固定为0,ICL中不连接”。如果工具支持显式的no connect声明,也要用上。

另一个细节是端口名的匹配规则。不同团队写RTL时喜欢加前缀后缀,导致ICL端口名和RTL端口名不总是一致。ICL本身只管ICL内部的连接一致性,但如果你想用Tessent做网表连接性分析,ICL里的端口名最终要能映射到网表端口上。所以端口名的映射关系需要集中管理,避免两边各写一套。

5.3 用Tessent ICL Compiler做语法检查

写完ICL后,推荐第一时间用Tessent ICL Compiler做语法检查和lint。命令行方式大概是:

tessent -icl file.icl -do "check"

不同版本的命令略有差异,但思路一致:单独编译ICL文件,先过滤掉语法层错误,再进入连接性检查。这一步非常快,却能拦下80%的明显错误。我每次改完ICL都会立刻跑一遍,绝不把带语法错误的文件带进后续流程。

5.4 阅读编译报告时要盯住的两个关键点

编译报告里最容易忽略的是两类信息:一是“warning”级别的连接问题,二是模块被重复定义的提示。Warning很多时候可以带过,但连接类warning要逐个看。曾经有个TDR的位宽不一致,工具只报了warning没报error,我没注意,结果生成的pattern长度不对,排查了半天。

另一个需要关注的点是工具是否成功识别了所有ICL_MODULE_ID。如果某个模块ID和预期不一致,报告里通常会列出已识别的ID列表。养成习惯:每次编译后花两分钟扫一眼ID列表,能提前发现很多低级错误。

6. 排查实录:ICL报错里最常见的五类问题

6.1 “port not connected”明明连了,为什么还报错

这类报错最常见的原因是端口名拼写不一致。ICL里端口匹配是精确字符串匹配,哪怕是多了个下划线也会报未连接。另一个容易被忽略的原因是大小写,工具对大小写敏感,写错一个字母就找不到端口。排查时优先用文本对比工具检查两端端口名是否完全一致,比肉眼盯屏幕可靠得多。

还有一种情况是端口在子模块中定义了,但在ICL_MODEL对应的模块描述里没有对应声明。此时工具认为该端口不存在,自然报not connected。这多半是模块版本不一致造成的,检查IP交付包里ICL文件是不是和RTL版本配套。

6.2 “TDR_ADDR重复”该怎么定位

TDR_ADDR重复时,工具会直接拒绝编译。定位方法是先看报错中提示的两个实例属于哪个父SIB,再检查它们是否在同一层。同一层的TDR地址必须唯一,不同层的则可以复用。如果工具没有给出明确实例名,可以临时注释掉部分TDR_ADDR,用二分法缩小范围。

我排查时还会额外检查地址格式。比如0x00和0x0看起来一样,但在某些版本中可能被解析成不同值。统一使用相同位宽的十六进制表达,不要一会儿4位一会儿8位。

6.3 为什么TDR在扫描链里“看不见”

TDR已经实例化,ICL也编译通过,但后续生成的扫描链里就是找不到这个TDR。这种问题多出在连接关系不完整,比如TDR的scan_in或scan_out没有连到上层SIB,工具在追踪扫描通路时链路中断,TDR自然进不了链。查看Tessent报告里的扫描路径列表,找到断开位置,基本能定位是哪个端口没接。

还有一种可能是TDR所在的SIB没有被成功使能。ICL里SIB和TDR的地址匹配关系如果对应不上,TDR路径便不会被激活,表现为“在链里看不见”。

6.4 多TAP场景下工具识别错了主TAP

多TAP设计里,工具识别错主TAP通常和ICL顶层实例化顺序有关,也可能是因为多个TAP模块都定义了ICL_TAP_STATE_MACHINE,工具默认取了第一个。解决办法是在顶层模块中显式声明主从关系,或者在实例化时用参数标记主TAP。如果Tessent版本支持TAP连接关系声明,优先使用它,不要依赖文件中的出现顺序。

这类问题很容易被忽视,因为单独编译每个TAP文件都正常,但合并后行为就变了。合并前先明确设计意图:哪个TAP是外部唯一入口,哪些TAP是内部从属,然后再写连接关系。

6.5 不同Tessent版本之间的ICL语法差异

Tessent不同版本对ICL的支持程度和关键字略有差异。老版本可能不支持某些模板,新版本可能对端口的默认行为做了调整。最稳妥的做法是固定一个团队统一使用的Tessent版本,并且把官方ICL参考手册的版本号记录在项目文档里。升级工具前,先跑一遍全量ICL编译回归,确认语法兼容性。

错误现象常见原因快速排查方法
port not connected端口名拼写或大小写不一致对比两端的端口名,确认完全一致
TDR_ADDR重复同一父节点下地址冲突检查报错实例的父SIB,二分法定位
TDR在链中不可见scan_in/scan_out连接中断查看扫描路径报告,寻找断开点
多TAP识别错主实例化顺序或未声明主从显式声明主TAP,指定TAP连接关系
版本语法差异工具版本升级导致统一版本,升级前做全量ICL编译回归

我个人的体会是,ICL语法本身不难,难点在于“语义一致性”。你必须时刻记得ICL描述的是一张意图层面的接线图,它要和RTL实现严丝合缝。工具不会替你做这个检查,它只是忠实地按照你的描述干活。所以每写完一个模块,我都会问自己三个问题:地址对不对?端口连全了吗?和RTL命名一致吗?这套自检习惯帮我避免过太多在ATE上才发现的问题。

最后再分享一个小技巧:写完ICL不要急着往下走,花五分钟用Tessent的lint/check功能跑一遍,再花十分钟把报告里的warning逐个过目。磨刀不误砍柴工,后面做pattern retargeting、做诊断分析时你就知道,这一步的投入有多值。

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

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

立即咨询