UVM 中 virtual 关键字的使用详解

UVM 中 virtual 关键字的使用详解

一、概述

在 UVM 验证环境中,virtual 关键字出现在两个核心场景:

场景 用法 作用
虚接口 virtual axi_if 将 RTL 接口与验证组件解耦
虚方法 virtual function/task 实现多态(Polymorphism),支撑 UVM 的工厂机制和回调体系

这两个场景看似不同,但背后遵循同一个设计哲学:面向接口编程,依赖抽象而非具体实现


二、virtual interface --- 虚接口

2.1 为什么需要虚接口

在 SystemVerilog 中,interface 是一个硬件 构造(可以包含 modportclockingalways 块等),而 UVM 组件是软件 对象(class)。

systemverilog 复制代码
// interface 是硬件,不是对象
interface axi_if(input clk, rst_n);
    logic [31:0] addr;
    logic [31:0] wdata;
    logic        wr_en;
    // ...
endinterface

// UVM 组件是软件 class,不能直接"包含" interface
class my_driver extends uvm_driver;
    // ❌ 不能这样声明
    // axi_if vif;  // axi_if 不是数据类型,不能用作 class 的成员
endclass

解决方案 :使用 virtual interface,它本质上是一个句柄,指向一个实际的 interface 实例,可以被赋值、传递、存储在 class 中。

systemverilog 复制代码
class my_driver extends uvm_driver;
    virtual axi_if vif;  // ✅ 正确:virtual interface 是一个句柄
endclass

2.2 没有 virtual 会怎样

systemverilog 复制代码
// ❌ 错误代码
interface my_if;
    logic [31:0] data;
    logic        valid;
endinterface

class bad_driver;
    my_if vif;  // 编译错误:interface 不能作为 class 非虚成员

    task drive();
        vif.data <= 32'h1234;  // 永远到不了这里
    endtask
endclass

编译错误Non-interface Port / Interface not allowed in class non-virtual member

2.3 实际含义对比

systemverilog 复制代码
// 在 module 中(硬件世界)
axi_if vif();          // 实例化一个 interface → 实际生成硬件连线

// 在 class 中(软件世界)
virtual axi_if vif;    // 声明一个句柄 → 指向已有的 interface 实例
                       // 不生成任何硬件,只是一个"指针"

2.4 虚接口的传递链路

复制代码
┌─────────────────────────────────────────────────────────┐
│ tb_top (module)                                         │
│   axi_if dut_if();                    ← 硬件实例化       │
│   initial begin                                          │
│     uvm_config_db#(virtual axi_if)::set(...);             │
│     run_test();                                          │
│   end                                                    │
└─────────────────────────────────────────────────────────┘
                            │
                            │ uvm_config_db
                            ▼
┌─────────────────────────────────────────────────────────┐
│ my_driver (class)                                       │
│   virtual axi_if vif;         ← 软件句柄,指向 dut_if  │
│   task run_phase(...);                                  │
│     vif.addr <= tx.addr;     ← 通过句柄驱动硬件          │
│   endtask                                               │
└─────────────────────────────────────────────────────────┘

2.5 为什么必须用 uvm_config_db 传递

因为 virtual interface 是句柄,不能通过构造函数传递(function new 的参数列表固定),UVM 也没有为每个组件预留 set_interface 方法,所以必须通过全局配置数据库传递:

systemverilog 复制代码
// tb_top
initial begin
    uvm_config_db#(virtual axi_if)::set(null, "uvm_test_top", "vif", dut_if);
    run_test();
end

// test
class my_test extends uvm_test;
    virtual axi_if vif;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif);
        uvm_config_db#(virtual axi_if)::set(this, "env.agent.driver", "vif", vif);
    endfunction
endclass

// driver
class my_driver extends uvm_driver#(my_transaction);
    virtual axi_if vif;

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif);  // 获取句柄
    endfunction
endclass

2.6 虚接口的核心优势

优势 说明
软硬件分离 RTL 和验证环境可以独立开发、独立编译
接口切换 更换 DUT 接口时只需修改 tb_top 和 driver 的信号驱动部分,组件结构无需变化
多接口支持 同一个 driver 可以通过不同的虚接口句柄驱动多个实例
仿真加速 通过 virtual interfaceclocking 块可以实现 cycle-level 驱动,与具体时序无关

2.7 多态接口的典型用法

systemverilog 复制代码
// 定义统一的虚接口基类
virtual class virtual_if_base;
    pure virtual task reset();
    pure virtual task drive_one_pkt(bit [31:0] data[]);
endclass

// 不同的 DUT 接口各自继承并实现
class axi_vif_wrapper extends virtual_if_base;
    virtual axi_if vif;

    task reset();
        vif.rst_n <= 1'b0;
        repeat(10) @(posedge vif.clk);
        vif.rst_n <= 1'b1;
    endtask

    task drive_one_pkt(bit [31:0] data[]);
        foreach(data[i]) begin
            @(posedge vif.clk);
            vif.addr  <= i;
            vif.wdata <= data[i];
            vif.wr_en <= 1'b1;
        end
    endtask
