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 就不存在了。

相关推荐
啄缘之间2 个月前
8.【学习】工业级详细接口约束&覆盖率
开发语言·笔记·学习·uvm·sv
Ether IC Verifier2 个月前
SystemVerilog 数据类型详解
php·systemverilog·uvm·ic验证
审判长烧鸡2 个月前
【Go Interface】接口诞生的意义
go·接口·interface
liuluyang5302 个月前
UVM 工厂机制 完整可编译运行 Demo
uvm·uvm工厂机制
liuluyang5302 个月前
UVM工厂机制
uvm·工厂机制
liuluyang5302 个月前
UVM工厂机制(二)
uvm·工厂机制
liuluyang5302 个月前
SystemVerilog常用关键词与函数
uvm·systermverilog
liuluyang5302 个月前
SV主要关键词详解
fpga开发·uvm·sv
liuluyang5303 个月前
clk_mux_seq sv改进
fpga开发·uvm