现在,你已经准备好学习UVM的"总指挥"了------ uvm_test 。它是整个验证工厂的最高指挥官,负责设定任务、调配资源并下令开工。
简单来说,uvm_test 不是一个具体的测试动作,而是一个可配置、可重用的"测试方案"或"作战计划"。它不直接驱动信号,而是通过配置环境、启动不同的"作战指令"(序列)来验证DUT的不同功能。
🎯 Test的本质:一个可执行的"验证方案"
你可以把验证计划 (Verification Plan)里的一条条功能点(Feature)想象成需要攻克的"战略目标"。一个 uvm_test 就是一个针对其中一个战略目标的完整作战计划。它包含:
- 作战环境:需要哪些"部队"(Agent)参与。
- 部队配置:各部队用什么"装备"、以什么"模式"(主动/被动)作战。
- 作战指令:具体的进攻顺序和策略(Sequence)。
- 战果评估:如何判断目标是否达成(通过Scoreboard等检查)。
🏗️ 编写一个UVM Test的四步核心流程
下图展示了创建一个"作战计划"(Test)从搭建指挥部到下达作战指令的完整流程:

第一步:建立指挥部
你的 Test 类需要从 uvm_test 继承,并进行工厂注册。注意,它虽然不叫 uvm_component,但本质上是一个顶级组件。
c
class my_base_test extends uvm_test;
`uvm_component_utils(my_base_test) // 使用组件宏注册
function new(string name = "my_base_test", uvm_component parent = null);
super.new(name, parent);
endfunction
// ... 后续步骤写在这里
endclass
第二步:组建与配置部队 (核心)
在 build_phase 中,你需要:
- 创建环境(
env)和配置对象(cfg)。 - 为配置对象赋值(如设置工作模式、获取虚拟接口等)。
- 通过
uvm_config_db将配置对象"下发"到环境中的具体Agent。
c
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 1. 创建
m_env = my_env::type_id::create("m_env", this);
m_cfg = my_cfg::type_id::create("m_cfg");
// 2. 配置
if (!uvm_config_db #(virtual dut_if)::get(this, "", "dut_vif", m_cfg.vif))
`uvm_error("CFG", "Interface not found!")
m_cfg.is_active = UVM_ACTIVE;
// 3. 下发
uvm_config_db #(my_cfg)::set(this, "m_env.m_agent", "cfg", m_cfg);
endfunction
第三步:战前检阅(调试)
在 end_of_elaboration_phase 中,所有组件已创建并连接好。此时打印拓扑结构,可以清晰地看到你搭建的整个"部队编制"是否正确。
c
virtual function void end_of_elaboration_phase(uvm_phase phase);
uvm_top.print_topology(); // 打印出完整的UVM组件树
endfunction
第四步:下达作战指令(核心)
在 run_phase(这是一个 task,会消耗仿真时间)中,你需要:
- 申请作战时间 :通过
raise_objection(this)防止仿真立刻结束。 - 创建并启动主序列:这是测试的灵魂,决定了要发送什么数据。
- 结束作战 :序列执行完毕后,
drop_objection(this)。
c
virtual task run_phase(uvm_phase phase);
my_main_sequence main_seq;
super.run_phase(phase); // 好习惯
phase.raise_objection(this);
main_seq = my_main_sequence::type_id::create("main_seq");
main_seq.start(m_env.v_sqr); // 通常启动在虚拟序列器上
phase.drop_objection(this);
endtask
🚀 如何"启动"一个Test:两种方式
Test 的启动不是在你代码里调用一个函数,而是在顶层模块(tb_top)的 initial 块中,通过 run_test() 这个全局任务来"召唤"。
方式一:代码内指定(不灵活)
c
initial begin
run_test("my_base_test"); // 固定运行 my_base_test
end
方式二(推荐):命令行指定
c
initial begin
run_test(); // 参数为空,从命令行获取
end
然后在仿真时通过命令行指定:
bash
# 使用不同的 +UVM_TESTNAME 来运行不同的测试,无需重新编译!
<simulator_command> +UVM_TESTNAME=my_base_test
<simulator_command> +UVM_TESTNAME=test_feature_a
<simulator_command> +UVM_TESTNAME=test_stress
这是UVM框架灵活性的重要体现,一定要掌握。
🔄 衍生测试:实现极致的重用
这是UVM测试策略最强大的地方。你不需要为每个功能点从头写一个Test,而是通过继承来复用和调整。
假设你有一个验证寄存器读写功能的测试 reg_test:
c
// 1. 基础测试:搭建通用环境,启动基础序列
class reg_test extends uvm_test;
`uvm_component_utils(reg_test)
reg_env m_env;
reg_cfg m_cfg;
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_env = reg_env::type_id::create("m_env", this);
m_cfg = reg_cfg::type_id::create("m_cfg");
m_cfg.reg_model = "RAL"; // 使用寄存器模型
uvm_config_db #(reg_cfg)::set(this, "*", "cfg", m_cfg);
endfunction
virtual task run_phase(uvm_phase phase);
base_reg_seq seq = base_reg_seq::type_id::create("seq");
phase.raise_objection(this);
seq.start(m_env.v_sqr);
phase.drop_objection(this);
endtask
endclass
// 2. 衍生测试A:复用环境,但更换更复杂的"作战指令"
class test_reg_stress extends reg_test; // 关键:继承
`uvm_component_utils(test_reg_stress)
virtual task run_phase(uvm_phase phase);
stress_reg_seq seq = stress_reg_seq::type_id::create("seq"); // 更换序列
phase.raise_objection(this);
seq.start(m_env.v_sqr); // 环境 m_env 是从父类继承来的!
phase.drop_objection(this);
endtask
endclass
// 3. 衍生测试B:复用环境和序列,但调整"部队配置"
class test_reg_passive extends reg_test;
`uvm_component_utils(test_reg_passive)
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase); // 先调用父类,完成通用搭建
m_cfg.is_active = UVM_PASSIVE; // 然后覆盖配置:将Agent设为被动模式
endfunction
endclass
⚠️ 核心要点与总结
| 概念 | 说明 | 关键点 |
|---|---|---|
uvm_test 角色 |
验证环境的总指挥和配置中心。 | 不直接干活,只负责"排兵布阵"。 |
| 配置下发 | 通过 uvm_config_db::set 在 build_phase 将配置对象(cfg)下发。 |
实现环境行为的动态控制。 |
| 启动序列 | 在 run_phase 中 create 并 start 序列。 |
必须使用 raise/drop_objection 包裹。 |
| 运行方式 | 通过 run_test() + +UVM_TESTNAME 命令行参数。 |
实现不修改代码、不重新编译即可切换测试。 |
| 测试重用 | 通过类继承创建衍生测试。 | 可重写 build_phase 以修改配置 ,或重写 run_phase 以更换主序列。 |
给你的最终建议:
现在,请将你之前搭建的 env、agent、sequence 组合起来。
- 创建一个
my_base_test,在build_phase中创建你的env。 - 在
run_phase中启动你的序列,并确保有raise/drop_objection。 - 在
tb_top中,使用run_test("my_base_test")启动仿真。 - 成功后,尝试用
run_test()+ 命令行参数+UVM_TESTNAME=my_base_test的方式再次运行。 - 最后,创建一个衍生测试,仅仅更换另一个不同的序列,并通过命令行运行它。
当你成功运行起第一个由Test指挥的完整UVM环境时,你就真正打通了UVM从底层到顶层的任督二脉。