3. UVM基础组件:从uvm_object到uvm_test
各位同学,今天我们来聊聊UVM里最基础、也最绕不开的几个组件。说实话,我刚学UVM那会儿,被uvm_component和uvm_object的区别搞得晕头转向。后来在项目里踩了几次坑,才算真正搞明白。
嗯,咱们就从最底层的uvm_object说起。
3.1 uvm_object:UVM世界的"细胞"
uvm_object是UVM所有数据类的基类。说白了,只要你想在UVM环境里传递数据、做配置、搞事务,都得从它派生。
我个人习惯把uvm_object理解成"轻量级对象"。它没有生命周期概念,不参与phase机制,也不会被自动构建。你什么时候想用,就什么时候new一个出来。
核心特点:
- 没有phase机制
- 不参与UVM树形结构
- 可以随时创建和销毁
- 支持copy、compare、print等通用方法
举个例子,我们常用的transaction、sequence_item,都是从uvm_sequence_item派生,而uvm_sequence_item又继承自uvm_object。
class my_transaction extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
这里要注意,uvm_object的构造函数只有一个name参数。我在项目中见过有人试图传parent进去,结果编译报错——嗯,那是uvm_component才有的待遇。
3.2 uvm_component:UVM世界的"器官"
uvm_component就不一样了。它是有生命周期的,参与phase调度,并且会挂载到UVM的树形结构里。
你可以这么理解:uvm_object是数据,uvm_component是处理数据的模块。数据可以随处飘,但模块必须待在树上。
我的经验: 判断一个类该继承哪个,就问自己一个问题——这个东西需要参与phase吗?需要被自动构建吗?如果答案是"是",就用uvm_component;否则用uvm_object。
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 在这里创建子组件
endfunction
endclass
看到没?构造函数有两个参数:name和parent。这个parent就是用来构建树形结构的。我曾经在重构代码时忘了传parent,结果UVM报了个"component not in tree"的警告——虽然能跑,但很多功能会失效。
3.3 uvm_agent:验证组件的"集装箱"
uvm_agent是个很有意思的组件。它本质上是一个容器,用来封装driver、monitor、sequencer这些"干活"的组件。
为什么需要agent?你想想看,一个接口协议可能有多个信号,每个信号都需要driver去驱动、monitor去采样。如果把这些组件散落在env里,管理起来会很混乱。agent就是用来做这件事的——把属于同一个接口的组件打包在一起。
agent的两种模式:
| 模式 | is_active | 包含组件 |
|---|---|---|
| 主动模式 | UVM_ACTIVE | driver + monitor + sequencer |
| 被动模式 | UVM_PASSIVE | monitor(只观察,不驱动) |
class my_agent extends uvm_agent;
my_driver drv;
my_monitor mon;
my_sequencer seqr;
`uvm_component_utils(my_agent)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
if(get_is_active() == UVM_ACTIVE) begin
drv = my_driver::type_id::create("drv", this);
seqr = my_sequencer::type_id::create("seqr", this);
end
endfunction
function void connect_phase(uvm_phase phase);
if(get_is_active() == UVM_ACTIVE) begin
drv.seq_item_port.connect(seqr.seq_item_export);
end
endfunction
endclass
这里有个细节:get_is_active()是uvm_agent自带的方法。我建议在顶层配置里设置agent的模式,而不是在agent内部硬编码。这样同一个agent可以复用在不同的场景里。
3.4 uvm_env:验证平台的"总装车间"
uvm_env是验证平台的顶层容器。它负责实例化所有的agent、reference model、scoreboard等组件,并完成它们之间的连接。
说白了,env就是整个验证环境的"总装车间"。你在env里把各个模块拼起来,连好线,然后test就去调用这个env。
class my_env extends uvm_env;
my_agent agt;
my_model mdl;
my_scoreboard scb;
`uvm_component_utils(my_env)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt = my_agent::type_id::create("agt", this);
mdl = my_model::type_id::create("mdl", this);
scb = my_scoreboard::type_id::create("scb", this);
endfunction
function void connect_phase(uvm_phase phase);
agt.mon.ap.connect(mdl.analysis_export);
mdl.ap.connect(scb.analysis_export);
endfunction
endclass
避坑指南: 我曾经在env里忘了调用super.build_phase(),结果所有子组件都没创建出来。UVM的phase机制要求父类必须调用super方法,否则子类的行为会异常。这个坑我踩过一次就再也没忘过。
3.5 uvm_test:验证的"总指挥"
uvm_test是验证测试的入口。它负责创建env,配置参数,启动sequence。每个测试用例都是一个uvm_test的派生类。
我个人习惯把test做得尽量轻量——只做配置和启动,具体的验证逻辑交给sequence和env去处理。这样换测试用例时,只需要改test里的配置参数就行。
class my_test extends uvm_test;
my_env env;
`uvm_component_utils(my_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(env.agt.seqr);
#100ns;
phase.drop_objection(this);
endtask
endclass
注意raise_objection和drop_objection。这是UVM控制仿真结束的机制。如果不raise objection,仿真可能在你还没开始跑sequence就结束了。我记得有次调试一个bug,折腾了半天才发现是objection没处理好。
3.6 组件关系图
说了这么多,咱们用一张图来总结这些组件的关系:
从这张图可以看得很清楚:uvm_test包含uvm_env,uvm_env包含uvm_agent,而uvm_agent又包含driver、monitor、sequencer。这些组件都继承自uvm_component,构成了UVM的树形结构。
至于uvm_object,它不在这个树里。它更像是"数据包",在组件之间传递。比如driver从sequencer拿到transaction(继承自uvm_object),然后驱动到接口上。
总结一下:
uvm_object:数据载体,轻量级,无phaseuvm_component:功能模块,有phase,在树中uvm_agent:接口级容器,封装driver/monitor/sequenceruvm_env:平台级容器,封装agent/model/scoreboarduvm_test:测试入口,配置env并启动sequence
嗯,这些组件搞清楚了,UVM的骨架也就搭起来了。后面咱们再往里面填充血肉——sequence、config_db、callback这些,就水到渠成了。