2. SystemVerilog基础回顾:面向对象编程、接口、断言、随机化、功能覆盖率
好,咱们进入第二章。说实话,很多刚入行的朋友觉得UVM难,其实根子往往出在SystemVerilog基础上。你想想看,UVM本质上就是一套用SV写的库,基础不牢,后面肯定跑偏。这一章,我把SV里跟UVM最相关的几个核心点拎出来,咱们过一遍。
2.1 面向对象编程(OOP)——UVM的骨架
UVM整个框架,说白了就是用类堆起来的。你要是对OOP不熟,看UVM源码就跟看天书一样。
2.1.1 类与对象
类就是模板,对象是实例。我习惯把类想象成“蛋糕模具”,对象就是做出来的“蛋糕”。
class packet;
rand bit [7:0] addr;
rand bit [7:0] data;
function void display();
$display("addr=%0d, data=%0d", addr, data);
endfunction
endclass
packet pkt; // 声明句柄
pkt = new(); // 创建对象
pkt.display(); // 调用方法
2.1.2 继承与多态
UVM里到处都是继承。比如你写一个my_driver,就得从uvm_driver继承。多态呢?说白了就是“同一个接口,不同实现”。
class base_packet;
virtual function void display();
$display("I am base");
endfunction
endclass
class ext_packet extends base_packet;
function void display();
$display("I am extended");
endfunction
endclass
base_packet pkt;
ext_packet ext = new();
pkt = ext;
pkt.display(); // 输出:I am extended
嗯,这里要注意:如果你不用virtual关键字,那调用的就是基类的方法,多态就失效了。我在项目中遇到过有人忘了加virtual,结果跑出来的结果怎么都不对,查了两天才找到原因。
2.2 接口(Interface)——连接DUT的桥梁
接口这东西,我觉得是SV里最实用的设计之一。它把信号捆在一起,方便管理。
2.2.1 基本接口定义
interface axi_if(input clk, input rst_n);
logic [31:0] awaddr;
logic awvalid;
logic awready;
logic [31:0] wdata;
logic wvalid;
logic wready;
// ... 其他信号
modport master(
output awaddr, awvalid,
input awready,
output wdata, wvalid,
input wready
);
modport slave(
input awaddr, awvalid,
output awready,
input wdata, wvalid,
output wready
);
endinterface
2.2.2 虚拟接口
UVM里不能直接传接口,得用virtual interface。为什么?因为类是动态的,接口是静态的,virtual就是中间的“适配器”。
class my_driver extends uvm_driver#(my_transaction);
virtual axi_if vif;
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual axi_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "vif not set!")
endfunction
endclass
2.3 断言(Assertion)——让Bug无处遁形
断言这东西,说白了就是给设计加“监控摄像头”。你规定好什么情况是对的,它帮你盯着。
2.3.1 立即断言
always_ff @(posedge clk) begin
assert (data_valid && data_ready)
else $error("data_valid and data_ready should be high together");
end
2.3.2 并发断言
property p_req_ack;
@(posedge clk) req |=> ##[1:5] ack;
endproperty
assert property (p_req_ack)
else $error("req not acked within 5 cycles");
我个人习惯在验证环境里大量使用断言。你想想看,有些协议违例可能几万拍才出现一次,靠人眼盯波形?不现实。断言能帮你自动抓出来。
2.4 随机化——验证的发动机
UVM的核心思想之一就是“随机测试”。你手动写100个测试用例,不如让机器随机跑10000个。
2.4.1 随机变量与约束
class my_transaction extends uvm_sequence_item;
rand bit [7:0] addr;
rand bit [7:0] data;
rand bit wr_en;
constraint addr_range {
addr inside {[0:100], [200:255]};
}
constraint data_valid {
data != 8'h00;
data != 8'hFF;
}
endclass
2.4.2 随机化控制
my_transaction tr;
tr = my_transaction::type_id::create("tr");
assert(tr.randomize());
// 或者带约束的随机化
assert(tr.randomize() with {addr == 100; data inside {[1:10]};});
2.5 功能覆盖率——验证的“体检报告”
功能覆盖率告诉你:你测了哪些场景,还有哪些没测到。没有覆盖率,你就是在“盲测”。
2.5.1 覆盖组与覆盖点
class my_coverage;
my_transaction tr;
covergroup cg_addr @(tr.addr);
addr_cp : coverpoint tr.addr {
bins low = {[0:63]};
bins mid = {[64:127]};
bins high = {[128:191]};
bins top = {[192:255]};
}
endgroup
covergroup cg_data @(tr.data);
data_cp : coverpoint tr.data {
bins zero = {0};
bins max = {255};
bins others = default;
}
endgroup
function new();
cg_addr = new();
cg_data = new();
endfunction
function void sample(my_transaction t);
tr = t;
cg_addr.sample();
cg_data.sample();
endfunction
endclass
2.5.2 交叉覆盖率
covergroup cg_cross @(tr.addr, tr.data);
addr_cp : coverpoint tr.addr;
data_cp : coverpoint tr.data;
cross addr_cp, data_cp {
ignore_bins ignore_comb = binsof(addr_cp) intersect {[0:10]};
}
endgroup
嗯,这里要注意:交叉覆盖率很容易产生大量bin,导致仿真变慢。我一般会先用ignore_bins过滤掉不关心的组合,只保留关键场景。
2.6 知识体系总览
为了让你看得更清楚,我画了张图,把这一章的核心知识点串起来:
这五个模块,说白了就是UVM的“五根柱子”。面向对象编程让你能搭框架,接口让你连DUT,断言帮你抓Bug,随机化帮你自动生成测试,功能覆盖率告诉你测够了没有。少了任何一个,你的验证环境都不完整。
我个人建议,如果你对上面这些内容还有模糊的地方,先别急着往下学UVM。花点时间把SV基础打牢,后面会顺畅很多。我在带新人时,发现那些SV基础扎实的,学UVM基本两周就能上手;基础不牢的,三个月还在原地打转。