2. UVM基础组件回顾:uvm_component、uvm_object、uvm_agent、uvm_env、uvm_test

好,咱们直接进入正题。这一节我打算带大家快速过一遍UVM里最核心的几个基础组件。你可能会说:“这些我早就会了。” 但别急,我重点讲的是它们在重用架构设计中的定位和用法。说白了,就是告诉你每个组件“为什么存在”,而不仅仅是“怎么用”。

2.1 uvm_object:一切数据的根

先聊聊 uvm_object。它是UVM里所有数据对象的基类。你想想看,无论是transaction、sequence item,还是configuration对象,都是从它派生出来的。

核心特点:

  • 它不是UVM树形结构的一部分,没有parent
  • 支持copy、compare、print、record等基本操作
  • 生命周期由用户管理,不自动创建和销毁

我个人习惯:所有需要被sequence或driver处理的数据类,都从uvm_sequence_item派生,而uvm_sequence_item本身又是uvm_transaction的子类,最终根还是uvm_object。

举个例子,你定义一个简单的packet:

class my_packet extends uvm_sequence_item;
  rand bit [7:0] addr;
  rand bit [31:0] data;
  rand bit       wr_en;

  `uvm_object_utils_begin(my_packet)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(data, UVM_ALL_ON)
    `uvm_field_int(wr_en, UVM_ALL_ON)
  `uvm_object_utils_end

  function new(string name = "my_packet");
    super.new(name);
  endfunction
endclass

嗯,这里要注意:uvm_object的构造函数只有一个name参数。没有parent参数。这是它和uvm_component最直观的区别。

避坑指南:我曾经在项目中看到有人把transaction的new函数写成new(string name, uvm_component parent),结果编译报错。记住,uvm_object没有parent概念。

2.2 uvm_component:UVM树上的节点

接下来是uvm_component。它和uvm_object最大的区别是什么?它有生命周期,有parent,有phase机制

说白了,所有需要“一直存在”的东西,比如driver、monitor、agent、env、test,都是component。它们构成了UVM的树形结构。

关键点:

  • 构造函数有两个参数:name和parent
  • 参与UVM的phase执行(build_phase、connect_phase等)
  • 自动创建和销毁,由UVM框架管理
class my_driver extends uvm_driver #(my_packet);
  `uvm_component_utils(my_driver)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // 在这里创建子组件
  endfunction
endclass

你想想看,为什么driver要继承uvm_component而不是uvm_object?因为它需要持续监控接口,需要响应reset,需要和sequencer通信。这些都需要phase机制的支持。

2.3 uvm_agent:把driver和monitor打包

uvm_agent是一个很实用的封装。它把driver、sequencer、monitor打包在一起。我经常把它比作一个“接口代理”。

两种模式:

模式 is_active 包含组件
主动模式 UVM_ACTIVE driver + sequencer + monitor
被动模式 UVM_PASSIVE 仅monitor

我在项目中遇到过:一个AHB总线agent,在验证环境里用主动模式驱动总线,在系统级环境里用被动模式只做监听。只需要改一个is_active参数,agent内部自动调整。这就是重用的精髓。

class my_agent extends uvm_agent;
  my_driver    driver;
  my_sequencer sequencer;
  my_monitor   monitor;

  `uvm_component_utils(my_agent)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    monitor = my_monitor::type_id::create("monitor", this);
    if (get_is_active() == UVM_ACTIVE) begin
      driver    = my_driver::type_id::create("driver", this);
      sequencer = my_sequencer::type_id::create("sequencer", this);
    end
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    if (get_is_active() == UVM_ACTIVE) begin
      driver.seq_item_port.connect(sequencer.seq_item_export);
    end
  endfunction
endclass

2.4 uvm_env:验证环境的“容器”

uvm_env是验证环境的顶层容器。它里面放什么?放agent、放reference model、放scoreboard、放coverage collector。说白了,它就是整个验证平台的“骨架”。

设计原则:

  • env不包含任何协议相关的逻辑
  • env只负责实例化和连接子组件
  • env可以被上层env嵌套使用

我的建议:不要把env写得太大。一个env只负责一个协议或一个接口的验证环境。如果你有多个接口,用多个env,然后在test里组合它们。

class my_env extends uvm_env;
  my_agent    agent;
  my_scoreboard sb;
  my_coverage  cov;

  `uvm_component_utils(my_env)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agent = my_agent::type_id::create("agent", this);
    sb    = my_scoreboard::type_id::create("sb", this);
    cov   = my_coverage::type_id::create("cov", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    agent.monitor.item_port.connect(sb.analysis_export);
    agent.monitor.item_port.connect(cov.analysis_export);
  endfunction
endclass

2.5 uvm_test:验证的“入口”

最后是uvm_test。它是整个验证环境的入口点。UVM框架从test开始,自动执行build_phase创建整个环境树。

test的职责:

  • 创建env(通常只创建一个)
  • 配置env的参数(通过config_db)
  • 启动sequence(在run_phase里)
  • 设置超时和结束条件
class my_base_test extends uvm_test;
  my_env env;

  `uvm_component_utils(my_base_test)

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // 设置agent为主动模式
    uvm_config_int::set(this, "env.agent", "is_active", UVM_ACTIVE);
    env = my_env::type_id::create("env", this);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_sequence seq;
    phase.raise_objection(this);
    seq = my_sequence::type_id::create("seq");
    seq.start(env.agent.sequencer);
    #100ns;
    phase.drop_objection(this);
  endtask
endclass

注意:我曾经犯过一个错误,在test里创建了多个env实例,结果config_db的路径全乱了。记住,一个test通常只创建一个env。如果你需要多个环境,用env嵌套,而不是test里创建多个env。

小结一下

这五个组件的关系,我画个简单的图给你看:

  • uvm_object:数据载体,无生命周期
  • uvm_component:有生命周期的节点,构成树
  • uvm_agent:接口代理,封装driver+monitor
  • uvm_env:环境容器,组装agent和验证组件
  • uvm_test:测试入口,启动一切

你想想看,这个层次结构是不是很清晰?从数据到接口,从接口到环境,从环境到测试。每一层都有自己的职责,互不重叠。这就是UVM架构设计的核心思想——关注点分离

下一节我会深入讲uvm_agent的重用设计模式,包括如何设计一个可配置、可重用的agent。到时候我会分享一些实际项目中的经验教训,保证让你少踩坑。