简介:这份PPT资料围绕内存的故障模型与测试算法设计展开,面向计算机体系结构、集成电路测试及嵌入式存储方向的学习者与工程人员,帮助理解内存工作模式、静态与动态故障分类,以及March系列等经典RAM测试算法的设计思路。压缩包内共1个PPT文件,约238KB,以幻灯片形式系统梳理了离线RAM测试、功能故障模型、耦合与桥接故障、故障覆盖率分析及RAMFLT评估方法等核心内容,并涉及BIST设计与嵌入式内存测试场景。目前已有335人学习下载。资料从功能单元阵列模型出发,讲解粘滞、过渡、数据保留等故障的敏化与检测机制,并对比确定性测试与伪随机测试的覆盖度量,适合作为内存测试入门与算法选型时的参考框架,也可用于理解测试时间复杂性与故障覆盖率之间的权衡关系。
1. 为什么 256 位小内存的故障覆盖率,能决定一颗 SoC 的测试成败
很多人第一次接触内存测试,会觉得这是后端测试工程师的事:跑个 March C-,覆盖率报表好看就行。但真正做过嵌入式存储 BIST 的人都知道,问题往往出在“覆盖率数字是怎么算出来的”。同一颗 256 位、16 行 × 16 列的存储阵列,March X 对三单元耦合故障的覆盖率可能只有 25%,而 GALPAT 能到 48% 以上;对 Type I 邻域敏感故障,March C- 只有 12.5%,GALPAT 却能到 40.6%。这些差距不是算法“好坏”的直觉判断,而是由故障模型定义、敏化/去敏化规则和仿真方法共同决定的。
RAMFLT 这套方法论的价值就在这里:它不把故障模型硬编码进仿真器,而是把故障模型当作输入,用统一的敏化、去敏化、检测三态来描述任意测试序列。这样你既能评估已有的确定性 March 算法,也能评估伪随机 BIST 方案,还能给多端口、FIFO 这类特殊存储结构做覆盖率排序。适合谁读?做 DFT/BIST 的验证工程师、写内存测试算法的同学,以及需要向团队解释“为什么换算法”的人。
2. 功能单元阵列模型与故障行为的形式化
2.1 位寻址二维阵列与三值状态
RAM 的功能模型可以抽象成一个位寻址的二维阵列,每个存储单元 m(i,j) 保存一个二进制值。但在故障仿真里,单元状态不是简单的 {0,1},而是 {0, 1, 0/1, x}:0 和 1 是正确值,0/1 表示故障导致值不确定,x 表示未知。读、写 0、写 1 是三种基本操作。这个三值集合是后面所有敏化和检测判断的基础。
提示:把 x 单独拿出来,是为了处理多故障同时存在时的掩蔽效应。如果只用二值逻辑,很多耦合故障的传播路径会被错误地判定为已检测。
2.2 敏化、去敏化与检测的判定规则
一次写操作可能让某个故障进入敏化态,也可能把它打回去敏化态;一次读操作则把当前敏化的故障标记为已覆盖。形式化地说:
- case write:判断哪些故障被敏化或去敏化;
- case read:把所有处于敏化态的故障归类为 covered。
这个规则看起来简单,但它允许仿真器处理任意测试序列,而不要求测试必须是规则 March 结构。下面这段伪代码展示了单次写操作的处理逻辑:
# 单次写操作对故障集合的影响 def apply_write(faults, addr, value, mem): for f in faults: if f.is_sensitized(mem, addr, value): f.state = 'sensitized' # 本次写操作敏化该故障 elif f.is_desensitized(mem, addr, value): f.state = 'desensitized' # 本次写操作去敏化该故障 mem[addr] = value # 更新功能模型中的存储值 # 单次读操作:把敏化态故障标记为已覆盖 def apply_read(faults, addr, mem): for f in faults: if f.state == 'sensitized': f.covered = True return mem[addr]is_sensitized和is_desensitized由具体故障模型提供,仿真器本身不关心故障类型。mem是功能阵列的当前值,写操作先判断故障再更新存储值,顺序不能反,否则敏化判断会用到错误的内存状态。
2.3 故障模型的输入式规格
故障模型不是写死在代码里的,而是用“写操作 + 内存模式”的序列来描述。格式是:
< write op. > < mem. pattern > < write op. > < mem. pattern > ...以两单元 OR 型桥接故障为例,敏化和去敏化序列可以写成:
| 阶段 | 操作 | 内存模式 | 作用 |
|---|---|---|---|
| 敏化 | write-1, a | a=0, b=0 | 建立初始态 |
| 敏化 | write-1, b | a=1, b=0 | 触发 OR(a,b) 错误 |
| 检测 | read a 或 read b | — | 返回 OR(a,b) |
| 去敏化 | write-0, a | — | 清除敏化态 |
| 去敏化 | write-0, b | — | 清除敏化态 |
这种规格化描述让新增故障模型只需要写配置,不用改仿真内核。常见做法是把 25 种以上功能故障模型都做成这样的输入文件,覆盖单单元、耦合、桥接、邻域敏感四大类。
3. 从静态故障到动态故障:模型分类与延迟状态转换
3.1 单单元与耦合故障的分类
单单元故障包括 stuck-at、transition、stuck-open、data-retention。耦合故障分两单元和三单元版本,再按 idempotent、inversion、state、dynamic 细分。桥接故障有 AND 型和 OR 型,同样分两单元和三单元。邻域敏感故障(NPSF)分 active 和 passive,static 又分 Type I 和 Type II 邻域。
这些分类不是学术摆设。仿真结果里,March X 对 local 3-cell CFid 的覆盖率是 25.0%,对 global 2-cell CFid 是 50.0%;March C- 对 local 3-cell CFid 是 50.0%,对 global 2-cell 是 100%。同一类“耦合故障”,局部和全局的覆盖率能差一倍,原因就在于算法遍历地址的顺序是否覆盖了耦合单元对的空间关系。
3.2 延迟状态转换与 DRAM 睡眠病
延迟状态转换用来建模 retention 故障,典型场景是 DRAM 的“sleeping-sickness”失效:单元在写入后经过延迟 tD 才发生 0→1 或 1→0 的翻转。仿真时,敏化和去敏化不是立即生效,而是在 tD 之后发生。这意味着测试序列中如果两次访问同一单元的间隔小于 tD,故障可能来不及表现。
# 带延迟的状态转换 def apply_write_with_delay(faults, addr, value, mem, t, tD): for f in faults: if f.delay_type == 'retention': # 延迟 tD 后才更新敏化状态 schedule_event(t + tD, f.update_sensitization, mem, addr, value) else: f.update_sensitization(mem, addr, value) mem[addr] = valuetD是模型参数,通常按工艺和刷新周期设定。schedule_event把状态更新排进事件队列,保证延迟语义正确。如果忽略这个延迟,retention 故障会被误判为已覆盖。
3.3 多故障掩蔽与敏化序列的相互影响
多故障同时存在时,一个被敏化的故障会改变内存模式,进而影响其他故障的敏化/去敏化序列。原文给了一个很直观的例子:单元 C 周围的模式全为 1 时,sleeping-sickness 故障才被敏化;但如果故障 A 或 B 先被敏化并改变了周围值,C 的敏化条件就被破坏。这就是 error masking。
仿真器必须按测试序列逐条推进,每步都重新计算所有故障的状态,不能假设故障之间独立。复杂度上,对测试序列长度 t 是 O(t),对邻域大小 k 是 O(2^k),对内存大小 n 和耦合单元数 k 是 O(2^k · n^k)。NPSF 的单元在物理上相邻,耦合故障的单元可以在阵列中任意位置,这个区别直接决定了仿真时要不要做空间剪枝。
4. March 算法覆盖率对比与 RAMFLT 仿真流程
4.1 March X、March C-、GALPAT 的序列结构
三种算法的操作序列:
March X: ⇑(w0); ⇑(r,w1); ⇓(r,w0); ⇓(r,w1); ⇓(r,w0); ⇓(r) March C-: ⇑(w0); ⇑(r,w1); ⇑(r,w0); ⇓(r,w1); ⇓(r,w0); ⇓(r) GALPAT: 以每个单元为基点,先全阵列写反值再读回,逐单元推进⇑表示地址递增,⇓表示地址递减,r是读,w0/w1是写 0/写 1。March 类算法的复杂度是 O(n),GALPAT 是 O(n²)。复杂度差异直接反映在覆盖率上:GALPAT 对 local 3-cell CFid 达到 48.2%,对 global 2-cell CFid 达到 99.7%,而 March X 分别是 25.0% 和 50.0%。
4.2 256 位阵列上的覆盖率数据
原文给出的 256 位(16×16)仿真结果:
| 故障类 | March X | March C- | GALPAT |
|---|---|---|---|
| Type I NPSF Active | 6.25% | 12.5% | 11.7% |
| Type I NPSF Passive | 6.25% | 12.5% | 15.6% |
| Type I NPSF Static | 0.39% | 0.78% | 0.98% |
| Type II NPSF Active | 1.76% | 3.52% | 4.10% |
| Type II NPSF Static | 0.39% | 0.78% | 0.81% |
| local 3-cell CFid | 25.0% | 50.0% | 48.2% |
| global 2-cell CFid | 50.0% | 100% | 99.7% |
注意 March C- 在 global 2-cell CFid 上达到 100%,但 GALPAT 只有 99.7%。这说明覆盖率不是“越慢越高”,算法结构和故障模型的空间关系匹配才是关键。
4.3 用 RAMFLT 跑一次覆盖率仿真的步骤
常见做法是先把故障模型写成输入规格,再喂测试序列,最后统计 covered 比例。一个可复现的流程:
# 1. 准备故障模型库(输入式规格,非硬编码) ramflt --load-faults faults/npsf_type1_active.spec # 2. 指定测试序列,支持 March 和任意序列 ramflt --test tests/march_c_minus.seq --mem 16x16 # 3. 运行仿真,输出覆盖率统计 ramflt --simulate --report coverage.csv # 4. 按故障类聚合,便于对比算法 ramflt --aggregate --by fault_class --input coverage.csv--load-faults加载的是规格文件,不是编译进二进制的模型;--test接受规则 March 或伪随机序列;--mem指定阵列尺寸,16x16 对应 256 位;--report输出逐故障的 covered 状态;--aggregate按故障类汇总,方便做算法排名。失败时先看规格文件里的敏化/去敏化序列是否和算法操作顺序对得上,再看内存模式是否在写操作前正确建立。
注意:仿真结果对阵列尺寸敏感。256 位上的覆盖率不能直接外推到 1Mb 阵列,因为耦合故障的空间距离分布变了。
5. 覆盖率曲线追踪与 BIST 方案评估的实操技巧
5.1 用测试序列条目定位覆盖率拐点
原文给了一条 ANPSF 测试的仿真追踪曲线,横轴是测试序列条目(0 到 1307010),纵轴是覆盖率百分比,曲线里能看到 SAF、TF、2-cell CFid、3-cell CFid、Type I ANPSF、Type II ANPSF 各自的增长拐点。实操时,把--report输出按序列条目排序,找出覆盖率从 0 跳到目标值的那个条目,就能定位是哪条写操作触发了敏化。
import csv # 读取逐条目覆盖率,找每个故障类的首次覆盖点 def first_covered(csv_path): first = {} with open(csv_path) as f: for row in csv.DictReader(f): fc = row['fault_class'] if fc not in first and float(row['covered']) > 0: first[fc] = int(row['seq_index']) return first # 输出:{'SAF': 12, 'TF': 48, '2-cell CFid': 130, ...} print(first_covered('coverage.csv'))seq_index是测试序列条目编号,covered是该条目后的累计覆盖率。这个脚本帮你把“覆盖率什么时候涨上去的”变成可查的数字,而不是只看最终百分比。
5.2 算术 BIST 方案的覆盖率排序
对嵌入式内存的 BIST,常见做法是用线性反馈移位寄存器生成伪随机序列,再用 RAMFLT 评估覆盖率。评估时要注意三点:一是伪随机序列长度要足够,否则覆盖率曲线还没收敛;二是要和确定性 March 算法做同故障类对比,不能只比总覆盖率;三是多端口和 FIFO 结构要单独建模,因为端口间的耦合故障模型和单端口不同。
| 评估维度 | 确定性 March | 伪随机 BIST |
|---|---|---|
| 序列长度 | O(n) | 通常更长 |
| 覆盖率收敛 | 可预测 | 需仿真确认 |
| 适用结构 | 规则阵列 | 多端口/FIFO |
| 评估方法 | RAMFLT 直接跑 | RAMFLT + 序列生成器 |
5.3 覆盖率数字之外的验证习惯
我一般会在拿到覆盖率报表后做两件事:第一,抽查几个被标记为 covered 的故障,手动推一遍敏化序列,确认不是仿真器的误判;第二,把同一算法在不同阵列尺寸上各跑一次,看覆盖率随尺寸的变化趋势。如果某个故障类的覆盖率随尺寸急剧下降,说明算法对该故障的空间覆盖不足,换更长的序列或改地址遍历顺序比单纯增加测试时间更有效。覆盖率是手段,不是目的,能解释清楚“为什么这个数字是这样”才算真正跑通了 RAMFLT 这套方法。
本文还有配套的精品资源,点击获取