手把手教你用Python解析WGL/STIL文件:一个脚本搞定扫描链状态提取与可视化
手把手教你用Python解析WGL/STIL文件:一个脚本搞定扫描链状态提取与可视化
在芯片验证的后期阶段,工程师们常常需要处理大量的测试向量文件,其中WGL和STIL是最常见的两种格式。这些文件包含了扫描链的状态信息、时序定义和测试模式,是验证流程中不可或缺的部分。然而,手动解析这些文件不仅效率低下,还容易出错。本文将带你用Python构建一个轻量级解析工具,实现从文件解析到可视化展示的全流程自动化。
1. 理解WGL与STIL文件的核心结构
WGL(Waveform Generation Language)和STIL(Standard Test Interface Language)是半导体测试领域广泛使用的两种文件格式。它们虽然目的相似,但在结构和表达方式上存在显著差异。
1.1 WGL文件的关键组成部分
WGL文件通常包含以下几个核心部分:
- Signal定义:明确每个端口的类型(input/output/inout)和初始状态
- Scancell声明:列出扫描链内部所有cell的清单
- Scanchain结构:定义完整的扫描链组成,包括SI(Scan In)和SO(Scan 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 { 0ns=D; 20ns=S; 40ns=D; } }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_format='waveform'): 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 = r'Scanstate\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 = r'Pattern\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 = r'vector\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(f"Unknown 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(f"Pattern 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 doesn't 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_file=None): 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, k+1], [y_pos, y_pos], color=color, linewidth=2) ax.text(k+0.5, y_pos+0.05, f'I{bit}', ha='center') # 绘制输出状态 y_pos = num_chains - i + 0.2 for k, bit in enumerate(output_state): color = 'green' if bit == '1' else 'purple' ax.plot([k, k+1], [y_pos, y_pos], color=color, linewidth=2) ax.text(k+0.5, y_pos-0.05, f'O{bit}', ha='center') # 设置图表属性 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_cache=True): 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(description='WGL/STIL文件解析工具') parser.add_argument('file', help='输入文件路径') parser.add_argument('--format', choices=['wgl', 'stil'], help='文件格式') parser.add_argument('--visualize', action='store_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()