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里只做两件事:

  1. 创建子组件(用create)
  2. 读取配置(用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树形结构的示意图:

my_test (根) my_env my_agent my_scoreboard my_driver my_monitor my_sequencer my_coverage 图例说明 build_phase: 从上往下执行(test → env → agent → driver/monitor/sequencer) connect_phase: 从下往上执行(driver/monitor/sequencer → agent → env → test)

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从下往上浇水。把这个逻辑理清了,后面的内容就好理解了。


可可精品资料库