简介:这份文档面向数字IC设计与测试验证方向的学习者,尤其是刚接触存储器内建自测试的工程师和学生,用于建立MBIST的完整知识框架。内容围绕Mentor MBIST工具展开,从内嵌memory在芯片面积与失效率中的影响切入,讲解memory的类型划分、MBIST的TPG/CUT/RA/DM基本结构、适用场景与优缺点、可检测的故障类型,并延伸到LFSR、PRNG等测试算法以及Mbistarchitect生成输入输出的方法;附录部分还涉及ROM/SRAM的lib形式、ROM content file格式和BIST脚本实现,覆盖从原理认知到工具实操的链路。资源包共1个docx文件,约319KB,篇幅紧凑,适合作为入门梳理与查阅提纲。目前已有1448人学习,可作为MBIST学习的起步材料,配合工具实践逐步消化。
1. Memory BIST 全景地图:一块占四成面积的电路,值得单独建一套测试体系
MBIST(存储器内建自测试)本质上是把一小台测试机搬进芯片里:片上产生激励、片上比较响应,最后只把一根 pass/fail 信号线送到管脚。它属于 DFT 中最偏工程实践的一部分——算法是教科书上的 March C+,模型是几十行事件描述,但真正让人出错的是时序少写一个 wait、脚本重跑时用的还是上一版网表。整条链路可以拆成五段:判断要不要做、分清被测 memory、选故障模型与算法、把 memory 写成可被工具读懂的模型、用脚本把生成流程固化下来。这篇梳理适合两类人:正在系统学 DFT 的,拿来当一张地图;准备复习或面试的,直接奔重点和易错点去。
2. 被测对象与测试结构:先把 Memory 分清,再谈 BIST 怎么搭
同一套 March 算法,对 SRAM 效果很好,对 DRAM 就未必管用——因为它们的访问协议根本不是一个形状。所以顺序不能颠倒:先搞清楚片内嵌的是什么 memory、有几个端口、同步还是异步,再决定 BIST 电路怎么挂、算法怎么绑。
2.1 Memory 的四个划分维度
内嵌 memory 之所以常见,是因为它能解决芯片端口数量的问题,也能在整体方案上压低物料成本;代价是面积变大,同时多了一块失效率风险区。一个直观的估算:逻辑部分(数字加模拟)占掉六成面积,剩下四成全被 memory 占着。工艺环境带进来的灰尘、生产阶段的种种缺陷,落在这四成面积上的概率一点不低,这就是 memory 必须被单独测一遍的根本理由。
| 划分维度 | 常见取值 | 对 MBIST 的影响 |
|---|---|---|
| 容量形状 | 深度大 / 位宽大 | 决定地址位宽、数据位宽,直接进模型字段 |
| 时钟 | 同步 / 异步 | 同步 memory 时序固定,最适合固定 BIST;异步的要额外约束 |
| 端口 | 单端口 / 多端口 | 多端口可以并行施加激励,测试时间能摊薄 |
| 器件类型 | SRAM / DRAM / ROM / EPROM / EEPROM | SRAM、ROM 访问方式单一,适合固定 MBIST;DRAM 要刷新,EEPROM 要擦写时序,路子完全不同 |
梳理下来会发现,能进标准 MBIST 流程的其实是一个很窄的交集:SRAM 和 ROM 时序固定、访问方式单一,用一套固定的 BIST 结构就能覆盖;DRAM 有刷新周期,Flash 类要按块擦写,用同一套 March 去测它们基本是白费力气。这个判断很重要,它决定了后面所有工作的边界。
2.2 MBIST 的三段式结构:TPG、CUT、RA 与外围的一圈 MUX
标准结构是在 memory 周围加一圈 BIST 逻辑,这圈逻辑和系统控制部分用 MUX 二选一:正常工作时走系统通路,测试时切到 BIST 通路。BIST 内部靠一个有限状态机推进算法步骤,对外只暴露少量测试控制端口(启动、时钟、模式、结果)。
拆开看是四块:TPG 负责按算法产生地址、数据和控制信号的序列;CUT 就是被测的那块 memory;RA 把读回的数据和期望值比对,压缩成 pass/fail;诊断模块在出错时把失败的地址、数据、周期号记下来,供后续定位。四块里前三块决定“能不能测出来”,第四块决定“测出来之后能不能修”,很多设计只做了前三块,出事时就只剩一根 fail 线,只能干瞪眼。
MUX 这圈逻辑不是免费的。它在 memory 的数据路径上多了一级选择器,正常功能路径的延迟会因此变长,同时多出一小块面积。这两项代价必须在设计早期就算清楚,别等到时序收敛阶段才发现关键路径上多了一级 MUX,那时候改结构就是伤筋动骨。
2.3 什么时候该上 MBIST:四条判据与实速测试
判断标准其实相当实用,命中任意一条就值得考虑:
- 中大型嵌入式 memory 的系统,memory 面积占比高,缺陷概率不可忽略;
- IP 类型的 memory,内部时序对外不可见,只能靠内建逻辑去测;
- 需要快速测试的 memory,测试时间直接影响量产成本;
- 片内有多个嵌入 memory 的芯片,共享控制器能把开销摊薄。
其中“快速”这个词容易被误解。它指的是实速测试:memory 的目标应用跑在 100MHz,就必须保证它在 100MHz 下没问题,而不是降到 10MHz 慢慢测。降频测通过、实速下失效,是典型的时序缺陷,低频测试根本抓不到。所以 BIST 时钟要能切到功能时钟,这条在约束阶段就得留好。
| 维度 | MBIST 方案 | 外部测试机直测 |
|---|---|---|
| 测试速度 | 可实速,接近功能频率 | 受管脚带宽限制,通常降频 |
| 面积代价 | 增加少量面积与一圈 MUX | 片内几乎无额外面积 |
| 路径影响 | 数据路径多一级 MUX 延迟 | 无 |
| 可复用性 | 量产、老化、上电自检都能复用 | 依赖设备,片内不可复用 |
3. 故障模型与 March 算法:四类错误怎么被“打”出来
算法不是背下来的,是推出来的。每一种 March 元素都对应一类物理缺陷,知道缺陷长什么样,才知道为什么要先写再立刻读、为什么要正着扫一遍再倒着扫一遍。
3.1 四类典型故障与对应的施加手段
| 故障类型 | 表现 | 施加手段 | 关键点 |
|---|---|---|---|
| 短路错误 | 单元固定为 0 或固定为 1 | 逐单元写 0 再逐个读回;写 1 再逐个读回 | 两个方向都要做,只测一种极性会漏掉固定 1 |
| 转换错误 | 无法 1→0 或无法 0→1 | 写反值后立即读回 | 必须立刻读,慢了单元可能自己转换回来 |
| 耦合错误 | 一个单元的状态影响另一个单元 | 写/读扫描全部单元 | 需要遍历,局部抽样测不出来 |
| 邻居错误 | 某单元影响相邻单元,或相邻单元都影响它 | 需要足够强的 pattern | 最复杂,复杂度高时可以按风险取舍 |
转换错误那条“立即读出”是最容易写错的地方。单元的转换需要时间,如果写完隔了很多个周期再读,出问题的单元可能已经恢复,测试就漏过去了。所以 March 元素里读紧跟写,中间不插多余的空拍,这个约束在模型里体现为 wait 的个数,写多一个就前功尽弃。
耦合错误和邻居错误的区别也值得停一下:耦合是任意两个单元之间的影响,扫描一遍所有单元就能大概率覆盖;邻居是空间局部性影响,需要专门设计地址遍历顺序和背景数据(棋盘、行列条带这类),单靠一条线性 March 覆盖不全。工程上的常见做法是:耦合必测,邻居按 memory 的物理布局风险决定要不要加码。
3.2 March 家族的演进与各自擅长
| 算法 | 大致规模 | 擅长覆盖 | 代价 |
|---|---|---|---|
| March A / March B | 较小 | 部分链接类错误 | 覆盖率有限,现已少用 |
| March C | 中等 | 固定故障、转换故障、多数耦合 | 步数偏多 |
| March C- | 10N 级别 | 与 C 接近,去掉部分冗余元素 | 少数链接错误覆盖不到 |
| March C+ | 比 C- 多若干元素 | 在 C- 基础上补链接错误 | 步数增加,测试时间变长 |
| March 3 | 较大 | 更复杂的耦合与地址译码故障 | 时间代价高 |
| Column March | 按列组织 | 位线方向的耦合、列固定故障 | 依赖物理版图信息 |
| 地址唯一棋盘法 | 与容量相关 | 相邻单元耦合、地址译码故障 | 需要特定的数据背景 |
选型的逻辑不复杂:容量小、时间宽裕,就上 March C+ 或 March 3 把覆盖率拉满;容量大、实速测试时间吃紧,就在 March C- 和 Column March 之间做组合。真正需要人判断的是“这块 memory 的物理版图里,相邻关系是行方向更紧还是列方向更紧”,答案不同,遍历方向和背景数据就不同。
3.3 March C+ 在 1Mbit RAM 上的分解
把 1Mbit 的 RAM 拿出来走一遍 March C+,序列大致是这样几段:
| 段 | 动作 | 目的 |
|---|---|---|
| 1 | 全空间写 0(初始化) | 建立已知背景 |
| 2 | 正序读 0、写 1,再正序读 1 | 抓固定 0、0→1 转换失败 |
| 3 | 正序读 1、写 0,再正序读 0 | 抓固定 1、1→0 转换失败 |
| 4 | 倒序读 0、写 1,再倒序读 1 | 换个方向再扫,抓方向相关的耦合 |
| 5 | 倒序读 1、写 0,再倒序读 0 | 同上,补另一极性 |
| 6 | 正序读 0 | 收尾校验,确认最终状态 |
数下来是 6 段、12 次读写动作,不同资料会数成 14 步——差别在于初始化算几步、读写合并算一步还是两步。这里的经验是:文档里的步数和自己的实现对不上时,先对计数口径,别急着怀疑算法写错。整条序列跑在实速时钟上,就是常说的实速测试。
3.4 算法取舍:覆盖率、测试时间与实现代价
测试时间是能被算出来的,算完再决定要不要加算法,比凭感觉砍步骤靠谱。
def mbist_time(words, steps, freq_mhz, ports=1): """估算一条 March 算法跑完一遍的时间。 words : 存储字数(不是 bit 数),如 512x32 的 RAM 是 512 steps : 算法步数,以 N 为单位,March C+ 取 14 左右 freq_mhz : 实速测试时钟,单位 MHz ports : 端口数,多端口可并行分摊激励 """ cycles = words * steps / ports # 总测试周期数 return cycles / (freq_mhz * 1e6) # 折算成秒 print(round(mbist_time(32768, 14, 100) * 1000, 2)) # 32K 字、100MHz,约 4.59 ms print(round(mbist_time(32768, 14, 50) * 1000, 2)) # 时钟减半,时间翻倍几个参数怎么调:words一定填字数,填成 bit 数会把估算结果放大几十倍,这是最常见的翻车点;steps用算法本身的名义步数,March C- 取 10 左右、March C+ 取 14 左右;freq_mhz填实速时钟而不是 BIST 的慢速调试时钟,两者差一个量级;ports只在多端口同时施加激励时才大于 1,单端口填 1。算出来的量级如果已经逼近可接受的测试时间上限,就该考虑砍算法或者拆块并行,而不是硬扛。
4. 从模型到网表:ATPG 语法与脚本化生成流程
工具不认“这块 RAM 是 512x32 同步单端口”这种话,它只认模型文件里的事件序列。所以这一步的分水岭很清楚:模型写得准,后面全顺;模型写歪,跑出来的网表看起来正常,实际测的是别的东西。
4.1 bist_definition 的字段结构
一个 memory 模型的核心是bist_definition块,里面把端口、位宽、地址范围、读写周期全部声明清楚。
model spram_512x32 (Q, CLK, CEN, WEN, A, D, OEN) ( bist_definition ( address A(array=8:0;); // 地址 9 位,对应 512 深度 data_in D(array=31:0;); // 写数据 32 位 data_out Q(array=31:0;); // 读数据 32 位 clock CLK high; // 时钟上升沿有效 chip_enable CEN low; // 片选低有效 write_enable WEN low; // 写使能低有效 dont_touch OEN high; // 输出使能,BIST 期间保持不变 tech = fab18; // 工艺标识,仅作记录 vendor = memlib; // 库来源标识 version = "1.0"; message = "synchronous 512x32 RAM"; address_size = 9; min_address = 0; max_address = 511; // 必须等于 2^address_size - 1 data_size = 32; read_write_port ( write_cycle ( change A; change D; assert CEN; assert WEN; wait; ) read_cycle ( change A; assert CEN; wait; wait; expect Q move; ) ) ) )| 字段 | 含义 | 写错的后果 |
|---|---|---|
address/data_in/data_out | 地址与数据的位宽声明 | 位宽不匹配,生成的网表连不上 memory |
clock ... high | 采样沿 | 沿搞反,整个测试在错误时刻采样 |
chip_enable ... low | 控制信号的有效电平 | 极性写反,memory 永远不被选中 |
address_size/max_address | 容量边界 | 越界访问,工具报错或静默截断 |
read_write_port | 读写周期描述 | 时序不对,读回的数据和期望对不上 |
有一条自检习惯很值:max_address必须等于 2 的address_size次方减一。这两个值不一致,说明容量和地址位宽对不上,工具未必报错,但生成的地址扫描范围会少一截,测试覆盖率悄悄缩水。
4.2 change、assert、expect、wait 四类事件
描述语法是基于事件的,一句话一个动作。理解这四类事件,等于理解了整份模型的时序语义。
change:指定输入信号(地址或数据总线)换到新值,效果类似被寄存器打了一拍输出。assert:控制信号进入有效状态,有效电平由bist_definition里的 high/low 决定;被 assert 的信号在下一次wait后自动回到无效状态,想保持就得再 assert 一次。expect:声明输出信号会因前面的事件而变,作用是让比较器在这个时刻对 memory 输出打 strobe。wait:插入一个测试时钟周期,所有写在wait之前的事件都在同一拍内完成。
// 写周期:地址和数据先就位,控制信号再拉有效,最后一拍采样 write_cycle ( change A; // 地址换新值 change D; // 写数据同时就位 assert CEN; // 片选有效,低有效由 chip_enable 声明决定 assert WEN; // 写使能有效 wait; // memory 在这一拍采到地址、数据、控制 ) // 读周期:两个 wait 表示读延迟为 2 拍,第二个 wait 之后才 expect read_cycle ( change A; assert CEN; wait; // 第一拍:memory 内部取数 wait; // 第二拍:数据到达输出 expect Q move; // 此时比较器采样 )读周期里wait的个数不是随便写的,它必须等于 memory 的实际读延迟。少写一个,比较器会在数据还没出来的时候采样,好芯片被判成坏;多写一个,采样点落后于数据有效期,同样可能误判。这一段是整份模型里最容易出错、也最难靠肉眼发现的地方。
assert的自动回落特性也值得单独记一笔。写成assert CEN; wait;是一个完整的单拍片选;想连续两拍有效,必须写成assert CEN; wait; assert CEN; wait;。如果只写一次 assert 就等两个 wait,第二拍片选已经掉了,memory 根本没在干活——但模型语法上完全合法,工具不会报错。
4.3 ROM 模型与 content file 的写法
ROM 的模型比 SRAM 简单:没有写数据、没有写使能,只有读通路。相应地,它的“期望值”不来自算法,而来自一份内容文件。
model rom_2k (Q, CLK, CEN, A) ( bist_definition ( address A(array=10:0;); // 11 位地址,2048 深度 data_out Q(array=31:0;); clock CLK high; chip_enable CEN low; tech = fab18; vendor = memlib; version = "1.0"; message = "synchronous 2048x32 ROM"; address_size = 11; min_address = 0; max_address = 2047; data_size = 32; read_port ( // 只有读端口,没有 write_cycle read_cycle ( change A; assert CEN; wait; wait; expect Q move; ) ) ) )内容文件描述每个地址上应该读到什么,逐行写地址、斜杠、数据,支持区间合并:
0 / ffffffff; 1 / ffffffff; 2 / ffffffff; 3 / ffffffff; 4 / ffffffff; 5 / ffffffff; 6 / ffffffff; 7 / ffffffff; 8 / ffffffff; 9 / ffffffff; a / ffffffff; b / ffffffff; c / ffffffff; d / ffffffff; e / ffffffff; f / ffffffff; 10-7ff / 00000000;格式上有两点要盯住:地址和数据都是十六进制,数据位宽要和data_size对齐,32 位就是 8 个十六进制字符,少写一位整段数据全错位;区间写法起始-结束的左端和右端都要真实存在,边界少一个地址就会出现一条无法解释的 fail。ROM 的 BIST 本质上是“把所有内容读一遍、逐字比对”,所以内容文件错了,功能完全正常的芯片也会被大批判坏,这类问题在量产时最贵。
4.4 从 dofile 到 Makefile:把生成流程固化
手工敲命令能跑通一次,但跑不了第二十次。稳妥的做法是把整个过程写进批处理脚本,再用 Makefile 管起来。
# 批处理脚本的核心步骤:建目录、加载模型、绑定算法、生成、保存 mkdir -p ./sram8k_bist/{rtl,script,log} # 输出分目录,便于版本管理 mbist_gen -nogui -replace \ -dofile ./sram8k_bist.do \ -logfile ./sram8k_bist/log/sram8k_bist.log # -nogui 无图形界面,-replace 覆盖旧输出.do文件里按顺序写清楚要做什么:load lib加载模型文件,add memory models绑定具体 memory,setup mbist algorithms指定每条端口的算法,setup observation scheme选择观测方案(是否压缩、是否用局部比较器),set design name和set file naming决定输出的 HDL、脚本、CTDL、WGL 文件名,最后run加save bist。文件命名这一段千万别省,输出默认落在当前目录,多块 memory 一起跑就会互相覆盖。
# Makefile:把每块 memory 的生成过程做成独立目标 RBIST_DIR := rom_bist SBIST_DIR := sram8k_bist rbist: mkdir -p $(RBIST_DIR)/{rtl,script,log} mbist_gen -nogui -replace -dofile rom_bist.do -logfile $(RBIST_DIR)/log/rom_bist.log sram8k: mkdir -p $(SBIST_DIR)/{rtl,script,log} mbist_gen -nogui -replace -dofile sram8k_bist.do -logfile $(SBIST_DIR)/log/sram8k_bist.log clean: rm -rf $(RBIST_DIR) $(SBIST_DIR) # 清理旧产物,避免签核用到过期网表这套结构的价值在于可复现:改了模型,make clean && make sram8k一条命令重出全部产物,日志按 memory 分文件落盘,出问题直接翻对应日志。-replace要留着,它保证输出被覆盖而不是叠加;但正因为会覆盖,clean目标必须写,两者配合才不会出现“新网表混着旧文件”的局面。
5. 易漏点:四个让“跑通了”变成“白测了”的坑
流程能跑出三个 HDL 文件,只说明语法对了,不说明测试有效。下面这四处是梳理过程中反复冒出来的,按“踩中概率 × 后果严重度”排。
5.1 时序坑:read_cycle 的 wait 数与实际读延迟不符
易漏点:模型里读周期写了两个wait,实际 memory 的读延迟是一个或三个周期。
为什么漏:这个错误不会报错。工具照样生成网表、照样能仿真跑完,只是比较器的采样时刻偏了一拍。偏晚时数据还在总线上,测试看起来全过;偏早时读到上一拍的残留值,测试大面积报 fail,人第一反应是“memory 有问题”,回头查算法、查内容,就是想不到查模型时序。
怎么补:拿到 memory 的时序说明后,先把读延迟、写延迟、输出有效窗口三个数字抄在模型文件顶部当注释,再逐个对着wait数。做完模型先跑一遍只含读操作的短序列,在波形上确认expect打点落在数据稳定的中间位置,而不是边沿。这一步花十分钟,能省掉后面几天的排查。
5.2 诊断坑:只留了一根 pass/fail,出错后无法定位
易漏点:观测方案只开了压缩输出,没有开诊断,也没有保留失败地址、失败周期号。
为什么漏:生成 BIST 时默认追求面积小、管脚少,压缩比较器是最省事的选项,一路回车就过去了。功能验证阶段全都 pass,完全看不出问题,直到量产出现零星失效,才发现手上只有“这块 memory 坏了”这一条信息。
怎么补:生成时显式打开诊断相关选项,保留失败地址寄存器;同时把 BIST 运行日志按 memory 分开落盘。面积允许的话,压缩和诊断可以分模式:量产模式走压缩,调试模式切到带地址记录的诊断通路。这一步在流片前加进去几乎不花代价,流片后想加就没有机会了。
5.3 多 memory 坑:控制器共享后,端口分组与 mux 延迟失控
易漏点:片内有若干块 memory,为省面积共用一个 BIST 控制器,但没有按端口和算法分组,也没有在插入 MUX 后重跑时序检查。
为什么漏:单块 memory 跑通之后,人容易默认“多块就是复制粘贴”。实际上共享控制器要解决三件事:哪些 memory 能并行测、每条端口绑什么算法、BIST 时钟怎么切。任何一件没定清楚,都会出现某块 memory 只测了一半或者根本没被选中的情况。而 MUX 引入的数据路径延迟,属于功能路径的问题,BIST 自己的仿真完全看不出来。
怎么补:先在纸上画出 memory 分组表,一行一块,列清楚容量、端口数、算法、是否并行,再照表配控制器;插入 MUX 之后,务必用功能模式重跑一遍时序检查,重点看 memory 到下游逻辑的那条路径。别在 BIST 的仿真里找这个答案,它不在那儿。
5.4 内容坑:ROM 的 content file 与真实内容不一致
易漏点:ROM 模型加载了内容文件,但文件是早期版本,或者地址区间、数据位宽与最终烧录内容对不上。
为什么漏:ROM 的 BIST 不产生激励模式,它只做“读出来比对”,所以模型看起来特别简单,容易被当成最没风险的一环而放到最后处理。而 content file 通常是另一个团队或另一条流程产出的,版本对齐这件事没人主动盯。
怎么补:把 content file 纳入版本管理,和 ROM 的最终内容用同一个版本号命名;加载前做两条机器校验——数据位宽与data_size一致、区间总长度覆盖min_address到max_address的全部地址。校验写成脚本挂在生成流程前面,跑不过就不允许生成。这属于典型的后悔药,提前吃比事后追便宜得多。
6. 复习清单:把 MBIST 从“能跑”练到“能讲”
复习这一块,光看流程图没用,得能对着空白纸把下面这张表填出来。填不出来的格子,就是下一轮要补的地方。
| 模块 | 自检问题 | 卡住说明什么 |
|---|---|---|
| 必要性 | 内嵌 memory 带来的面积与失效率代价怎么权衡 | 没建立成本意识,只记住了结论 |
| 分类 | 哪些 memory 适合固定 MBIST,为什么 | 没抓住“时序固定、访问单一”这个判据 |
| 结构 | TPG / CUT / RA / 诊断模块各解决什么问题 | 把结构和算法混为一谈了 |
| 场景 | 实速测试为什么不能用低频替代 | 没理解时序缺陷的触发条件 |
| 故障 | 转换错误为什么必须写完立刻读 | 只背了名词,没理解物理机制 |
| 算法 | March C- 与 C+ 差在哪,代价是什么 | 只会背名字,不会做取舍 |
| 建模 | 四类事件各自的时序语义 | 模型是抄的,不是推的 |
| 流程 | 从模型到三个 HDL 文件,中间经历了什么 | 只会敲命令,不知道每步在干什么 |
| 诊断 | 出错后怎么定位到具体地址 | 没考虑过“测出来之后” |
查漏补缺的具体做法,我用下来最有效的是“反向重建”:拿一块已经跑通的 memory,把模型文件里的read_cycle整段删掉,凭记忆重写一遍,再和原文件逐字比对。差异处就是理解薄弱的地方,比通读十遍文档都准。第二步是口述:找个人(或者对着录音)用三分钟讲清楚“这块 memory 为什么这么测”,讲不顺的地方立刻回去补。
还有一个容易忽略的练习:把测试时间算一遍。给一个 32K 字、100MHz、March C+ 的配置,手算出毫秒级的结果,再用脚本验证。算得出来,说明你对容量、步数、频率三者的关系是真的有数;算不出来,说明算法那章还停在背诵层面。
从那以后我每次建 memory 模型,都强制先把读延迟、写延迟、地址边界这三个数写在文件顶上,再动笔写wait,最后跑一段只读短序列在波形上确认采样点。这个习惯救过我两次,两次都是差一个wait的活儿。希望帮到你。
本文还有配套的精品资源,点击获取