现在,你已经准备好学习UVM的“总指挥”了——uvm_test。它是整个验证工厂的最高指挥官,负责设定任务、调配资源并下令开工。
简单来说,uvm_test不是一个具体的测试动作,而是一个可配置、可重用的“测试方案”或“作战计划”。它不直接驱动信号,而是通过配置环境、启动不同的“作战指令”(序列)来验证DUT的不同功能。
🎯 Test的本质:一个可执行的“验证方案”
你可以把验证计划(Verification Plan)里的一条条功能点(Feature)想象成需要攻克的“战略目标”。一个uvm_test就是一个针对其中一个战略目标的完整作战计划。它包含:
- 作战环境:需要哪些“部队”(Agent)参与。
- 部队配置:各部队用什么“装备”、以什么“模式”(主动/被动)作战。
- 作战指令:具体的进攻顺序和策略(Sequence)。
- 战果评估:如何判断目标是否达成(通过Scoreboard等检查)。
🏗️ 编写一个UVM Test的四步核心流程
下图展示了创建一个“作战计划”(Test)从搭建指挥部到下达作战指令的完整流程:
第一步:建立指挥部
你的 Test 类需要从uvm_test继承,并进行工厂注册。注意,它虽然不叫uvm_component,但本质上是一个顶级组件。
class my_base_test extends uvm_test;`uvm_component_utils(my_base_test)// 使用组件宏注册functionnew(string name=“my_base_test”,uvm_component parent=null);super.new(name,parent);endfunction// ... 后续步骤写在这里endclass第二步:组建与配置部队 (核心)
在build_phase中,你需要:
- 创建环境(
env)和配置对象(cfg)。 - 为配置对象赋值(如设置工作模式、获取虚拟接口等)。
- 通过
uvm_config_db将配置对象“下发”到环境中的具体Agent。
virtual functionvoidbuild_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中,所有组件已创建并连接好。此时打印拓扑结构,可以清晰地看到你搭建的整个“部队编制”是否正确。
virtual functionvoidend_of_elaboration_phase(uvm_phase phase);uvm_top.print_topology();// 打印出完整的UVM组件树endfunction第四步:下达作战指令(核心)
在run_phase(这是一个task,会消耗仿真时间)中,你需要:
- 申请作战时间:通过
raise_objection(this)防止仿真立刻结束。 - 创建并启动主序列:这是测试的灵魂,决定了要发送什么数据。
- 结束作战:序列执行完毕后,
drop_objection(this)。
virtual taskrun_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()这个全局任务来“召唤”。
方式一:代码内指定(不灵活)
initial beginrun_test(“my_base_test”);// 固定运行 my_base_testend方式二(推荐):命令行指定
initial beginrun_test();// 参数为空,从命令行获取end然后在仿真时通过命令行指定:
# 使用不同的 +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:
// 1. 基础测试:搭建通用环境,启动基础序列class reg_test extends uvm_test;`uvm_component_utils(reg_test)reg_env m_env;reg_cfg m_cfg;virtual functionvoidbuild_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 taskrun_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 taskrun_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 functionvoidbuild_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从底层到顶层的任督二脉。