来源:qingxuanxx/sv_uvm_axi_notes · Copyright (c) 2026 qingxuanxx。按 MIT 许可 引用;内容版本:aa3213b。本站调整标题、栏目导航与阅读链接。

返回 UVM 学习笔记目录 · 上一章 · 下一章

第 3 章 · 组件层次与 config_db 配置

核心结论:UVM 以 uvm_object 作为大多数类的共同基础,以 uvm_component 构造长期存在的验证平台树;field automation 负责对象字段的通用操作,report 机制负责日志策略,config_db 负责沿 UVM 路径传递配置。

记忆主线:先分清 object/component -> 再看 component 怎样组成树 -> 再学字段自动化 -> 再学日志过滤与行为 -> 最后掌握 config_db 的路径、覆盖优先级和调试。


3.0 本章定位

第 2 章已经搭出完整平台,第 3 章开始解释平台能够运行的基础机制。 本章五条主线:

章节核心问题
3.1什么类应该是 object,什么类应该是 component
3.2parent 和实例名如何形成 UVM 树
3.3如何自动 copy、compare、print、pack、unpack
3.4如何过滤、重载、计数、停止和分流 UVM 日志
3.5config_db 如何定位组件、决定优先级并完成配置

本章最容易混淆的三组概念:

  1. 变量名、实例名和完整路径。
  2. 类型继承关系与 UVM 树的父子关系。
  3. config_db 的层次优先级与同层时间优先级。

版本背景:教材示例主要基于 UVM 1.1d。涉及 starting_phase、set_config_* 等内容时,应注意不同 UVM 版本的差异。

零基础阅读提示

  • uvm_object 可以理解成普通数据对象,不会长期挂在验证平台树上。
  • uvm_component 可以理解成平台里的固定设备,会组成一棵树并自动经历 phase。
  • parent 决定组件挂在哪里,name 决定它在这一层叫什么。
  • field automation 是帮你自动完成 copy、compare、print 等重复劳动。
  • config_db 类似按“类型 + 路径 + 字段名”投递配置,任意一项不匹配都会取不到。

读代码时先判断当前类是 object 还是 component,再看它的创建方式和生命周期,很多困惑会自然消失。


3.1 uvm_component 与 uvm_object(🔴 高)

3.1.1 uvm_component 派生自 uvm_object(🔴 高)

uvm_component 与 uvm_object 不是并列关系。 真正的关系是:

uvm_void
└── uvm_object
    ├── uvm_transaction
    │   └── uvm_sequence_item
    │       └── uvm_sequence_base
    │           └── uvm_sequence #(REQ, RSP)
    └── uvm_report_object
        └── uvm_component
            ├── uvm_driver
            ├── uvm_monitor
            ├── uvm_agent
            ├── uvm_env
            ├── uvm_scoreboard
            ├── uvm_test
            └── uvm_root

因此:

  • 所有 component 都是 object。
  • object 具有的许多基础能力,component 也继承到了。
  • 不是所有 object 都是 component。
  • 只有 component 能成为 UVM component 树的结点。

component 比普通 object 多出的核心能力

能力uvm_objectuvm_component
factory 注册与创建支持支持
print/copy/compare支持继承支持,但部分操作受限制
name支持支持
parent没有 component parent有
UVM 树结点否是
phase 自动执行否是
report 层次控制基础能力可沿 component 层次控制
config 层次查找不作为普通树结点支持

component 两个最有代表性的特性:

  1. 构造时通过 parent 建立树形关系。
  2. UVM 自动调用其 phase 方法。

类型继承与结构层次不要混淆 类型继承回答:

my_driver 是哪种类?
my_driver extends uvm_driver

结构层次回答:

drv 实例挂在哪个 component 下面?
uvm_test_top.env.i_agt.drv

同一个 my_driver 类型可以在不同 agent 中创建多个实例,它们类型相同,路径不同。

3.1.2 常用的派生自 uvm_object 的类(🔴 高)

除 component 分支外,验证环境中的许多短生命周期对象都继承自 uvm_object。

类别常见基类主要作用
transactionuvm_sequence_item封装一次协议事务
sequenceuvm_sequence组织并产生 sequence item
config objectuvm_object集中保存某个组件或环境的配置
register itemuvm_reg_item寄存器访问事务
register model 类uvm_reg*描述寄存器、字段、存储器和地址映射
phase objectuvm_phase表示和控制 phase

transaction 应继承 uvm_sequence_item 虽然 UVM 中存在 uvm_transaction,用户定义的 transaction 通常仍应直接继承 uvm_sequence_item。

class packet extends uvm_sequence_item;
    rand bit [47:0] dmac;         // 目的 MAC,rand 表示可参与随机化
    rand bit [47:0] smac;         // 源 MAC
    rand byte       payload[];    // 动态数组,长度也可由约束控制
 
    `uvm_object_utils(packet)     // 注册 object,后续可用 factory 创建
 
    function new(string name = "packet");
        super.new(name);          // object 没有 parent 参数
    endfunction
endclass

原因:

  • uvm_sequence_item 已经继承 uvm_transaction。
  • 它增加了 sequence/sequencer 握手所需信息。
  • 可以直接用于 uvm_sequence #(packet) 和 uvm_driver #(packet)。

sequence 也是 object

class packet_sequence extends uvm_sequence #(packet);
    `uvm_object_utils(packet_sequence) // sequence 本身也是 object
 
    function new(string name = "packet_sequence");
        super.new(name);
    endfunction
 
    task body();
        packet req;
 
        repeat (10) begin
            `uvm_do(req)          // 创建、随机化并发送一个 packet
        end
    endtask
endclass

sequence:

  • 不进入 component 树。
  • 没有 parent 参数。
  • 启动时与 sequencer 建立临时关系。
  • body 执行完成后,其任务生命周期结束。

config object 与 config_db 的区别 二者不是同一个概念。

名称含义
config object用户定义的配置对象,把多个参数组织在一个类中
config_dbUVM 提供的传递配置的机制

典型做法:

class agent_config extends uvm_object;
    uvm_active_passive_enum is_active; // 控制 agent 是否包含 driver/sequencer
    int pre_num;                       // 项目自定义配置参数
    virtual my_if vif;                 // 指向 top 中的真实 interface
 
    `uvm_object_utils(agent_config)    // 配置对象也建议通过 factory 创建
 
    function new(string name = "agent_config");
        super.new(name);
    endfunction
endclass

然后把整个 config object 通过 config_db 传给 agent。

3.1.3 常用的派生自 uvm_component 的类(🔴 高)

uvm_driver driver 把 transaction 转换成 DUT 接口信号。 参数化 driver 中常用的内建成员包括:

