做DFT的兄弟,“Read, Then Speak”这个说法你听起来是不是也有点懵?我第一次看到这个标题的时候,脑子里蹦出来的不是测试原理,而是一句没头没尾的英文口语。直到自己踩了几次坑,把Tessent DFT整套流程摸了一遍,才反应过来,这句话其实说的就是DFT里最核心也最容易卡住新人的那条主线:先让工具把设计读进去,再让工具把结论“说”出来。读不对,后面全是白跑;说不清,前面读了也白读。这篇东西主要写给刚接触Tessent DFT Flow、正被“我读进来了,但工具不甩我”“报告出来了,但看不懂它在讲什么”折磨的人,也适合所有想在dft flow里把基础打扎实的工程师慢慢翻。
1. 先搞清楚“Read, Then Speak”在DFT flow里到底卡在哪
1.1 它不是英文口语,而是DFT流程的“输入—处理—输出”主线
很多人刚进DFT这个方向,第一件事就是被一堆英文术语砸晕:Tessent、ATPG、BIST、JTAG、压缩、覆盖率……这些词单拎出来都能找到教材解释,但放到一条真实的dft flow里,就变成了一盘散沙。我最开始也是这样,拿着一个综合后的门级网表,知道下一步要做scan insertion,但打开Tessent Shell之后,完全不知道第一句命令该敲什么,敲了之后又该怎么判断“这件事做对了”。
后来我把整个流程压缩成一句话:先把设计读进来,然后让工具用它的语言告诉你,这个设计能不能测、怎么测、测完覆盖率多少。这句话对应的就是标题里那个“Read, Then Speak”。读,是读网表、读库、读约束、读测试协议;说,是工具通过报告、日志、DRC结果、pattern文件,把它对设计的理解反馈给你。整个DFT Engineer的日常,本质上就是在“读”和“说”之间反复对齐,直到工具说的东西和你想要的东西完全一致。
这个视角特别重要。因为一旦你把这个主线立起来,就不会再东一榔头西一棒子地乱试命令,而是会自然而然地想:我现在是卡在读入阶段,还是卡在工具输出阶段?是文件格式没给对,还是约束没写清楚?有了这个整体感,下面所有细节才真正有意义。
1.2 “我不会”的背后,通常是三段断层
标题里说“重难点,我不会”,我特别理解这种状态。但我接触下来,绝大多数人说自己“不会”,不是真的不会敲命令,而是被三个断层卡住了。
第一个断层在“读”之前:不知道要准备哪些文件,也不知道这些文件之间的格式要求。比如有人拿RTL直接喂给Tessent,期望它帮你做scan insertion,结果报错一屏接一屏;还有人给的是综合后的Verilog网表,但库里用的模块名跟网表里完全对不上,工具一个模块都认不了。
第二个断层在“读”和“说”之间:设计读进去了,但不知道用什么约束让工具理解测试结构。比如scan chain怎么分组、scan enable信号接哪、异步复位怎么处理,这些约束一旦写错,工具后续“说”出来的DRC报告就会全是violation,而且很多violation根本不是你设计的问题,是你约束没给对。
第三个断层在“说”之后:工具输出了一大堆报告,覆盖率也吐出来了,但很多人不知道怎么看重点。哪些violation可以waive、哪些FAIL是因为库文件缺引脚、哪些覆盖率低是本来就不可能测到的冗余逻辑,这些判断能力是需要积累的,但前提是你得先知道报告里的信息是从哪来的。
这三个断层,正好对应我在下面要展开的三个大块:怎么把设计读对、怎么让工具把话说清楚、以及怎么读懂工具说的话。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节拆解:读什么、拿什么读、读对了没有
2.1 读入数据的四类输入和格式校验
想在一个Tessent项目里跑通dft flow,你至少需要四类输入,缺一个都会让工具“闭嘴”。
第一是库文件。最常用的是Liberty格式的.lib,它会描述每个标准单元的功能、时序、功耗和引脚属性。对DFT工具来说,它更关心的是每个单元能不能被配置成扫描单元、有没有测试端、扫描使能端是什么。你可以在.lib里看到诸如test_cell、scan_enable、scan_in、scan_out这些属性,这些就是工具做scan识别的基础。
第二是门级网表。综合之后吐出来的Verilog或VHDL网表,里面全是标准单元的实例和互联。注意这里有个常见误区:有人拿RTL行为级代码直接跑DFT,不是完全不行,但工具能识别的DFT结构非常有限,做scan insertion之前你必须拿到综合后的门级网表。还有一个特殊情况是CDL或SPICE网表,模拟IP或IO电路有时只提供CDL格式,Tessent也能读,但需要额外的映射配置,新手不建议一上来就搞这个,容易把自己绕晕。
第三是约束。这里面又分两类:一类是时序约束,比如SDC文件,给的是时钟周期、输入输出延迟;另一类是DFT约束,比如tool properties、dft setup脚本,告诉工具哪些信号是scan clock、哪个是scan enable、哪些pin需要tie。很多新人只给SDC不给DFT约束,工具自然没法正确“理解”设计。
第四是测试协议。Tessent里最常见的是WGL、STIL或SPF,还有一种更底层的是test procedure文件,通常用它定义测试时的基本时序关系。对于只做scan chain测试和ATPG的工程师,很多流程会把测试协议隐藏在工具配置里,不一定会直接让你写,但你得知道它是干什么的。
有一个更隐蔽的问题:文件之间的格式匹配。比如说.lib库的版本和综合用的库不一致,网表里例化了一个库单元叫DFFQ_X1,但.lib里只定义了DFFQ_X2,工具直接报unknown module。这种问题非常恶心,因为报错可能要到很后面才出现,排查起来特别费劲。我的习惯是把综合用的.v、.lib、SDC、约束脚本全部放到一个固定的release目录里,每次做DFT之前先核对一遍版本,宁可多花十分钟确认,也不要等报告白跑了三个小时才发现库和网表对不上。
2.2 Tessent DFT里的“读”不是load design那么简单
当年我第一次跑Tessent,以为跟用VCS仿真一样,read_netlist一下就完事了。结果实际用起来,发现Tessent的“读”有一个自己独立的框架。
首先要进入正确的上下文。Tessent的命令很多,但不同命令必须在特定的context下才能用。比如你想要做scan insertion的流程,通常要先set_context dft_insertion;你要做ATPG,就得走到set_context atpg。context的作用是告诉工具接下来你要干什么,它才会把相关的输入数据和命令集合加载出来。有人在自己没搞清context的概念前,所有命令都在默认shell环境下敲,能跑通才怪。
其次是“读”的命令有层次。你会用到read_lib去读综合库,用read_netlist去读门级网表,用read_sdc或add_clock去读时序约束,还会用add_scan_group、add_scan_enable这类命令把测试结构描述给工具。这一整套下来,才算把设计完整“读”进工具里。用生活类比来说,read_netlist是拿到一张零件清单,read_lib是拿到每个零件的规格说明书,而DFT约束是你告诉装配工“这几个零件要按扫描链的顺序串起来”,缺了任何一部分,后面的机器人(工具)都没法干活。
还有一个特别关键的点,就是读入之后要检查“设计是否被正确消化”。我身边的很多工程师,包括我自己早期,最容易犯的错是读完之后不检查就往下跑。结果跑到DRC阶段,报出一个奇怪的fail,你根本不知道是本来设计就有问题,还是读的时候端口映射对应错了。后来我养成习惯:每次读入完网表,至少先跑一遍get_cells和report_modules,看看工具认出的模块数量、标准单元数量跟综合log里的数据能不能对上。数量差太远,一定是读入阶段出了幺蛾子,早发现比晚发现好一百倍。
2.3 读入后的“自检”:怎么判断有没有读对
自检说起来很虚,实际做起来就是几个干巴巴的命令,但非常管用。
第一个是report_design -verbose或者类似的命令,不同版本可能名字略有差别,作用是把你当前读进工具的设计信息完整吐出来。你要在这里重点看三件事:模块数量、端口数量、实例数量。每个数量都去跟综合报告里对一遍。有出入,就说明网表或库有问题,先解决这个问题再继续。
第二个是report_lib系列命令。你可以用它看看工具到底识别了哪些库单元,尤其是有没有识别出扫描单元。一个标准的scan DFF,在.lib里应该带有test_cell属性。如果你发现工具报出来的库单元清单里,扫描单元数量是0,那基本可以断定库里没有定义扫描单元,或者你给的库根本不是综合库而是行为模型库。这种情况在仿真库被误当综合库用的时候特别常见。
第三个是check_design类的命令。它能帮你检查网表是否有浮空输入、未连接引脚、多驱动节点等结构问题。虽然这些问题不一定都会让DFT跑挂,但在后面ATPG时会出现很多莫名其妙的X态,让你半天定位不到根因。所以我的习惯是,读到设计后先花几分钟跑一次自检,把这几种报告都留档,和后面DRC结果放在一起,方便回追。
3. 把“Then Speak”做实:从网表到DRC报告/ATPG向量的核心流程
3.1 第一步:准备干净的输入环境
这一步听起来像废话,但做DFT必须养成的第一个职业习惯,就是“环境干净”。
什么叫干净?就是你的工作目录里,所有输入文件都有明确版本号,所有脚本都能用相对路径或者固定的环境变量引用,绝不出现“v2_final_最新版.v”这种文件。因为DFT流程跑一轮短则几十分钟,长则好几个小时,如果你中间换一个网表版本重新跑,出来的结果跟之前的报告对不上,你会疯掉的。
我做DFT时通常这样搭目录:
text复制proj/
data/
lib/ # .lib库文件
netlist/ # 综合门级网表
sdc/ # 时序约束
dft/ # tcl脚本、dft约束
work/
run_0312/ # 每次跑批独立目录,日期命名
run_0313/
report/
每次跑批都开一个以日期命名的独立目录,脚本都用相对路径指向data目录。这样日志、报告、生成文件都在一个目录里,不会互相污染,也方便回溯。
然后就是前面说过的版本对齐。在开始跑之前,我会先看一眼综合报告里的library names、cell counts、netlist版本号,再检查目录里的.lib和网表是不是跟这个版本对应。这也是为什么目录里每个文件都带版本号那么重要,否则你靠文件名根本猜不出这份网表是不是你现在想用的那一份。
3.2 第二步:跑通一个最小DFT flow
下面我给一个非常典型的Tessent DFT流程示例。这里用的是Tessent Shell配合Tcl语法,是工程里很常见的一种写法。不同版本的工具命令名可能略有出入,但主结构是一致的。
tcl复制# 1. 切到DFT插入的上下文
set_context dft_insertion -mode scan
# 2. 读入库和网表
read_lib ../data/lib/tt.lib
read_netlist ../data/netlist/top.v
# 3. 读入设计
set_current_design top
# 4. 添加时钟和扫描控制约束
add_clock clk -period 10
add_scan_enable scan_en
add_scan_group chain_1 -scan_in sin1 -scan_out sout1 -cells {u_core/u_ff_1 u_core/u_ff_2}
add_scan_group chain_2 -scan_in sin2 -scan_out sout2 -cells {u_core/u_ff_3 u_core/u_ff_4}
# 5. 对未连接/控制信号做处理,比如异步复位
add_pin_constraint rst_n -tie 0 -type reset
# 6. 运行DFT DRC
dft_drc
# 7. 查看结果
report_fail
report_coverage
# 8. 如果没问题,写出最终的网表和测试协议
write_design ../data/netlist/top_dft.v
write_test_protocol ../data/dft/top.spf
write_patterns ../data/dft/top.wgl -format wgl
这一段脚本,就是“Read, Then Speak”的最小例子:读入库、网表、时钟、扫描控制,然后让工具做DRC检查并speak出报告,最后再speak出带scan结构的网表和测试pattern。
你可能注意到了add_scan_group里我手动指定了单元列表,这在真实项目里不会这么干。真实项目里通常是用insert_scan或者工具自动扫描所有可测单元,让工具根据连接关系自动分配chain。手动指定是为了让初学者理解scan chain的本质:它就是把一堆DFF串起来,一端进一端出,中间用测试逻辑控制。理解了这个本质,后面才知道为什么约束写不好会导致chain shift都做不了。
还有一个容易忽略的点是第5步的add_pin_constraint。异步复位在DFT里非常典型:如果你不告诉工具rst_n在测试模式必须保持某个固定值,工具在DRC时就会报一个“uncontrolled signal”的错,因为它没法预测这条线的值,后续ATPG会大量产生X态。这一步就是提前帮工具“摆平”它看不懂的信号。
3.3 第三步:“Speak”出来的结果怎么读
跑完上面脚本,工具会生成一堆输出。很多人到这里就卡住了,因为报告密密麻麻,不知道从哪个数字开始看。
先看DRC报告。工具通常会给你一个pass/fail的总表,每个rule有编号,比如Rule 1、Rule 2之类,后面跟着fail的数量。初学者最容易犯的错误是想把一个fail都搞定,这是不可能的。DFT DRC里很多fail是可以接受或者可以waive的,你真正需要关注的是那些会影响扫描链插入和ATPG的致命violation。
怎么看哪些是致命的?我的经验是,当看到跟scan enable、clock、async reset、tristate bus相关的fail时,要非常敏感。比如报告里出现RULE: TAP-01告诉你“scan_enable is not held to a constant value during capture”,那就是约束没做好,导致工具认为捕获阶段扫描使能不能稳定,这种violation会直接影响ATPG结果,必须处理。反过来,如果只是一个内部节点的transition time或者fanout负载偏大,就要结合芯片的实际sigcon要求判断,不一定非要改。
然后看覆盖率报告。Tessent在跑完ATPG之后,会给你一个按fault类型分类的覆盖率表。常见的有stuck-at fault coverage、transition fault coverage,有时候也有IDDQ或cell-aware。我的建议是,先看总体的uncollapsed fault coverage和collapsed coverage,如果覆盖率比你预期低很多,不要急着调工具参数,先回头查DRC报告,看看有多少fault被aborted或untestable标记。很多覆盖率低,根本原因是约束没写对导致大量逻辑被认为不可测,而不是芯片本身不可测。
后续如果还要把测试pattern写给机台或做诊断,工具还会输出WGL/STIL/SPF文件。这些文件拿到后,我一般会再跑一遍格式检查,确保里面的pattern数量、scan chain定义跟报告对得上。这一步看似多余,实际上能省下在ATE机台上试跑才发现pattern错乱的痛苦。
4. 从“我不会”到“我能跑”:UDFM、工具链和内功心法
4.1 UDFM在“Read, Then Speak”主线里的位置
热词里有个dft udfm,很多新人看到这串字母就慌。其实UDFM的全称是User-Defined Fault Model,用户自定义故障模型。默认情况下,DFT工具只会处理标准模型,比如stuck-at(固定故障)、transition(跳变故障)和toggle。但真实芯片里,有的失效模式不在这些标准模型里,比如单元内部的桥接故障、特定模拟模块在某种偏置下才出现的失效、存储器接口上的时序毛刺。这些场景下,你就可以用UDFM去定义一个“自定义故障”,然后让工具识别它并产生对应的测试向量。
UDFM在整个“Read, Then Speak”主线里,属于“高级扩展”那一层。你先把设计读进来,工具说话时默认说的是标准模型的语言;你加入UDFM之后,等于教工具一门新语言,让它在“说”故障覆盖率时,把你关心的那部分特殊故障也算进去。它不会替代传统ATPG流程,但它能让测试设计跟芯片实际失效模式贴得更近。
使用UDFM的门槛在于,你需要比较精确地描述故障的行为:故障发生在哪个电路节点、需要什么激励条件才能激活、在哪个观察点能看到响应。Tessent会提供相应的仿真或形式化机制来验证你的UDFM描述是否合理。对初学者来说,我的建议是先把标准流程和标准报告吃透,再去碰UDFM。否则连stuck-at覆盖率都解释不清楚,加上UDFM只会让你更头晕。但如果你在项目里遇到了标准的stuck-at模型覆盖不了的真实故障,UDFM就很有价值了。
4.2 新手上手Tessent DFT Flow的五条实战心法
第一条,不要一上来就啃工具手册的全部内容。Tessent的手册用“大部头”来形容一点都不过分,你从头看到尾基本什么都不记得。正确做法是先跟着一个完整的示例工程跑通最小流程,让“读设计、跑DRC、看覆盖率、写pattern”这个循环在你脑子里转起来,之后再按需去查手册里对应的章节。
第二条,命令敲不下去的时候,先查context。我见过太多人是把Tcl脚本从一个流程里复制到另一个流程里,然后出现一个莫名其妙的no command matches current context,就开始慌了。Tessent的报错不算特别友好,但大部分“命令不存在”的问题,根源都是当前context不对,比如你在dft_insertion的context下想用ATPG的命令,工具当然不认。所以记住一句话:命令前面先考虑“我现在在哪个context,这条命令属于哪个context”。
第三条,日志文件就是你的现场保护神。跑批结束之后,至少把run目录里的synthesis_log、dft_drc.log、report_coverage.log留好。万一后续比对结果有差异,打开这些日志,每一行都有时间戳和命令记录,很多问题立刻就能定位。
第四条,加约束时先做最小集,再逐步补充。很多新手喜欢一开始就把几十条约束全部塞进去,跑挂了根本不知道是哪条惹的祸。我会先把scan clock、scan enable、scan chain加好,跑一次DRC;然后加复位约束、tie-off约束,再跑一次DRC;每一步都只加一类约束,报告对比着看,谁引入新的violation一目了然。
第五条,多跟综合和后端工程师对“名字”。DFT里面大批问题都出在netlist里信号名和约束脚本里信号名对不上。比如综合端给时钟起名叫clk,你在脚本里写clock,工具直接忽略你的约束,设计就当没有时钟处理。名字一致性,project中没有小事。
5. 常见问题与排查技巧实录
5.1 读取阶段翻车现场
读取阶段最常见的问题有这么几类,我列一个速查表,方便你对照排查。
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
Unknown module type 报错 |
库和目标网表不匹配 | 先确认综合用的.lib版本,检查网表里例化的单元名是否在.lib里存在 |
Cannot open library file |
路径错误或文件损坏 | 检查相对路径,建议所有路径统一用环境变量或绝对路径根变量 |
Design is empty |
网表顶层名字不对 | 用get_cells或文本编辑器看网表里的module名,再设set_current_design |
| 读入后单元数量异常少 | 库只是行为模型库,没有完整的test cell定义 | 查看report_lib,确认标准单元有test属性,换真正的综合库 |
| Constraint被忽略 | SDC里时钟名和网表端口名不一致 | report_clocks查看工具识别出的时钟,和SDC里的create_clock对象名对齐 |
这里特别说一下,很多人看到Design is empty就以为工具坏了,其实80%的情况是set_current_design指定的顶层名字和网表里的module名不一致。用文本编辑器打开网表,看第一行module xxx的名字,比你猜一百次都有效。
5.2 DRC和约束阶段翻车现场
DRC阶段的典型问题集中在约束缺失。我最早做DFT时,最喜欢在DRC报fail之后去找工具规则的详细说明,结果越查越晕。后来我总结出一个处理fail的通用步骤:先看fail项涉及的信号是时钟、复位、扫描控制还是普通逻辑;然后看violation描述里有没有出现和你设计匹配的关键字;最后再回到你的约束脚本里,看是否对这类信号做了充分的约束说明。
举几个高频case:
RULE: UNCONNECTED或者UNCONTROLLEDFail:通常是指某条线没有可控的驱动源。如果是复位信号,加add_pin_constraint;如果是tie-off信号,加add_tieoff_cells或对应约束。RULE: SCAN_IN/SCAN_OUTFail:通常是scan group定义有误,比如scan_in和scan_out接错了方向,或者两个scan group之间共享了不该共享的信号。RULE: LATCHFail:设计里有电平敏感锁存器,工具没法像处理DFF那样直接放进scan chain,通常需要blackbox掉或者特殊处理。
很多工程师处理DRC fail的误区是“想把fail清零”。但DFT DRC的fail分致命和非致命,有些fail是设计本身的冗余逻辑导致的,工具标出来提醒你“这里测不到覆盖率”,但你为了清零去改设计或加约束,反而浪费时间和资源。所以要学会分类fail,而不是无脑清fail。
5.3 故障模型与覆盖率阶段翻车现场
跑完ATPG之后,最常见的两类问题是:覆盖率异常低,以及pattern数量异常大。
覆盖率低,先看两个数字:aborted faults和untestable faults。aborted是工具在推理时因为资源或约束限制没敢下结论的fault,通常可以通过增加ATPG effort、修约束或优化时序来减少。untestable是工具明确判定测不到的fault,比如某些冗余逻辑、无观察点的内部节点,这类数字很难降到0,重点是看它是否合理。如果untestable比例很高,去DRC报告里查是不是某些模块被误blackbox了。
pattern数量异常大,很多时候是因为没有做测试压缩(test compression)。XOR压缩、MISR这些结构上了之后,pattern数量会有显著下降。当然压缩结构本身又会引出新的DRC问题,但从整个dft flow的投入产出比来讲,压缩几乎是必选项。如果你在flow里没看到任何压缩相关的配置,可以往add_scan_compression方向查一查。
另外在UDFM相关的场景里,跑完自定义故障模型后,工具给出的故障覆盖率会比标准模型低很多,这很正常,因为自定义故障模型本来就只覆盖你定义的那一小类缺陷。千万别拿它和stuck-at coverage直接比,越比越焦虑。
这里我把五个常见场景整理成一个速查表,方便你贴在工作台前:
| 场景 | 关键行为 | 首选排查方向 |
|---|---|---|
| 网表读入后空设计 | set_current_design 指定顶层 |
对比网表module名与工具识别的design名 |
| DRC报Uncontrolled reset | 复位端在测试模式下无法固定 | 添加add_pin_constraint -type reset |
| DRC报Scan chain长度不一致 | 不同chain长度差距过大 | 重新分配scan group,确保chain长度接近 |
| ATPG后覆盖率显著偏低 | 大量fault被abort或untestable | 查DRC报告,检查blackbox和约束完整性 |
| Pattern数量爆炸 | 未做压缩或压缩结构有问题 | 检查scan compression配置和时钟复用关系 |
这些都是我实际跑过、修过、复盘过的case。写在这里,是想告诉你,DFT里绝大多数问题都能沿着“读进去、说出来的信息是否真实准确”这条线找到根源。新人的“我不会”,往往只是还没有把“工具在说什么”跟“设计的真实结构”对应起来。等你把这一步打通,再回头看“Read, Then Speak”这句话,会发现它整个就是DFT工作的最好总结。我自己刚入行时,也在这三个词上栽过不少跟头——尤其是每次拿到一份第三方IP的网表和库,一读就报错,对着log一行行看才排查出是库里少了一组电源引脚定义,可以说这一路就是靠“读、查、对、修”四个动作磨过来的。希望这篇文章能让你少走一点弯路。