endclass

这样,driver 只需持有 virtual_if_base 句柄,而无需关心具体是 AXI 还是 AHB:

systemverilog 复制代码
class my_driver extends uvm_driver;
    virtual_if_base vif_wrapper;  // 面向抽象编程

    task drive();
        vif_wrapper.reset();
        vif_wrapper.drive_one_pkt(tx.data);
    endtask
endclass

三、virtual method --- 虚方法

3.1 什么是虚方法

virtual function / virtual task 是 SystemVerilog OOP 的多态机制。声明为 virtual 的方法,在通过基类句柄 调用时,实际执行的是对象实际类型的方法。

3.2 没有 virtual 会怎样

systemverilog 复制代码
// 基类
class packet extends uvm_object;
    function void my_print();           // ❌ 非虚函数
        $display("This is a packet");
    endfunction
endclass

// 派生类
class eth_packet extends packet;
    function void my_print();           // 同名函数,但不是 override
        $display("This is an eth_packet");
    endfunction
endclass

// 测试
packet p;
eth_packet e;
e = new();
p = e;          // 基类句柄指向派生类对象

p.my_print();   // 输出:"This is a packet"   ❌ 调用了基类版本!
e.my_print();   // 输出:"This is an eth_packet"

没有 virtual,多态失效。 基类句柄永远只能调用基类的方法,无法调用派生类的重写版本。

3.3 加上 virtual

systemverilog 复制代码
class packet extends uvm_object;
    virtual function void my_print();   // ✅ 虚函数
        $display("This is a packet");
    endfunction
endclass

class eth_packet extends packet;
    virtual function void my_print();   // ✅ override
        $display("This is an eth_packet");
    endfunction
endclass

packet p;
eth_packet e;
e = new();
p = e;          // 基类句柄指向派生类对象
p.my_print();   // 输出:"This is an eth_packet"  ✅ 多态正确工作!

3.4 UVM 中必须用 virtual 的关键方法

UVM 的整个框架依赖多态,以下方法是 UVM 架构正常运行的基石:

(1) Phase 方法
systemverilog 复制代码
class uvm_component;
    virtual function void build_phase(uvm_phase phase);   // ✅ 必须 virtual
    virtual function void connect_phase(uvm_phase phase);
    virtual task run_phase(uvm_phase phase);
    virtual function void report_phase(uvm_phase phase);
    // ...
endclass

class my_test extends uvm_test;
    virtual function void build_phase(uvm_phase phase);   // ✅ override
        super.build_phase(phase);
        // 用户代码
    endfunction
endclass

UVM 内核通过 uvm_component 句柄调用 phase 方法:

systemverilog 复制代码
// UVM 内部(伪代码)
uvm_component comp_list[$];
foreach(comp_list[i])
    comp_list[i].build_phase(phase);  // 如果没有 virtual,永远调用 uvm_component::build_phase
                                       // 用户写的派生类重写版本永远不会被执行
(2) do_* field automation 方法
systemverilog 复制代码
class uvm_object;
    virtual function void do_copy(uvm_object rhs);
    virtual function bit  do_compare(uvm_object rhs, uvm_comparer comparer);
    virtual function void do_print(uvm_printer printer);
    virtual function void do_pack(uvm_packer packer);
    // ...
endclass

// 用户重写这些 do_* 来实现自定义行为
class my_packet extends uvm_object;
    virtual function void do_print(uvm_printer printer);
        super.do_print(printer);
        printer.print_field("crc", crc, 32, UVM_HEX);
    endfunction
endclass
(3) convert2string
systemverilog 复制代码
class uvm_object;
    virtual function string convert2string();
endclass

class my_transaction extends uvm_sequence_item;
    virtual function string convert2string();
        return $sformatf("addr=%0h data=%0h", addr, data);
    endfunction
endclass

3.5 如果不加 virtual,UVM 会直接崩溃

考虑 UVM 的 print() 实现:

systemverilog 复制代码
// UVM 内部
function void uvm_object::print(uvm_printer printer = null);
    // ...
    do_print(printer);         // ✅ do_print 是 virtual → 调用实际类型的版本
    // ...
endfunction

如果 do_print 不是 virtual,那么无论对象是什么类型,print() 都只会调用 uvm_object::do_print,打印出空内容。用户重写的 do_print 永远不会被执行。


四、virtual class --- 虚类(抽象类)

4.1 什么是虚类

virtual class(也称为抽象类)是不能被直接实例化的类,只能作为基类使用。它通常包含 pure virtual method(纯虚方法),强制派生类实现。

4.2 在 UVM 中的使用

systemverilog 复制代码
// ❌ 错误:virtual class 不能被实例化
// packet p = packet::type_id::create("p");  // 编译错误

// 定义抽象基类
virtual class packet_base extends uvm_sequence_item;
    pure virtual function bit [31:0] get_addr();
    pure virtual function bit [31:0] get_data();
endclass

