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