1. 从零理解Tessent MBIST:为什么需要参考脚本
1.1 MBIST在DFT体系中的位置
做DFT这行的朋友都清楚,芯片测试里最让人头疼的不是逻辑测试,而是存储器测试。一颗SoC里面动辄几十上百个SRAM实例,每个SRAM的地址位宽、数据位宽、深度、mux策略都不一样。如果靠人工去写测试向量,那基本等于自己给自己挖坑。MBIST(Memory Built-In Self-Test)就是为解决这个问题而生的——在芯片内部集成一套自测试逻辑,上电后自动对存储器进行读写遍历,把故障覆盖率拉上去。
Tessent是业界主流的DFT工具链之一,它的MBIST流程覆盖了从memory model导入、BIST控制器插入、pattern生成到仿真验证的完整链路。但问题在于,Tessent的文档虽然全,却散落在各种User Guide和Reference Manual里,新手拿到项目之后往往不知道从哪下手。这时候一套可复用的参考脚本就成了救命稻草。
我见过太多团队在项目初期花两三周时间摸索Tessent MBIST的脚本框架,最后发现其实核心逻辑就那么几十行。这篇内容就是把我自己踩过的坑和最终沉淀下来的脚本框架完整拆开,让你能在最短时间内搭起一套能跑通的内存测试环境。
1.2 谁适合看这份实战记录
如果你属于以下几类人,这篇内容应该能帮你省下不少时间:
- 刚接触DFT、第一次用Tessent做MBIST的工程师
- 需要快速搭建memory测试环境、但不想从零啃文档的从业者
- 想理解MBIST脚本背后逻辑、而不只是复制粘贴的验证人员
- 需要维护多项目脚本框架、希望统一流程的团队负责人
我假设你已经对Verilog和基本DFT概念有了解,但不需要你精通Tessent。脚本部分我会从最基础的环境变量设置讲起,每一步都解释为什么这么做。
1.3 参考脚本的核心价值
一套好的MBIST参考脚本,本质上解决三个问题:可复用、可调试、可扩展。可复用意味着换个项目只需要改配置参数,不用重写流程;可调试意味着出问题的时候能快速定位是memory model的问题、controller配置的问题还是pattern生成的问题;可扩展意味着后续要加新的memory类型或者新的test algorithm时,框架不用大改。
我下面要拆解的这套脚本框架,核心思路是“配置与流程分离”——把所有跟具体memory相关的参数抽到一个配置文件里,主流程脚本只负责调用Tessent的命令。这样做的直接好处是,当你手上有五个项目并行的时候,你不需要维护五套脚本,只需要维护五份配置。
2. 环境准备与目录结构设计
2.1 Tessent环境的关键设置
在跑任何脚本之前,环境必须先通。Tessent的环境设置主要涉及几个部分:工具安装路径、license配置、工艺库路径。我习惯在项目根目录下放一个env_setup.sh,把所有环境变量集中管理。
#!/bin/bash # env_setup.sh - Tessent环境初始化 export TESSENT_HOME=/tools/tessent/2023.1 export PATH=$TESSENT_HOME/bin:$PATH export TESSENT_LICENSE_FILE=27020@license_server export STD_CELL_LIB=/projects/libs/tsmc28/stdcell export MEM_LIB=/projects/libs/tsmc28/memory这里有个细节值得说:TESSENT_LICENSE_FILE的格式取决于你们公司的license服务器配置,有的是port@host,有的是直接指向license文件。这个一定要跟IT确认清楚,否则工具启动就卡住了。
注意:环境变量设置完之后,一定要用
which tessent确认工具路径正确,再用tessent -version验证license能正常获取。我遇到过好几次环境变量看着没问题、但license端口被防火墙挡了的情况。
2.2 推荐的目录结构
脚本要跑得顺,目录结构必须先理清楚。我推荐的布局是这样的:
mbist_project/ ├── env/ # 环境配置 │ └── env_setup.sh ├── config/ # 配置文件 │ ├── memory_list.cfg │ └── mbist_config.cfg ├── scripts/ # 主流程脚本 │ ├── run_mbist.tcl │ └── run_simulation.tcl ├── models/ # memory model │ └── sram_models/ ├── work/ # 运行目录 │ ├── dft_insert/ │ └── simulation/ └── reports/ # 输出报告 ├── dft/ └── sim/这个结构的好处是职责清晰。config/放所有可变的参数,scripts/放不变的流程逻辑,work/放工具生成的中间文件,reports/放最终结果。当你需要归档或者交接的时候,直接把config/和scripts/打包就够了。
2.3 Memory Model的准备与检查
Memory model是MBIST流程的输入基础。Tessent支持多种memory model格式,常见的有Liberty、Verilog behavioral model和Tessent专用的memory model格式。我建议优先使用foundry提供的Tessent memory model,因为里面已经包含了BIST相关的引脚定义和timing信息。
拿到memory model之后,第一件事是检查model的完整性。我一般会写一个小脚本快速扫描:
# check_memory_model.tcl set mem_files [glob -nocomplain ./models/sram_models/*.lib] foreach mem_file $mem_files { puts "Checking: $mem_file" if {[catch {read_cell_library $mem_file} err]} { puts "ERROR: Failed to read $mem_file" puts "Reason: $err" } else { puts "OK: $mem_file loaded successfully" } }这个检查步骤看起来简单,但能帮你提前发现model缺失、格式不兼容等问题。我踩过的坑是:有些memory model的Liberty文件里没有定义memory属性,Tessent读进去之后不认它是memory,导致后续BIST controller插入失败。遇到这种情况,需要在脚本里手动指定memory类型。
3. MBIST脚本核心流程拆解
3.1 配置文件的组织方式
配置文件是整个脚本框架的灵魂。我把配置分成两个层次:memory级别的配置和BIST controller级别的配置。
Memory级别的配置用表格形式管理最清晰:
| 参数名 | 说明 | 示例值 |
|---|---|---|
| memory_name | memory实例名 | SRAM_1024x32 |
| memory_type | memory类型 | SRAM |
| addr_width | 地址位宽 | 10 |
| data_width | 数据位宽 | 32 |
| depth | 深度 | 1024 |
| mux_factor | mux因子 | 4 |
| model_file | model文件路径 | ./models/sram_1024x32.lib |
BIST controller级别的配置则包括:
| 参数名 | 说明 | 示例值 |
|---|---|---|
| controller_name | controller名称 | MBIST_CTRL_TOP |
| test_algorithm | 测试算法 | March_SSA |
| data_bg | 数据背景 | 0x00,0xFF,0xAA,0x55 |
| run_bist | 是否自动运行 | true |
| pipeline_stages | 流水级数 | 2 |
把这些参数抽到.cfg文件里,主脚本用Tcl的source命令读进来,后续所有命令都引用变量。这样换项目的时候,只需要改.cfg文件,主脚本一行不动。
3.2 主流程脚本的骨架
主流程脚本我习惯分成五个阶段:读库、配置、插入、验证、输出。每个阶段用Tcl的proc封装,方便单独调用和调试。
# run_mbist.tcl - MBIST主流程脚本 # 阶段1: 读取设计库 proc read_design_libs {} { global STD_CELL_LIB MEM_LIB read_cell_library $STD_CELL_LIB/typical.lib foreach mem_lib [glob $MEM_LIB/*.lib] { read_cell_library $mem_lib } puts "INFO: All libraries loaded" } # 阶段2: 读取设计 proc read_design {} { set design_verilog "./design/top.v" read_verilog $design_verilog set_current_design top puts "INFO: Design top loaded" } # 阶段3: 配置MBIST proc config_mbist {} { source ./config/mbist_config.cfg # 设置BIST controller set_dft_signal -view existing_dft -type ScanClock -port clk -timing {45 55} set_dft_signal -view existing_dft -type Reset -port rst_n -active_state 0 puts "INFO: MBIST configuration done" } # 阶段4: 插入BIST逻辑 proc insert_mbist {} { set_context dft -mbist # 自动识别memory并插入controller analyze_mbist -memory_model_file ./config/memory_list.cfg insert_mbist -controller_name MBIST_CTRL_TOP puts "INFO: MBIST insertion complete" } # 阶段5: 输出与报告 proc output_results {} { write_design -output ./work/dft_insert/top_mbist.v report_mbist -summary > ./reports/dft/mbist_summary.rpt puts "INFO: Results written" } # 主执行流程 read_design_libs read_design config_mbist insert_mbist output_results这个骨架看起来简单,但每个proc里面都有很多细节可以展开。比如insert_mbist阶段,analyze_mbist命令会自动扫描设计中的memory实例,但前提是memory model已经正确加载,而且设计中的memory实例名跟model里的cell名能对上。
3.3 为什么选择Tcl而不是Shell
有人可能会问,为什么不用Shell脚本而用Tcl?原因很简单:Tessent本身就是Tcl-based的工具,它的所有命令都是Tcl命令。用Tcl写脚本可以直接调用工具命令,不需要通过Shell去拼接字符串再传给工具。而且Tcl的变量作用域、过程定义、错误处理机制都比Shell完善得多。
我试过用Shell包装Tessent命令,结果在传递复杂参数的时候各种转义问题,调试起来非常痛苦。后来全部改用Tcl,流程顺畅了很多。Shell脚本适合做外层调度,比如批量跑多个项目、管理日志文件,但MBIST流程本身一定要用Tcl。
4. 实操过程与关键环节实现
4.1 Memory识别与BIST Controller插入
analyze_mbist是整个流程里最关键的一步。它的作用是扫描设计网表,识别出所有memory实例,并根据memory model里的信息推断出BIST需要的信号。这一步出问题,后面全白搭。
我一般会先跑一个dry-run,看看工具识别出了哪些memory:
# dry-run检查memory识别情况 set_context dft -mbist analyze_mbist -memory_model_file ./config/memory_list.cfg -dry_run report_memory_instances > ./reports/dft/memory_instances.rpt打开memory_instances.rpt,重点看三列:instance name、memory type、model matched。如果model matched显示为“NO”,说明这个memory实例没有匹配到model,需要检查model文件是否加载正确,或者实例名是否跟model里的cell名一致。
我遇到过一个典型问题:设计里memory实例名是u_sram/u_sram_0,但model里的cell名是SRAM_1024X32,工具匹配不上。解决办法是在配置文件里加一个mapping:
set_memory_mapping -instance u_sram/u_sram_0 -model SRAM_1024X32匹配成功之后,insert_mbist命令会为每个memory或者每组memory插入BIST controller。这里有个策略选择:是每个memory单独一个controller,还是多个memory共享一个controller?我的经验是,如果memory数量少于10个,可以每个memory独立controller,调试方便;如果超过20个,建议共享controller,否则面积开销太大。
4.2 BIST算法选择与参数配置
Tessent支持多种BIST算法,常用的有March C-、March SS、March SSA等。不同算法的故障覆盖率和测试时间不同,选择的时候需要权衡。
| 算法 | 故障覆盖率 | 测试周期 | 适用场景 |
|---|---|---|---|
| March C- | 中等 | 较短 | 一般SRAM |
| March SS | 高 | 中等 | 高可靠性SRAM |
| March SSA | 高 | 较长 | 安全关键SRAM |
| Checkerboard | 低 | 短 | 快速筛查 |
配置算法的时候,在mbist_config.cfg里指定:
set_mbist_algorithm -controller MBIST_CTRL_TOP -algorithm March_SSA set_mbist_data_background -controller MBIST_CTRL_TOP -values {0x00 0xFF 0xAA 0x55}数据背景的选择也有讲究。0x00和0xFF用于检测stuck-at故障,0xAA和0x55用于检测相邻位之间的耦合故障。如果memory的data width是32位,工具会自动把这些pattern扩展到32位。
实操心得:数据背景不是越多越好。每增加一个背景,测试时间就线性增加。我一般先用
0x00和0xFF跑一轮,看覆盖率报告,如果覆盖率达标就不加更多背景了。
4.3 Pattern生成与仿真验证
BIST逻辑插入完成之后,下一步是生成测试pattern并跑仿真。Tessent可以生成Verilog testbench和pattern文件,我一般用VCS或者Xcelium跑仿真。
# 生成pattern和testbench set_context patterns -mbist create_patterns -output ./work/simulation/mbist_patterns write_testbench -output ./work/simulation/tb_mbist.v -pattern ./work/simulation/mbist_patterns仿真脚本我习惯单独写一个run_simulation.tcl,因为仿真环境跟DFT插入环境是分开的:
# run_simulation.tcl set DESIGN ./work/dft_insert/top_mbist.v set TESTBENCH ./work/simulation/tb_mbist.v set PATTERN ./work/simulation/mbist_patterns # 编译 analyze -format verilog $DESIGN analyze -format verilog $TESTBENCH elaborate top_mbist # 加载pattern并仿真 read_pattern $PATTERN run 100000仿真跑完之后,重点看两个东西:一是pattern有没有全部pass,二是BIST controller的status信号有没有正确拉高。如果pattern fail了,先检查memory model的timing是否跟实际一致,再检查BIST controller的clock和reset有没有正确连接。
4.4 覆盖率分析与报告解读
Tessent会生成详细的覆盖率报告,包括fault coverage、test coverage和fault efficiency。我一般重点关注fault coverage,因为它直接反映了测试pattern能检测到多少故障。
# 生成覆盖率报告 report_fault_coverage -summary > ./reports/dft/fault_coverage.rpt report_test_coverage -summary > ./reports/dft/test_coverage.rpt打开报告之后,如果fault coverage低于预期,通常有几个原因:memory model里的fault list不完整、BIST算法选择不当、或者某些memory实例没有被BIST覆盖到。我遇到过一次覆盖率只有85%的情况,排查后发现是一个小容量SRAM被工具自动排除了,因为它的深度小于BIST controller支持的最小深度。解决办法是在配置里强制指定:
set_mbist_controller -controller MBIST_CTRL_TOP -min_depth 165. 常见问题与排查技巧实录
5.1 Memory Model加载失败
这是最常见的问题,表现是read_cell_library报错或者analyze_mbist识别不到memory。排查步骤:
- 确认Liberty文件语法正确,可以用
lc_shell或者read_liberty命令单独验证 - 检查Liberty文件里是否有
memory属性,没有的话需要手动添加 - 确认memory的pin定义跟设计中的实例端口能对上
我整理了一个速查表:
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| read_cell_library报语法错误 | Liberty文件格式问题 | 用liberty parser检查 |
| analyze_mbist识别不到memory | model缺少memory属性 | 手动添加memory属性 |
| model匹配失败 | 实例名与cell名不一致 | 添加memory mapping |
| BIST插入后DRC报错 | clock/reset未正确连接 | 检查set_dft_signal配置 |
5.2 BIST Controller插入后DRC违例
DRC违例是另一个高频问题。Tessent在插入BIST逻辑后会跑一遍DRC检查,常见的违例包括clock domain crossing、reset同步问题、test mode信号冲突等。
我遇到最多的是clock domain问题。如果memory的clock和BIST controller的clock不是同一个时钟域,工具会报CDC违例。解决办法有两种:一是让BIST controller使用跟memory相同的clock,二是在CDC路径上插入同步逻辑。我一般优先选第一种,因为实现简单、面积开销小。
注意:DRC违例不要强行waive,除非你完全理解违例的原因和影响。我见过有人为了赶进度把CDC违例全部waive掉,结果芯片回来之后memory测试不稳定,排查了两个月才发现是CDC问题。
5.3 仿真Pattern Fail的排查思路
仿真fail的排查是最耗时的,因为可能的原因太多。我一般按照以下顺序排查:
- 先看fail的pattern编号,定位到具体的memory和操作
- 检查memory model的timing是否跟仿真环境一致
- 检查BIST controller的clock频率是否在memory支持范围内
- 检查reset释放时机是否正确
- 如果以上都没问题,用波形工具抓BIST controller的内部信号,看状态机卡在哪一步
我踩过的一个坑是:memory model里的setup/hold时间跟实际仿真环境不一致,导致pattern在仿真里fail但实际芯片没问题。后来统一用foundry提供的timing model,问题就消失了。
5.4 多项目脚本复用的经验
当你手上有多个项目的时候,脚本复用就变得很重要。我的做法是把所有项目相关的参数抽到独立的.cfg文件,主脚本框架保持不变。每个项目一个目录,目录里放自己的config/和models/,scripts/目录用软链接指向公共脚本库。
# 项目目录结构 project_a/ ├── config/ │ └── mbist_config.cfg ├── models/ │ └── sram_models/ └── scripts -> ../common_scripts/这样维护的好处是,当公共脚本有更新的时候,所有项目自动生效。但要注意版本控制,我一般会在公共脚本库里打tag,项目目录里记录使用的tag版本,避免脚本更新导致老项目跑不通。
6. 脚本调试与性能优化技巧
6.1 Tcl脚本的调试方法
Tcl脚本调试不像Python那么方便,没有pdb那样的交互式调试器。我常用的方法是在关键位置插入puts语句,输出变量值和执行状态。另外Tessent支持-verbose选项,打开之后会输出详细的执行日志。
# 调试示例 puts "DEBUG: Current design = [get_current_design]" puts "DEBUG: Memory count = [llength [get_memory_instances]]" puts "DEBUG: Controller = $controller_name"如果脚本比较复杂,我会用Tcl的catch命令捕获错误,然后输出错误信息和堆栈:
if {[catch {insert_mbist -controller_name $ctrl} err]} { puts "ERROR: insert_mbist failed" puts "Error message: $err" puts "Error info: $::errorInfo" return -code error }6.2 大型设计的运行时间优化
当设计规模比较大的时候,MBIST插入和pattern生成可能跑几个小时。我总结了几条优化经验:
- 用
-parallel选项开启多线程,Tessent支持多核并行处理 - 把memory分组,每组单独跑,最后合并结果
- 减少不必要的data background,只保留覆盖率贡献最大的几个
- 用
-incremental模式,只重新处理修改过的部分
# 开启并行处理 set_parallel_options -threads 8 # 增量模式 set_incremental_options -enable true -checkpoint ./work/checkpoint6.3 报告自动化与结果归档
每次跑完MBIST流程,我都会自动生成一份汇总报告,包括memory列表、controller配置、覆盖率数据和运行时间。这样项目交接或者review的时候,直接看报告就够了。
# 生成汇总报告 set rpt [open ./reports/dft/summary.rpt w] puts $rpt "MBIST Run Summary" puts $rpt "Date: [clock format [clock seconds]]" puts $rpt "Design: [get_current_design]" puts $rpt "Memory count: [llength [get_memory_instances]]" puts $rpt "Fault coverage: [get_fault_coverage]" close $rpt报告归档我习惯按日期建目录,每次运行的结果单独存放,方便回溯。如果项目周期长,还可以用git管理配置文件和脚本,每次修改都有记录。
7. 从参考脚本到项目落地
7.1 脚本框架的定制化调整
参考脚本拿到手之后,不能直接照搬,需要根据项目实际情况调整。我一般会先跑一个最小化的demo,用一个简单的memory model验证流程能跑通,然后再逐步加入项目里的真实memory。
调整的重点包括:memory model的适配、BIST controller的命名规范、clock/reset信号的映射、输出文件的路径和格式。这些调整都集中在配置文件和少量脚本参数里,不需要改动主流程逻辑。
7.2 与后端流程的衔接
MBIST插入完成之后,输出的网表需要交给后端做物理实现。这里有几个衔接点需要注意:BIST controller的物理位置、test mode信号的布线、memory和controller之间的连线长度。我一般会在脚本里输出一份constraint文件,给后端参考:
# 输出后端约束 write_constraints -output ./work/dft_insert/mbist_constraints.sdc这份约束文件里包含了BIST相关的clock定义、false path和multicycle path,后端读进去之后可以避免时序违例。
7.3 团队协作中的脚本管理
如果是团队协作,脚本管理就很重要。我的做法是:公共脚本库用git管理,每个项目一个branch;配置文件跟项目代码一起管理;运行结果和报告用共享目录存放。另外,脚本里所有硬编码的路径都抽成变量,放在env_setup.sh里统一管理,这样不同人的机器上都能跑。
我还会在脚本库里放一份README,说明每个脚本的作用、依赖关系和运行方法。新人拿到之后,照着README跑一遍就能上手,不需要每次都来问我。
7.4 持续维护与版本更新
Tessent的版本更新比较频繁,每次升级都可能影响脚本的兼容性。我一般会在升级之前,先用一个小设计跑一遍回归测试,确认脚本在新版本上能正常工作。如果发现问题,先查Release Note,看是不是命令语法变了或者默认行为改了。
另外,foundry的memory model也会更新,新版本可能修复了timing问题或者增加了新的fault model。每次更新model之后,需要重新跑一遍MBIST流程,确认覆盖率和仿真结果没有退化。
我个人在实际操作中的体会是,MBIST脚本框架的搭建不是一蹴而就的,而是在项目中不断迭代出来的。第一版可能只能跑通基本流程,第二版加入了错误处理和报告自动化,第三版才做到多项目复用。关键是先把流程跑通,再逐步优化,不要一开始就追求完美。踩过的坑越多,脚本就越健壮。