第二章:脚本语言基础(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)} 个错误")

核心思路:正则不是背出来的,是用出来的。我每次写正则都打开一个在线测试工具,边试边写。熟练了之后,你会发现它比任何字符串方法都好用。

知识体系总览

说了这么多,咱们用一张图把这一章的内容串起来。你想想看,从环境搭建到文件操作,再到正则表达式,其实是一条清晰的技能链。

Python脚本基础——知识体系 Python验证脚本 环境配置 安装Python 3.8+ 虚拟环境隔离 requirements.txt 基本语法 变量与数据类型 条件判断 if/elif/else 循环 for/enumerate 文件操作 读取文件 with open 写入文件 w/a模式 路径操作 pathlib 正则表达式 基础符号 . * + ? \d re.findall 提取信息

这张图把咱们这一章的内容都串起来了。从环境搭建开始,到基本语法,再到文件操作和正则表达式,每一步都是下一环的基础。我个人觉得,只要把这四个模块吃透,写验证脚本就够用了。

可可精品资料库