成员作用
seq_item_port从 sequencer 拉取请求并完成握手
req当前请求 item
rsp可选响应 item
rsp_port发布 response
class my_driver extends uvm_driver #(packet);
    `uvm_component_utils(my_driver)
    task main_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);
            drive_one_packet(req);       // 把 req 转为接口时序
            seq_item_port.item_done();
        end
    endtask
endclass

uvm_monitor monitor 把 DUT 接口信号恢复成 transaction。 uvm_monitor 相比 uvm_component 没有增加很多字段,但继承专用基类能明确组件语义。

class my_monitor extends uvm_monitor;
    uvm_analysis_port #(packet) ap;
    `uvm_component_utils(my_monitor)
endclass

技术上直接继承 uvm_component 也能实现 monitor,但不推荐破坏语义分类。 uvm_sequencer sequencer:

  • 管理一个或多个 sequence。
  • 仲裁 sequence 的 item 请求。
  • 与 driver 的 seq_item_port 建立连接。
  • 把 sequence item 转交给 driver。
class my_sequencer extends uvm_sequencer #(packet);
    `uvm_component_utils(my_sequencer)
endclass

uvm_scoreboard scoreboard 比较 reference model 和 monitor 的输出。 uvm_scoreboard 也没有增加大量专用 API,但能清楚表达组件职责。 reference model UVM 没有专门的 uvm_reference_model 类。 reference model 通常直接继承 uvm_component。

class my_model extends uvm_component;
    `uvm_component_utils(my_model)
endclass

它可以使用 SystemVerilog 算法,也可以通过 DPI 调用 C/C++ 模型。 uvm_agent agent 封装同一协议的 sequencer、driver 和 monitor。 uvm_agent 提供 is_active:

值结构
UVM_ACTIVEsequencer + driver + monitor
UVM_PASSIVEmonitor

agent 的主要价值是协议封装与复用。 uvm_env env 容纳平台中固定存在的组件和连接。 不同 test 通常复用同一个 env,只改变配置和 sequence。 uvm_test 所有具体测试用例通常继承 uvm_test 或项目自己的 base_test。 test 负责:

  • 创建 env。
  • 配置平台。
  • 选择或启动 sequence。
  • 设置超时和报告策略。

3.1.4 与 uvm_object 相关的宏(🔴 高)

object 注册宏对照

类是否参数化是否使用 field automation注册宏
否否uvm_object_utils
是否uvm_object_param_utils
否是uvm_object_utils_begin/end
是是uvm_object_param_utils_begin/end

普通 object:

class packet extends uvm_sequence_item;
    `uvm_object_utils(packet)
endclass

参数化 object:

class data_item #(int WIDTH = 32) extends uvm_sequence_item;
    rand bit [WIDTH-1:0] data;
    `uvm_object_param_utils(data_item #(WIDTH))
endclass

使用字段自动化:

class packet extends uvm_sequence_item;
    rand bit [31:0] data;
    `uvm_object_utils_begin(packet)
        `uvm_field_int(data, UVM_ALL_ON)
    `uvm_object_utils_end
endclass

begin/end 之间没有 field 宏在语法上也可以成立,但没有字段自动化收益。

3.1.5 与 uvm_component 相关的宏(🔴 高)

component 注册宏对照

类是否参数化是否使用 field automation注册宏
否否uvm_component_utils
是否uvm_component_param_utils
否是uvm_component_utils_begin/end
是是uvm_component_param_utils_begin/end

普通 component:

class my_driver extends uvm_driver #(packet);
    `uvm_component_utils(my_driver)
endclass

带字段自动化的 component:

class my_driver extends uvm_driver #(packet);
    int pre_num;
    `uvm_component_utils_begin(my_driver)
        `uvm_field_int(pre_num, UVM_ALL_ON)
    `uvm_component_utils_end
endclass

component 中使用 field automation 的特殊价值之一,是配合 super.build_phase 自动应用同名配置字段。

三个条件缺一不可:[1] begin/end 版注册宏;[2] 字段用 uvm_field_* 登记;[3] build_phase 调用 super.build_phase(phase)。三者齐备时,config_db 同名配置自动填进成员变量,无需手写 get。

class my_driver extends uvm_driver #(packet);
    int pre_num;                            // [1] 成员变量
    `uvm_component_utils_begin(my_driver)   // [2] begin/end 版注册宏
        `uvm_field_int(pre_num, UVM_ALL_ON) // [3] 字段用 uvm_field_* 登记
    `uvm_component_utils_end
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);           // [4] super 内部自动 get 同名配置,无需手写 get
    endfunction
endclass

test 只需 set:uvm_config_db#(int)::set(this, "env.i_agt.drv", "pre_num", 100); // [5] 同名配置,自动填入 pre_num

注意:object 无 build_phase,此机制仅 component 可用;自动配置较隐式,重要配置建议显式 get。

3.1.6 uvm_component 的限制(🟡 中)

component 虽然继承 object,但由于它是树结点,并非所有 object 操作都适合它。 clone 不适合 component object 的 clone() 可以理解为: clone = new + copy(新建一个对象,再把内容复制过去)

object 示例:

packet p1;
packet p2;
p1 = packet::type_id::create("p1");
assert(p1.randomize());
$cast(p2, p1.clone());            // clone 自己分配目标对象

component clone 存在问题:

问题解释
parent 无法指定clone 是”自己分配新对象”,过程中没人给它传 parent → 新组件挂到哪?
名字可能冲突树里每个结点名字要唯一,clone 出的新对象名字无法保证唯一
没有明确挂树时机component 必须在 build_phase 阶段挂到树上,clone 发生在任意时刻,破坏生命周期

因此 object 可以随便复制,component 是”树上的固定设备”,不能随便克隆——树的规则(parent、唯一名、build 时机)不允许。 copy 与 clone 的区别

操作目标对象是否必须先创建是否分配新对象
copy✅ 要(先 new 出目标)否(复制到已有对象里)
clone❌ 不用(clone 自己分配)是(新建一个)

component 已经被正确创建并拥有 parent 后,理论上可以对字段执行 copy;但不要用 copy 复制结构关系。

代码对比:

// copy:先有目标,再复制
packet dst = packet::type_id::create("dst");  // [1] 先创建目标
dst.copy(src);                                // [2] 把 src 内容复制进 dst
 
// clone:一步到位
packet dst2;
$cast(dst2, src.clone());                     // clone 内部自己 new + copy

记忆:copy 是”填进已有的盒子”,clone 是”自动造个新盒子再填”。后者 = 前者 + new。

对 component 的态度:

  • clone:❌ 禁止(树的问题)
  • copy:⚠️ 可以复制字段,但不要用 copy 复制结构关系(parent 这种”结构”不能靠 copy 建立)

同一父结点下名字必须唯一 错误示例:

a1 = comp_a::type_id::create("a1", this);
a2 = comp_a::type_id::create("a1", this); // 错:同一 parent 下重名

正确:

a1 = comp_a::type_id::create("a1", this);
a2 = comp_a::type_id::create("a2", this);

原因:

  • child 的名字参与 UVM 路径。
  • 同级重名会使路径无法唯一标识组件。

