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();   // 调用方法
避坑指南: 我曾经见过新手直接拿句柄就用,忘了new(),结果仿真报空指针错误。记住:声明句柄只是给了你一个“遥控器”,new()才是把电视机搬回家。

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
个人习惯: 我一般会在接口里定义modport,明确方向。这样不管是driver还是monitor,一看就知道哪些信号是输出、哪些是输入,省得接线时搞反。

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
注意: 虚拟接口的配置一定要在build_phase里完成,而且名字要跟顶层传下来的一致。我曾经因为大小写写错,折腾了一下午。

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
避坑指南: 约束别写太死。我曾经见过有人把约束写成addr == 100,那跟直接赋值有什么区别?随机化的意义就没了。记住:约束是“边界”,不是“定点”。

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 知识体系总览

为了让你看得更清楚,我画了张图,把这一章的核心知识点串起来:

SystemVerilog 核心知识体系 面向对象编程 接口 断言 随机化 功能覆盖率 类、继承、多态 virtual interface、modport 立即断言、并发断言 rand、constraint、randomize covergroup、coverpoint、cross 这五个模块是UVM验证环境的基石,缺一不可

这五个模块,说白了就是UVM的“五根柱子”。面向对象编程让你能搭框架,接口让你连DUT,断言帮你抓Bug,随机化帮你自动生成测试,功能覆盖率告诉你测够了没有。少了任何一个,你的验证环境都不完整。

我个人建议,如果你对上面这些内容还有模糊的地方,先别急着往下学UVM。花点时间把SV基础打牢,后面会顺畅很多。我在带新人时,发现那些SV基础扎实的,学UVM基本两周就能上手;基础不牢的,三个月还在原地打转。

总结一下: 这一章我们回顾了SV里跟UVM最相关的五个核心知识点。记住,UVM不是魔法,它只是把这些SV特性用设计模式组织起来而已。理解了这些基础,你就能看懂UVM源码,也能自己动手搭建验证环境了。