来源: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.2 | parent 和实例名如何形成 UVM 树 |
| 3.3 | 如何自动 copy、compare、print、pack、unpack |
| 3.4 | 如何过滤、重载、计数、停止和分流 UVM 日志 |
| 3.5 | config_db 如何定位组件、决定优先级并完成配置 |
本章最容易混淆的三组概念:
- 变量名、实例名和完整路径。
- 类型继承关系与 UVM 树的父子关系。
- 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_object | uvm_component |
|---|---|---|
| factory 注册与创建 | 支持 | 支持 |
| print/copy/compare | 支持 | 继承支持,但部分操作受限制 |
| name | 支持 | 支持 |
| parent | 没有 component parent | 有 |
| UVM 树结点 | 否 | 是 |
| phase 自动执行 | 否 | 是 |
| report 层次控制 | 基础能力 | 可沿 component 层次控制 |
| config 层次查找 | 不作为普通树结点 | 支持 |
component 两个最有代表性的特性:
- 构造时通过
parent建立树形关系。 - 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。
| 类别 | 常见基类 | 主要作用 |
|---|---|---|
| transaction | uvm_sequence_item | 封装一次协议事务 |
| sequence | uvm_sequence | 组织并产生 sequence item |
| config object | uvm_object | 集中保存某个组件或环境的配置 |
| register item | uvm_reg_item | 寄存器访问事务 |
| register model 类 | uvm_reg* | 描述寄存器、字段、存储器和地址映射 |
| phase object | uvm_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
endclasssequence:
- 不进入 component 树。
- 没有 parent 参数。
- 启动时与 sequencer 建立临时关系。
- body 执行完成后,其任务生命周期结束。
config object 与 config_db 的区别 二者不是同一个概念。
| 名称 | 含义 |
|---|---|
| config object | 用户定义的配置对象,把多个参数组织在一个类中 |
| config_db | UVM 提供的传递配置的机制 |
典型做法:
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
endclassuvm_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)
endclassuvm_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_ACTIVE | sequencer + driver + monitor |
UVM_PASSIVE | monitor |
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
endclassbegin/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
endclasscomponent 中使用 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
endclasstest 只需 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 吗 | component | object |
| 只是数据、配置或临时行为吗 | 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
);
endfunctionthis 使 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_top | null |
uvm_test_top | uvm_top |
| test 下的 env | test |
| env 下的 agent/model/scoreboard | env |
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
)
endget_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));
endchild_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 |
| real | uvm_field_real |
| enum | uvm_field_enum |
| uvm_object 句柄 | uvm_field_object |
| event | uvm_field_event |
| string | uvm_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 |
| enum | uvm_field_array_enum |
| object | uvm_field_array_object |
| string | uvm_field_array_string |
rand byte payload[];
`uvm_field_array_int(payload, UVM_ALL_ON)静态数组字段
| 静态数组元素 | 宏 |
|---|---|
| int/bit/byte 等 | uvm_field_sarray_int |
| enum | uvm_field_sarray_enum |
| object | uvm_field_sarray_object |
| string | uvm_field_sarray_string |
队列字段
| 队列元素 | 宏 |
|---|---|
| int/bit/byte 等 | uvm_field_queue_int |
| enum | uvm_field_queue_enum |
| object | uvm_field_queue_object |
| string | uvm_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/unpack | bit |
pack_bytes/unpack_bytes | byte |
pack_ints/unpack_ints | int |
所有 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 标志
| 功能 | 打开 | 关闭 |
|---|---|---|
| copy | UVM_COPY | UVM_NOCOPY |
| compare | UVM_COMPARE | UVM_NOCOMPARE |
UVM_PRINT | UVM_NOPRINT | |
| record | UVM_RECORD | UVM_NORECORD |
| pack | UVM_PACK | UVM_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_NONE | 0 |
UVM_LOW | 100 |
UVM_MEDIUM | 200 |
UVM_HIGH | 300 |
UVM_FULL | 400 |
UVM_DEBUG | 500 |
显示规则:
消息 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 里所有 warning | set_report_severity_override(UVM_WARNING, UVM_ERROR) |
| 按 severity+ID 重载 | 只重载 ID 为 “DRV_PROTOCOL” 的 warning | set_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(递归) | 子树内所有该 severity | set_report_severity_action_hier |
| 按 ID | 该 ID 的所有 severity | set_report_id_action |
| 按 severity+ID | 该 ID 的该 severity | set_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);
endfunction3.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_INFO | UVM_DISPLAY |
| UVM_WARNING | UVM_DISPLAY |
| UVM_ERROR | UVM_DISPLAY + UVM_COUNT |
| UVM_FATAL | UVM_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)
endfunctionget_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); 这样代码和日志更容易对应。
| 是什么 | 出现在哪 | |
|---|---|---|
变量名 drv | class 里的成员变量 | 代码访问(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] 配置值
);四个参数:
| 参数 | 含义 | 类比 |
|---|---|---|
| 1 | context component | 寄件人所在位置 |
| 2 | 相对 context 的实例路径 | 收件地址 |
| 3 | field name/key | 包裹上的标签 |
| 4 | value | 包裹内容 |
参数 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")
endget 的第二个参数 "" 表示”当前实例自身”(不用往上找别人)。get 有返回值:1=成功,0=失败,所以用 if (!get(...)) 检查,失败通常 uvm_fatal(配置没到位,平台跑不了)。
set/get 必须匹配的内容
| 项目 | 要求 | 对不上会怎样 |
|---|---|---|
| 参数化类型 | 必须兼容,例如两边都用 int | get 返回 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
endclasstest 中仍要 set:
uvm_config_db#(int)::set(
this,
"env.i_agt.drv",
"pre_num", // 必须与成员变量名一致
100
);自动 get 的三个条件
- component 使用 begin/end 版本的注册宏。
- 字段使用对应的
uvm_field_*宏注册。 super.build_phase(phase)被调用。- 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。
- 最终匹配了哪条资源。
- 通配符如何展开。
- 多重设置中哪个值生效。
推荐调试顺序
- 在目标 component 打印
get_full_name()。 - 检查 set/get 参数化类型。
- 检查 field name 拼写。
- 检查 set 路径是否匹配真实实例名。
- 检查 set 是否早于 get。
- 调用
check_config_usage()。 - 用
print_config(1)查看可见资源。 - 打开
+UVM_CONFIG_DB_TRACE。 - 检查是否有更高层或更晚的 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 学习笔记目录 · 上一章 · 下一章