3.1.7 uvm_component 与 uvm_object 的二元结构(🔴 高)

UVM 把验证元素分成两类管理。 长期存在的结构 component 通常在 build_phase 创建,贯穿整个仿真:

  • test。
  • env。
  • agent。
  • sequencer。
  • driver。
  • monitor。
  • model。
  • scoreboard。

按需产生的数据或行为 object 可以频繁创建、传递和销毁:

  • transaction。
  • sequence。
  • config object。
  • register item。
判断问题是否
需要成为 UVM 树结点吗component继续判断
需要自动执行 phase 吗componentobject
只是数据、配置或临时行为吗object重新审视职责

最实用的判断:长期存在并参与平台结构的是 component;短生命周期的数据和场景通常是 object。


3.2 UVM 的树形结构(🔴 高)

UVM 用一棵 component 树管理验证平台。 树形结构使框架能够:

  • 统一执行 phase。
  • 递归设置 report 策略。
  • 根据路径查找 config。
  • 构造稳定的组件层次。
  • 遍历和调试平台结构。

3.2.1 uvm_component 中的 parent 参数(🔴 高)

标准 component 构造函数:

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

在父 component 中创建子 component:

function void my_env::build_phase(uvm_phase phase);
    super.build_phase(phase);
    i_agt = my_agent::type_id::create(
        "i_agt",                 // child 的实例名
        this                     // parent 是当前 env
    );
endfunction

this 使 UVM 得知:

  • env 拥有 child i_agt。
  • i_agt 的 parent 是 env。
  • env 的 child 列表中应加入 i_agt。

成员变量关系不等于 UVM parent 关系 SystemVerilog 知道 i_agt 是 env 的成员变量,但 UVM 框架不会自动从语言成员关系推导 parent。 parent 必须在 component 创建时显式传递。 错误: i_agt = new("i_agt", null); // 会挂到 uvm_top,而不是当前 env 正确: i_agt = my_agent::type_id::create("i_agt", this);

3.2.2 UVM 树的根(🔴 高)

测试类 uvm_test_top 不是整棵 UVM 树真正的根。 真正的根是全局唯一的 uvm_top。

uvm_top : uvm_root
└── uvm_test_top : my_caseN
    └── env : my_env
        ├── i_agt : my_agent
        │   ├── sqr
        │   ├── drv
        │   └── mon
        ├── mdl
        ├── scb
        └── o_agt
            └── mon

重要关系:

结点parent
uvm_topnull
uvm_test_topuvm_top
test 下的 envtest
env 下的 agent/model/scoreboardenv

parent 为 null 的 component 若创建 component 时 parent 为 null,UVM 会把它挂到 uvm_top 下。 extra_env = my_env::type_id::create("extra_env", null); 结果类似:

uvm_top
├── uvm_test_top
└── extra_env

这样保证系统中仍只有一棵 UVM 树。 获取 uvm_root

uvm_root root;
root = uvm_root::get();           // 获取全局唯一 uvm_root

也可以使用全局变量 uvm_top。 uvm_root::get() 体现 singleton 模式:系统中只有一个根实例。

3.2.3 层次结构相关函数(🟡 中)

get_parent

uvm_component parent;
parent = get_parent();

当前 component 只有一个 parent,因此不需要参数。 get_child

uvm_component child;
child = get_child("drv");

一个 component 可以有多个 child,因此按实例名获取。 get_children

uvm_component children[$];
get_children(children);           // 把所有直接 child 放入队列
foreach (children[i]) begin
    `uvm_info(
        "CHILD",
        children[i].get_full_name(),
        UVM_LOW
    )
end

get_children 只返回直接孩子,不会自动递归整个子树。 get_first_child 与 get_next_child

string child_name;
uvm_component child;
if (get_first_child(child_name)) begin
    do begin
        child = get_child(child_name);
        child.print();
    end
    while (get_next_child(child_name));
end

child_name 是遍历状态变量:

  • 不必预先赋值。
  • 遍历期间不要随意修改。
  • 通过 ref 在函数之间传递状态。

get_num_children

int count;
count = get_num_children();

返回当前 component 的直接 child 数量。 常用层次函数速查

函数作用
get_parent()获取父 component
get_child(name)按实例名获取直接 child
get_children(queue)获取全部直接 child
get_first_child(name)开始 child 迭代
get_next_child(name)继续 child 迭代
get_num_children()获取直接 child 数量
get_name()获取当前实例名
get_full_name()获取完整 UVM 路径

3.3 field automation 机制(🟡 中)

field automation 通过 uvm_field_* 宏注册字段,并为这些字段提供通用操作。 主要能力:

  • copy。
  • compare。
  • print。
  • pack/unpack。
  • record。
  • component 配置字段自动应用。

3.3.1 field automation 机制相关的宏(🟡 中)

标量字段

字段类型宏
整数、bit、logic 等uvm_field_int
realuvm_field_real
enumuvm_field_enum
uvm_object 句柄uvm_field_object
eventuvm_field_event
stringuvm_field_string

示例:

typedef enum {GOOD_FRAME, BAD_FRAME} frame_kind_e;
class packet extends uvm_sequence_item;
    rand bit [31:0] data;
    rand frame_kind_e kind;
    string source;
    `uvm_object_utils_begin(packet)                       // [1] 注册 object + 开始字段登记
        `uvm_field_int   (data,   UVM_ALL_ON)             // [2] 登记整数字段:copy/compare/print/pack 全自动
        `uvm_field_enum  (frame_kind_e, kind, UVM_ALL_ON) // [3] 登记枚举字段(必须显式带枚举类型名)
        `uvm_field_string(source, UVM_ALL_ON)             // [4] 登记字符串字段
    `uvm_object_utils_end                                 // [5] 结束字段登记
endclass

枚举标量宏需要显式给出枚举类型。 动态数组字段

动态数组元素宏
int/bit/byte 等uvm_field_array_int
enumuvm_field_array_enum
objectuvm_field_array_object
stringuvm_field_array_string
rand byte payload[];
`uvm_field_array_int(payload, UVM_ALL_ON)

静态数组字段

静态数组元素宏
int/bit/byte 等uvm_field_sarray_int
enumuvm_field_sarray_enum
objectuvm_field_sarray_object
stringuvm_field_sarray_string

队列字段

队列元素宏
int/bit/byte 等uvm_field_queue_int
enumuvm_field_queue_enum
objectuvm_field_queue_object
stringuvm_field_queue_string

关联数组字段 关联数组宏名称同时编码“元素类型”和“索引类型”。 格式可理解为: uvm_field_aa_<value_type>_<index_type> 例如:

int counters[string];
`uvm_field_aa_int_string(counters, UVM_ALL_ON)

表示:

  • 存储值类型是 int。
  • 索引类型是 string。

常见宏:

  • uvm_field_aa_int_string。
  • uvm_field_aa_string_string。
  • uvm_field_aa_object_string。
  • uvm_field_aa_int_int。
  • uvm_field_aa_string_int。
  • uvm_field_aa_object_int。

