STDF V4二进制解析:记录链、缺失值与自校验排错
简介面向半导体测试工程师、ATE 系统开发者与测试数据分析人员这份 PDF 手册系统整理了 STDF 第4版规范重点解决不同厂商测试设备之间测试数据格式不统一、交换与追溯困难的问题。内容覆盖文件属性记录、主信息记录、最终结果记录等记录类型并纳入审计追踪记录、重测数据记录、晶圆与零件测试信息、参数测试结果等模块同时说明记录头结构、数据类型表示、可选字段与缺失值处理以及 V3 到 V4 在记录类型、字段定义和并行测试支持方面的变化便于读者理解规范全貌并用于数据解析、接口开发和兼容性核对。包内为 1 个 PDF 文件约 550KB轻量便于随查随用。已有 669 人学习适合需要查阅 STDF 记录定义、规划数据字段映射或完善测试数据审计与重测流程的从业者作为案头规范参考。1. STDF V4 手册到手文件读不通时先查规范再改代码一个 .stdf 文件读出来 PRR 分箱号对不上PTR 测试项又少了十几条第一反应是改解析器改到第三个小时才发现根因是文件用了 V4 新增的记录类型而解析器按 V3 字段偏移硬编码。半导体测试数据分析里这种翻车太常见STDFStandard Test Data Format是面向 ATE 的二进制数据记录格式V4 在记录类型、可选字段判定和并行测试支持上都做了扩展。手里这份是 V4 规范 PDF主体是记录结构、记录类型目录与字段定义三块。适合要把 STDF 写入数据库的数据工程师、给测试机写转换程序的上位机开发者以及核对分箱与参数良率的测试工程师。下面按“结构 → 记录链 → 缺失值 → 排错 → 自校验”的顺序走一遍。2. 四字节记录头与数据类型码把顺序读文件的基础打牢STDF 是纯二进制、无索引、只能顺序扫的格式。这决定了第一件事不是理解每条记录里有什么字段而是怎么在文件里“走”。记录头加数据类型码就是这一层的全部内容很多人急着写字段解析跳过这一段后面所有错误都是从这里开始积累的。2.1 REC_LEN / REC_TYP / REC_SUB 的寻址与跳过逻辑每条记录开头固定是 4 字节REC_LEN 为 2 字节无符号整数表示记录头之后的负载字节数注意它不包含这 4 字节头本身REC_TYP 为 1 字节标识一组相关记录REC_SUB 为 1 字节标识组内具体记录。REC_TYP 与 REC_SUB 的组合唯一确定一种记录类型这个设计让解析器可以按“族”做批处理也方便按族快速过滤。规范里 REC_TYP 小于 200 的编码由标准制定方保留大于 200 的开放给自定义应用。这一条在真实文件里是必须处理的遇到不认识的 (REC_TYP, REC_SUB)正确动作是按 REC_LEN 读出并跳过负载同时记一条日志而不是直接抛异常。一条带厂商私有扩展的记录如果让它把整条数据处理管道打断代价极大。常用记录类型分组整理成表更直观写解析器时可以直接当映射字典用REC_TYP含义组内 REC_SUB 与记录0文件信息FAR10、ATR201Lot 级数据MIR10、MRR20、PCR30、HBR40、SBR50、PMR60、PGR62、PLR63、RDR70、SDR802Wafer 级数据WIR10、WRR20、WCR305Part 级数据PIR10、PRR2010按测试程序TSR3015按测试执行PTR10、MPR15、FTR2020程序段BPS10、EPS2050通用数据GDR10、DTR30这张表还有一个容易被忽视的用途如果你要做“只读某几类记录”的轻量扫描器比如只抽 PRR 和 PTR 做良率统计那么在读到表外的记录时直接按长度跳过能省掉大量字段解析开销。2.2 数据类型码的坑定长串、变长串和位编码STDF 的字段类型用类似C*12、R*4、U*2这样的码表示。粗看很直观真正写代码时容易翻车的正好是那些带*n、*f后缀的类型。固定长度字符串C*12不足部分必须左对齐、用空格补齐读出来要rstrip掉空格否则不同文件里同一个工序名长度不一样数据库里会变成多个“名字”。变长字符串C*n首字节是跟随的无符号字节数最大 255长度字节为 0 就是“这个字段缺失”。C*f则是字符串长度存在别的字段里解析时不能自己猜。位编码有三类很容易混B*6是固定 6 字节位数组B*n首字节是字节数数据从第二字节的低位开始D*n前两字节是位计数最大 65535 位未使用的高位补零。三者长度字段的单位一个是定长、一个是字节、一个是位写通用读取函数时必须分开实现不能复用同一个read_bytes。还有V*n首字节先给一个数据类型码然后才是数据——这是一种“字段类型本身可变”的编码。N*1是半字节打包低 4 位是第一个数高 4 位是第二个数读到奇数个半字节时高 4 位补零。kxTYPE是数组元素个数k来自记录中更早的一个字段解析顺序错了就会越界。时间字段另外单独说规范用 4 字节无符号整数存储自 1970 年 1 月 1 日午夜起、按本地时区调整过的秒数。它不是一个普通整数做跨机房汇总时如果直接当 UTC 用时间会整体偏离几个小时这在按小时做良率趋势图时非常明显。2.3 一个可运行的记录扫描骨架把上面的规则落成一个最小扫描器是所有后续解析的公共底座import struct from typing import Iterator # 只登记常用记录未登记的类型按长度跳过保证扫描不中断 REC_NAME { (0, 10): FAR, (0, 20): ATR, (1, 10): MIR, (1, 20): MRR, (1, 30): PCR, (1, 40): HBR, (1, 50): SBR, (1, 60): PMR, (1, 62): PGR, (1, 63): PLR, (1, 70): RDR, (1, 80): SDR, (2, 10): WIR, (2, 20): WRR, (2, 30): WCR, (5, 10): PIR, (5, 20): PRR, (10, 30): TSR, (15, 10): PTR, (15, 15): MPR, (15, 20): FTR, (20, 10): BPS, (20, 20): EPS, (50, 10): GDR, (50, 30): DTR, } def scan(path: str) - Iterator[tuple]: with open(path, rb) as f: offset 0 while True: hdr f.read(4) if not hdr: break # 正常到达文件尾 if len(hdr) 4: raise ValueError(f偏移 {offset} 处记录头不完整文件可能被截断) # 小端读记录头2 字节长度 1 字节类型 1 字节子类型 rec_len, rec_typ, rec_sub struct.unpack(HBB, hdr) payload f.read(rec_len) if len(payload) rec_len: raise ValueError( f偏移 {offset} 处记录 ({rec_typ},{rec_sub}) 负载长度不足 ) name REC_NAME.get((rec_typ, rec_sub), UNKNOWN) yield name, rec_typ, rec_sub, payload offset 4 rec_len代码里三个位置值得说清楚。struct.unpack(HBB, hdr)里的决定了按小端解释这是绝大多数现有文件的实际写法如果 FAR 里的 CPU_TYPE 显示写入方属于异种字节序平台就需要在读完 FAR 之后切换到对应的转换分支而不是在这里一刀切。f.read(rec_len)直接读负载而不逐字段解析是因为这一层只负责“走”不负责“懂”。最后的offset累加是留给报错定位的——真实文件动辄几百兆出错时没有字节偏移排查基本靠猜。提示REC_LEN 不含 4 字节头这是最容易多算或少算一位的地方。每写一个新解析器先用一个已知记录的十六进制串对一遍长度比事后 debug 便宜得多。3. Lot / Wafer / Part 三级记录链从 FAR 顺到 MRR单条记录会解了接下来是记录之间的关系。STDF 用一套文件级、Lot 级、Wafer 级、Part 级、测试级的层次来组织数据顺序本身就是语义的一部分。打乱这个顺序或者把“先出现 MIR 再出现 PTR”当成可选项分析结果就会出现某些测点全部丢失却没有任何报错的情况。3.1 文件级与 Lot 级记录的合法顺序文件第一条记录按规范必须是 FAR它承载文件属性其中 CPU_TYPE 决定了后续所有多字节字段要不要做字节序转换所以读任何字段之前都应该先把 FAR 解出来。ATR 是审计追踪V4 新增用于记录文件被谁改过、改了什么做数据可追溯时很有用。Lot 级以 MIR 开始里面是批次号、测试程序名、测试机标识等MIR 之后是规范里说的“初始序列”常见排布是 PCR零件计数、HBR硬件分箱定义、SBR软件分箱定义、PMR引脚图、PGR引脚组可选、PLR引脚列表、SDR站点描述。这一段的顺序不是随便排的HBR 和 SBR 必须在任何 PRR 之前出现因为 PRR 里的分箱号是拿这两张定义表去查的先有结果再有定义就没法反查。Lot 结束时写 MRR里面是最终良率、总测试数、总通过数。写 MRR 之前必须有 PCR否则计数对不上。做数据校验时一个很实用的检查是MRR 里的总数是否等于 PCR 累计数以及是否等于 PRR 条数。这三个数不一致基本可以确定文件在写入过程中被中断过或者解析器漏读了中间某一类记录。3.2 单个 Die 的记录链与初始序列Wafer 测试场景下先有 WIR晶圆信息与 WCR晶圆配置每个 Die 测试完写 WRR晶圆结果整片结束后汇总。Part 测试场景下每个器件一对 PIR / PRRPIR 标识“开始测第几个”PRR 收结果包括硬件分箱、软件分箱、重测次数、坐标等。测试级记录夹在 PIR 和 PRR 之间按测试程序顺序写PTR 是参数测试一条记录对应一个测试项MPR 是多结果参数测试一条记录里带一组结果FTR 是功能测试。BPS 和 EPS 用来标记测试程序的段落做分段良率分析时它们能告诉你某个失败项属于哪一段。TSR 在 V4 里是测试概要记录测试名称与序号映射如果没有 TSRPTR 里的 TEST_NUM 就只是一个编号需要靠程序文件对照。还有几个 V4 或 V3 后期新增、容易被旧解析器忽略的记录RDR 存重测数据同一个器件连续测多次时没有它就只能看到最后一次结果SDR 定义站点并行测试下 SITE_NUM 的语义靠它确定PMR/PGR/PLR 共同描述引脚映射做失效分析时要把测试项映射回物理引脚靠的就是这三类记录。3.3 从 PTR / MPR 抽参数测试结果从这一层开始就能写“可抄作业”的抽取脚本了。下面的代码不追求覆盖 PTR 的全字段而是把分析最常用的几项拉出来def parse_ptr(payload: bytes): 按手册 PTR 一节的字段表顺序读取前置必需字段。 字段顺序与偏移以 PDF 中 ParametricTestRecord 的字段表为准 不同版本若有字段增删以手册为准同步调整。 off 0 def rd_u4(b, o): return int.from_bytes(b[o:o 4], little, signedFalse), o 4 def rd_u1(b, o): return b[o], o 1 def rd_r4(b, o): return struct.unpack(f, b[o:o 4])[0], o 4 def rd_cn(b, o): n b[o] # 长度字节为 0 表示该字符串缺失 if n 0: return , o 1 return b[o 1:o 1 n].decode(ascii, replace).rstrip(), o 1 n test_num, off rd_u4(payload, off) head_num, off rd_u1(payload, off) site_num, off rd_u1(payload, off) test_flg, off rd_u1(payload, off) # 位标记含结果是否有效 parm_flg, off rd_u1(payload, off) result, off rd_r4(payload, off) test_txt, off rd_cn(payload, off) # 测试项名称 return { test_num: test_num, head_num: head_num, site_num: site_num, test_flg: test_flg, parm_flg: parm_flg, result: result, test_txt: test_txt, }几个参数要说明。test_flg不是普通数值它是位域其中若干位用来声明结果是否有效、是否告警具体位定义要看手册该字段的说明不能当布尔值直接用。head_num和site_num在做多工位并行数据分析时必须分开聚合混在一起算良率会把不同工位的偏移抹平。test_txt用长度字节判定缺失长度字节为 0 就表示这个测试项没有名称此时应回退到test_num加 TSR 映射去取名而不是塞空字符串。如果一个文件里 PTR 条数远小于程序中的测试项数先别怀疑解析器去看看test_flg规范允许把一批测试项合并成 MPR 写入也可能因为采样策略没有逐项落 PTR。这两种情况在字段层面完全合法只按 PTR 计数会漏。4. 可选字段与缺失数据-1、空串和尾部截断怎么判做过一阵 STDF 解析的人都会遇到同一个问题字段读出来了但不知道它是“真的等于 0”还是“没有测”。规范对这件事有专门设计理解不到位良率统计里就会混进大量伪值。4.1 Optional Data bit 与两种缺失值标记规范规定可选字段必须“存在”但可以用两种方式声明它无效。第一种是预定义值变长字符串的长度字节置 0固定长度字符串填满空格时间日期字段用二进制 0有符号数值用 -1。第二种是记录里带一个 Optional Data 位域每一位对应一个可选字段置位表示该字段数据无效应忽略。这两种方式不能混用。有的字段所有取值包括 -1都合法那它只能用 Optional Data 位来标缺失有的字段规范直接指定了 -1 或空串为缺失标记那就不要去查位域。写代码时每读一个可选字段先回手册确认它属于哪一类这是最省事的做法。有一个很隐蔽的坑位域置位表示“无效”而不是“有效”。有些实现按“置位表示有值”写结果所有正常数据的字段被丢弃而缺失数据反而被保留表现出来的现象是“大部分测点值为空”很容易被误判成文件损坏。4.2 尾部省略的合法边界规范允许省略记录末尾的可选字段以节省空间但有严格条件被省略的字段必须是缺失或无效的并且它后面所有字段也必须是缺失或无效的可选字段。从记录中间省略任何一个可选字段都是非法的。这条规则对解析器的直接影响是不能假设负载长度一定覆盖到最后一个字段。常见做法是先按字段表算出“必需字段最小长度”读负载时以len(payload)为上限逐字段推进读不到就把该字段标记为缺失并停止而不是抛下标越界。这样即使文件由不同版本的写入器生成字段数略有差异解析也能跑完。4.3 解析器里的缺失值归一化真实的 STDF 文件里同一种缺失语义可能来自三种途径字段被截断、字段值等于预定义缺失值、Optional Data 位被置位。如果不在入口处统一后面每张报表都要写一遍三种判断。比较稳的做法是在解析层产出一个带标记的值对象或者用None统一表示缺失绝不用 0 或空串代替。用 0 代替缺失是这类项目里最常见的血泪经验平均值被拉低、良率被算高而且从结果上完全看不出问题因为 0 本身是一个看起来非常正常的值。def normalize(value, opt_bit, truncated, predefined_missing): 缺失值归一三种来源统一成 None避免 0 和空串冒充缺失。 if truncated: # 尾部省略导致字段未出现 return None if opt_bit: # Optional Data 位声明无效 return None if predefined_missing: # 规范预定义缺失值空串、-1、时间 0 等 return None return value参数含义按顺序是字段是否因尾部省略而未读到、对应可选位是否置位、值是否等于手册规定的预定义缺失标记。三个条件任一成立就返回None。这个函数看着简单但把它放在读取字段的第一现场比事后在业务代码里补判断可靠得多。注意时间日期字段的缺失标记是二进制 0但 0 本身也对应 1970-01-01如果直接转成时间戳再格式化会得到一串看似合法的远早期时间。转换前先判缺失。5. 避坑与排查STDF 解析里最常见的五类翻车这一节按“现象 → 原因 → 解决”记录我实际踩过或帮人查过的五类问题。它们覆盖了大部分“解析器能跑但结果不对”的场景遇到对不上的数据可以按这个顺序排查。5.1 现象整条记录字段整体错位一格现象是 PRR 里的分箱号读出来像测试项编号PTR 的site_num读出来像标志位。几乎每次都是字段表的读取起点错了。原因是把 REC_LEN 当成了包含 4 字节头的总长度或者在跳过记录时多减了 4 字节导致下一条记录的起始位置整体偏移。也有一种是把C*n当成C*12读多读了 11 字节的固定长度。解决方式是给扫描器加一个偏移断言在读到 MIR、PIR 这类结构稳定的记录时检查负载首字段是否符合预期取值范围比如 TEST_NUM 一般不会大到异常。更彻底的方案是用一个已知正确的文件建一条十六进制转储对着字段表手工走一遍把每个字段的偏移写进单元测试。5.2 现象浮点数读出天文数字现象是R*4字段解出来是接近 10 的几十次方或者一个极小的次正规数。原因是字节序判断错了。STDF 允许写入方用本机表示由 FAR 的 CPU_TYPE 声明读方需要据此决定是否转换。很多解析器默认小端遇到由异种字节序平台写出的文件就直接错。解决办法是在打开文件后立刻解 FAR 的 CPU_TYPE把它作为全局开关。如果 CPU_TYPE 指示的表示与运行机器不一致对所有U*、I*、R*字段套一层转换。注意B*、C*这类字节序列字段不受影响不要一起转。5.3 现象PTR 数量对不上测试项现象是按 PTR 条数统计的测试项数明显少于测试程序里列出的项目数而且缺的往往是同一批。原因有两个。一是这些测试项被合并成 MPR 写入一条 MPR 带多个结果只数 PTR 自然不够。二是写入方启用了采样只记录部分测试项这在字段层面完全合法。解决办法是统计时把 PTR 与 MPR 一起计入并对 MPR 里的结果数组按元素展开。同时用 TSR 建立测试项名称与编号的映射在没有 PTR 的编号上做一致性检查确认是采样还是真的丢失。5.4 现象读到某个记录后抛异常现象是文件前面几十万条记录都正常到某个位置开始报长度不足或解析越界。原因通常是文件本身被截断比如传输中断、磁盘写满也可能是遇到了不认识的记录类型后没有按长度跳过而是继续按已知类型解析负载。前者是数据问题后者是解析器问题。解决办法是两层扫描层对每条记录先校验len(payload) rec_len不等就记录偏移并停止把已解析的部分先落盘解析层对不认识的(REC_TYP, REC_SUB)一律跳过。两条都加上之后一个残缺文件至少能拿到残缺之前的数据而不是整批报废。5.5 现象同一个记录两种解析器结果不同现象是用两套工具打开同一个文件test_txt一个有值一个是空或者可选字段一边有值一边没有。原因多半出在缺失值判定上一边用长度字节判空一边用 Optional Data 位判断还有一种是把截断当成了空值但没有区分“字段不存在”和“字段存在但无效”。解决办法是先统一语义再比结果。用一份字段表把每个可选字段归到“预定义值类”或“位域类”中的某一类两边都按同一套规则归一再比对。这一步做完之后差异通常只剩时间字段的时区处理而这也能通过固定时区转换消掉。6. 拿规范当测试用例给解析器补上自校验解析器能跑通只是及格线能自己证明“我读对了”才算能用。PDF 里那些定义性条款其实每一条都可以翻译成一个断言下面是我现在固定会做的一轮自校验。6.1 用 V3 / V4 差异做静默降级检测V4 相对 V3 在记录类型、数据类型、文件名字符集、必需记录以及部分具体记录的字段上都做了调整。这意味着同一个解析器如果只按 V3 实现碰到 V4 文件不一定会报错而是安静地少读几类记录。一个很实用的自检是读文件头部的版本线索与记录集合如果出现 V4 才有的记录类型比如 ATR、RDR、SDR而解析器里对应的处理器是空的就打印一条显式警告而不是默默跳过。文件名一侧也有可校验项规范对文件名字符集有要求遇到超出字符集的路径名先怀疑文件被非规范工具重命名过。6.2 端到端校验清单与一条字段级断言我一般会跑这么一组检查任何一条不过就不往下游送数据检查项判定方式不通过的典型含义首记录类型第一条必须是 FAR文件不是 STDF 或头部损坏记录长度守恒所有len(payload) rec_len文件被截断Lot 级顺序MIR 在 MRR 之前HBR/SBR 在首个 PRR 之前写入器实现不规范或解析漏读计数一致性MRR 总数 PCR 累计 PRR 条数写入中断或漏读记录引脚映射完备出现 PMR 时 PLR 不应缺失V3 遗留文件或解析器只实现一半字段级再补一条最能兜底的断言对每类记录解析完必需字段后检查已消费的偏移量是否不超过REC_LEN若恰好等于则说明无尾部可选字段若小于则把剩余字节数记下来与字段表中可选字段的最小长度之和做比较差值为负就说明字段表用错了版本。这个断言在我这里抓到过不止一次“字段表照抄旧版”的问题。def check_record(rec_len: int, consumed: int, min_optional: int) - None: 字段级兜底已消费偏移 剩余可选字段最小长度 不应超过 REC_LEN。 remain rec_len - consumed if remain 0: raise ValueError(必需字段读超记录长度字段表版本可能不匹配) if remain and remain min_optional: raise ValueError(f剩余 {remain} 字节不足以容纳可选字段疑似字段表错版)min_optional从当前记录类型的字段表里事先算出来一旦在实际文件上稳定通过就可以把它固化成回归测试用例后续换版本时先跑这一组。把那以后我每次接手新的 STDF 数据源都强制先跑一遍“首记录 长度守恒 计数一致”三件套再谈业务逻辑因为它能在几分钟内把“文件有问题”和“代码有问题”分开省下的时间远比写这三个检查多。希望帮到你。本文还有配套的精品资源点击获取