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。到时候我会分享一些实际项目中的经验教训,保证让你少踩坑。