宏选择口诀

先看容器:标量 / array / sarray / queue / aa
再看元素:int / enum / object / string
若是关联数组,再看 index 类型

3.3.2 field automation 机制的常用函数(🟡 中)

copy

packet src;
packet dst;
src = packet::type_id::create("src");
dst = packet::type_id::create("dst");
assert(src.randomize());
dst.copy(src);                    // 调用者 dst 是复制目标

记忆: 目标.copy(来源) 目标对象必须先创建。 compare

bit same;
same = actual.compare(expected);

返回值:

结果含义
1已注册并参与比较的字段一致
0至少一个比较字段不一致

比较是否对称取决于自定义实现和 comparer 配置;常规字段比较可按 actual.compare(expected) 理解。 pack_bytes 与 unpack_bytes

byte unsigned bytes[];
int bit_count;
bit_count = tr.pack_bytes(bytes); // 返回打包的 bit 数

恢复:

packet tr2;
tr2 = packet::type_id::create("tr2");
// 若 tr2 含动态数组,必要时先确定数组大小
bit_count = tr2.unpack_bytes(bytes);

pack 与 unpack

函数流元素类型
pack/unpackbit
pack_bytes/unpack_bytesbyte
pack_ints/unpack_intsint

所有 pack/unpack 都要关注:

  • field 注册顺序。
  • 大小端配置。
  • 动态数组长度。
  • 不应在线上传输的控制字段。

print tr.print(); print 会显示参与打印的注册字段。 调试 transaction 时比逐字段 $display 更统一。 clone

packet copy_tr;
$cast(copy_tr, tr.clone());
//  [1] tr.clone()  → 复制 tr,造出新 packet(但以 uvm_object 句柄返回)
//  [2] $cast(...)  → 把 uvm_object 句柄转回 packet 类型,存入 copy_tr
//  [3] 之后 copy_tr.dmac 就能用了 ✅

clone 返回 uvm_object 句柄,所以常需要 $cast 转回具体类型。

3.3.3 field automation 机制中标志位的使用(🟢 低)

有些字段需要 print/compare/copy,但不能进入协议 pack。 例如 crc_err 只是测试控制标志,不是以太网帧字段。

class packet extends uvm_sequence_item;
    rand bit [31:0] crc;
    rand bit        crc_err;      // 控制是否故意产生错误 CRC
    function void post_randomize();
        if (!crc_err)
            crc = calc_crc();     // 正常帧计算正确 CRC
        // crc_err==1 时保留随机 CRC,制造错误激励
    endfunction
    `uvm_object_utils_begin(packet)
        `uvm_field_int(crc,     UVM_ALL_ON)
        `uvm_field_int(crc_err, UVM_ALL_ON | UVM_NOPACK)
    `uvm_object_utils_end
endclass

这样 crc_err:

  • 可以被 print。
  • 可以被 copy。
  • 可以参与 compare。
  • 不会被 pack 到 DUT 接口数据流。

UVM_ALL_ON | UVM_NOPACK 的叠加效果:

UVM_ALL_ON  = copy ✅ + compare ✅ + print ✅ + pack ✅
UVM_NOPACK  = 把 pack 关掉
─────────────────────────── 叠加结果 ───────────────────────────
crc_err     = copy ✅ + compare ✅ + print ✅ + pack ❌

常用 field 标志

功能打开关闭
copyUVM_COPYUVM_NOCOPY
compareUVM_COMPAREUVM_NOCOMPARE
printUVM_PRINTUVM_NOPRINT
recordUVM_RECORDUVM_NORECORD
packUVM_PACKUVM_NOPACK

常见组合:

