3、UVM树形结构:组件层次化构建、parent-child关系、build_phase与connect_phase
好,咱们今天聊聊UVM里最基础也最核心的一个概念——树形结构。
说白了,UVM的验证环境就是一棵树。你想想看,一棵树有根、有干、有枝、有叶。UVM环境也一样,从顶层的test开始,往下挂env,env里挂agent、scoreboard、coverage collector,agent里再挂driver、monitor、sequencer。一层套一层,清清楚楚。
我刚开始学UVM的时候,总觉得这玩意儿有点多余。直接new一个对象不就完了吗?干嘛非得搞这么复杂?后来做项目做多了才明白——没有这棵树,你的环境就是一盘散沙,组件之间谁是谁的爹都搞不清楚,更别提什么phase执行顺序了。
3.1 为什么要用树形结构?
UVM用树形结构,核心原因有三个:
- 生命周期管理:父组件负责创建和销毁子组件,你不需要手动管理内存
- phase自动执行:build_phase从根节点往下执行,connect_phase从叶子节点往上执行,顺序是固定的
- 资源共享:子组件可以通过
get_parent()找到父组件,兄弟组件之间也能通过共同的父节点互相访问
核心要点:UVM树中的每个节点都是uvm_component的子类。只有uvm_component才能参与树形结构,uvm_object不行。
3.2 parent-child关系怎么建立?
这个其实很简单。每个组件在创建的时候,都要指定它的父组件。看代码:
class my_env extends uvm_env;
my_agent m_agent;
my_scoreboard m_sb;
function new(string name, uvm_component parent);
super.new(name, parent); // 关键!把parent传进去
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
m_agent = my_agent::type_id::create("m_agent", this); // this就是父组件
m_sb = my_scoreboard::type_id::create("m_sb", this);
endfunction
endclass
看到没?create的第二个参数就是父组件。你传this,那当前组件就是父节点。如果你传null,那这个组件就成了孤儿,不挂在树上。
注意:我曾经见过有人把父组件传错了,结果build_phase执行顺序全乱套。子组件的build_phase会在父组件的build_phase里执行,如果你把父组件设错了,那phase调度就全崩了。
3.3 build_phase:从上往下建树
build_phase的执行顺序是从根到叶子。什么意思呢?就是先执行test的build_phase,再执行env的,再执行agent的,最后执行driver、monitor的。
为什么会这样?你想想看,父组件得先把自己创建好,才能去创建子组件吧?如果env还没创建,你怎么往env里挂agent?所以这个顺序是合理的。
我个人习惯在build_phase里只做两件事:
- 创建子组件(用
create) - 读取配置(用
config_db)
别在build_phase里做太多计算或者连线的操作,那是connect_phase的事。
3.4 connect_phase:从下往上连线
connect_phase的执行顺序正好反过来——从叶子到根。
为什么?因为连线需要两端的组件都已经存在。driver和monitor先创建好,然后agent才能把它们连起来。agent连好了,env才能把agent和scoreboard连起来。
看个例子:
function void my_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
// 把sequencer的export和driver的export连起来
m_driver.seq_item_port.connect(m_sequencer.seq_item_export);
endfunction
嗯,这里要注意:connect_phase里只能做连线操作,别再去创建对象了。对象在build_phase里就该创建完了。
3.5 树形结构的实际应用
我在项目中遇到过一个问题:多个agent需要共享同一个reference model。如果不用树形结构,你得手动把reference model的指针传来传去,烦得很。
用树形结构就简单了:把reference model挂在env下面,所有agent通过get_parent()找到env,再拿到reference model的句柄。
class my_agent extends uvm_agent;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 通过父组件找到reference model
my_env env;
$cast(env, get_parent());
ref_model = env.ref_model;
endfunction
endclass
小技巧:用get_parent()的时候一定要做类型转换($cast),因为get_parent()返回的是uvm_component基类指针。不做cast的话,你拿不到子类的成员变量。
3.6 树形结构可视化
为了让你更直观地理解,我画了一张UVM树形结构的示意图:
3.7 常见坑点与避坑指南
- 忘记调用super.build_phase():这是新手最容易犯的错。如果不调super,父类的build_phase不会执行,子组件就创建不出来。
- 在build_phase里做connect_phase的事:我曾经见过有人在build_phase里直接连TLM端口,结果连的时候子组件还没创建完,连了个寂寞。
- 父组件传null:除非你明确知道自己在做什么,否则永远不要传null。传null意味着这个组件不参与树形结构,phase调度会出问题。
- 用new而不是create:
type_id::create会自动处理工厂覆盖和父组件注册,用new的话这些功能就都没了。
我的建议:刚开始学UVM的时候,先把树形结构画出来。画清楚每个组件的父子关系,再动手写代码。这样build_phase和connect_phase的执行顺序就一目了然了。
好了,树形结构就聊到这儿。记住一句话:UVM环境是一棵树,build_phase从上往下种树,connect_phase从下往上浇水。把这个逻辑理清了,后面的内容就好理解了。