3. 验证环境架构设计

UVM 测试平台的价值不在于“把 driver、monitor、scoreboard 写出来”,而在于用一套可复用、可配置、可替换的分层结构,把验证意图与验证代码解耦。一个设计良好的 UVM 环境,应当让新增一个测试用例只改少量代码,让替换一个激励序列不影响 scoreboard,让同一套环境在模块级、子系统级和芯片级之间平滑迁移。本节围绕分层、复用、配置与工厂四条主线展开。

一、测试平台分层:从信号到场景的职责切分

UVM 环境通常划分为若干层次,每层只关心自己该关心的事。最底层是 uvm_agent,它封装一个接口的全部验证活动,内部包含 sequencer、driver 和 monitor。driver 负责把 sequence item 转换成引脚级时序,monitor 负责把引脚活动还原成事务,sequencer 负责仲裁多个 sequence 的激励请求。agent 之上是 uvm_env,它把多个 agent、scoreboard、reference model、coverage collector 组合成一个完整验证环境。最上层是 uvm_test,它决定本次仿真实例化哪个 env 配置、启动哪些 sequence、设置哪些超时与退出条件。

这种分层的核心收益是“变化隔离”。当 DUT 接口协议从 AXI4 改为 AXI4-Lite,只需替换对应 agent 的 driver 和 monitor,env 与 test 基本不动;当需要增加一个寄存器检查,只需在 env 中挂接一个 register model 并在 test 中启用,agent 无需感知。分层不是形式主义,而是把“可能变化的部分”收敛到最小范围。

uvm_test(测试层) 选择 env 配置 · 启动 sequence · 设置超时与退出条件 · 控制覆盖率采样开关 uvm_env(环境层) 组合 agent · scoreboard · reference model · coverage collector · virtual sequencer AXI Agent sequencer + driver + monitor APB Agent sequencer + driver + monitor Scoreboard / Ref Model 事务比对 · 参考计算 · 覆盖率收集 uvm_agent 内部结构(以 AXI Agent 为例) sequencer 仲裁 · 激励调度 driver item → 引脚时序 monitor 引脚 → 事务还原 DUT(待验证设计) AXI 接口 · APB 接口 · 内部逻辑 AXI 信号 APB 信号 监测点

二、Cadence UVM 库的组件复用

Cadence 提供的 UVM 库(如 uvm_pkg 及配套的 uvm_macros.svh)本身已经实现了大量可复用基类。实际项目中最常用的复用方式有三种:参数化复用、继承扩展和 TLM 连接复用。

参数化复用体现在 uvm_agent 的 is_active 参数上。同一个 agent 类,在模块级验证中设为 UVM_ACTIVE,既产生激励又监测;在子系统级验证中,如果该接口的激励由上层模块提供,则设为 UVM_PASSIVE,只保留 monitor 和 coverage。这样一套 agent 代码可以跨验证层级使用,不需要复制粘贴。

继承扩展是 UVM 最核心的复用手段。例如基础 driver 实现了标准的握手时序,项目中的 my_axi_driver 继承自它,只重写 drive_one_item 中与项目相关的部分。当基础协议时序需要修正时,所有子类自动受益。Cadence 库中的 uvm_driver #(REQ, RSP)、uvm_monitor、uvm_scoreboard 都是为这种扩展设计的。

TLM 连接复用则体现在端口命名与连接方式上。agent 对外暴露 analysis_port,env 中通过 connect 把多个 agent 的 analysis_port 连接到 scoreboard 的 analysis_export。只要端口类型一致,agent 内部如何实现 monitor 并不影响 env 的组装。这种“接口稳定、实现自由”的特性,是 UVM 环境能够大规模复用的基础。

三、环境配置机制:config_db 与 build_phase 的配合

UVM 的配置机制以 uvm_config_db 为核心。它本质上是一个全局的层次化键值数据库,允许在 build_phase 之前或之中,把配置对象、虚拟接口、参数值写入指定路径,再由目标组件在 build_phase 中读取。

典型用法如下:在 test 的 build_phase 中,通过 uvm_config_db#(virtual axi_if)::set(this, "env.axi_agt.*", "vif", axi_vif) 把虚拟接口传递给 agent 下的所有组件。agent 的 driver 在 build_phase 中调用 uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif) 获取接口。这里的关键是路径匹配:set 的第二个参数是目标路径,get 的第二个参数是相对路径,两者必须能对应上。

配置对象通常继承自 uvm_object,包含 int、bit、string 等字段。例如一个 axi_config 对象可以包含 data_width、addr_width、outstanding_depth 等参数。test 中设置 cfg.data_width = 64,agent 中读取后据此配置 monitor 的采样宽度和 coverage 的 bin 划分。这样同一套环境代码,通过不同的 config 对象,就能适配不同位宽、不同深度的 DUT 配置。

config_db 的 set/get 必须在 build_phase 中完成,且 set 通常发生在更高层次。如果 get 返回 0 或报 “config_db get failed”,优先检查路径字符串是否与组件层次一致,以及 set 是否在 get 之前执行。

四、工厂机制:override 实现运行时替换

UVM 工厂(factory)机制允许在不修改原有代码的情况下,用子类替换父类。其核心是 uvm_component_utils 和 uvm_object_utils 宏注册,以及 set_type_override_by_type 或 set_inst_override_by_type 调用。

例如,基础环境中使用 axi_driver,但在某个错误注入测试中,需要替换为 axi_error_driver。只需在 test 的 build_phase 中调用:

axi_error_driver::type_id::set_type_override(axi_driver::get_type());

工厂在创建组件时,会检查是否存在 override 记录,如果存在则创建子类实例。这样 test 不需要知道 env 内部具体实例化了哪个 driver,只需要声明“我要用错误注入版本”。同理,sequence 也可以通过工厂替换,实现不同激励场景的切换。

工厂机制与 config_db 配合,构成了 UVM 环境“可配置、可替换”的两大支柱。config_db 负责传递数据和接口,工厂负责替换类型和行为。两者结合,使得同一个 testbench 骨架可以覆盖从冒烟测试到压力测试、从正常功能到错误注入的多种验证需求。

在实际项目中,建议把 override 集中放在 test 的 build_phase 开头,并在注释中说明替换目的。避免在 env 或 agent 内部做 override,否则会破坏层次清晰度,导致调试时难以追踪实际运行的组件类型。

五、一个可参考的环境骨架

下面给出一个简化的环境骨架示例,展示 test、env、agent 三层的 build_phase 协作关系。参数为示例值,实际项目需根据 DUT 接口调整。

class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  my_env env;
  axi_config cfg;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    cfg = axi_config::type_id::create("cfg");
    cfg.data_width = 64;
    cfg.addr_width = 32;
    uvm_config_db#(axi_config)::set(this, "env.axi_agt.*", "cfg", cfg);
    uvm_config_db#(virtual axi_if)::set(this, "env.axi_agt.*", "vif", axi_vif);
    env = my_env::type_id::create("env", this);
  endfunction
endclass

agent 的 build_phase 中读取 cfg 和 vif,driver 和 monitor 再分别从 agent 获取。整个过程中,test 只负责“配置什么”,env 负责“组装什么”,agent 负责“怎么驱动和监测”。职责边界清晰,后续增加新接口或新测试时,改动范围可控。

总结来说,UVM 环境架构设计的核心是:用分层隔离变化,用继承和参数化实现复用,用 config_db 传递配置,用工厂实现运行时替换。这四者共同支撑起一个可维护、可扩展、可跨项目复用的验证平台。