// 具体的派生类必须实现所有 pure virtual 方法
class axi_packet extends packet_base;
    rand bit [31:0] addr;
    rand bit [31:0] data;

    virtual function bit [31:0] get_addr();
        return addr;
    endfunction

    virtual function bit [31:0] get_data();
        return data;
    endfunction
endclass

class ahb_packet extends packet_base;
    rand bit [31:0] addr;
    rand bit [31:0] wdata;

    virtual function bit [31:0] get_addr();
        return addr;
    endfunction

    virtual function bit [31:0] get_data();
        return wdata;  // AHB 用 wdata 表示写数据
    endfunction
endclass

这样,monitor / scoreboard 就可以面向抽象编程:

systemverilog 复制代码
class generic_monitor extends uvm_monitor;
    packet_base pkt;

    task run_phase(uvm_phase phase);
        forever begin
            // 收集到的数据,无论什么协议,都通过抽象接口读取
            $display("addr=%0h data=%0h", pkt.get_addr(), pkt.get_data());
        end
    endtask
endclass

4.3 UVM 中的预定义虚类

UVM 大量使用了虚类来定义接口:

虚类 说明
uvm_object 所有 UVO 数据对象的基类(虽然不是 virtual class,但不可直接实例化使用)
uvm_component 所有 UVM 组件的基类
uvm_sequence_item 所有 sequence item 的基类
uvm_sequence_base uvm_sequence 的基类
uvm_monitor 所有 monitor 的基类
uvm_subscriber 所有 subscriber(如 scoreboard)的基类

五、pure virtual --- 纯虚方法

5.1 定义

systemverilog 复制代码
class abstract_base;
    pure virtual function void must_implement();
    pure virtual task must_implement_task();
endclass
  • pure virtual 方法没有实现体
  • 包含 pure virtual 方法的类必须是 virtual class
  • 派生类必须 实现所有 pure virtual 方法(否则编译错误)
  • 用于定义"契约"或"接口规范"

5.2 使用场景:定义验证组件接口规范

systemverilog 复制代码
virtual class driver_base extends uvm_driver#(uvm_sequence_item);
    // 所有 driver 必须实现的接口
    pure virtual task reset_dut();
    pure virtual task drive_item(uvm_sequence_item item);
    pure virtual function void check_protocol();
endclass

// 具体实现
class axi_driver extends driver_base;
    virtual task reset_dut();
        // AXI 复位时序
    endtask

    virtual task drive_item(uvm_sequence_item item);
        // AXI 驱动逻辑
    endtask

    virtual function void check_protocol();
        // AXI 协议检查
    endfunction
endclass

六、完整对比总结

6.1 三种 virtual 用法的对比

类型 语法 作用 不用的后果
virtual interface virtual axi_if vif 将 interface 作为句柄传入 class 编译错误,完全不能用
virtual function/task virtual function void foo() 实现方法多态 多态失效,基类句柄调不到派生类方法,UVM 的 phase / print 等机制全部崩溃
virtual class virtual class foo 定义抽象基类,不能被实例化 可以被实例化,失去了抽象约束的意义
pure virtual method pure virtual function foo() 强制派生类实现 派生类可以"忘记"实现,编译不报错但运行出错

6.2 UVM 的生命线

UVM 的核心机制全部依赖 virtual

复制代码
UVM 核心机制                 依赖的 virtual
─────────────────────────────────────────
Phase 自动调度              virtual function build_phase()
Factory 创建               virtual function create()
Field automation           virtual function do_print/do_copy/do_compare()
Callback 机制              virtual function pre/post_*
Report 机制                virtual function convert2string()
Sequence 执行              virtual task body()
Interface 传递             virtual interface

任何一个去掉 virtual,UVM 的相应功能就会完全失效。

6.3 一句话总结

systemverilog 复制代码
// 没有 virtual:
//   class 中不能使用 interface(编译失败)
//   基类句柄无法调用派生类方法(多态失效)
//   UVM 的 phase、factory、field automation 全部无法工作
//
// 加上 virtual:
//   硬件接口可以通过句柄传递到软件 class 中
//   基类句柄→派生类方法(多态完美工作)
//   UVM 的所有核心机制正常运行

virtual 是 UVM 的基石。没有它,UVM 就不存在了。

相关推荐
fei_sun2 天前
UVM中的sequence
uvm
Salt & Light4 天前
UVM 字段自动化 Field Automation:让打印、复制、比较自动化
学习·systemverilog·uvm
fei_sun11 天前
UVM中的TLM1.0通信
运维·服务器·uvm
liuluyang53017 天前
UVM学习-2
uvm
liuluyang53018 天前
UVM入门和进阶实验2
uvm
啄缘之间19 天前
5.1 uvm‑component 的层级结构管理? new (name,parent) 参数
开发语言·笔记·学习·ic·uvm·sv
北方孤寂的灵魂1 个月前
关于AXI协议详解
verilog·systemverilog·sv·数字验证
happyDogg_1 个月前
uvm_root详解
uvm·数字验证
啄缘之间3 个月前
8.【学习】工业级详细接口约束&覆盖率
开发语言·笔记·学习·uvm·sv