3、Python编程基础回顾:语法、数据类型、控制流、函数定义,为ATE编程做准备
说实话,很多做ATE的兄弟一开始都问我:“老王,我C语言写得挺溜,干嘛还要学Python?”
嗯,这个问题我当年也问过。直到我接手了一个高速SerDes的测试程序——ATE机台用的是Python脚本控制pattern和Datalog。那一刻我才明白:Python在ATE领域不是替代C,而是做C做不到的事:快速分析Datalog、动态生成pattern、封装测试流程。
这一章我就带你快速过一遍Python最核心的基础。这些都是我平时在ATE项目里天天用的东西,不废话,直接上干货。
3.1 基本语法——别在小事上翻车
Python的语法极其简洁,但越是简单的东西,越容易大意。我见过老工程师写ATE脚本时,因为缩进问题debug了一整天——嗯,就是那种tab和空格混用的经典错误。
python -m tabnanny检查一下。
| 语法元素 | 说明 | ATE中的常见用法 |
|---|---|---|
| 注释 | # 单行注释 | 标注测试项、Datalog版本 |
| 变量赋值 | v = 3.3 无需声明类型 | 保存电源电压、频率值 |
| 行尾分号 | 不需要,但可以写(不推荐) | 避免旧C习惯污染 |
| 缩进 | 同一代码块必须对齐 | if/for/while的块结构 |
| print函数 | print("VDD=%.2f" % v) | 调试输出、Datalog打印 |
black格式化一遍再上传。
3.2 数据类型——选对容器,事半功倍
Python的数据类型比你想象的多,但ATE编程里常用的就那么几个。我来给你梳理一下,顺便说说我实际怎么用的。
3.2.1 数字与字符串
整型、浮点型、复数——基本够用。字符串用单引号或双引号都行,但ATE机台里的路径字符串经常带反斜杠,我习惯用原始字符串:r"C:\test\pattern",省得转义。
float("12.345")一步到位。如果字符串里有单位比如“12.345mV”,先用rstrip("mV")去掉。
3.2.2 列表、元组、字典、集合
| 容器 | 可变 | 有序 | 常用操作 | ATE场景 |
|---|---|---|---|---|
list | 是 | 是 | append(), pop() | 存储测试向量结果序列 |
tuple | 否 | 是 | 拆包:a,b = (1,2) | 函数返回多个值 |
dict | 是 | 否(3.7+ 有序) | d["key"], get() | 配置参数键值对、测试极限值 |
set | 是 | 否 | in, & 交集 | 去重、筛选有效测试项 |
举个例子,我在做Datalog分析时经常这样用:
# 把每行测试结果解析成字典
results = []
for line in lines:
parts = line.strip().split(",")
record = {
"test_name": parts[0],
"value": float(parts[1]),
"limit_low": float(parts[2]),
"limit_high": float(parts[3])
}
results.append(record)
# 然后就可以方便地判断是否pass
for r in results:
if r["value"] < r["limit_low"] or r["value"] > r["limit_high"]:
print("FAIL: " + r["test_name"])
3.3 控制流——让程序知道什么时候该干什么
ATE测试程序本质上是一连串的“判断→执行→记录”。控制流就是骨架。
3.3.1 if/elif/else
我习惯把条件判断和极限值写在程序头部,用字典索引而不是一长串elif:
limits = {
"VDD_CORE": (1.1, 1.3),
"VDD_IO": (3.0, 3.6),
}
def check_voltage(test_name, measured):
low, high = limits.get(test_name, (0, 100))
if measured < low or measured > high:
return "FAIL"
return "PASS"
为什么要这样?因为ATE测试项经常变,极限值改一个字典比改十行if判断安全得多。你想想看,改错一个elif的缩进,后果多严重。
3.3.2 for循环——遍历数据的主力
ATE测试里最常见的循环就是遍历所有测试项、Pattern、Datalog行。记住enumerate和zip这两个神器:
# 同时拿到索引和数据
for idx, result in enumerate(results):
print(f"第{idx+1}项: {result['test_name']} => {result['value']}")
# 同时遍历两个列表
measured_values = [1.25, 3.31, 0.98]
pass_fail = ["PASS", "PASS", "FAIL"]
for v, pf in zip(measured_values, pass_fail):
if pf == "FAIL":
print(f"异常值: {v}")
3.3.3 while循环——适合等待条件
比如等待DUT进入某个状态,或者轮询机台寄存器:
timeout = 1000
cnt = 0
while read_register(0x1A) != 0x80 and cnt < timeout:
time.sleep(0.001)
cnt += 1
if cnt >= timeout:
raise RuntimeError("寄存器响应超时")
对这种场景,我一般会加个超时保护,防止死循环把机台卡住。你想想,ATE机台一卡就是几分钟,产线leader得骂死我。
3.4 函数定义——把你的套路封装起来
ATE测试里有很多重复动作:设置电源、测量电压、写寄存器、读Datalog……不用函数的话,一千行代码你看都不想看。我每次写函数前都会问自己:这个操作以后会不会复用?如果会,就抽出来。
3.4.1 定义与调用
def measure_voltage(pin_name, settle_time_ms=10):
"""测量指定引脚的电压,返回float值(单位V)。"""
# 假装这里有ATE机台API
print(f"测量 {pin_name},等待{settle_time_ms}ms...")
time.sleep(settle_time_ms/1000)
return 1.25 # 实际会返回真实值
参数默认值、文档字符串(docstring)都加上。我习惯在docstring里写清楚:单位、返回值含义、异常情况。
3.4.2 返回值与多值返回
Python函数可以返回多个值,本质是返回元组:
def check_binary_output(pin_name, expected):
measured = read_pin(pin_name)
is_match = (measured == expected)
return is_match, measured
# 调用时直接拆包
ok, val = check_binary_output("DQ0", 1)
if not ok:
print(f"DQ0预期1,实测{val}")
我个人不喜欢返回太复杂的结构,超过3个返回值就用字典或命名元组,否则调用方搞不清顺序。
3.4.3 作用域与global
ATE脚本里经常有一个全局配置字典,比如test_params。我建议所有函数都不要直接修改全局变量,要么传参,要么用return新值。如果非要用global,那就要加注释说明为什么。
test_params = {"vdd": 1.2, "speed": 100}
def update_vdd(new_v):
# 坏写法:直接改全局
global test_params
test_params["vdd"] = new_v
# 好写法:返回新字典
def update_params(orig, key, value):
new_dict = orig.copy()
new_dict[key] = value
return new_dict
global test_params。后来机台内存不足,调试时发现某个回调函数意外修改了全局参数导致后续测试全错。从那以后我定了个规矩:函数里只读全局变量,改的话用返回值传递。
3.5 这一章的核心,其实就一句话
Python的语法、数据类型、控制流、函数,这些基础东西,你只要能熟练用到ATE场景里,就足以应付大部分日常测试脚本。说实话,很多ATE脚本也就一两百行,但结构一定要清晰,因为你写的程序大概率要交给产线同事维护。他们可没功夫看你的天马行空。