3、第一个虚拟验证项目:创建项目目录、编写第一个虚拟测试用例、运行并查看结果
好,咱们现在开始动手。
前面聊了那么多概念,说实话,不亲手跑一个用例,那些东西都是飘着的。我个人习惯是,学任何新工具,先让它「动起来」。哪怕只是点亮一个LED,或者打印一行Hello World,都比看十页文档管用。
这一章,我们就来创建你的第一个虚拟验证项目。我会带着你,从零开始搭目录、写用例、跑起来,最后看看结果长什么样。
3.1 项目目录结构:别小看这件事
很多新手上来就写代码,写到一半发现文件乱成一锅粥。我在项目中遇到过好几次,因为目录结构混乱,导致仿真脚本找不到文件,白白浪费半天时间。
所以,第一步先把「家」安好。
我推荐一个比较通用的目录结构,你直接照着建就行:
my_first_virt_project/
├── spec/ # 规格文档,放需求文档、接口定义
├── testbench/ # 测试平台,放顶层连接、时钟复位
│ ├── top.sv
│ └── clk_rst_gen.sv
├── tests/ # 测试用例,每个用例一个文件
│ ├── test_smoke.sv
│ └── test_reg_access.sv
├── vip/ # 虚拟接口、验证IP
│ ├── axi_vip.sv
│ └── i2c_vip.sv
├── scripts/ # 仿真脚本、编译脚本
│ ├── run.f
│ └── compile.tcl
├── logs/ # 仿真日志、波形文件
│ └── (自动生成)
└── Makefile # 一键编译运行
嗯,这里要注意:tests/ 和 vip/ 这两个目录,是虚拟验证项目的核心。测试用例放在 tests/ 里,虚拟接口和驱动放在 vip/ 里。这样分工明确,后期维护起来特别清爽。
README.md,写上项目用途、依赖工具、运行命令。三个月后你自己回来看,绝对感谢当时的自己。
3.2 编写第一个虚拟测试用例:从「冒烟测试」开始
第一个用例,我建议你写一个「冒烟测试」(Smoke Test)。说白了,就是验证整个验证环境能不能正常启动,时钟有没有、复位有没有、基本通路通不通。
你想想看,如果连冒烟都过不了,后面那些复杂用例根本跑不起来。
下面是一个最简单的虚拟测试用例,用SystemVerilog写的:
// 文件: tests/test_smoke.sv
class test_smoke extends base_test;
`uvm_component_utils(test_smoke)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info(get_type_name(), "=== 冒烟测试开始 ===", UVM_LOW)
// 等待复位释放
@(posedge vif.rst_n);
`uvm_info(get_type_name(), "复位已释放,系统启动", UVM_LOW)
// 等待几个时钟周期,观察基本行为
repeat (10) @(posedge vif.clk);
`uvm_info(get_type_name(), "时钟运行正常,已观测10个周期", UVM_LOW)
// 做一个简单的寄存器读写
vif.write_reg(0x00, 32'hA5A5_A5A5);
vif.read_reg(0x00, data);
if (data == 32'hA5A5_A5A5)
`uvm_info(get_type_name(), "寄存器读写测试通过", UVM_LOW)
else
`uvm_error(get_type_name(), "寄存器读写不匹配!")
`uvm_info(get_type_name(), "=== 冒烟测试通过 ===", UVM_LOW)
phase.drop_objection(this);
endtask
endclass
这段代码干了三件事:
- 等待复位释放——确保系统从复位状态正常出来。
- 观测时钟——确认时钟在跑,没有卡住。
- 读写寄存器——验证最基础的数据通路。
3.3 运行并查看结果:让代码真正跑起来
代码写好了,怎么跑?
我一般用Makefile来管理。你可以在项目根目录下放一个简单的Makefile:
# Makefile
SIM_TOOL = vcs
TEST_NAME = test_smoke
run:
$(SIM_TOOL) -f scripts/run.f +UVM_TESTNAME=$(TEST_NAME) -l logs/run.log
clean:
rm -rf logs/ *.log *.vcd
然后在终端里敲:
make run TEST_NAME=test_smoke
仿真开始后,你会看到屏幕上刷刷地滚日志。别慌,这是正常的。
跑完之后,去 logs/run.log 里看结果。你应该能看到类似这样的输出:
UVM_INFO @ 0ns: [test_smoke] === 冒烟测试开始 ===
UVM_INFO @ 100ns: [test_smoke] 复位已释放,系统启动
UVM_INFO @ 200ns: [test_smoke] 时钟运行正常,已观测10个周期
UVM_INFO @ 300ns: [test_smoke] 寄存器读写测试通过
UVM_INFO @ 400ns: [test_smoke] === 冒烟测试通过 ===
---
UVM Report: 0 errors, 0 warnings
看到 0 errors 了吗?这就是你想要的。
UVM_FATAL 或者 UVM_ERROR,别急着改代码。先看错误信息指向哪个文件、哪一行。我曾经有一次折腾了两小时,最后发现是文件名拼错了——大小写没对上。
3.4 避坑指南:新手最容易踩的三个坑
| 坑 | 表现 | 解决办法 |
|---|---|---|
| 路径不对 | 仿真报错:文件找不到 | 检查 run.f 里的路径,用相对路径 |
| UVM版本不匹配 | 编译报一堆语法错误 | 确认工具链支持UVM 1.2还是1.1d |
忘记加 +UVM_TESTNAME |
仿真跑起来了,但没执行你的用例 | 运行命令里一定要指定测试名 |
嗯,这三个坑我全踩过。尤其是第三个,我第一次跑虚拟验证时,用例没执行,我还以为环境坏了,折腾了半天才发现是参数没传进去。
3.5 小结:你已经迈出了第一步
到这里,你的第一个虚拟验证项目已经跑通了。你创建了目录、写了测试用例、运行了仿真、查看了结果。
说白了,虚拟验证的核心流程就是这三步:搭环境 → 写用例 → 看结果。后面所有的复杂场景,都是在这个基础上叠加的。
下一章,我们会深入虚拟接口的配置,让用例变得更灵活。但今天,先给自己鼓个掌——你已经不是纯新人了。
UVM_ERROR 长什么样。这样以后真遇到错误,你心里就有底了。