3、Hello World 的进阶:从打印语句到第一个可复用的验证函数,理解入口函数与模块化。
好,我们正式开始写代码了。
很多新手学验证,上来就写一个 print("Hello World")。然后呢?然后就没有然后了。说实话,这跟验证工程师的工作差得有点远。你想想看,真正的验证环境里,没人会写一个孤零零的打印语句就完事。我们需要的是——可复用的函数,清晰的入口,以及模块化的思维。
这一章,我们就从那个经典的 Hello World 出发,一步步把它改造成一个真正能用的验证函数。我个人习惯,每学一个新语言或新框架,都会先搭一个最小的可运行骨架。这个骨架就是我们的“入口函数”。
3.1 从打印语句到函数封装
先看最原始的写法:
print("Hello World, Verification!")
嗯,能跑。但问题来了——如果我想打印不同的消息呢?如果我想在多个地方打印呢?复制粘贴?别闹了,那是新手干的事。
我们把它包成一个函数:
def hello_verification(message):
"""打印验证相关的问候信息"""
print(f"Hello Verification: {message}")
# 调用
hello_verification("开始检查时序约束")
hello_verification("开始检查复位信号")
看到了吗?一次定义,多次调用。这就是复用的雏形。我在项目中遇到过很多次,有人把同样的打印逻辑写了十几遍,后来要改格式,改到崩溃。你想想看,如果一开始就封装成函数,改一处就全改了,多省心。
3.2 理解入口函数:为什么需要 main?
好,函数有了。但怎么组织执行顺序呢?
很多 Python 脚本会这样写:
def hello_verification(message):
print(f"Hello Verification: {message}")
hello_verification("开始检查时序约束")
hello_verification("开始检查复位信号")
这也能跑。但如果你把这个脚本 import 到别的文件里呢?糟糕了——那两行调用会立刻执行!
为什么会这样?因为 Python 解释器是从上往下执行的。它看到函数定义就记住,看到函数调用就执行。所以我们需要一个“入口函数”,也就是 main 函数:
def hello_verification(message):
"""打印验证相关的问候信息"""
print(f"Hello Verification: {message}")
def main():
"""主入口函数"""
hello_verification("开始检查时序约束")
hello_verification("开始检查复位信号")
hello_verification("开始检查电源域")
if __name__ == "__main__":
main()
这里有个关键点:if __name__ == "__main__"。它的意思是——只有当我直接运行这个脚本时,才执行 main()。如果我是被别的文件 import 的,那就不执行。这样,你的代码既可以作为独立脚本运行,也可以作为模块被导入。
main() 函数。哪怕只有一行代码。这就像验证环境里的 testbench 顶层,总得有个明确的起点。
3.3 第一个可复用的验证函数
好了,现在我们把 Hello World 升级成真正的验证函数。假设我们要检查一个信号是否在预期时间内稳定:
def check_signal_stable(signal_name, expected_value, timeout_ns=100):
"""
检查信号是否在超时时间内稳定到预期值
参数:
signal_name: 信号名称
expected_value: 预期值 (0 或 1)
timeout_ns: 超时时间,单位纳秒
返回:
bool: 是否稳定
"""
print(f"[CHECK] 检查信号 {signal_name} 是否稳定到 {expected_value}")
print(f"[CHECK] 超时时间: {timeout_ns} ns")
# 这里只是演示框架,实际会调用仿真器 API
# 模拟检查过程
import time
time.sleep(0.1) # 模拟仿真时间
result = True # 假设检查通过
if result:
print(f"[PASS] 信号 {signal_name} 稳定")
else:
print(f"[FAIL] 信号 {signal_name} 未稳定")
return result
def main():
"""验证流程主入口"""
print("=== 开始验证流程 ===")
# 检查复位信号
check_signal_stable("rst_n", 1, timeout_ns=1000)
# 检查时钟信号
check_signal_stable("clk", 1, timeout_ns=500)
print("=== 验证流程结束 ===")
if __name__ == "__main__":
main()
这个函数已经有点意思了。它有明确的输入参数,有返回值,还有清晰的日志输出。我在项目中遇到过很多类似的场景——检查信号稳定、检查协议握手、检查数据一致性。把这些检查逻辑封装成函数,就是验证组件的基础。
3.4 模块化:把函数放到该放的地方
函数多了怎么办?全塞在一个文件里?那文件会越来越臃肿。我们需要模块化。
在 Python 里,一个 .py 文件就是一个模块。我们可以把相关的函数放到同一个模块里:
| 模块名 | 功能 | 包含函数示例 |
|---|---|---|
signal_checks.py |
信号级检查 | check_signal_stable(), check_signal_toggle() |
protocol_checks.py |
协议级检查 | check_axi_handshake(), check_apb_write() |
data_checks.py |
数据一致性检查 | check_data_integrity(), check_crc() |
utils.py |
通用工具函数 | log_message(), format_time() |
然后,在主脚本里这样导入:
from signal_checks import check_signal_stable
from protocol_checks import check_axi_handshake
from data_checks import check_data_integrity
def main():
check_signal_stable("rst_n", 1)
check_axi_handshake("axi_master")
check_data_integrity("dma_data")
if __name__ == "__main__":
main()
你看,主脚本变得非常清晰。它只负责“编排”验证流程,具体的检查逻辑都在各自的模块里。这就是模块化的好处——各司其职,互不干扰。
- 可维护性: 改一个模块不影响其他模块
- 可复用性: 写好的模块可以在不同项目间复用
- 可读性: 主流程一目了然,不用看几百行的细节
3.5 避坑指南与最佳实践
最后,分享几个我这些年总结的经验:
- 函数命名要见名知意:
check_signal_stable比func1好一万倍。你想想看,三个月后你回来看代码,看到func1是什么感觉? - 每个函数只做一件事:如果一个函数既检查信号又打印日志又写文件,赶紧拆。我曾经见过一个函数写了 200 行,什么活都干,改一次崩一次。
- 写文档字符串:就是函数定义下面的那个三引号注释。别偷懒。Python 的
help()函数会直接显示它,团队协作时特别有用。 - 入口函数保持简洁:
main()只做流程编排,不做具体实现。具体逻辑都放到函数里。
好了,这一章的内容就到这里。我们从一句简单的 print 出发,走到了函数封装、入口函数、模块化设计。说白了,这就是验证脚本的骨架。后面的所有内容,都会在这个骨架上生长。
下一章,我们会开始接触真正的验证环境搭建。做好准备。