← UVM 基础
第 1 章 · 共 10 章
UVM 入门:它到底解决了什么问题?
从零理解 UVM 为何存在、testbench 的分层结构,以及 uvm_component 的基本写法。
SystemVerilog 基础系列的结尾,我们已经搭出一个能跑通的 testbench:一个持有 virtual mux2_if.tb_mp vif 句柄的 driver 类,通过 mailbox 与 generator() 任务解耦,两者用 fork...join 并发运行(SV 基础系列第 16 章的 capstone)。它能跑通。那还缺什么?
UVM(Universal Verification Methodology) 是建立在 SystemVerilog 面向对象特性之上的一套标准化验证方法学——它不是新语言。但与其空泛地这样断言,不如问一个更尖锐的问题:一个已经能跑通的 testbench,到底还差什么?
- 没有标准的组件层次结构。 capstone 里的
driver和generator()只是一个类和一个 task,没有统一的命名规则,没有内置的详细级别(verbosity)控制,也没有任何工具能程序化地遍历它们来做报告或调试。UVM 的uvm_component树(第 2 章)免费给 testbench 的每个部件一个名字、一个父节点,以及在标准化层次结构中的位置。 - 没有标准的替换机制。 现在要测试不同的 driver 行为,只能直接改
driver类的代码——没有办法让某个 test 在不动 testbench 结构代码的前提下替换成另一种实现。UVM 的 Factory 机制(第 6 章)正是为此而生:把类注册一次,之后任何 test 都能在不改动环境代码的情况下替换它。 - 测 N 个场景就要改 N 次或复制 N 份。 capstone 里的
generator()把"生成 5 个随机事务"写死了——测试不同的流量模式就得改这个 task,或者复制一整份 testbench。UVM 的 Sequence 机制(第 7 章,第 10 章组装成完整环境)把激励变成一层独立、可替换的东西,不同的 test 可以复用同一个环境,只换 sequence。 - 没有能交给别的团队或跨项目复用的东西。
generator()与driver之间基于mailbox的握手是这个 testbench 专属的——没法原样搬到别的项目里。UVM 通过 TLM(Transaction Level Modeling)端口(第 8 章)把这种通信标准化,driver/monitor 这一对搭好之后,可以原封不动地在别的环境里复用。
这四点——标准化的组件分层、factory、sequence、TLM——就是 UVM 的全部内容。
Testbench 的典型分层
一个典型的 UVM testbench 层级大致如下:
uvm_test
└── uvm_env
├── uvm_agent
│ ├── uvm_sequencer (产生/管理事务)
│ ├── uvm_driver (把事务转换为引脚级信号,驱动 DUT)
│ └── uvm_monitor (从引脚采样,还原成事务)
└── uvm_scoreboard (比对预期结果与实际结果)
uvm_agent 通常包含 sequencer、driver、monitor 三件套,可以配置为 active(既驱动又监测)或 passive(只监测,常用于总线一侧无需驱动的场景)。
一个最简单的 uvm_component
UVM 中几乎所有结构性部件都继承自 uvm_component。下面是一个只打印日志的最简驱动骨架:
class simple_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(simple_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
forever begin
my_transaction tr;
seq_item_port.get_next_item(tr);
`uvm_info("DRV", $sformatf("driving transaction: %s", tr.convert2string()), UVM_MEDIUM)
// 此处将 tr 中的字段转换为对 DUT 引脚的实际驱动
seq_item_port.item_done();
end
endtask
endclass几个关键点:
`uvm_component_utils(simple_driver)向 UVM 的 Factory 注册这个类,使其可以在不修改代码的情况下被子类覆盖(override)。new(string name, uvm_component parent)是所有uvm_component的标准构造函数签名,parent用于维护组件树。run_phase是 UVM 的运行期 phase 之一,driver 通常在这里通过seq_item_port从 sequencer 拿到事务并驱动 DUT。
uvm_component 与 uvm_object 的区别
uvm_component:testbench 的结构性部件(driver、monitor、env、test 等),在整个仿真期间存在,构造时必须传入name和parent,参与 UVM 的 phase 机制。uvm_object:轻量级数据对象(如事务 transaction、配置对象),生命周期更灵活,构造函数一般只需要name,不参与 phase。
一个典型的 uvm_agent 通常包含哪三个核心子组件?
以下哪一项是 uvm_component 与 uvm_object 的关键区别?