11. UVM Test [uvm_test]

现在,你已经准备好学习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 中,你需要:

  1. 创建环境(env)和配置对象(cfg)
  2. 为配置对象赋值(如设置工作模式、获取虚拟接口等)。
  3. 通过 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,会消耗仿真时间)中,你需要:

  1. 申请作战时间 :通过 raise_objection(this) 防止仿真立刻结束。
  2. 创建并启动主序列:这是测试的灵魂,决定了要发送什么数据。
  3. 结束作战 :序列执行完毕后,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::setbuild_phase 将配置对象(cfg)下发。 实现环境行为的动态控制。
启动序列 run_phasecreatestart 序列。 必须使用 raise/drop_objection 包裹。
运行方式 通过 run_test() + +UVM_TESTNAME 命令行参数 实现不修改代码、不重新编译即可切换测试。
测试重用 通过类继承创建衍生测试。 重写 build_phase 以修改配置 ,或重写 run_phase 以更换主序列

给你的最终建议:

现在,请将你之前搭建的 envagentsequence 组合起来。

  1. 创建一个 my_base_test,在 build_phase 中创建你的 env
  2. run_phase 中启动你的序列,并确保有 raise/drop_objection
  3. tb_top 中,使用 run_test("my_base_test") 启动仿真。
  4. 成功后,尝试用 run_test() + 命令行参数 +UVM_TESTNAME=my_base_test 的方式再次运行。
  5. 最后,创建一个衍生测试,仅仅更换另一个不同的序列,并通过命令行运行它。

当你成功运行起第一个由Test指挥的完整UVM环境时,你就真正打通了UVM从底层到顶层的任督二脉。

相关推荐
RisunJan2 小时前
【行测】类比推理-自称他称全同
学习
wan55cn@126.com2 小时前
人类文明可通过技术手段(如加强航天器防护、改进电网设计)缓解地球两极反转带来的影响
人工智能·笔记·搜索引擎·百度·微信
石像鬼₧魂石3 小时前
Termux ↔ Windows 靶机 反向连接实操命令清单
linux·windows·学习
非凡ghost3 小时前
JRiver Media Center(媒体管理软件)
android·学习·智能手机·媒体·软件需求
测绘小沫-北京云升智维3 小时前
无人机图传信号中断怎么办?
经验分享·无人机
会飞的土拨鼠呀3 小时前
docker部署 outline(栗子云笔记)
笔记·docker·容器
_Minato_3 小时前
数据库知识整理——数据库设计的步骤
数据库·经验分享·笔记·软考
hssfscv4 小时前
Mysql学习笔记——事务
笔记·学习·mysql
charlie1145141914 小时前
现代C++工程实践:简单的IniParser3——改进我们的split
开发语言·c++·笔记·学习