测试程序结构:Pattern、Test Method、Test Suite、Test Flow 的概念与关系
做 ATE 测试开发,说白了就是搭积木。但很多人一上来就写代码,结果程序乱成一锅粥。我见过不少新人,把几百个测试项全塞在一个文件里,调试的时候想死的心都有。
这一章,我们就来拆解 ATE 测试程序的四个核心积木块:Pattern、Test Method、Test Suite 和 Test Flow。搞懂它们各自是什么、怎么配合,你的程序才能既清晰又高效。
1. 四个概念,一张图说清
先看这张结构图,你就能明白它们之间的层级关系:
嗯,这张图你看懂了,这一章就掌握了一半。从上到下,是调度关系;从下到上,是封装关系。
2. Pattern:最底层的“指令集”
Pattern 是什么?说白了,就是告诉测试机台「什么时候拉高、什么时候拉低、什么时候采样」的时序指令集。
我习惯把 Pattern 比作乐高积木里最小的那个颗粒。单个颗粒没什么用,但组合起来就能搭出城堡。
Pattern 的核心要素:
- 向量(Vector):每个周期 pin 脚的状态(H/L/Z/X)
- 时序(Timing):什么时候驱动、什么时候比较
- 电平(Levels):VIH/VIL/VOH/VOL 的具体电压值
- 标签(Label):跳转标记,用于控制流程
举个例子,一个简单的 func pattern 片段:
// Pattern: func_basic.pat
// 功能:验证基本读写功能
// 作者:我(2024-03-15)
// 时序集:t1 (周期 100ns)
// 电平集:lv1 (VDD=1.8V, VIH=1.6V, VIL=0.2V)
// Vector 格式:CLK DATA ADDR CS WE OE DQ
START: L L L H H H Z
P H L L H H Z // 写使能
P L H L L H Z // 写地址
P H L L H L Z // 读使能
P Z Z H H H H // 读数据
P L L H H H Z // 空闲
JMP START // 循环
我的经验:Pattern 文件一定要加版本号和作者信息。有一次我调试一个 bug,发现是用了旧版 pattern,浪费了整整一天。从那以后,我每个 pattern 文件头部都加了个「修改记录」块。
3. Test Method:可复用的“测试函数”
Test Method 是真正干活的代码。它调用 Pattern,设置机台资源,采集数据,然后判断 Pass/Fail。
我见过最糟糕的写法,是把所有逻辑都写在一个巨大的 Test Method 里,几千行代码,谁都不敢动。正确的做法是:一个 Test Method 只做一件事。
| Test Method 名称 | 功能描述 | 输入参数 | 输出结果 |
|---|---|---|---|
measure_voh |
测量输出高电平电压 | pin, pattern, 电流负载 | VOH 值 (V) |
measure_vol |
测量输出低电平电压 | pin, pattern, 电流负载 | VOL 值 (V) |
measure_iddq |
测量静态漏电流 | 电压, 等待时间 | IDDQ 值 (A) |
run_scan_test |
执行扫描链测试 | scan_pattern, 频率 | Pass/Fail + 故障位 |
measure_freq |
测量时钟频率 | clock_pin, 采样数 | 频率值 (Hz) |
你看,每个 Test Method 的职责都很清晰。这样写的好处是:换个项目,这些函数直接拿过来用。我手头有个库,积累了 50 多个通用 Test Method,新项目 80% 的测试方法都不用重写。
4. Test Suite:把相关的测试打包
Test Suite 就是把一组相关的 Test Method 和 Pattern 打包在一起。为什么要打包?因为测试不是一个个跑的,是一批批跑的。
举个例子,DC 测试套件:
// Test Suite: DC_Tests
// 包含所有直流参数测试
TestSuite DC_Tests {
// 测试项 1:VOH 测试
Test {
test_method = measure_voh;
pattern = func_basic.pat;
params {
pin = "DQ[0:7]";
i_load = -1.0mA; // 拉电流
voh_min = 1.4V;
voh_max = 1.8V;
}
}
// 测试项 2:VOL 测试
Test {
test_method = measure_vol;
pattern = func_basic.pat;
params {
pin = "DQ[0:7]";
i_load = 1.0mA; // 灌电流
vol_min = 0.0V;
vol_max = 0.4V;
}
}
// 测试项 3:IDDQ 测试
Test {
test_method = measure_iddq;
pattern = idle_pattern.pat;
params {
vdd = 1.8V;
wait_time = 10ms;
iddq_max = 10uA;
}
}
}
我曾经踩过的坑:把功能测试和 DC 测试混在一个 Suite 里。结果 DC 测试失败时,功能测试的 pattern 还在跑,把芯片状态搞乱了。后来我强制规定:一个 Suite 只做一类测试,DC 归 DC,功能归功能,扫描归扫描。
5. Test Flow:整个测试的“总指挥”
Test Flow 是最高层的调度器。它决定:先跑哪个 Suite,失败后怎么办,要不要跳转到其他测试。
你想想看,一颗芯片可能有几百个测试项。如果全部跑完才发现前面某个测试失败了,那后面的测试全是浪费。Test Flow 就是用来解决这个问题的。
一个典型的 Test Flow 结构:
// Test Flow: Main_Flow
// 主测试流程
Flow Main_Flow {
// 第一步:接触测试
RunSuite(Contact_Tests) {
on_fail: abort; // 接触失败,直接终止
}
// 第二步:DC 测试
RunSuite(DC_Tests) {
on_fail: goto_cleanup; // DC 失败,跳转到清理流程
}
// 第三步:功能测试
RunSuite(Func_Tests) {
on_fail: goto_cleanup;
}
// 第四步:扫描测试
RunSuite(Scan_Tests) {
on_fail: goto_cleanup;
}
// 第五步:MBIST 测试
RunSuite(MBIST_Tests) {
on_fail: goto_cleanup;
}
// 正常结束
label cleanup:
RunSuite(Cleanup_Tests);
result: pass;
}
Test Flow 的设计原则(我总结的):
- 先粗后细:先跑接触测试、DC 测试,再跑功能测试
- 失败早退:关键测试失败,后面的就别跑了,省时间
- 有进有退:每个分支都要有 cleanup,别让机台处于异常状态
- 可读性第一:Flow 是给人看的,命名要清晰,注释要到位
6. 四者的关系:一个完整的例子
为了让你更直观地理解,我拿一个实际项目来说。假设我们要测试一颗 MCU 芯片:
- Pattern 层:我们有
contact.pat(接触测试向量)、dc_meas.pat(DC 测量向量)、func_core.pat(核心功能向量)、scan_top.pat(扫描向量) - Test Method 层:我们有
check_contact()、measure_vdd()、run_func()、run_scan() - Test Suite 层:我们把它们打包成
Contact_Suite、DC_Suite、Func_Suite、Scan_Suite - Test Flow 层:最终流程是:Contact → DC → Func → Scan → Cleanup
你看,每一层都只关心自己的事。Pattern 只管时序,Test Method 只管测量逻辑,Suite 只管打包,Flow 只管调度。这就是分层解耦的思想。
我的习惯:每次写新项目,我先画 Test Flow 的流程图,再拆 Suite,再写 Test Method,最后才写 Pattern。从上往下设计,从下往上实现。这样思路最清晰,返工最少。
7. 避坑指南
最后,分享几个我踩过的坑,你遇到了能少走弯路:
- 别把 Pattern 路径写死:用变量或配置文件管理路径,否则换个目录就得改代码
- Test Method 别写太长:超过 200 行的 Test Method,基本可以拆成两个
- Suite 之间要独立:前一个 Suite 跑完,要恢复机台默认状态,别影响后面的测试
- Flow 里加日志:每个 Suite 开始和结束都打印时间戳,调试时能定位问题
- 版本控制:Pattern、Test Method、Suite、Flow 都要纳入 Git 管理,别问我为什么
嗯,这一章的内容就到这里。四层结构搞清楚了,你的测试程序就有了骨架。后面几章,我们会往这个骨架里填肉——具体怎么写 Pattern、怎么封装 Test Method、怎么设计高效的 Test Flow。