UVM 基础

第 1 章 · 共 10 章

UVM 入门:它到底解决了什么问题?

从零理解 UVM 为何存在、testbench 的分层结构,以及 uvm_component 的基本写法。

SystemVerilog 基础系列的结尾,我们已经搭出一个能跑通的 testbench:一个持有 virtual mux2_if.tb_mp vif 句柄的 driver 类,通过 mailboxgenerator() 任务解耦,两者用 fork...join 并发运行(SV 基础系列第 16 章的 capstone)。它能跑通。那还缺什么?

UVM(Universal Verification Methodology) 是建立在 SystemVerilog 面向对象特性之上的一套标准化验证方法学——它不是新语言。但与其空泛地这样断言,不如问一个更尖锐的问题:一个已经能跑通的 testbench,到底还差什么?

  1. 没有标准的组件层次结构。 capstone 里的 drivergenerator() 只是一个类和一个 task,没有统一的命名规则,没有内置的详细级别(verbosity)控制,也没有任何工具能程序化地遍历它们来做报告或调试。UVM 的 uvm_component 树(第 2 章)免费给 testbench 的每个部件一个名字、一个父节点,以及在标准化层次结构中的位置。
  2. 没有标准的替换机制。 现在要测试不同的 driver 行为,只能直接改 driver 类的代码——没有办法让某个 test 在不动 testbench 结构代码的前提下替换成另一种实现。UVM 的 Factory 机制(第 6 章)正是为此而生:把类注册一次,之后任何 test 都能在不改动环境代码的情况下替换它。
  3. 测 N 个场景就要改 N 次或复制 N 份。 capstone 里的 generator() 把"生成 5 个随机事务"写死了——测试不同的流量模式就得改这个 task,或者复制一整份 testbench。UVM 的 Sequence 机制(第 7 章,第 10 章组装成完整环境)把激励变成一层独立、可替换的东西,不同的 test 可以复用同一个环境,只换 sequence。
  4. 没有能交给别的团队或跨项目复用的东西。 generator()driver 之间基于 mailbox 的握手是这个 testbench 专属的——没法原样搬到别的项目里。UVM 通过 TLM(Transaction Level Modeling)端口(第 8 章)把这种通信标准化,driver/monitor 这一对搭好之后,可以原封不动地在别的环境里复用。

这四点——标准化的组件分层、factory、sequence、TLM——就是 UVM 的全部内容。

Testbench 的典型分层

一个典型的 UVM testbench 层级大致如下:

uvm_test
  └── uvm_env
        ├── uvm_agent
        │     ├── uvm_sequencer   (产生/管理事务)
        │     ├── uvm_driver      (把事务转换为引脚级信号,驱动 DUT)
        │     └── uvm_monitor     (从引脚采样,还原成事务)
        └── uvm_scoreboard        (比对预期结果与实际结果)

uvm_agent 通常包含 sequencer、driver、monitor 三件套,可以配置为 active(既驱动又监测)或 passive(只监测,常用于总线一侧无需驱动的场景)。

一个最简单的 uvm_component

UVM 中几乎所有结构性部件都继承自 uvm_component。下面是一个只打印日志的最简驱动骨架:

class simple_driver extends uvm_driver #(my_transaction);
  `uvm_component_utils(simple_driver)
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction
 
  task run_phase(uvm_phase phase);
    forever begin
      my_transaction tr;
      seq_item_port.get_next_item(tr);
      `uvm_info("DRV", $sformatf("driving transaction: %s", tr.convert2string()), UVM_MEDIUM)
      // 此处将 tr 中的字段转换为对 DUT 引脚的实际驱动
      seq_item_port.item_done();
    end
  endtask
endclass

几个关键点:

  • `uvm_component_utils(simple_driver) 向 UVM 的 Factory 注册这个类,使其可以在不修改代码的情况下被子类覆盖(override)。
  • new(string name, uvm_component parent) 是所有 uvm_component 的标准构造函数签名,parent 用于维护组件树。
  • run_phase 是 UVM 的运行期 phase 之一,driver 通常在这里通过 seq_item_port 从 sequencer 拿到事务并驱动 DUT。

uvm_component 与 uvm_object 的区别

  • uvm_component:testbench 的结构性部件(driver、monitor、env、test 等),在整个仿真期间存在,构造时必须传入 nameparent,参与 UVM 的 phase 机制。
  • uvm_object:轻量级数据对象(如事务 transaction、配置对象),生命周期更灵活,构造函数一般只需要 name,不参与 phase。

一个典型的 uvm_agent 通常包含哪三个核心子组件?

以下哪一项是 uvm_component 与 uvm_object 的关键区别?