UVM 组件与 phase:env/agent/driver/monitor/scoreboard 怎么搭起来
更新时间:2026-09-14。本文是「集成电路大类 · 芯片设计 · 数字验证」实操序列第五篇。前四篇的自检 TB 把所有逻辑堆在一个 module 里,小模块够用,大 SoC 会失控。UVM(Universal Verification Methodology,基于 SystemVerilog 类库的方法学标准)用一套约定好的组件层次和执行阶段,让大团队用同一套结构搭可复用平台。下一篇 UVM sequence/driver/scoreboard 实战 讲数据怎么在组件间流动。
本文要回答的问题
- UVM 那一堆组件各自只负责什么?
- class 写的平台怎么碰到 module 里的真实信号?(virtual interface)
- 为什么要分那么多 phase,run_phase 和其他 phase 差在哪?
- 仿真什么时候该停,objection 是什么?
一、为什么要组件化
裸 SV TB 的问题是「激励、采样、比对、配置」全揉在一个 initial 块里,换个 DUT 就得重写。UVM 的解法是职责单一 + 标准层次 + 工厂机制:每个类只干一件事,可独立复用;组件在固定 phase 里自动按顺序构建连接,不用手写调用顺序;uvm_factory 让你在 test 里「不改平台代码」就替换某个 driver/sequence 类型(多态覆盖)。UVM 不是新语言,是一套 SystemVerilog 类库加一套结构约定,语法仍是前几篇的 class、虚接口、随机化。
二、组件层次与各司其职
| 组件 | 单一职责 |
|---|---|
| uvm_test | 顶层,选环境/序列、配参数;不同用例继承不同 test |
| uvm_env | 容器,把 agent + scoreboard + coverage 组装起来 |
| agent | 围绕一个物理接口打包 sequencer+driver+monitor |
| sequencer | 不产生激励,只把 sequence 产生的 item 派发给 driver |
| driver | 拿 transaction,翻译成引脚级波形经虚接口驱动 DUT |
| monitor | 永远被动采样引脚,还原成 transaction 向外广播 |
| scoreboard | 收事务,与参考模型预期比对、报错 |
| subscriber | 订阅事务,典型实现是功能覆盖率收集器 |
关键设计:driver 和 monitor 分开,monitor 只看不动。agent 可配成 passive(只留 monitor),挂在内部接口或别人主控的总线上旁路监听,同一份 monitor 在 active/passive 复用。
三、virtual interface:两个世界的桥
UVM 组件是 class,运行在软件对象世界,不能直接例化 RTL、不能直接写端口信号;真实信号在 module 里。桥是 virtual interface(interface 实例的句柄),经 uvm_config_db 传递:
module top;
fifo_if if0(.clk,.rst_n);
sync_fifo dut(.*);
initial begin
uvm_config_db#(virtual fifo_if)::set(null,
"uvm_test_top.env.agt.drv","vif",if0);
run_test("base_test");
end
endmodule
class fifo_driver extends uvm_driver #(fifo_item);
virtual fifo_if vif;
function void build_phase(uvm_phase phase);
if (!uvm_config_db#(virtual fifo_if)::get(this,"","vif",vif))
`uvm_fatal("NOVIF","virtual interface not set")
endfunction
endclassconfig_db 是一棵全局配置数据库:set 存、get 取,既传虚接口,也传 active/passive 模式、超时、开关,避免层次路径硬编码到处散落。
四、phase 机制:构建与运行分开
UVM 把仿真拆成自动按层次执行的阶段,保证「先全建好、再全连线、最后一起跑」:
| phase | 方向 | 干什么 |
|---|---|---|
build_phase | 自顶向下 | 创建子组件、取虚接口/配置 |
connect_phase | 自底向上 | 连 TLM port、driver 绑 sequencer |
end_of_elaboration | — | 拓扑定型后检查/打印 |
run_phase(task) | 并行 | 唯一耗时间,真正驱动时钟/事务 |
extract/check/report | — | 收结果、判 PASS/FAIL、出报告 |
除 run_phase 是 task(可 @(posedge clk)、耗时)外,其余基本是零时间 function。build 自顶向下(父先建,子才能存在),connect 自底向上(子先备好端口父才能连),这决定了你该在哪一步取句柄、哪一步连线。run_phase 还可细分为 reset/configure/main/shutdown 等 sub-phase 支持多阶段测试,简单平台用 run_phase 即可。
五、TLM:组件间不靠层次路径喊话
monitor 不该知道 scoreboard 在哪,它只广播「我看到的事务」。UVM 用 TLM(Transaction-Level Modeling)端口解耦:monitor 持 uvm_analysis_port,scoreboard/coverage 各声明 uvm_analysis_imp 订阅,在 connect_phase 里 mon.ap.connect(scb.item_imp);,monitor 调 ap.write(item) 时所有订阅者同时收到。一对多广播、收发双方互不相识,monitor 因此能同时喂比对和覆盖率。
六、objection:谁决定仿真结束
UVM 不用固定 #delay $finish。默认没有任何 objection 被举起时仿真就结束:
task run_phase(uvm_phase phase);
phase.raise_objection(this); // 要干活,别停
seq.start(sqr);
phase.drop_objection(this); // 干完,可收尾
endtask忘记 raise → 仿真 0 时刻立刻结束;忘记 drop → 挂到全局超时 set_global_timeout 才停,回归卡死。最终 PASS/FAIL 不靠 $display,而由 uvm_report_server 统计 uvm_error/fatal 数量、scoreboard 结果,在 report_phase 给出统一总结。
七、搭平台检查清单
- [ ] driver 只翻译波形、monitor 只被动采样,职责单一;
- [ ] agent 可 active/passive 切换,monitor 两种模式复用;
- [ ] 虚接口经 config_db 传递,get 失败必须 fatal;
- [ ] 子组件在 build_phase 创建,TLM 在 connect_phase 连接;
- [ ] monitor 用 analysis_port 一对多喂 scoreboard 与 coverage;
- [ ] run_phase 正确 raise/drop objection,并有全局超时兜底;
- [ ] test 只选型/配置,平台主体靠工厂覆盖而非改用例代码;
- [ ] PASS/FAIL 走 UVM report,可被回归脚本解析。
相关与延伸
激励如何变成引脚波形、预期如何比对见 UVM sequence/driver/scoreboard 实战;随机事务写法见 CRV,覆盖率订阅器见 代码与功能覆盖率,接口检查见 SVA。
总结
UVM 用标准组件层次解耦验证平台:test 选型、env 组装、active agent 里 sequencer 派发事务、driver 把事务翻译成引脚波形、被动 monitor 把引脚还原成事务并经 analysis_port 一对多广播给 scoreboard 和 coverage,passive agent 只挂 monitor 旁路监听;class 世界通过 config_db 拿到 module 世界的 virtual interface 才能碰真实信号;所有组件在 build(自顶向下)、connect(自底向上)等零时间 phase 自动装配,唯一耗时的 run_phase 用 objection 控制起止,最终由 UVM 报告机统一判决——价值不在背类名,而在可复用、可替换、团队统一的工程结构。