第二章:脚本语言基础(Python)——从零搭建你的验证利器
做芯片验证这些年,我越来越觉得Python就像一把瑞士军刀。不管是写测试用例、解析日志,还是搭自动化流程,它都能派上用场。说实话,刚开始我总觉得Perl才是验证工程师的标配,直到有一次项目周期特别紧,我用Python三天搭了个脚本框架,把同事两周的活儿干完了——嗯,从那以后我就彻底“叛变”了。
这一章,咱们就聊聊Python在验证脚本里的基本功。别担心,我不打算让你成为Python专家,够用就行。
2.1 Python环境配置——别让环境卡住你
我记得刚入行那会儿,最头疼的就是配环境。明明照着教程一步步来,就是跑不起来。后来发现,90%的问题出在版本和路径上。
2.1.1 安装Python
我个人习惯用Python 3.8以上版本。为什么?因为3.6以下有些语法糖用不了,而3.10以上又太新,有些验证工具还没适配好。
- Windows用户:去官网下载安装包,记得勾选“Add Python to PATH”
- Linux用户:直接用包管理器,
sudo apt install python3搞定 - 检查版本:打开终端,输入
python3 --version
我曾经踩过的坑:系统自带的Python 2.7和手动装的Python 3.x冲突。运行脚本时调用了错误的解释器,报错信息完全看不懂。后来我统一用虚拟环境,再也没出过这种问题。
2.1.2 虚拟环境——隔离的艺术
做验证项目时,不同项目可能依赖不同版本的库。你想想看,如果A项目用numpy 1.19,B项目用numpy 1.21,混在一起迟早要出事。
# 创建虚拟环境
python3 -m venv my_venv
# 激活(Linux/Mac)
source my_venv/bin/activate
# 激活(Windows)
my_venv\Scripts\activate
# 退出
deactivate
小技巧:我习惯在项目根目录下建一个 requirements.txt,把依赖都写进去。新同事拉代码后,一条命令 pip install -r requirements.txt 就能复现环境。
2.2 基本语法——够用就行
Python语法其实很简单。说白了,就是“怎么想就怎么写”。我教新人时,从来不讲那些花里胡哨的特性,先把这三样搞明白:变量、条件判断、循环。
2.2.1 变量和数据类型
Python是动态类型语言,不用声明类型。这在写脚本时特别爽,少写很多废话。
# 字符串——最常用
log_file = "simulation.log"
test_name = 'uart_test'
# 数字
timeout = 1000 # 整数
frequency = 1.2e9 # 浮点数,1.2GHz
# 列表——存一堆东西
test_list = ["test_a", "test_b", "test_c"]
# 字典——键值对,解析日志时特别好用
config = {
"simulator": "vcs",
"coverage": True,
"seed": 42
}
重点:字符串拼接别用 +,用f-string。我在项目中见过太多因为类型不匹配导致的崩溃了。
# 推荐写法
print(f"正在运行测试:{test_name},超时时间:{timeout}ms")
# 不推荐
print("正在运行测试:" + test_name + ",超时时间:" + str(timeout) + "ms")
2.2.2 条件判断——让脚本有脑子
验证脚本里,条件判断无处不在。比如检查仿真是否通过,判断日志里有没有错误。
return_code = 0 # 假设仿真返回码
if return_code == 0:
print("仿真通过")
elif return_code == 1:
print("仿真失败,有编译错误")
else:
print(f"未知错误,返回码:{return_code}")
为什么会这样?因为仿真工具通常用0表示成功,非0表示失败。这个约定在几乎所有EDA工具里都通用。
2.2.3 循环——批量处理的神器
做回归测试时,要跑几百个测试用例。手动一个个跑?别傻了,用循环。
test_cases = ["uart_basic", "uart_baud", "uart_parity", "uart_flow"]
for test in test_cases:
print(f"启动测试:{test}")
# 这里调用仿真命令
# run_simulation(test)
print(f"测试完成:{test}")
我的经验:循环里加个计数器,方便定位问题。比如第几个用例挂了,一眼就能看到。
for idx, test in enumerate(test_cases, 1):
print(f"[{idx}/{len(test_cases)}] 正在运行:{test}")
2.3 文件操作——读写日志是基本功
验证工程师每天打交道最多的就是文件。仿真日志、波形文件、配置文件……不会读写文件,寸步难行。
2.3.1 读取文件
我最常用的场景是解析仿真日志,提取关键信息。
# 读取整个文件
with open("sim.log", "r") as f:
content = f.read()
# 逐行读取——处理大文件时用
with open("sim.log", "r") as f:
for line in f:
if "ERROR" in line:
print(f"发现错误:{line.strip()}")
注意:一定要用 with 语句。我见过有人忘了关文件句柄,导致脚本跑着跑着就报“Too many open files”。
2.3.2 写入文件
生成测试报告、保存配置、输出结果,都离不开写文件。
# 写入新文件(覆盖)
with open("report.txt", "w") as f:
f.write("测试报告\n")
f.write("=" * 40 + "\n")
# 追加内容
with open("report.txt", "a") as f:
f.write("测试用例:uart_basic - 通过\n")
f.write("测试用例:uart_baud - 失败\n")
2.3.3 路径操作——别写死路径
我刚开始写脚本时,总喜欢把路径写死。结果换个目录就跑不了,被同事吐槽了好几次。
import os
# 获取当前脚本所在目录
current_dir = os.path.dirname(__file__)
# 拼接路径
log_path = os.path.join(current_dir, "logs", "sim.log")
# 检查文件是否存在
if os.path.exists(log_path):
print(f"日志文件存在:{log_path}")
else:
print("日志文件不存在,请检查路径")
最佳实践:用 pathlib 替代 os.path,更现代、更简洁。
from pathlib import Path
current_dir = Path(__file__).parent
log_path = current_dir / "logs" / "sim.log"
2.4 正则表达式入门——从日志里淘金
正则表达式,说白了就是“字符串的模糊匹配”。验证日志动辄几万行,人工翻找?不现实。用正则,一行代码就能把想要的信息提取出来。
2.4.1 为什么需要正则?
你想想看,仿真日志里经常有这样的信息:
[2024-01-15 10:30:45] INFO: Test uart_basic PASSED, coverage: 85.3%
[2024-01-15 10:31:02] ERROR: Test uart_baud FAILED, mismatch at cycle 12345
[2024-01-15 10:31:20] INFO: Test uart_parity PASSED, coverage: 92.1%
如果我想提取所有PASSED的测试用例名和覆盖率,用普通字符串方法得写一堆代码。正则一行搞定。
2.4.2 基础语法——记住这几个就够了
| 符号 | 含义 | 示例 |
|---|---|---|
. |
匹配任意单个字符 | t.st 匹配 "test"、"tast" |
* |
匹配前一个字符0次或多次 | tes* 匹配 "te"、"tes"、"tess" |
+ |
匹配前一个字符1次或多次 | tes+ 匹配 "tes"、"tess",不匹配 "te" |
? |
匹配前一个字符0次或1次 | tes? 匹配 "te"、"tes" |
\d |
匹配数字 | \d+ 匹配 "123"、"45" |
\w |
匹配字母、数字、下划线 | \w+ 匹配 "test_123" |
[] |
匹配括号内的任意字符 | [Pp]ass 匹配 "Pass"、"pass" |
2.4.3 实战:解析仿真日志
我记得有一次,项目要统计所有测试用例的通过率和覆盖率。日志文件有500多MB,手动统计?那得加班到天亮。
import re
log_content = """
[2024-01-15 10:30:45] INFO: Test uart_basic PASSED, coverage: 85.3%
[2024-01-15 10:31:02] ERROR: Test uart_baud FAILED, mismatch at cycle 12345
[2024-01-15 10:31:20] INFO: Test uart_parity PASSED, coverage: 92.1%
"""
# 提取所有PASSED的测试用例和覆盖率
pattern = r"Test (\w+) PASSED, coverage: ([\d.]+)%"
matches = re.findall(pattern, log_content)
for test_name, coverage in matches:
print(f"测试用例:{test_name},覆盖率:{coverage}%")
避坑指南:正则里的括号 () 是用来分组的。我曾经写正则时括号套括号,结果提取出来的数据完全不对。建议先写小段测试,确认匹配正确了再集成到脚本里。
2.4.4 常用场景——拿来就用
这里分享几个我在验证脚本里常用的正则模式:
import re
# 提取时间戳
time_pattern = r"\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]"
# 提取错误信息
error_pattern = r"ERROR: (.+)"
# 提取覆盖率
coverage_pattern = r"coverage: ([\d.]+)%"
# 提取测试用例名
test_pattern = r"Test (\w+)"
# 实战:一行代码提取所有错误信息
errors = re.findall(error_pattern, log_content)
print(f"共发现 {len(errors)} 个错误")
核心思路:正则不是背出来的,是用出来的。我每次写正则都打开一个在线测试工具,边试边写。熟练了之后,你会发现它比任何字符串方法都好用。
知识体系总览
说了这么多,咱们用一张图把这一章的内容串起来。你想想看,从环境搭建到文件操作,再到正则表达式,其实是一条清晰的技能链。
这张图把咱们这一章的内容都串起来了。从环境搭建开始,到基本语法,再到文件操作和正则表达式,每一步都是下一环的基础。我个人觉得,只要把这四个模块吃透,写验证脚本就够用了。
可可精品资料库