4、UVM基础入门:UVM是什么、UVM的核心类、UVM树形结构

4.1 UVM到底是什么?

UVM,全称是Universal Verification Methodology。说白了,它就是一套被业界公认的验证方法学标准。你想想看,如果没有UVM,我们写验证环境就像是在原始森林里开路——每个人都有自己的写法,代码风格千奇百怪,换个项目就得重写一遍。

我个人习惯把UVM理解成一个「验证乐高」。它提供了一堆标准化的积木块(也就是那些核心类),你只需要按照规则把它们拼起来,就能搭出一个结构清晰、可复用的验证环境。我在2015年刚接触UVM时,觉得这东西太复杂了,各种类、各种宏,看得头大。但真正用起来之后,我发现它解决了一个核心痛点:验证环境的可重用性

UVM的核心价值:

  • 标准化:全球验证工程师用同一套规则交流
  • 可重用:一个组件可以在多个项目中复用
  • 自动化:提供了sequence、scoreboard等自动化机制
  • 可扩展:基于OVM发展而来,继承了其优秀的架构

嗯,这里要注意,UVM不是一种语言,它是基于SystemVerilog的一套类库。你写UVM代码,本质上还是在写SystemVerilog,只是调用了UVM提供的那些类和方法。

4.2 UVM的核心类

UVM的类库非常庞大,但真正核心的、你每天都会打交道的,其实就那么几个。我给大家梳理一下,这些类就像是验证环境的「骨架」。

核心类 中文俗称 一句话说明
uvm_component 组件基类 所有验证组件的祖宗,有生命周期,能构建树形结构
uvm_object 对象基类 没有生命周期的数据对象,比如transaction、sequence_item
uvm_driver 驱动器 负责把sequence产生的transaction驱动到DUT接口上
uvm_monitor 监视器 被动地观察DUT接口,收集数据给scoreboard
uvm_agent 代理 把driver和monitor打包在一起,方便复用
uvm_sequencer 序列发生器 负责调度sequence,把transaction发给driver
uvm_scoreboard 计分板 比对期望值和实际值,判断验证是否通过
uvm_env 环境 把agent、scoreboard等组件组装起来的顶层容器
uvm_test 测试用例 每个测试用例的入口,配置整个验证环境

我曾经在一个项目中,新人把uvm_object和uvm_component搞混了,结果transaction里写了build_phase,编译报错半天找不到原因。记住:有phase的用uvm_component,纯数据的用uvm_object,这个区分很关键。

我的小技巧:刚开始学UVM时,别急着把所有类都搞懂。先抓住uvm_component、uvm_object、uvm_driver、uvm_monitor这四个,其他的慢慢来。就像学开车,先学会挂挡、踩油门、打方向盘,再学漂移。

4.3 UVM树形结构

UVM的树形结构,说白了就是验证环境的「组织架构图」。为什么需要树形结构?因为一个复杂的验证环境,可能有几十个甚至上百个组件。如果没有一个清晰的层级关系,管理起来就是一团乱麻。

UVM的树形结构有几个特点:

  • 根节点是uvm_top:整个验证环境的根,由UVM自动创建
  • 每个组件都有唯一的路径名:比如"uvm_test_top.env.agent.driver"
  • 父组件管理子组件的生命周期:父组件创建、配置、销毁子组件
  • 通过名字查找组件:使用uvm_top.find()可以按路径名找到任意组件

我给大家画一张图,看看典型的UVM树形结构长什么样:

UVM树形结构示意图 uvm_top (根) my_test (uvm_test) my_env (uvm_env) my_agent (uvm_agent) sb (scoreboard) my_monitor (monitor) my_driver (driver) my_sequencer (sequencer) 每个组件都有唯一的路径名,例如:uvm_top.my_test.my_env.my_agent.my_driver

你看这张图,从uvm_top开始,一层一层往下展开。每个组件都知道自己的父亲是谁,孩子是谁。这种树形结构带来了几个好处:

  • 配置传递:父组件的配置可以自动传递给子组件
  • phase同步:所有组件的phase按照树形结构顺序执行
  • 资源管理:UVM的资源池可以按路径名精确查找
  • 调试方便:打印整个树形结构,一眼就能看出环境搭得对不对

我曾经踩过的坑:有一次我在build_phase里手动new了一个子组件,但没有调用super.build_phase()。结果那个子组件虽然存在,但UVM的树形结构里没有它,导致phase无法自动执行。排查了整整一个下午才发现问题。记住:创建组件一定要用create()方法,不要直接new,这样才能保证组件被正确注册到树形结构中。

4.4 树形结构的代码体现

说了这么多理论,咱们看看代码里树形结构是怎么体现的。下面是一个简单的env代码片段:

class my_env extends uvm_env;
    `uvm_component_utils(my_env)
    
    my_agent    agt;
    my_scoreboard sb;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // 使用create方法创建子组件
        agt = my_agent::type_id::create("agt", this);
        sb  = my_scoreboard::type_id::create("sb", this);
    endfunction
endclass

注意看create方法的第二个参数——this。这个this就是当前组件的指针,它告诉UVM:「我是你爸爸」。UVM内部会把这个父子关系记录下来,构建成树形结构。

我个人习惯在build_phase的最后,用`uvm_info打印一下当前组件的层次路径,方便调试:

function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("BUILD", $sformatf("当前路径: %s", get_full_name()), UVM_LOW)
    // ... 创建子组件
endfunction

这样在仿真日志里,你就能看到类似这样的输出:

UVM_INFO @ 0: uvm_top.my_test.my_env [BUILD] 当前路径: uvm_top.my_test.my_env

嗯,这个技巧帮我快速定位过很多组件创建顺序的问题,建议大家也养成这个习惯。

关于树形结构的几个关键点:

  1. 所有组件最终都挂在uvm_top下面,它是唯一的根
  2. 组件的名字在创建时指定,同一层不能重名
  3. 通过get_parent()可以拿到父组件指针
  4. 通过get_child()可以按名字拿到子组件指针
  5. 打印整个树:调用uvm_top.print()即可

最后说一句,UVM的树形结构不是UVM独有的,很多软件框架都有类似的设计模式。但UVM把它和硬件验证的生命周期管理结合得特别好,这也是它成为业界标准的重要原因之一。


公众号:蓝海资料掘金营,微信deep3321