半导体数据分析(二):徒手玩转STDF格式文件 -- 码农切入半导体系列
一、概述
在上一篇文章中,我们一起学习了STDF格式的文件,知道了这是半导体测试数据的标准格式文件。也解释了为什么码农掌握了STDF文件之后,好比掌握了切入半导体行业的金钥匙。
从今天开始,我们一起来一步步地学习如何解构、熟悉、掌握、玩弄这个STDF。并最终尝试写一个完整的 STDF解析器,最后发布到网上成为一个公共库。
了解这个文件,首先需要了解STDF的标准。我们知道,了解标准是一件很繁琐的事情,所以我们现在要做的是一步步开始,从创建和读取最简单的stdf格式的文件作为起始点,逐步探索STDF的核心。
实际上PYTHON 已经有两种第三方库来读取STDF文件,但经过我的测试,并不是很理想,多数时候会在读取STDF的时候报错,或者漏掉一些关键信息。所以,我们今天徒手来盘弄这个stdf文件。
二、文件结构
一个基本的STDF文件,结构是这样的:
STDF File
├── Record (repeated for each record in the file)
│ ├── Header (6 bytes)
│ │ ├── REC_LEN (2 bytes) : Record Length (total length of the record including header)
│ │ ├── REC_TYP (1 byte) : Record Type (identifies the kind of record)
│ │ └── REC_SUB (1 byte) : Record Sub-Type (further specifies the record type)
│ └── Body (variable length, depending on REC_TYP and REC_SUB)
│ ├── Field (repeated for each field in the record)
│ │ ├── Field Name : Descriptive name for the field
│ │ ├── Data Type : Specifies the data type of the field
│ │ └── Value : The actual data stored in the field
│ └── [Additional Fields as specified by the record type]
└── [More Records as needed]
上面的内容可以看出,标准STDF包含了 头文件 和记录文件 两个主要内容。
其中
- REC TYPE 是主记录的标识。
- REC_SUB 是主记录下的子标识。
以下是STDF的全部记录类型
今天我们要创建一个最简单的带有头文件的STDF格式文件,就需要理解STDF(Standard Test Data Format)的基本结构。STDF是一种二进制格式,用于半导体测试设备之间交换测试数据。每个STDF记录由一个固定长度的头和可变长度的数据字段组成。
根据标准,我们知道每个STDF记录都包含一个头部,该头部至少包括三个字段:记录长度、记录类型(REC_TYP)、记录子类型(REC_SUB)。我们将创建一个最小的STDF文件,它将仅包含File Attributes Record (FAR) 和 Master Information Record (MIR),因为它们是每个STDF文件必需的。
它的格式如下:
STDF File
├── FAR (File Attributes Record)
│ ├── Header (6 bytes)
│ │ ├── REC_LEN (2 bytes) : Length of FAR record including header
│ │ ├── REC_TYP (1 byte) : 0 (for FAR)
│ │ └── REC_SUB (1 byte) : 10 (for FAR)
│ └── Body (variable length)
│ ├── Version : STDF version number (1 byte)
│ └── CPU_Type : Type of CPU used to generate the file (1 byte)
├── MIR (Master Information Record)
│ ├── Header (6 bytes)
│ │ ├── REC_LEN (2 bytes) : Length of MIR record including header
│ │ ├── REC_TYP (1 byte) : 1 (for MIR)
│ │ └── REC_SUB (1 byte) : 10 (for MIR)
│ └── Body (variable length)
│ ├── Head Count : Number of test heads (2 bytes)
│ ├── Site Count : Number of test sites per head (2 bytes)
│ ├── Date/Time First Part Tested (4 bytes each)
│ ├── Date/Time Last Part Tested (4 bytes each)
│ ├── Test Operator : ID of the operator who ran the tests (variable length string)
│ ├── Test Station ID : ID of the station where tests were performed (variable length string)
│ ├── Lot ID : Identifier for the lot being tested (variable length string)
│ ├── Wafer ID : Identifier for the wafer being tested (variable length string)
│ └── Flat/Notch Indicator: Orientation of the wafer flat or notch (1 byte)
└── [Potentially more records, depending on the file content]
三、文件创建
以下是Python代码示例,它将创建一个简单的STDF文件,其中包含FAR和MIR记录:
import struct
def create_stdf_file(filename):
# FAR - File Attributes Record
far_rec_typ = 0 # Record Type for FAR
far_rec_sub = 10 # Record Subtype for FAR
far_data = b'\x04\x00' # Version and CPU type, example values
# MIR - Master Information Record
mir_rec_typ = 1 # Record Type for MIR
mir_rec_sub = 10 # Record Subtype for MIR
mir_data = (
b'\x02\x00' + # Head count, site count
b'\xFF\xFF' + # Date of first part tested (not used)
b'\xFF\xFF\xFF\xFF' + # Time of first part tested (not used)
b'\xFF\xFF' + # Date of last part tested (not used)
b'\xFF\xFF\xFF\xFF' + # Time of last part tested (not used)
b'\x00\x00\x00\x00' + # Test Operator (not used)
b'\x00\x00\x00\x00' + # Test Station ID (not used)
b'\x00\x00\x00\x00' + # Lot ID (not used)
b'\x00\x00\x00\x00' + # Wafer ID (not used)
b'\x00\x00\x00\x00' # Flat/notched indicator (not used)
)
with open(filename, 'wb') as f:
# Write FAR record
far_length = len(far_data) + 4 # Including header length
far_header = struct.pack('>HBB', far_length, far_rec_typ, far_rec_sub)
f.write(far_header + far_data)
# Write MIR record
mir_length = len(mir_data) + 4 # Including header length
mir_header = struct.pack('>HBB', mir_length, mir_rec_typ, mir_rec_sub)
f.write(mir_header + mir_data)
create_stdf_file('simple.stdf')
代码解释:
这段代码用于创建一个简单的 STDF 文件,并写入两个类型的记录:FAR(File Attributes Record)和 MIR(Master Information Record)。它会按照 STDF 格式组织数据,并将其写入文件。
1. 导入模块
import struct
struct
模块用于处理二进制数据的打包(packing)和解包(unpacking),即将数据转换为字节流,这在读取或写入二进制文件时非常有用。
2. 定义 create_stdf_file
函数
def create_stdf_file(filename):
- 这是定义的一个函数,接收文件名
filename
作为参数,用来生成 STDF 文件。
3. FAR(File Attributes Record)记录
# FAR - File Attributes Record
far_rec_typ = 0 # Record Type for FAR
far_rec_sub = 10 # Record Subtype for FAR
far_data = b'\x04\x00' # Version and CPU type, example values
- FAR 记录的类型和子类型分别是 0 和 10。它用于存储与文件相关的属性(解析的时候,可以对照标准中的表来解析)。
far_data = b'\x04\x00'
是一个示例字节数据,这里表示 类型的信息。数据是字节串(bytes
)。
4. MIR(Master Information Record)记录
# MIR - Master Information Record
mir_rec_typ = 1 # Record Type for MIR
mir_rec_sub = 10 # Record Subtype for MIR
mir_data = (
b'\x02\x00' + # Head count, site count
b'\xFF\xFF' + # Date of first part tested (not used)
b'\xFF\xFF\xFF\xFF' + # Time of first part tested (not used)
b'\xFF\xFF' + # Date of last part tested (not used)
b'\xFF\xFF\xFF\xFF' + # Time of last part tested (not used)
b'\x00\x00\x00\x00' + # Test Operator (not used)
b'\x00\x00\x00\x00' + # Test Station ID (not used)
b'\x00\x00\x00\x00' + # Lot ID (not used)
b'\x00\x00\x00\x00' + # Wafer ID (not used)
b'\x00\x00\x00\x00' # Flat/notched indicator (not used)
)
- MIR 记录的类型和子类型分别是 1 和 10。它用于存储关于测试的主信息,如测试设备、测试站等。
mir_data
是一个字节串,包含多个字段的示例数据。每个字段都使用字节串表示。例如,b'\x02\x00'
是头计数和站点计数的占位符,b'\xFF\xFF'
表示未使用的字段。
5. 打开文件并写入数据
with open(filename, 'wb') as f:
- 使用
with open(filename, 'wb')
打开文件并准备以二进制模式('wb'
)写入文件。这意味着文件中的数据将按字节流写入。
6. 写入 FAR 记录
# Write FAR record
far_length = len(far_data) + 4 # Including header length
far_header = struct.pack('>HBB', far_length, far_rec_typ, far_rec_sub)
f.write(far_header + far_data)
far_length = len(far_data) + 4
计算 FAR 记录的总长度,包含了记录头部的 4 字节(2 字节长度字段,1 字节类型字段,1 字节子类型字段)。far_header = struct.pack('>HBB', far_length, far_rec_typ, far_rec_sub)
使用struct.pack
将 FAR 记录的头部打包成字节流:>H
表示一个 2 字节的无符号短整型,表示记录的长度。B
表示一个字节的无符号字符型,分别表示记录类型和子类型。
- 然后通过
f.write(far_header + far_data)
将头部和数据一起写入文件。
7. 写入 MIR 记录
# Write MIR record
mir_length = len(mir_data) + 4 # Including header length
mir_header = struct.pack('>HBB', mir_length, mir_rec_typ, mir_rec_sub)
f.write(mir_header + mir_data)
mir_length = len(mir_data) + 4
计算 MIR 记录的总长度,同样包括头部的 4 字节。mir_header = struct.pack('>HBB', mir_length, mir_rec_typ, mir_rec_sub)
使用struct.pack
打包 MIR 记录的头部。- 然后通过
f.write(mir_header + mir_data)
将头部和数据一起写入文件。
8. 调用函数
create_stdf_file('simple.stdf')
- 这行代码调用
create_stdf_file
函数并将'simple.stdf'
作为文件名传入,创建并写入 STDF 文件。
四、读取和解析文件
读取和解析文件,相对逻辑就比较容易了,因为我们已经有了创建文件的逻辑,所以反着来就行了!
为了读取并打印STDF文件的内容,我们需要编写一个Python脚本来解析二进制数据。下面是一个简单的例子,它会读取我们之前创建的最简STDF文件,并打印出每个记录的头部信息。
import struct
def parse_stdf_file(filename):
with open(filename, 'rb') as f:
# 读取 FAR (File Attributes Record)
far_header = f.read(4)
far_length, far_rec_typ, far_rec_sub = struct.unpack('>HBB', far_header)
far_data = f.read(far_length - 4) # 读取 FAR 数据
print(f"FAR Record (Length: {far_length}):")
print(f" Record Type: {far_rec_typ}")
print(f" Record Subtype: {far_rec_sub}")
print(f" FAR Data: {far_data.hex()}")
# 读取 MIR (Master Information Record)
mir_header = f.read(4)
mir_length, mir_rec_typ, mir_rec_sub = struct.unpack('>HBB', mir_header)
mir_data = f.read(mir_length - 4) # 读取 MIR 数据
print(f"\nMIR Record (Length: {mir_length}):")
print(f" Record Type: {mir_rec_typ}")
print(f" Record Subtype: {mir_rec_sub}")
print(f" MIR Data: {mir_data.hex()}")
# 解析 MIR 数据(可根据需要进一步解析每个字段)
print("\nParsed MIR Data:")
print(f" Head Count and Site Count: {struct.unpack('>H', mir_data[0:2])[0]}")
print(f" Date of First Part Tested (not used): {mir_data[2:4].hex()}")
print(f" Time of First Part Tested (not used): {mir_data[4:8].hex()}")
print(f" Date of Last Part Tested (not used): {mir_data[8:10].hex()}")
print(f" Time of Last Part Tested (not used): {mir_data[10:14].hex()}")
print(f" Test Operator (not used): {mir_data[14:18].hex()}")
print(f" Test Station ID (not used): {mir_data[18:22].hex()}")
print(f" Lot ID (not used): {mir_data[22:26].hex()}")
print(f" Wafer ID (not used): {mir_data[26:30].hex()}")
print(f" Flat/Notched Indicator (not used): {mir_data[30:34].hex()}")
# 调用函数解析并打印内容
parse_stdf_file('simple.stdf')
代码解释:
以下是对我提供的 Python 代码的概要解释:
1. parse_stdf_file(filename)
函数:
该函数用于解析 .stdf
文件并打印文件中包含的记录数据。
2. 打开文件:
with open(filename, 'rb') as f:
使用 open
函数以二进制模式读取文件 ('rb'
)。with
语句确保文件在读取后被自动关闭。
3. 读取和解析 FAR(File Attributes Record):
far_header = f.read(4)
far_length, far_rec_typ, far_rec_sub = struct.unpack('>HBB', far_header)
far_data = f.read(far_length - 4)
- 首先,读取 FAR 记录的头部(4 字节)。
- 使用
struct.unpack
解包头部数据,>HBB
表示将数据按大端格式解包为:1 个unsigned short
(2 字节)表示far_length
,2 个unsigned char
(1 字节)分别表示far_rec_typ
和far_rec_sub
。 - 然后,读取 FAR 记录的数据部分,数据长度为
far_length - 4
(因为头部已经占用 4 字节)。
4. 打印 FAR 记录内容:
print(f"FAR Record (Length: {far_length}):")
print(f" Record Type: {far_rec_typ}")
print(f" Record Subtype: {far_rec_sub}")
print(f" FAR Data: {far_data.hex()}")
- 打印 FAR 记录的长度、类型和子类型。
- 将 FAR 数据转为十六进制格式进行打印(
far_data.hex()
)。
5. 读取和解析 MIR(Master Information Record):
mir_header = f.read(4)
mir_length, mir_rec_typ, mir_rec_sub = struct.unpack('>HBB', mir_header)
mir_data = f.read(mir_length - 4)
- 读取 MIR 记录的头部(4 字节)并解包,获取
mir_length
、mir_rec_typ
和mir_rec_sub
。 - 读取 MIR 数据部分,数据长度为
mir_length - 4
(同样考虑到头部的 4 字节)。
6. 打印 MIR 记录内容:
print(f"\nMIR Record (Length: {mir_length}):")
print(f" Record Type: {mir_rec_typ}")
print(f" Record Subtype: {mir_rec_sub}")
print(f" MIR Data: {mir_data.hex()}")
- 打印 MIR 记录的长度、类型和子类型。
- 将 MIR 数据转为十六进制格式打印。
7. 解析 MIR 数据的具体字段:
print("\nParsed MIR Data:")
print(f" Head Count and Site Count: {struct.unpack('>H', mir_data[0:2])[0]}")
print(f" Date of First Part Tested (not used): {mir_data[2:4].hex()}")
print(f" Time of First Part Tested (not used): {mir_data[4:8].hex()}")
...
- 我逐个解析 MIR 数据中的各个字段。每个字段在数据流中的位置是已知的,所以下标指定了每个字段的范围。
- 使用
struct.unpack
解析特定字段(例如Head Count and Site Count
字段),并打印出它们的值。 - 对于不使用的字段,直接显示它们的十六进制表示。
执行后,结果就是我们写入的内容:
五、本节小结:
1. 写入 STDF 文件代码
-
目标:创建一个简单的
.stdf
文件并写入两个记录类型:FAR (File Attributes Record) 和 MIR (Master Information Record)。 -
代码功能:
- FAR 记录:
far_rec_typ
:设置为 0(FAR 记录类型)。far_rec_sub
:设置为 10(FAR 记录子类型)。far_data
:包含示例数据(如b'\x04\x00'
,表示版本和 CPU 类型)。- FAR 头部:包含 FAR 记录的长度(
far_length
)、类型(far_rec_typ
)和子类型(far_rec_sub
),然后将其与数据一起写入文件。
- MIR 记录:
mir_rec_typ
:设置为 1(MIR 记录类型)。mir_rec_sub
:设置为 10(MIR 记录子类型)。mir_data
:包含多个字段的示例数据,如头计数、日期、时间等,很多字段是未使用的(例如,测试操作员、测试站点 ID 等设置为0x00
)。- MIR 头部:类似于 FAR 记录,包含 MIR 记录的长度(
mir_length
)、类型(mir_rec_typ
)和子类型(mir_rec_sub
),然后将其与数据一起写入文件。 - 输出:一个名为
simple.stdf
的文件,其中包含 FAR 和 MIR 两个记录。
- FAR 记录:
2. 读取 STDF 文件代码
-
目标:解析刚刚生成的
.stdf
文件,读取 FAR 和 MIR 记录,并打印它们的内容。 -
代码功能:
- 读取 FAR 记录:
- 读取 4 字节头部,获取
far_length
(记录长度)、far_rec_typ
(记录类型)和far_rec_sub
(记录子类型)。 - 然后读取剩余的
far_data
并打印十六进制数据。
- 读取 4 字节头部,获取
- 读取 MIR 记录:
- 类似于 FAR 记录,读取 4 字节头部,获取
mir_length
(记录长度)、mir_rec_typ
(记录类型)和mir_rec_sub
(记录子类型)。 - 然后读取剩余的
mir_data
并打印十六进制数据。
- 类似于 FAR 记录,读取 4 字节头部,获取
- 解析 MIR 数据:
- 对 MIR 数据中的各个字段进行进一步解析,例如:头计数和站点计数、日期时间、操作员信息等,并打印它们的值。虽然很多字段在示例中没有实际使用,但可以展示它们的十六进制表示。
-
输出:
- 读取并打印 FAR 和 MIR 记录的详细信息,包括它们的长度、类型、子类型以及数据部分的十六进制表示。
- MIR 数据会根据字段解析逻辑显示各个字段的值,尽管示例中的很多字段未被使用。
- 读取 FAR 记录: