手把手教你用Python解析WGL/STIL文件一个脚本搞定扫描链状态提取与可视化在芯片验证的后期阶段工程师们常常需要处理大量的测试向量文件其中WGL和STIL是最常见的两种格式。这些文件包含了扫描链的状态信息、时序定义和测试模式是验证流程中不可或缺的部分。然而手动解析这些文件不仅效率低下还容易出错。本文将带你用Python构建一个轻量级解析工具实现从文件解析到可视化展示的全流程自动化。1. 理解WGL与STIL文件的核心结构WGLWaveform Generation Language和STILStandard Test Interface Language是半导体测试领域广泛使用的两种文件格式。它们虽然目的相似但在结构和表达方式上存在显著差异。1.1 WGL文件的关键组成部分WGL文件通常包含以下几个核心部分Signal定义明确每个端口的类型input/output/inout和初始状态Scancell声明列出扫描链内部所有cell的清单Scanchain结构定义完整的扫描链组成包括SIScan In和SOScan Out端口Timeplate定义时序参数包括周期长度和各信号在周期内的事件状态Scanstate块描述特定时刻所有scan cell的状态以扫描链为单位分别定义输入和输出状态# WGL文件示例片段 Signal { clk : input initialp D; reset : input initialp D; data_in[7:0] : input; data_out[7:0] : output; } Scanchain grp1chain1 { SI - cell1 - cell2 - ... - cellN - SO } Timeplate gen_tp1 { Period 40ns; Channel clk { 0nsD; 20nsS; 40nsD; } }1.2 STIL文件的特点相比之下STIL文件更加面向ATE自动测试设备测试其结构特点包括直接的向量序列映射扫描序列直接对应扫描链每个单元简化的状态转换直接将扫描向量shift in扫描链即可达到指定状态明确的模式定义Pattern块清晰定义了测试模式的操作流程STIL文件的这种设计使其对ATE工具更加友好但在解析时需要特别注意向量序列与扫描链单元的对应关系。2. 构建Python解析框架要实现一个健壮的解析器我们需要设计合理的架构来处理这两种文件格式。以下是核心模块的设计思路2.1 基础解析器类设计我们首先创建一个基础解析器类定义通用的接口和方法class TestVectorParser: def __init__(self, file_path): self.file_path file_path self.signals [] self.scan_chains {} self.timeplates {} self.patterns [] def parse(self): raise NotImplementedError(Subclasses must implement this method) def visualize(self, output_formatwaveform): raise NotImplementedError(Subclasses must implement this method)2.2 WGL解析器实现对于WGL文件我们需要特别注意Scanstate块的处理这是理解扫描链状态的关键import re from collections import defaultdict class WGLParser(TestVectorParser): def __init__(self, file_path): super().__init__(file_path) self.scan_states defaultdict(dict) def parse(self): with open(self.file_path, r) as f: content f.read() # 解析Signal部分 self._parse_signals(content) # 解析Scanchain定义 self._parse_scan_chains(content) # 解析Timeplate self._parse_timeplates(content) # 解析Scanstate self._parse_scan_states(content) def _parse_scan_states(self, content): pattern rScanstate\s(\w)\s*\{([^}]*)\} matches re.finditer(pattern, content, re.DOTALL) for match in matches: state_name match.group(1) state_content match.group(2) # 解析每个扫描链的状态 chain_pattern r(\w)\s*:\s*([IO])\s*(\w) chain_matches re.finditer(chain_pattern, state_content) for cm in chain_matches: chain_name, io_type, state cm.groups() self.scan_states[state_name][(chain_name, io_type)] state2.3 STIL解析器实现STIL解析器的重点在于Pattern和向量序列的处理class STILParser(TestVectorParser): def __init__(self, file_path): super().__init__(file_path) self.vectors [] def parse(self): with open(self.file_path, r) as f: content f.read() # 解析Signal部分 self._parse_signals(content) # 解析Pattern self._parse_patterns(content) def _parse_patterns(self, content): pattern rPattern\s(\w)\s*\{([^}]*)\} matches re.finditer(pattern, content, re.DOTALL) for match in matches: pattern_name match.group(1) pattern_content match.group(2) # 解析向量序列 vector_pattern rvector\s*\(([^)]*)\)\s*\{([^}]*)\} vector_matches re.finditer(vector_pattern, pattern_content) vectors [] for vm in vector_matches: params vm.group(1) vector_data vm.group(2).strip().split() vectors.append((params, vector_data)) self.patterns.append({ name: pattern_name, vectors: vectors })3. 扫描链状态提取与转换解析文件只是第一步我们需要将提取的信息转换为可操作的扫描链状态表示。3.1 WGL状态转换逻辑WGL文件中的状态需要通过多个循环操作来还原确定扫描链长度根据Scanchain定义获取单元数量解析Scanstate提取目标状态下每个单元的实际值模拟移位操作通过循环操作将扫描链转换到目标状态def simulate_wgl_scanstate(self, state_name, scan_chain_name): if state_name not in self.scan_states: raise ValueError(fUnknown state: {state_name}) chain_length len(self.scan_chains[scan_chain_name][cells]) input_state self.scan_states[state_name].get((scan_chain_name, I), 0*chain_length) output_state self.scan_states[state_name].get((scan_chain_name, O), 0*chain_length) # 模拟移位操作 shifted_in [] for i in range(chain_length): # 根据timeplate定义模拟时钟操作 # 这里简化处理实际需要考虑timeplate定义 shifted_in.append(input_state[i]) return { input_state: input_state, output_state: output_state, simulated_shift: .join(shifted_in) }3.2 STIL向量映射STIL文件的处理相对直接因为向量序列已经与扫描链单元对应def map_stil_vectors(self, pattern_name): pattern next((p for p in self.patterns if p[name] pattern_name), None) if not pattern: raise ValueError(fPattern not found: {pattern_name}) # 假设第一个扫描链 scan_chain next(iter(self.scan_chains.values())) chain_length len(scan_chain[cells]) results [] for params, vector_data in pattern[vectors]: if scan in params.lower(): # 扫描向量直接映射到扫描链 if len(vector_data) ! chain_length: raise ValueError(Vector length doesnt match scan chain length) results.append({ type: scan, vector: vector_data, mapping: dict(zip(scan_chain[cells], vector_data)) }) return results4. 可视化与报告生成将解析结果可视化是理解扫描链状态变化的关键。我们提供两种输出方式波形图和CSV报告。4.1 使用Matplotlib生成波形图import matplotlib.pyplot as plt import numpy as np def plot_scan_chain_states(self, states, output_fileNone): fig, ax plt.subplots(figsize(12, 6)) # 准备数据 chain_names list(self.scan_chains.keys()) num_chains len(chain_names) for i, chain_name in enumerate(chain_names): chain_length len(self.scan_chains[chain_name][cells]) # 为每个状态创建波形 for j, state_name in enumerate(states): state self.scan_states[state_name] input_state state.get((chain_name, I), 0*chain_length) output_state state.get((chain_name, O), 0*chain_length) # 绘制输入状态 y_pos num_chains - i - 0.2 for k, bit in enumerate(input_state): color red if bit 1 else blue ax.plot([k, k1], [y_pos, y_pos], colorcolor, linewidth2) ax.text(k0.5, y_pos0.05, fI{bit}, hacenter) # 绘制输出状态 y_pos num_chains - i 0.2 for k, bit in enumerate(output_state): color green if bit 1 else purple ax.plot([k, k1], [y_pos, y_pos], colorcolor, linewidth2) ax.text(k0.5, y_pos-0.05, fO{bit}, hacenter) # 设置图表属性 ax.set_yticks(range(num_chains)) ax.set_yticklabels(chain_names) ax.set_xlabel(Scan Chain Position) ax.set_title(Scan Chain States Visualization) ax.grid(True) if output_file: plt.savefig(output_file) else: plt.show()4.2 生成CSV报告对于需要进一步分析或导入其他工具的情况CSV格式更加实用import csv def generate_csv_report(self, output_file): with open(output_file, w, newline) as csvfile: writer csv.writer(csvfile) # 写入表头 writer.writerow([Scan Chain, Cell Position, Cell Name, State, Input Value, Output Value]) # 写入数据 for chain_name, chain_data in self.scan_chains.items(): for pos, cell_name in enumerate(chain_data[cells]): for state_name in self.scan_states: state self.scan_states[state_name] input_val state.get((chain_name, I), 0*len(chain_data[cells]))[pos] output_val state.get((chain_name, O), 0*len(chain_data[cells]))[pos] writer.writerow([ chain_name, pos, cell_name, state_name, input_val, output_val ])5. 实战应用与性能优化在实际项目中我们还需要考虑一些实用技巧和性能优化策略。5.1 处理大型文件当面对GB级别的WGL/STIL文件时内存效率变得至关重要流式处理逐行读取而非一次性加载整个文件并行解析对独立的部分使用多线程处理增量可视化只渲染当前关注的部分波形def parse_large_wgl(self, file_path): 流式处理大型WGL文件 current_section None section_content [] with open(file_path, r) as f: for line in f: line line.strip() # 检测新的section开始 section_start re.match(r(\w)\s*\{, line) if section_start: if current_section: # 处理完整的section self._process_section(current_section, .join(section_content)) current_section section_start.group(1) section_content [line] elif current_section: section_content.append(line) # 处理最后一个section if current_section: self._process_section(current_section, .join(section_content))5.2 缓存解析结果为了避免重复解析我们可以实现结果缓存机制import pickle import os class CachedParser(TestVectorParser): def __init__(self, file_path): super().__init__(file_path) self.cache_file f{file_path}.cache def parse(self, use_cacheTrue): if use_cache and os.path.exists(self.cache_file): with open(self.cache_file, rb) as f: cached_data pickle.load(f) self.__dict__.update(cached_data) return # 正常解析流程 super().parse() # 保存缓存 with open(self.cache_file, wb) as f: pickle.dump(self.__dict__, f)5.3 命令行接口为了方便集成到自动化流程中我们可以添加命令行支持import argparse def main(): parser argparse.ArgumentParser(descriptionWGL/STIL文件解析工具) parser.add_argument(file, help输入文件路径) parser.add_argument(--format, choices[wgl, stil], help文件格式) parser.add_argument(--visualize, actionstore_true, help生成波形图) parser.add_argument(--report, help生成CSV报告路径) args parser.parse_args() if args.format wgl or args.file.endswith(.wgl): parser WGLParser(args.file) else: parser STILParser(args.file) parser.parse() if args.visualize: parser.visualize() if args.report: parser.generate_csv_report(args.report) if __name__ __main__: main()