ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Chroma Pattern转爱德万V93000格式:IC测试向量转换脚本实战详解

Chroma Pattern转爱德万V93000格式:IC测试向量转换脚本实战详解 做IC测试的同行应该都经历过这种场景开发阶段用Chroma机台调好的pattern到量产导入时突然被告知换到爱德万Advantest平台跑所有测试向量得重新写一遍。人工对着两套文件一个个翻译眼睛花了不说稍不留神一个时序沿就差了几纳秒上机跑完直接pattern mismatch排错排到半夜。这个Chromat pattern转爱德万pattern脚本解决的正是这个让人头大的问题。我写过也维护过这类转换工具今天把整个思路、踩过的坑、还有核心实现细节都摊开讲希望能帮到正在被两套机台格式来回折腾的兄弟。文章不绕弯子直接给你能落地的方案但你最好也把自己手里的两套pattern样本都拿出来对着看只有你自己的数据才能验证到底转得对不对。1. 为什么非写这个转换脚本不可Chroma与爱德万的Pattern差异1.1 两套测试机两套完全不同的“语言”Chroma在模拟、混合信号测试领域用得非常多比如3380、3680这类机型很多做电源管理IC、驱动IC的工厂基本是标配。它有自己的pattern格式和命令集优点是上手快、界面直观test program里嵌着pattern调试起来方便。爱德万那边以V93000为代表配合SmarTest软件环境是数字测试、高性能SoC测试的主力机型。V93000的pattern体系比Chroma要复杂讲究的是时序集、电平组、向量序列三层分离文件格式也完全独立后缀可能是.flf、.bin或者直接在测试程序里用专门的pattern generator生成。这两套系统描述同一个测试向量时思路完全不一样。举个例子同样是让某个pin在T时刻输出高电平Chroma可能就是在vector里写一个状态位加一串电平值而爱德万要对应到某个timing set里的波形定义再引用电平组的VIH/VIL值。这种本质上的差异决定了你没法靠简单的find-and-replace完成转换。1.2 转换脚本到底解决什么问题我先把这个脚本的适用场景说清楚免得你走偏。它做的是“pattern文件的格式转换”不是“test program的自动移植”。什么意思呢就是说你在Chroma里已经调好了一组测试向量这套向量的逻辑内容是对的但格式是Chroma专属的脚本负责把它翻译成爱德万能识别的pattern文件格式。最典型的几个场景同一款芯片在研发阶段用Chroma验证量产导入到爱德万设备上产能更大需要快速迁移测试向量。同一款产品在两个平台的工厂都生产两边都要维护pattern手动双写容易不一致。外购IP或代工厂提供的测试向量模板是Chroma格式但内部产线主力是爱德万需要自动转换。人工转换最大的问题不只是慢而是不可追溯。你今天心情好某个label翻译成jump代码的偏移量就算对了明天着急上线同一个label可能就算错了。脚本一旦写好转换规则是固定的输出可回归、可对比这才是它真正的价值。2. 动手前必须吃透的6类关键信息刚开始做转换的人容易犯一个错误拿到两个文件就开始写正则结果转出来的文件一个都对不上。我建议你先花时间把下面6个维度的信息全部确认清楚这个准备工作比写代码本身更重要。2.1 时序参数与时基单位Pattern的灵魂是时序。Chroma里的时序定义通常是周期加各个信号沿的时间点比如周期20nspin D0在10ns时翻转。爱德万这边则是用timing set来管理一个timing set里有waveform table对应每个pin在某个cycle里的边沿位置、电平状态。转换时最怕的单位问题Chorma有些老的pattern文件可能用10ns、100ns这种整数写法爱德万那边默认单位可能是皮秒ps20ns直接写成了20000这个还好办但万一文件里既有ns也有us混着写解析错了就全盘崩。浮点精度也不能忽略。比如Chroma里写了1.5ns转成ps是1500如果一个沿被写成1499.999或者1500.001某些机台的比较器会直接报timing check fail。我的习惯是一开始就定一个统一的内部表示单位全部转成整数皮秒所有计算都基于整数做输出时再按目标机台需要的单位格式化。这样能避开绝大多数浮点误差。2.2 电平设定与电压档位电平信息在Chroma里往往和pattern关联得比较紧VIH、VIL、VOH、VOL、VT这些值可能在程序的不同段里定义。爱德万则明确划分成level setpattern通过引用level set的名字来获得完整的电压配置。转换的要点是建立“电平组映射表”。不是简单地把电压值抄过去而是要做到同一个测试项里用到的VIH/VIL组合要对应到爱德万一个命名的level set。如果两个测试项的电平组合完全一样尽量复用同一个level set别生成一堆重复定义否则文件冗余还容易超资源限制。注意电平精度Chroma可能写3.3V爱德万的level set里要写3300mV还是3.3V取决于你配置的单位。2.3 向量数据与通道映射这是最机械但也最容易出错的部分。Chroma的pattern里通常用字符序列标记每个通道在每个cycle的状态H、L、Z、X有些还带掩码标记。爱德万的vector要么是展开的位序列要么是按pin组织的长串。转换时一定要把两边的pin map对齐。我遇到过一个案例Chroma侧通道顺序是D0到D31按顺序排列爱德万侧的测试程序里物理通道映射到site上的pin名顺序是反的结果转出来的pattern从第一个cycle就是错乱的排查了整整一天。一定要先把两边的“pin名到pattern位索引”映射关系确认清楚。脚本里的channel_map配置必须来自测试程序的pin文档而不是猜。2.4 标签与流程控制命令Pattern不只是线性的向量序列它还有流程控制跳转、循环、子程序调用。Chroma里常见的是label加jump类指令爱德万的pattern里也有类似的label和jump机制但语法和偏移计算方式可能不同。转换时最关键的坑是标签偏移量的计算。Chroma的jump可能是“跳到第n行vector”爱德万可能是“当前地址加偏移”。如果你的脚本没有搞清楚目标格式用的到底是绝对地址还是相对偏移转出来的pattern流程全是乱的。我建议转换时先把所有label收集起来建立完整的符号表再在第二阶段统一计算跳转目标。先解析再写文件别指望一遍扫完就全部搞定。2.5 数字捕获与掩码设置很多pattern不只是“发激励”还要“收响应”。Chroma里响应比较、掩码设置都有自己的一套标记方式爱德万则需要把它们翻译成对应的capture和mask配置。掩码处理是转换脚本里最容易“丢信息”的地方。有些人只关注向量位把掩码位忽略了结果转出来的pattern对上电的设备直接误判FAIL。所以我特别强调在解析数据结构里必须单独保留mask字段转换后在日志里输出统计比如“共处理12000条向量其中300条有掩码”方便你核对有没有丢。2.6 文件编码与路径规范这个看起来低级但真的坑过不少人。Chroma的pattern文件可能是GBK编码爱德万的工具链在Linux环境跑默认UTF-8如果你的脚本不做编码处理遇到中文字符的注释文件直接乱码解析直接崩或者标签错乱。还有路径分隔符、文件后缀的大小写Windows下开发、Linux下跑脚本这些细节都要提前统一。我的建议是脚本里显式声明打开文件时用encoding参数不要依赖系统默认编码。3. 脚本怎么写解析、映射、输出三步走3.1 整体架构别搞成一个大循环我第一次写这个脚本就是个典型反面教材一个巨大的while循环边读行边写输出中间塞满了各种if判断。结果就是调试起来完全没法定位问题加一个格式分支就得动主逻辑。后来重构成了三段式架构用着非常顺解析层Parser读取Chroma格式源文件把内容转换成不依赖具体格式的中间数据结构。转换层Transformer操作中间数据结构按规则生成爱德万格式的等价逻辑表示。输出层Writer把表示结果按爱德万格式要求格式化写入目标文件。关键点是中间数据结构一定要“中立”。它既不是Chroma的也不是爱德万的而是一个通用的pattern模型包含时序集、电平组、向量序列、标签表、掩码信息。这样以后不管你要转什么新格式只要替换掉Parser和WriterTransformer基本不用动。3.2 中间数据结构怎么设计我用Python实现主要的类大致是这样的dataclass class TimingPoint: time_ps: int # 时间点统一用皮秒整数 state: str # H/L/Z/X/D/U 等状态 dataclass class TimingSet: name: str cycles: list[TimingPoint] # 每个cycle的波形定义 dataclass class LevelSet: name: str vih_mv: int vil_mv: int voh_mv: int vol_mv: int vt_mv: int dataclass class PatternVector: line_number: int # 源文件行号方便报错定位 label: str | None # 如果这行有label command: str # NOP/TEST/JUMP 等操作码 vectors: list[str] # 每个pin在该cycle的状态 masks: list[str] # 对应的掩码标记 timing_set_ref: str # 引用哪个timing set level_set_ref: str # 引用哪个level set jump_target: str | None # 跳转目标label名 dataclass class PatternProgram: timing_sets: dict[str, TimingSet] level_sets: dict[str, LevelSet] vectors: list[PatternVector] labels: dict[str, int] # label名到vector索引的映射中间数据结构建好了你就会发现解析和输出变成了纯粹的文件格式问题而业务逻辑集中在Transformer里思路瞬间清晰。3.3 转换规则设计的核心思想转换规则看起来很多很杂其实核心就一句话语义保持语法替换。每条源pattern经历几个阶段先判断它是普通向量行、时序定义行、电平定义行还是流程控制行。普通向量行根据channel_map重排pin顺序状态字符按映射表翻译。比如Chroma的“1”可能对应爱德万的“H”但有些机台里“1”反而是“L”必须看具体设备约定不能想当然。时序定义行转换成TimingSet对象的参数TimingSet名字要唯一且符合爱德万的命名规范。流程控制行统一变成中间的jump_target字符串等所有label收集齐了再计算最终偏移。全部转换完成后再做一次自检检查有没有未定义的label引用、有没有超范围的偏移、有没有掩码信息丢失。这里有个实用的技巧转换后的pattern先不要直接拿去上机把它转成一种中间文本格式做“人肉可读的diff”。我一般是转出后跟原文件打印成两侧对比表人工快速扫一遍关键的时序和跳转点没问题再丢给机台验证。4. 实测完整流程从Chroma向量到V93000 Pattern落地4.1 环境准备和脚本结构我用的是Python 3.10不需要额外装第三方库只用标准库的re、dataclasses、argparse和pathlib。开发环境就是VS Code目标运行环境是Windows和Linux都要兼容所以脚本里所有路径操作统一用pathlib不写死路径分隔符。目录结构大概是这样的pattern_convert/ ├── config/ │ └── channel_map.yaml # 通道映射配置手工维护 ├── samples/ │ ├── input.chr # Chroma原文件样本 │ └── expected.flf # 期望输出的爱德万文件样本 ├── src/ │ ├── parser_chroma.py # Chroma解析层 │ ├── model.py # 中间数据模型 │ ├── transformer.py # 转换核心逻辑 │ ├── writer_advantest.py # 爱德万输出层 │ └── main.py # 命令行入口 └── output/ └── converted.flf通道映射配置用YAML但不引第三方库的话可以简化为简单的文本文件或者Python模块。我实际用的是Python模块因为很多工程师的机器上装不了额外依赖一个config.py里放字典最省事。4.2 核心代码片段解析Chroma以我手上某个Chroma 3380的pattern文件为例它的格式大概是这样的; cycle period 200ns ; VDD3.3V, VIH3.0V, VIL0.3V L START T 1 1 1 0 Z Z 1 0 M 0 0 0 0 X X 0 0 J START END第一行是周期注释后面跟着电平注释然后label STARTT开头的是test向量行M开头的是掩码行J是跳转指令。这个格式不同机型可能有差别但思路一样。对应解析代码import re from pathlib import Path from model import PatternVector, TimingSet, LevelSet def parse_chroma(file_path: Path): lines file_path.read_text(encodingutf-8, errorsreplace).splitlines() vectors [] labels {} current_label None for idx, raw_line in enumerate(lines, start1): # 去掉首尾空白跳过空行和注释 line raw_line.strip() if not line or line.startswith(;) or line.startswith(#): continue # 清理行内注释 line re.sub(r;.*$, , line).strip() if not line: continue if line.startswith(L ): # label定义 label_name line[2:].strip() current_label label_name labels[label_name] len(vectors) elif line.startswith(T ) or line.startswith(J ): parts line.split() op parts[0] data parts[1:] vec PatternVector( line_numberidx, labelcurrent_label, commandop, vectorsdata, masks[], timing_set_ref, level_set_ref, jump_targetparts[1] if op J else None, ) vectors.append(vec) current_label None # 确保label只绑到第一条向量上 elif line.startswith(M ): # 掩码行与上一条向量关联 if vectors and vectors[-1].command T: vectors[-1].masks line.split()[1:] else: # 别的语法可以在这加分支 pass return vectors, labels这个解析器刻意保持“宽容”遇到不认识的指令先跳过但会打印debug日志。因为格式版本太多你不认识的不代表文件是错的直接返回错误会误伤。但跳过的行必须留下记录等转换完成后再人工确认。4.3 核心代码片段映射与输出转换层做的事情是把中间vector的每个状态字符按映射表翻译然后再按目标格式输出。状态映射表一般长这样STATE_MAP { 1: H, 0: L, Z: Z, X: X, D: U, # 不同机器对未知态叫法不同 }输出爱德万格式时重点是把TimingSet和LevelSet定义写到文件头再逐行输出vector。这里要注意爱德万对label和jump的处理我用的简化输出形式如下def write_advantest(program, output_path: Path): lines [] lines.append(TIMING_SET ts_main) # 根据program.timing_sets内容生成波形定义 for name, ts in program.timing_sets.items(): lines.append(f {ts.time_ps}) lines.append(END_TIMING_SET) lines.append(LEVEL_SET ls_main) for name, ls in program.level_sets.items(): lines.append(f VIH {ls.vih_mv}mV) lines.append(f VIL {ls.vil_mv}mV) lines.append(END_LEVEL_SET) for vec in program.vectors: if vec.label: lines.append(fLABEL {vec.label}) if vec.command J: lines.append(fJUMP {vec.jump_target}) else: mapped .join(STATE_MAP.get(s, s) for s in vec.vectors) lines.append(fV {mapped}) if vec.masks: lines.append(fMASK {.join(vec.masks)}) output_path.write_text(\n.join(lines), encodingutf-8)上面这个是功能演示版的简化输出实际产线用的爱德万pattern格式可能比这个复杂得多比如要带checksum、带header信息、带site信息。但核心逻辑是完全一致的timing和level先进header然后按行处理向量label先登记后输出jump目标统一计算。4.4 转换完怎么自检我每次转换完不会直接就把文件丢给机台会先跑自己的三关检查。第一关是脚本内建检查所有label引用有没有目标、所有mask长度和vector长度是否一致、有没有未转换的非法状态字符。第二关是文本对比把转换前后的关键数据导成CSV人工或自动diff一下向量数量、跳转次数、掩码数量。第三关才是上机做dry run确认机台能正常load pattern文件不报语法错误。5. 转换中最容易踩的坑与排查方法这一节是我觉得最有价值的部分全是我实际踩过或者帮同事排查过的坑。整理成了一张表方便你遇到问题的时候直接对着查。现象根因排查思路解决方案上机报pattern mismatch前后的电平定义不一致或者某个cycle期望值反转了对比转换前后同一个cycle的期望状态把电平组映射表拿出来逐个值核对尤其是VIH/VOH这类容易被混用的电压jump全乱跳跑到第几十万行突然跑飞label偏移量计算方式不对打印label符号表手动算一次跳转地址统一采用先收集label再计算偏移的两阶段方案时序报timing check fail皮秒换算时的浮点误差对比源文件里的时间值和转换后的时间值所有时间统一转整数皮秒存储禁止用浮点运算转换后mask全丢了掩码行没有和上一行vector正确关联检查解析后的数据结构里mask字段是否为空解析时碰到M开头的行要绑定到最近的T行而不是当成独立行文件编码问题导致乱码label全找不到源文件是GBK脚本按UTF-8读检查出错行附近的原始字节打开文件时指定encodingerrors参数或用chardet判断通道错位D0变成D15两边的pin map顺序不一样脚本直接用了默认顺序输出一条全通道的对比向量人工核对顺序用显式的channel_map配置表禁止默认顺序转换5.1 Pattern mismatch是最大的坑很多人在转换后上机最常遇到的就是pattern mismatch。这里我要特别说一句真机上报mismatch不一定就是你的转换脚本有问题。它可能是pattern本身没问题但timing set选错了可能是level set引用错了也可能问题根本出在测试程序调用pattern时传的参数不对。排查的时候我建议先把问题范围缩小只用一条最简单的向量做测试然后逐步增加复杂度。我会先转一条“全1”的向量上机跑看所有pin的采样结果是不是预期全1再转一条“全0”再转一个有跳转的序列。一步步来永远不要一次性把几千条向量全部倒进去debug那样只会撞墙。5.2 排除自己脚本问题的三个“对照实验”如果你怀疑脚本有问题做这三件事能快速定性固定的输入样本做回归。把一份确认无误的Chroma pattern放进去转换结果跟之前已经验证过的输出做diff任何差异都是脚本改动导致的。构造极简输入。写一个只有两三条向量的最小pattern文件就一条向量、一个跳转转换完人工检查基本能立刻看出转换逻辑哪里不对。反向转换验证。如果有可能把生成的flf文件再imap心中的格式转回Chroma格式对比与原始文件的差异多转一轮能过滤掉大量单向转换中“看起来没问题但实际已经丢失信息”的情况。6. 脚本后续维护还能怎么扩展转换脚本写出来不是一锤子买卖芯片产品一多、机台型号一多需求自然就来了。我这里聊几个我实际用得很顺的扩展方向。6.1 增加配置驱动的格式支持我最早把所有转换规则都hardcode在代码里每遇到一个新格式版本就要改代码。后来改成配置驱动everything从状态映射表、命令对应关系、时序单位换算规则全部外置到配置文件里。适应的格式多了直接加配置项就行代码基本不动。特别注意Chroma不同机型、不同软件版本的pattern语法细节可能不一样。有的老设备支持的命令新设备反而不支持。脚本要在配置里记录“这个格式对应的最低/最高版本”转换时显式检查防止拿老语法反馈到新设备上。6.2 自动化回归校验解放双手有了固定样本集之后建议把转换脚本接入CI或者简单的定时任务。每次改完代码跑一遍回归用10份以上覆盖不同特征的源pattern文件有时序变化、有跳转、有掩码、有特殊电平转换结果和golden文件之前人工确认过的正确输出做diff自动生成差异报告哪个文件、哪一行、什么内容不同一清二楚我那会刚开始没做这个结果后来改了一版配置不小心把某一类指令的映射改错了跑了三天转换程序直到产线报错才发现。做了回归之后再也没出现过这种低级问题。6.3 做个简易的可视化比对工具我后来加了一个把源文件和目标文件逐行并列展示的HTML报告功能。Chroma原文件在左边转换后的爱德万文件在右边相同颜色标出对应的向量行不一致的地方高亮标红。这个工具对Debug真的帮了大忙。因为你一次性转几万行pattern时间久了根本不可能逐行记住原始内容。有了可视化报告带着生产部的兄弟一起看对方指着屏幕说“就是这一条向量不对”你一眼就能定位是解析问题还是映射问题。如果你们团队有前端能力强的甚至可以把timing波形画出来做对比但我实测下来文本级的对比已经能满足90%的需求。这套脚本我前前后后迭代了快两年从最初只有几百行的粗糙脚本到现在的配置化、可回归、可追溯的小工具。最大的体会是这种工具表面上拼的是代码能力实际上拼的是你对两套机台pattern格式的理解深度。格式文档不全很正常但只要你手里攒够足够的样本、足够多的“机台反馈信号”工具就会越用越顺手。希望这篇分享能让你少走一些我走过的弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进