news 2026/2/23 7:32:30

11. UVM Test [uvm_test]

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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,但本质上是一个顶级组件

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中,你需要:

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

  1. 申请作战时间:通过raise_objection(this)防止仿真立刻结束。
  2. 创建并启动主序列:这是测试的灵魂,决定了要发送什么数据。
  3. 结束作战:序列执行完毕后,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::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从底层到顶层的任督二脉。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/14 22:06:12

m4s-converter:3分钟解决B站缓存播放难题的终极方案

m4s-converter&#xff1a;3分钟解决B站缓存播放难题的终极方案 【免费下载链接】m4s-converter 将bilibili缓存的m4s转成mp4(读PC端缓存目录) 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter 还在为B站缓存的m4s文件无法播放而烦恼吗&#xff1f;m4s-conv…

作者头像 李华
网站建设 2026/2/16 23:17:53

27、深入理解库 I/O 函数:原理、应用与实现

深入理解库 I/O 函数:原理、应用与实现 1. 库 I/O 函数算法 在文件操作中,库 I/O 函数起着至关重要的作用。下面详细介绍几个关键库 I/O 函数的算法。 - fread 算法 - 首次调用 :当首次调用 fread() 时, FILE 结构的缓冲区为空。它会使用保存的文件描述符 fd …

作者头像 李华
网站建设 2026/2/17 20:09:57

FastAPI 路由系统深度探索:超越基础 CRUD 的高级模式与架构实践

FastAPI 路由系统深度探索&#xff1a;超越基础 CRUD 的高级模式与架构实践 引言&#xff1a;为什么需要深入研究 FastAPI 路由&#xff1f; FastAPI 作为现代 Python Web 框架&#xff0c;以其卓越的性能、直观的类型提示和自动 API 文档生成而广受欢迎。大多数教程停留在基础…

作者头像 李华
网站建设 2026/2/18 7:28:41

Python数据可视化进阶:超越基础图表,构建专业级数据叙事

Python数据可视化进阶&#xff1a;超越基础图表&#xff0c;构建专业级数据叙事 在数据科学领域&#xff0c;可视化远不止是生成图表那么简单&#xff0c;它是数据探索、分析与叙事的关键桥梁。尽管Matplotlib、Seaborn等传统库为人熟知&#xff0c;但现代数据可视化需求已超越…

作者头像 李华
网站建设 2026/2/6 19:25:06

Player.js 终极指南:掌控嵌入式视频播放的完整教程

Player.js 终极指南&#xff1a;掌控嵌入式视频播放的完整教程 【免费下载链接】player.js Interact with and control an embedded Vimeo Player. 项目地址: https://gitcode.com/gh_mirrors/pl/player.js Player.js 是一个强大的 JavaScript 库&#xff0c;专门用于与…

作者头像 李华
网站建设 2026/2/18 8:52:34

35、I/O 缓冲区管理算法:从 Unix 到新算法的演进

I/O 缓冲区管理算法:从 Unix 到新算法的演进 1. 异步写入与物理块设备 I/O 1.1 异步写入函数 awrite 异步写入函数 awrite 用于启动对缓冲区的异步 I/O 操作,其代码如下: awrite(BUFFER *bp) {bp->opcode = ASYNC;// for ASYNC write;start_io(bp); }awrite 调…

作者头像 李华