`uvm_field_int(ctrl, UVM_ALL_ON | UVM_NOPACK)

标志本质上是位掩码,因此使用按位或 | 组合。 当同时存在 PACK 与 NOPACK 时,禁止标志用于覆盖对应功能。 哪些字段通常不应该 pack

  • 错误注入开关。
  • transaction 来源标签。
  • 调试计数器。
  • 时间戳。
  • 期望值辅助字段。
  • 不属于线协议的数据。

3.3.4 field automation 中宏与 if 的结合(🟢 低)

某些协议字段只在特定类型事务中出现。 例如 VLAN 帧比普通以太网帧多一组 VLAN 字段。

class packet extends uvm_sequence_item;
    rand bit [47:0] dmac;
    rand bit [47:0] smac;
    rand bit [15:0] vlan_info1;
    rand bit [2:0]  vlan_info2;
    rand bit        vlan_info3;
    rand bit [11:0] vlan_info4;
    rand bit [15:0] ether_type;
    rand byte       payload[];
    rand bit [31:0] crc;
    rand bit        is_vlan;      // 控制是否包含 VLAN 字段
    `uvm_object_utils_begin(packet)                    // [1] 注册 + 开始字段登记
        `uvm_field_int(dmac, UVM_ALL_ON)               // [2] 登记 dmac(两种帧都有)
        `uvm_field_int(smac, UVM_ALL_ON)               // [3] 登记 smac(两种帧都有)
        if (is_vlan) begin                             // [4] 只有 VLAN 帧才登记下面 4 个 VLAN 字段
            `uvm_field_int(vlan_info1, UVM_ALL_ON)
            `uvm_field_int(vlan_info2, UVM_ALL_ON)
            `uvm_field_int(vlan_info3, UVM_ALL_ON)
            `uvm_field_int(vlan_info4, UVM_ALL_ON)
        end                                            // [5] 条件结束
        `uvm_field_int      (ether_type, UVM_ALL_ON)   // [6] 登记 ether_type
        `uvm_field_array_int(payload,    UVM_ALL_ON)   // [7] 登记 payload(动态数组用 array 宏)
        `uvm_field_int      (crc,        UVM_ALL_ON)   // [8] 登记 crc
        `uvm_field_int(is_vlan, UVM_ALL_ON | UVM_NOPACK) // [9] 控制开关:全自动但 NOPACK(不进数据流)
    `uvm_object_utils_end                              // [10] 结束字段登记
endclass

普通帧: assert(tr.randomize() with { is_vlan == 0; }); VLAN 帧: assert(tr.randomize() with { is_vlan == 1; }); 优点:

  • print 时字段语义明确。
  • compare 失败能定位具体 VLAN 字段。
  • pack 时普通帧不会携带 VLAN 字段。

注意:

  • 条件字段结构增加了宏展开复杂度。
  • pack 与 unpack 两侧必须对 is_vlan 有一致认知。
  • unpack 前需要知道当前包是否包含 VLAN 字段。

3.4 UVM 中打印信息的控制(🟡 中)

UVM report 机制把日志分成三个正交维度:

维度回答的问题
severity这条消息有多严重(INFO/WARNING/ERROR/FATAL)
verbosity这条 info 有多详细
action消息出现后执行什么行为(打印/计数/退出)

另外还可以按 ID、component 路径和日志文件进行控制。

3.4.1 设置打印信息的冗余度阈值(🟡 中)

verbosity 等级

级别数值
UVM_NONE0
UVM_LOW100
UVM_MEDIUM200
UVM_HIGH300
UVM_FULL400
UVM_DEBUG500

显示规则:

消息 verbosity <= 当前阈值 -> 显示
消息 verbosity > 当前阈值  -> 不显示

默认阈值通常为 UVM_MEDIUM。 因此 LOW 和 MEDIUM 显示,HIGH 通常不显示。

uvm_info(..., UVM_LOW)    -> 100 ≤ 200 -> 显示 ✅
uvm_info(..., UVM_MEDIUM) -> 200 ≤ 200 -> 显示 ✅
uvm_info(..., UVM_HIGH)   -> 300 > 200 -> 不显示 ❌(这就是为什么 HIGH 日志"看不见")

查询当前阈值

int level;
level = env.i_agt.drv.get_report_verbosity_level();  // 查 driver 当前的阈值

get_report_verbosity_level() = “我现在阈值是多少?” ,返回一个整数(100/200/300…)。

只设置单个 component

env.i_agt.drv.set_report_verbosity_level(UVM_HIGH);

只影响 driver 一个组件。agent 里的 sqr、mon 不受影响。

递归设置 component 子树(set + _hier) env.i_agt.set_report_verbosity_level_hier(UVM_HIGH); 影响:

  • i_agt。
  • i_agt.sqr。
  • i_agt.drv。
  • i_agt.mon。
  • 其他后代结点。

按 ID 设置 verbosity

env.i_agt.drv.set_report_id_verbosity(
    "DRV_DATA",
    UVM_HIGH
);

只调整该 component 中 ID 为 DRV_DATA 的消息阈值。 递归版本:

env.i_agt.set_report_id_verbosity_hier(
    "DRV_DATA",
    UVM_HIGH
);

命令行设置(不改代码) <sim_command> +UVM_VERBOSITY=UVM_HIGH

全局生效,等价于把所有组件的阈值调到 HIGH。适合临时调试:

  • 不用改源码、不用重新编译
  • 调完去掉参数重跑就恢复默认
  • +UVM_VERBOSITY=UVM_HIGH 和 +UVM_VERBOSITY=HIGH 等价(可省略 UVM_ 前缀)

为什么常在 connect_phase 设置

因为设置的目标组件可能还没创建出来:

build_phase:组件被创建(drv、mon 诞生)
connect_phase:所有组件已创建完毕 ✅ 可以按路径访问
  • env.i_agt.drv.set_report_verbosity_level(...) 这类写法要通过路径访问后代组件(先点 env,再点 i_agt,再点 drv)→ 这些组件必须已经存在 → 最早只能在 connect_phase 或之后设置。

  • 如果只设置当前组件自己(比如在 test 里 set_report_verbosity_level(...),不访问别人),可以更早(build_phase 就能做)。

3.4.2 重载打印信息的严重性(🟢 低)

UVM 常见 severity:

  • UVM_INFO。
  • UVM_WARNING。
  • UVM_ERROR。
  • UVM_FATAL。

可把某种 severity 重载为另一种。

env.i_agt.drv.set_report_severity_override(
    UVM_WARNING,                 // 原严重性
    UVM_ERROR                    // 新严重性
);

作用:driver 内所有 warning 按 error 处理——打印成 error、计入 error 计数、可能触发退出逻辑。 按 severity + ID 重载

env.i_agt.drv.set_report_severity_id_override(
    UVM_WARNING,
    "DRV_PROTOCOL",
    UVM_ERROR
);

只重载特定 ID 的 warning。 命令行重载 +uvm_set_severity=<component>,<id>,<old_severity>,<new_severity> 示例: +uvm_set_severity="uvm_test_top.env.i_agt.drv,DRV_PROTOCOL,UVM_WARNING,UVM_ERROR" 所有 ID 可使用 _ALL_。

三种重载粒度:

方法影响范围代码
整体重载driver 里所有 warningset_report_severity_override(UVM_WARNING, UVM_ERROR)
按 severity+ID 重载只重载 ID 为 “DRV_PROTOCOL” 的 warningset_report_severity_id_override(UVM_WARNING, "DRV_PROTOCOL", UVM_ERROR)
命令行重载全局,不改码+uvm_set_severity=组件,ID,旧,新

使用场景

  • 第三方 VIP 把项目必须禁止的情况仅报告为 warning。
  • 某项 warning 在当前项目中实际意味着测试失败。
  • 临时降低已知、无害消息的严重性。

不要随意把真正错误降级,否则回归结果可能出现假通过。

3.4.3 UVM_ERROR 到达一定数量结束仿真(🟢 低)

大量 error 出现后继续运行可能只会产生重复日志。 设置最大退出计数:

function void base_test::build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
    set_report_max_quit_count(5); // 第 5 次计数事件后结束仿真
endfunction

查询:

int limit;
limit = get_report_max_quit_count();

教材部分版本接口名称可能显示为 get_max_quit_count;使用时以项目 UVM 版本定义为准。 阈值为 0 通常表示不因计数达到上限而退出。

命令行设置

+UVM_MAX_QUIT_COUNT=6,NO 含义:

  • 退出阈值为 6。
  • NO 表示后续设置不允许覆盖该值。

3.4.4 设置计数的目标(🟢 低)

report 消息出现后执行什么操作,由 action 决定。 默认 UVM_ERROR 通常包含 UVM_COUNT。

方法一:按 severity 设置(整个组件内所有 warning)

env.i_agt.drv.set_report_severity_action(
    UVM_WARNING,              // 针对 severity=warning 的消息
    UVM_DISPLAY | UVM_COUNT   // 行为:打印 + 计数
);

效果:driver 里所有 warning 变成”打印 + 计数”(原来只有打印)。

递归版:

env.i_agt.set_report_severity_action_hier(
    UVM_WARNING,
    UVM_DISPLAY | UVM_COUNT
);
// 带 _hier:i_agt 整个子树的所有 warning 都计数

记忆规律又来了:_hier = 递归到子树。

方法二:按 ID 设置(只针对某个标签)

env.i_agt.drv.set_report_id_action(
    "DRV_PROTOCOL",           // 针对 ID=DRV_PROTOCOL 的消息
    UVM_DISPLAY | UVM_COUNT
);

⚠️ 注意:按 ID 设置时,这个 ID 的所有 severity(INFO/WARNING/ERROR)都采用该 action——所以 DRV_PROTOCOL 的 info、warning、error 全部”打印 + 计数”。

方法三:按 severity + ID(最精确)

env.i_agt.drv.set_report_severity_id_action(
    UVM_WARNING,       // severity
    "DRV_PROTOCOL",    // ID
    UVM_DISPLAY | UVM_COUNT
);

只针对”DRV_PROTOCOL 这个 ID 的 warning”——同 ID 的 info、error 不受影响。

方法四:从计数中移除 error(反操作)

env.i_agt.drv.set_report_severity_action(
    UVM_ERROR,
    UVM_DISPLAY          // 只有打印,没有 UVM_COUNT
);

效果:error 显示但不计数 → 不会触发 3.4.3 的限次退出。

⚠️ 一般不推荐!原因:

  • error 不计数 → “报错到上限自动退出”失效。
  • 回归里即使有 error 也可能显示 PASS(假通过)。

五种设置方法总结

方法影响范围函数
按 severity组件内所有该 severity 消息set_report_severity_action
按 severity(递归)子树内所有该 severityset_report_severity_action_hier
按 ID该 ID 的所有 severityset_report_id_action
按 severity+ID该 ID 的该 severityset_report_severity_id_action
移除计数该 severity 不再计数set_report_severity_action(sev, UVM_DISPLAY)

3.4.5 UVM 的断点功能(🟢 低)

把 action 设置为 UVM_STOP,消息出现时进入仿真器交互调试状态。

env.i_agt.drv.set_report_severity_action(
    UVM_WARNING,
    UVM_DISPLAY | UVM_STOP  // 打印 + 停下
);

精确到 ID:

env.i_agt.drv.set_report_severity_id_action(
    UVM_WARNING,
    "DRV_PROTOCOL",
    UVM_DISPLAY | UVM_STOP
);

两种设置粒度:

方法影响范围代码
按 severity所有 warning 出现就停set_report_severity_action(UVM_WARNING, UVM_DISPLAY | UVM_STOP)
按 severity + ID只有 “DRV_PROTOCOL” 的 warning 出现才停set_report_severity_id_action(UVM_WARNING, "DRV_PROTOCOL", UVM_DISPLAY | UVM_STOP)

用途:

  • 第一次协议错误出现时立即检查波形和调用栈。
  • 避免日志滚动后难以定位最初异常。
  • 统一不同仿真器上的 report 触发断点入口。

是否真正进入可交互状态仍取决于仿真器启动方式。

3.4.6 将输出信息导入文件中(🟢 低)

按 severity 分文件

UVM_FILE info_log;
UVM_FILE error_log;
function void base_test::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    info_log  = $fopen("info.log",  "w");
    error_log = $fopen("error.log", "w");
    env.i_agt.drv.set_report_severity_file(
        UVM_INFO,
        info_log
    );
    env.i_agt.drv.set_report_severity_file(
        UVM_ERROR,
        error_log
    );
    // UVM_LOG 必须出现在 action 中,消息才写入指定文件
    env.i_agt.drv.set_report_severity_action(
        UVM_INFO,
        UVM_DISPLAY | UVM_LOG
    );
    env.i_agt.drv.set_report_severity_action(
        UVM_ERROR,
        UVM_DISPLAY | UVM_COUNT | UVM_LOG
    );
endfunction

只设置 file 而不加入 UVM_LOG,不会得到预期文件输出。 按 ID 分文件

env.i_agt.drv.set_report_id_file(
    "DRV_DATA",
    driver_log
);
env.i_agt.drv.set_report_id_action(
    "DRV_DATA",
    UVM_DISPLAY | UVM_LOG
);

按 severity + ID 分文件

env.i_agt.drv.set_report_severity_id_file(
    UVM_WARNING,
    "DRV_PROTOCOL",
    warning_log
);

关闭日志文件

function void base_test::final_phase(uvm_phase phase);
    super.final_phase(phase);
    if (info_log)
        $fclose(info_log);
    if (error_log)
        $fclose(error_log);
endfunction

3.4.7 控制打印信息的行为(🟢 低)

action 类型

action行为
UVM_NO_ACTION不执行任何操作
UVM_DISPLAY输出到标准输出
UVM_LOG输出到已配置日志文件
UVM_COUNT增加 quit count
UVM_EXIT退出仿真
UVM_CALL_HOOK调用 report hook
UVM_STOP停止并进入交互模式

action 是位掩码,可以组合: UVM_DISPLAY | UVM_COUNT | UVM_LOG= “打印 + 计数 + 写文件” 三个行为同时做。 默认行为

severity典型默认 action
UVM_INFOUVM_DISPLAY
UVM_WARNINGUVM_DISPLAY
UVM_ERRORUVM_DISPLAY + UVM_COUNT
UVM_FATALUVM_DISPLAY + UVM_EXIT

完全关闭某类信息

env.i_agt.drv.set_report_severity_action(
    UVM_INFO,
    UVM_NO_ACTION
);

与提高 verbosity 阈值不同,NO_ACTION 从行为层面关闭信息。 report 控制函数命名规律 set_report_<severity/id/severity_id>_<verbosity/action/file> 若函数名以 _hier 结尾,通常表示递归应用到 component 子树。 严重性 override 并非所有场景都提供 hier 版本,使用时查当前 UVM API。


3.5 config_db 机制(🔴 高)

config_db 用于在 UVM component 层次中传递配置。 典型配置:

  • virtual interface。
  • active/passive 模式。
  • 整数阈值。
  • 字符串路径。
  • 枚举模式。
  • config object。
  • default sequence wrapper。

3.5.1 UVM 中的路径(🔴 高)

获取完整路径

function void my_driver::build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("PATH", get_full_name(), UVM_LOW)
endfunction

get_full_name() = “我在树上的完整地址是什么?” 会返回从根到自己的整条路径,

可能输出: uvm_test_top.env.i_agt.drv

路径由实例名决定 drv = my_driver::type_id::create("driver", this); 变量名仍是 drv,但路径最后一段是 driver。

变量访问:i_agt.drv
UVM 路径:uvm_test_top.env.i_agt.driver

推荐让变量名和实例名一致: drv = my_driver::type_id::create("drv", this); 这样代码和日志更容易对应。

是什么出现在哪
变量名 drvclass 里的成员变量代码访问(drv.xxx)
实例名 "driver"create 时传的字符串UVM 路径 / 日志 / config_db

create 传的字符串才是路径的一部分——变量叫什么不影响路径。

uvm_top 的名字通常不显示

真正的树根是 uvm_top(uvm_root)。但常规完整路径从 uvm_test_top 开始显示:

实际结构:uvm_top → uvm_test_top → env → i_agt → drv
显示路径:uvm_test_top.env.i_agt.drv     ← uvm_top 被省略了

所以 config_db 写绝对路径时:

// ✅ 正确写法:从 uvm_test_top 开始
uvm_config_db#(...)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);
 
// ❌ 不用写 __top__ 之类的前缀

原因:uvm_top 是 UVM 框架自己的根,固定不变;uvm_test_top 才是你的测试起点,路径写它就够了。

3.5.2 set 与 get 函数的参数(🔴 高)

set

uvm_config_db#(int)::set(
    this,                         // [1] 起始上下文
    "env.i_agt.drv",              // [2] 相对 this 的目标路径
    "pre_num",                    // [3] 字段键名
    100                           // [4] 配置值
);

四个参数:

参数含义类比
1context component寄件人所在位置
2相对 context 的实例路径收件地址
3field name/key包裹上的标签
4value包裹内容

参数 1 与参数 2 联合确定目标范围——this(test)+ "env.i_agt.drv" = “从 test 出发,往下找到 env.i_agt.drv”。 get

int pre_num;
if (!uvm_config_db#(int)::get(
        this,                     // [1] 当前 driver
        "",                       // [2] 当前实例自身
        "pre_num",                // [3] 必须与 set 的 key 一致
        pre_num                   // [4] 写入此变量
    )) begin
    `uvm_fatal("NO_CFG", "pre_num was not configured")
