跳到主要内容
SystemVerilog

Class 实用示例

SystemVerilog面向对象编程实用示例

SV class 面向对象

Class 实用示例

示例1:基础transaction

class transaction;
    // 属性
    logic [31:0] addr;
    logic [31:0] data;
    logic        wr;      // 1=写, 0=读

    // 构造函数
    function new(input logic [31:0] a = 0, 
                 input logic [31:0] d = 0, 
                 input logic        w = 1);
        addr = a;
        data = d;
        wr   = w;
    endfunction

    // 显示方法
    function void display();
        $display("wr=%b, addr=%h, data=%h", wr, addr, data);
    endfunction
endclass

// 使用
transaction txn;
initial begin
    txn = new(32'h100, 32'hABCD, 1);  // 写操作
    txn.display();
    
    txn = new(32'h200, 0, 0);         // 读操作
    txn.display();
end

补充:使用 extern 将方法写在 class 外

extern 不会改变方法的功能,主要用于把“类有哪些成员”与“方法具体如何实现”分开,使较长的 class 更容易阅读。

class simple_transaction;
    logic [31:0] addr;
    logic [31:0] data;

    // class内只保留方法声明,不写方法体
    extern function void display();
endclass

// class外编写方法实现
// ::表示display属于simple_transaction类
function void simple_transaction::display();
    $display("addr=%h, data=%h", addr, data);
endfunction

// 使用方式与写在class内部时相同
simple_transaction txn;
initial begin
    txn = new();
    txn.addr = 32'h100;
    txn.data = 32'hABCD;
    txn.display();
end

上面的写法与直接在 class 内定义 display() 功能相同:

extern function void display();
        ↓ 只声明方法
function void simple_transaction::display();
        ↓ 在class外实现方法
txn.display();
        ↓ 调用方式不变

使用时注意:类外实现必须位于 class 声明之后,方法名、返回类型和参数需要与 extern 声明一致。短方法直接写在 class 内更直观;当 UVM class 包含较长的 build_phase()run_phase() 等方法时,再使用 extern 拆分结构通常更有价值。


示例2:继承 - 基础与扩展transaction

// 基础transaction
class base_transaction;
    logic [31:0] addr;
    logic        wr;

    function new(input logic [31:0] a = 0, input logic w = 0);
        addr = a;
        wr   = w;
    endfunction

    function void display();
        $display("wr=%b, addr=%h", wr, addr);
    endfunction
endclass

// 扩展transaction(增加data字段)
class ext_transaction extends base_transaction;
    logic [31:0] data;

    function new(input logic [31:0] a = 0, 
                 input logic [31:0] d = 0, 
                 input logic w = 0);
        super.new(a, w);  // 调用父类
        data = d;
    endfunction

    // 重写display
    function void display();
        $display("wr=%b, addr=%h, data=%h", wr, addr, data);
    endfunction
endclass

// 使用
ext_transaction ext_txn;
initial begin
    ext_txn = new(32'h100, 32'hDEAD_BEEF, 1);
    ext_txn.display();
end

补充:合成(composition)

继承表示“是一种”,合成表示“拥有一个”。合成在 SystemVerilog class 中通常通过成员句柄实现:

class simple_driver;
endclass

class simple_monitor;
endclass

class simple_agent;
    simple_driver  driver;
    simple_monitor monitor;

    function new();
        driver  = new();
        monitor = new();
    endfunction
endclass

simple_agent agent;
initial begin
    agent = new();
end

这里不是 simple_agent extends simple_driver,因为 agent 不是一种 driver,而是拥有 driver 和 monitor:

继承:ext_transaction is-a base_transaction
合成:simple_agent has-a driver and monitor

成员声明只创建句柄,初始值为 null;构造函数中的 new() 才创建实际对象。句柄是语言机制,合成是由这些成员表达出的对象组织关系。


示例3:随机化 - 带约束的transaction

class rand_transaction;
    rand logic [31:0] addr;
    rand logic [31:0] data;
    rand logic        wr;
    randc bit [7:0]   id;     // 循环不重复

    // 地址约束:0x000~0x0FF
    constraint c_addr {
        addr inside {[32'h0000:32'h00FF]};
    }

    // 数据约束:不能为0
    constraint c_data {
        data != 0;
    }

    // 写操作时数据范围
    constraint c_wr_data {
        if (wr)
            data inside {[32'h0000:32'h0000_FFFF]};
    }
endclass

// 使用
rand_transaction txn;
initial begin
    txn = new();
    repeat(5) begin
        assert(txn.randomize())
            $display("id=%0d, wr=%b, addr=%h, data=%h", 
                     txn.id, txn.wr, txn.addr, txn.data);
    end
end

示例4:队列 - 存储多个transaction

这个例子先创建多个独立的 transaction 对象,再把它们的句柄依次放入队列。队列初始为空,不会自动收集之前创建的对象。

class transaction_queue;
    // queue是一个无固定长度的队列,每个元素保存一个transaction句柄
    transaction queue[$];

    // 入队:将句柄添加到队尾
    function void push(transaction txn);
        if (txn == null) begin
            $error("不能将null transaction放入队列");
            return;
        end

        queue.push_back(txn);
    endfunction

    // 出队:删除并返回队首句柄;空队列返回null
    function transaction pop();
        if (queue.size() == 0) begin
            $warning("transaction队列为空");
            return null;
        end

        return queue.pop_front();
    endfunction

    // 返回当前元素数量
    function int unsigned size();
        return queue.size();
    endfunction

    // 判断队列是否为空
    function bit is_empty();
        return (queue.size() == 0);
    endfunction

    // 显示队列中的所有transaction
    function void display_all();
        $display("queue size = %0d", queue.size());

        foreach (queue[i]) begin
            $display("[%0d] wr=%b, addr=%h, data=%h",
                     i, queue[i].wr, queue[i].addr, queue[i].data);
        end
    endfunction
endclass

// 使用
transaction_queue tq;
transaction txn;
transaction popped_txn;

initial begin
    tq = new();

    // 每次new()都会创建一个独立对象。
    // txn只是重复使用的句柄变量,队列会继续保存之前对象的句柄。
    txn = new(32'h100, 32'hAAAA, 1'b1);
    tq.push(txn);

    txn = new(32'h200, 32'h0000, 1'b0);
    tq.push(txn);

    txn = new(32'h300, 32'hCCCC, 1'b1);
    tq.push(txn);

    // 此时队列中有三个不同的transaction对象
    tq.display_all();

    // FIFO:最先放入的对象最先取出
    popped_txn = tq.pop();
    if (popped_txn != null) begin
        $display("pop result:");
        popped_txn.display();
    end

    // pop之后,队列中还剩两个对象
    tq.display_all();
end

执行过程如下:

初始                    queue = []
第一次 push             queue = [对象A]
第二次 push             queue = [对象A, 对象B]
第三次 push             queue = [对象A, 对象B, 对象C]
执行 pop,返回对象A      queue = [对象B, 对象C]

需要特别注意:push_back() 保存的是对象句柄,不会自动复制对象。下面三次入队的是同一个对象:

txn = new();                 // 只创建一次对象

txn.addr = 32'h100;
tq.push(txn);

txn.addr = 32'h200;
tq.push(txn);

txn.addr = 32'h300;
tq.push(txn);

此时 queue[0]queue[1]queue[2] 都指向同一个对象,最终看到的 addr 都是 32'h300。需要保存三个独立对象时,必须像完整示例那样分别执行三次 new()


示例5:driver - 驱动DUT

class driver;
    virtual axi_if vif;  // 虚接口连接 DUT

    function new(virtual axi_if vif);
        this.vif = vif;  // this.访问成员变量
    endfunction

    // 写操作
    task write(input logic [31:0] addr, input logic [31:0] data);
        @(posedge vif.clk);
        vif.awvalid <= 1;
        vif.awaddr  <= addr;
        @(posedge vif.clk);
        vif.wvalid  <= 1;
        vif.wdata   <= data;
        @(posedge vif.clk);
        vif.wvalid  <= 0;
        vif.awvalid <= 0;
    endtask

    // 读操作
    task read(input logic [31:0] addr, output logic [31:0] data);
        @(posedge vif.clk);
        vif.arvalid <= 1;
        vif.araddr  <= addr;
        @(posedge vif.clk);
        vif.arvalid <= 0;
        @(posedge vif.rvalid);
        data = vif.rdata;
    endtask
endclass

// 使用
driver drv;
initial begin
    drv = new(vif);  // 传入接口
    drv.write(32'h100, 32'hDEAD_BEEF);
end

示例6:monitor - 采集DUT信号

class monitor;
    virtual axi_if vif;
    transaction txn;

    function new(virtual axi_if vif);
        this.vif = vif;
    endfunction

    // 监测写操作
    task run();
        forever begin
            @(posedge vif.clk);
            if (vif.awvalid && vif.awready) begin
                txn = new(vif.awaddr, vif.wdata, 1);
                $display("Monitor: %h", txn.addr);
            end
        end
    endtask
endclass

// 使用
monitor mon;
initial begin
    mon = new(vif);
    mon.run();
end

示例7:scoreboard - 比较结果

class scoreboard;
    int pass_count = 0;
    int fail_count = 0;

    // 比较预期和实际
    function void check(input logic [31:0] expected, 
                        input logic [31:0] actual);
        if (expected === actual) begin
            $display("PASS: %h == %h", expected, actual);
            pass_count++;
        end else begin
            $display("FAIL: %h != %h", expected, actual);
            fail_count++;
        end
    endfunction

    // 显示统计
    function void report();
        $display("Pass: %0d, Fail: %0d", pass_count, fail_count);
    endfunction
endclass

示例8:parameterized class - 参数化类

// 通用FIFO类,位宽可配置
class fifo #(parameter WIDTH = 8, DEPTH = 16);
    logic [WIDTH-1:0] queue[$];

    function void push(input logic [WIDTH-1:0] data);
        if (queue.size() < DEPTH)
            queue.push_back(data);
        else
            $error("FIFO full");
    endfunction

    function logic [WIDTH-1:0] pop();
        if (queue.size() > 0)
            return queue.pop_front();
        else
            $error("FIFO empty");
    endfunction

    function int size();
        return queue.size();
    endfunction
endclass

// 使用
fifo #(8, 16)   fifo8;    // 8位宽,深度16
fifo #(32, 64)  fifo32;   // 32位宽,深度64

initial begin
    fifo8 = new();
    fifo8.push(8'hAA);
    fifo8.push(8'hBB);
end

最后更新: 2026-07-15