end

get 的第二个参数 "" 表示”当前实例自身”(不用往上找别人)。get 有返回值:1=成功,0=失败,所以用 if (!get(...)) 检查,失败通常 uvm_fatal(配置没到位,平台跑不了)。 set/get 必须匹配的内容

项目要求对不上会怎样
参数化类型必须兼容,例如两边都用 intget 返回 0(类型不同=另一个柜子)
路径set 的目标必须覆盖 get 所在实例收不到(除非 set 是 get 祖先前缀)
field name字符串必须一致get 返回 0
时间get 之前必须已有有效 set取不到(先寄后收)

四个条件任一不满足 → get 失败。

get 第四个变量名不必与 field name 相同,但保持同名更易维护。 null 等价于 uvm_root::get() 在 top_tb 中没有 this component(module 不是 component),可写:

uvm_config_db#(virtual my_if)::set(
    null,                            // [1] context:top_tb 没有 this,用 null
    "uvm_test_top.env.i_agt.drv",    // [2] 完整路径(从 uvm_test_top 开始)
    "vif",                           // [3] 字段名
    input_if                         // [4] 配置值:真实 interface 实例
);

可以理解为:

uvm_config_db#(virtual my_if)::set(
    uvm_root::get(),                 // [1] null 的等价写法:全局根
    "uvm_test_top.env.i_agt.drv",    // [2] 完整路径(从 uvm_test_top 开始)
    "vif",                           // [3] 字段名
    input_if                         // [4] 配置值:真实 interface 实例
);

规律:

  • 有 this 的组件(test/env/driver)→ context 用 this,路径写相对路径。
  • 没 this 的 module(top_tb)→ context 用 null,路径写完整路径(从 uvm_test_top 开始)。 灵活拆分 context 和相对路径 以下目标可表示同一个 driver:
// 从 test 出发
uvm_config_db#(int)::set(
    this,
    "env.i_agt.drv",
    "pre_num",
    100
);
// 从 env 出发
uvm_config_db#(int)::set(
    env,
    "i_agt.drv",
    "pre_num",
    100
);

两条路径指向同一个 driver——context 不同,相对路径就不同(env 出发少走一级:env → i_agt → drv),但最终目标一致。

推荐选择最清晰、最符合当前代码层次的写法:在 env 里就用 env 出发,在 test 里就用 test 出发。

3.5.3 省略 get 语句(🔴 高)

component 字段使用 field automation 注册后,super.build_phase 可自动应用同名配置。

class my_driver extends uvm_driver #(packet);
    int pre_num;
    `uvm_component_utils_begin(my_driver)          // [1] 条件[1]:begin/end 版注册宏
        `uvm_field_int(pre_num, UVM_ALL_ON)        // [2] 条件[2]:字段登记
    `uvm_component_utils_end
    function new(string name = "my_driver",
                 uvm_component parent = null);
        super.new(name, parent);
        pre_num = 3;                               // [3] 本地默认值(super 前)
    endfunction
    function void build_phase(uvm_phase phase);
        `uvm_info(
            "CFG",
            $sformatf("before super: pre_num=%0d", pre_num),   // [4] super 前打印:3
            UVM_LOW
        )
        super.build_phase(phase);                  // [5] 条件[3]:super 自动 get 同名配置,覆盖 pre_num
        `uvm_info(
            "CFG",
            $sformatf("after super: pre_num=%0d", pre_num),    // [6] super 后打印:100
            UVM_LOW
        )
    endfunction
endclass

test 中仍要 set:

uvm_config_db#(int)::set(
    this,
    "env.i_agt.drv",
    "pre_num",                    // 必须与成员变量名一致
    100
);

自动 get 的三个条件

  1. component 使用 begin/end 版本的注册宏。
  2. 字段使用对应的 uvm_field_* 宏注册。
  3. super.build_phase(phase) 被调用。
  4. set 的 field name 与成员变量名一致。

set 不能省略。 工程建议 虽然自动配置写法短,但显式 get 往往更清楚:

if (!uvm_config_db#(int)::get(this, "", "pre_num", pre_num))
    pre_num = 3;

显式 get 的优势:

  • 能检查返回值。
  • 能清楚表达必选配置还是可选配置。
  • 不依赖 field automation 隐式行为。
  • 调试更直接。

3.5.4 跨层次的多重设置(🔴 高)

同一个目标字段可能被多个层次 set。 在 build 阶段,UVM 主要按“设置者的层次”决定优先级。 高层设置优先 test 设置:

uvm_config_db#(int)::set(
    this,
    "env.i_agt.drv",
    "pre_num",
    999
);

env 设置:

uvm_config_db#(int)::set(
    this,
    "i_agt.drv",
    "pre_num",
    100
);

driver 通常得到 999,因为 test 的设置层次高于 env。

uvm_test_top     高层,优先级高
└── env          低一层
    └── i_agt
        └── drv

为什么高层设置优先 可复用 env 可能自带默认配置。 具体 test 必须能覆盖 env 默认值,而不修改 env 源码。 因此:

  • env 提供默认策略。
  • base_test 提供项目公共配置。
  • 具体 test 提供场景级覆盖。

context 被写成 root 时会发生什么 如果 test 和 env 都用 uvm_root::get() 作为 context,它们在资源层次上看起来来自同一高层。 此时更可能由写入时间决定结果。 build_phase 自顶向下:

test build 先执行
env build 后执行

env 后写入的值可能覆盖 test 的值。 所以教材建议: 在 component 内调用 set 时,第一参数尽量使用 this;只有无法获得 component this 的 top_tb 才使用 null/root。

3.5.5 同一层次的多重设置(🔴 高)

当两次 set 来自同一层次时,通常后写入者生效。

uvm_config_db#(int)::set(
    this, "env.i_agt.drv", "pre_num", 100
);
uvm_config_db#(int)::set(
    this, "env.i_agt.drv", "pre_num", 109
);

最终通常得到 109。 base_test 默认值 + 子 test 覆盖

class base_test extends uvm_test;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        // 绝大多数测试使用的默认值
        uvm_config_db#(int)::set(
            this,
            "env.i_agt.drv",
            "pre_num",
            7
        );
    endfunction
endclass

特殊测试:

class long_preamble_test extends base_test;
    function void build_phase(uvm_phase phase);
        super.build_phase(phase); // 先执行默认 set
        // 同层次、后执行,覆盖默认值
        uvm_config_db#(int)::set(
            this,
            "env.i_agt.drv",
            "pre_num",
            100
        );
    endfunction
endclass

这个模式可以避免在大量 test 中重复公共 set。 多重设置优先级速记

情况通常优先规则
build 阶段跨层次越靠近根的设置越优先
同一层次重复设置后写入优先
都伪装成 root context层次差异消失,时间更关键

实际项目遇到复杂组合时,应使用 trace 验证,而不是只凭记忆。

3.5.6 非直线的设置与获取(🟡 中)

直线设置 祖先 component 给后代设置: test -> env -> agent -> driver 例如 test 设置 driver。 非直线设置 兄弟分支给另一分支设置:

env
├── scoreboard  --set--> driver
└── agent
    └── driver

示例:

uvm_config_db#(int)::set(
    get_parent(),                 // scoreboard 的 parent 是 env(先爬到共同祖先)
    "i_agt.drv",                  // 再从 env 往下指到 driver
    "pre_num",
    200
);

原理:scoreboard 用 get_parent() 先拿到共同祖先 env,再以 env 为 context 指到 i_agt.drv——绕了一圈实现”兄弟间传递”。

风险:

  • 同级 component 的 build_phase 顺序不应作为设计依赖。
  • driver get 时,scoreboard 的 set 可能尚未执行。
  • 平台结构耦合变强。

建议:避免非直线 set,把配置集中放在 test、env 或 config object。 非直线获取 model 想读取本来设置给 driver 的配置:

int drv_pre_num;
void'(uvm_config_db#(int)::get(
    get_parent(),                 // 从 env 出发
    "i_agt.drv",
    "pre_num",
    drv_pre_num
));

非直线 get 可减少重复 set,但也让 model 依赖 driver 路径。 更可维护的方案往往是:

  • 创建共享 config object。
  • 同时把 config object 传给 driver 和 model。
  • 或由 env 读取一次,再显式分配给相关组件。

3.5.7 config_db 机制对通配符的支持(🟡 中)

完整路径设置:

uvm_config_db#(virtual my_if)::set(
    null,
    "uvm_test_top.env.i_agt.drv",
    "vif",
    input_if
);
uvm_config_db#(virtual my_if)::set(
    null,
    "uvm_test_top.env.i_agt.mon",
    "vif",
    input_if
);

使用通配符:

uvm_config_db#(virtual my_if)::set(
    null,
    "uvm_test_top.env.i_agt*",  // "i_agt*" 匹配 i_agt 及其所有后代
    "vif",
    input_if
);

这可同时覆盖 input agent 下的 driver 和 monitor。 通配符的优点

  • 减少重复 set。
  • 便于给整个 agent 子树传同一配置。
  • 对规则且稳定的层次较方便。

通配符的风险

  • 可能意外匹配新加入的 component。
  • 阅读代码时不清楚真实接收者。
  • 重构命名后匹配范围可能变化。
  • 多个通配符配置叠加后优先级难判断。
  • 调试 config 泄漏更加困难。

推荐与不推荐的写法对比:

// ✅ 推荐:保留稳定前缀
"uvm_test_top.env.i_agt*"
 
// ❌ 不推荐:前缀太宽
"*i_agt*"
  • "uvm_test_top.env.i_agt*":从根开始写,只有末尾是通配 → 匹配范围可控。
  • "*i_agt*":通配符在开头 → 匹配范围太大,可能误伤路径中任何含 i_agt 的组件。

原则:通配符要尽可能保留稳定的路径前缀——前缀越具体,误伤越少。

3.5.8 check_config_usage(🟢 低)

config_db 路径是字符串。 拼写错误仍是合法字符串,编译器不会报错。 例如:

uvm_config_db#(int)::set(
    this,
    "env.i_atg.drv",              // 错把 i_agt 写成 i_atg
    "pre_num",
    7
);

driver 永远读不到该值(路径对不上),但 set 本身不报错(字符串 “env.i_atg.drv” 是合法的,编译器不会检查它指向哪里)。 检查写入但未读取的配置

function void my_test::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    check_config_usage();           // 检查:有没有写了没人读的配置?
endfunction

为什么放在 connect_phase:

  • 大多数普通配置已在 build_phase get。
  • build 已结束,可以检查未消费资源。

3.5.9 set_config 与 get_config(🟢 低)

教材还介绍了旧式的 set/get_config_int、set/get_config_string 和 set/get_config_object。它们在旧版本中可与相应类型的 config_db 操作互通,但支持的类型较少。

set_config_int("env.i_agt.drv", "pre_num", 999);
void'(get_config_int("pre_num", pre_num));

新代码优先使用参数化 config_db,它还能传递 enum、virtual interface 和自定义 config object:

uvm_config_db#(int)::set(...);
uvm_config_db#(int)::get(...);

命令行仍可使用 +uvm_set_config_int 和 +uvm_set_config_string;整数可带 'b、'o、'd、'h 进制前缀。

版本提醒:教材基于 UVM 1.1d;UVM 1.2 后旧式 set/get_config 已不再是推荐写法。

3.5.10 config_db 的调试(🟢 低)

print_config

function void my_test::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    print_config(1);              // 1:递归打印子树可见配置
endfunction

参数:

值含义
0只打印当前 component 可见配置
1递归打印整个子树

输出可能很长,适合定位某个字段是否对目标 component 可见。 UVM_CONFIG_DB_TRACE 命令行: <sim_command> +UVM_CONFIG_DB_TRACE 它会打印 config_db set/get 的查找过程。 适合分析:

  • 谁写入了某个字段。
  • 哪个 component 执行了 get。
  • 最终匹配了哪条资源。
  • 通配符如何展开。
  • 多重设置中哪个值生效。

推荐调试顺序

  1. 在目标 component 打印 get_full_name()。
  2. 检查 set/get 参数化类型。
  3. 检查 field name 拼写。
  4. 检查 set 路径是否匹配真实实例名。
  5. 检查 set 是否早于 get。
  6. 调用 check_config_usage()。
  7. 用 print_config(1) 查看可见资源。
  8. 打开 +UVM_CONFIG_DB_TRACE。
  9. 检查是否有更高层或更晚的 set 覆盖当前值。

本章总结(3.1-3.5)

学习重点排序

优先级必须掌握
🔴 高uvm_component 继承自 uvm_object,但只有 component 组成 UVM 树
🔴 高parent、实例名、变量名和完整路径的关系
🔴 高config_db 的类型、context、路径、field name 和 value
🟡 中field automation 的宏、函数和控制标志
🟡 中verbosity、severity、ID 与 action 的区别

返回 UVM 学习笔记目录 · 上一章 · 下一章