2. 编写第一个UVM测试用例

任务目标

本案例将编写一个最小可运行的UVM测试用例:通过UVM验证环境向一个简单的DUT(组合逻辑加法器)发送一组激励,并自动检查输出是否正确。完成本案例后,你将理解UVM测试用例从启动到结束的完整执行流程。

环境依赖

需要支持UVM-1.2的仿真器(如VCS、QuestaSim、Xcelium)。以下代码基于标准UVM库编写,无需额外安装包。

DUT设计(加法器)

// adder.v - 被测设计:8位加法器
module adder(
    input  [7:0] a,
    input  [7:0] b,
    output [8:0] sum
);
    assign sum = a + b;
endmodule

UVM验证环境与测试用例

// test_adder.sv - 完整UVM测试用例
`include "uvm_macros.svh"
import uvm_pkg::*;

// 1. 事务对象:定义激励与期望值
class adder_trans extends uvm_sequence_item;
    rand bit [7:0] a;
    rand bit [7:0] b;
    bit [8:0] expected_sum;

    `uvm_object_utils_begin(adder_trans)
        `uvm_field_int(a, UVM_ALL_ON)
        `uvm_field_int(b, UVM_ALL_ON)
    `uvm_object_utils_end

    function new(string name = "adder_trans");
        super.new(name);
    endfunction

    // 约束:限制随机范围便于人工核对
    constraint c_range { a inside {[0:15]}; b inside {[0:15]}; }
endclass

// 2. 驱动器:将事务驱动到DUT接口
class adder_driver extends uvm_driver #(adder_trans);
    virtual adder_if vif;

    `uvm_component_utils(adder_driver)

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    task run_phase(uvm_phase phase);
        forever begin
            seq_item_port.get_next_item(req);
            vif.a = req.a;
            vif.b = req.b;
            #10;  // 等待DUT输出稳定
            seq_item_port.item_done();
        end
    endtask
endclass

// 3. 监视器:采样输出并发送给计分板
class adder_monitor extends uvm_monitor;
    virtual adder_if vif;
    uvm_analysis_port #(adder_trans) mon_ap;

    `uvm_component_utils(adder_monitor)

    function new(string name, uvm_component parent);
        super.new(name, parent);
        mon_ap = new("mon_ap", this);
    endfunction

    task run_phase(uvm_phase phase);
        adder_trans trans;
        forever begin
            @(posedge vif.clk);
            trans = adder_trans::type_id::create("trans");
            trans.a = vif.a;
            trans.b = vif.b;
            trans.expected_sum = vif.a + vif.b;  // 期望值由DUT输入直接计算
            mon_ap.write(trans);
        end
    endtask
endclass

// 4. 计分板:比较期望值与DUT实际输出
class adder_scoreboard extends uvm_scoreboard;
    `uvm_component_utils(adder_scoreboard)
    uvm_analysis_imp #(adder_trans, adder_scoreboard) sb_imp;

    function new(string name, uvm_component parent);
        super.new(name, parent);
        sb_imp = new("sb_imp", this);
    endfunction

    function void write(adder_trans trans);
        if (trans.expected_sum !== vif.sum) begin
            `uvm_error("SCB", $sformatf("Mismatch! a=%0d b=%0d expected=%0d actual=%0d",
                        trans.a, trans.b, trans.expected_sum, vif.sum))
        end
        else begin
            `uvm_info("SCB", $sformatf("PASS: a=%0d b=%0d sum=%0d", trans.a, trans.b, vif.sum), UVM_LOW)
        end
    endfunction
endclass

// 5. 环境:连接各组件
class adder_env extends uvm_env;
    adder_driver drv;
    adder_monitor mon;
    adder_scoreboard scb;

    `uvm_component_utils(adder_env)

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        drv = adder_driver::type_id::create("drv", this);
        mon = adder_monitor::type_id::create("mon", this);
        scb = adder_scoreboard::type_id::create("scb", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        mon.mon_ap.connect(scb.sb_imp);
    endfunction
endclass

// 6. 序列:生成一组激励
class adder_seq extends uvm_sequence #(adder_trans);
    `uvm_object_utils(adder_seq)

    function new(string name = "adder_seq");
        super.new(name);
    endfunction

    task body();
        repeat (5) begin
            adder_trans trans = adder_trans::type_id::create("trans");
            start_item(trans);
            assert(trans.randomize());
            finish_item(trans);
        end
    endtask
endclass

// 7. 测试用例:顶层入口
class adder_test extends uvm_test;
    adder_env env;

    `uvm_component_utils(adder_test)

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = adder_env::type_id::create("env", this);
    endfunction

    task run_phase(uvm_phase phase);
        adder_seq seq = adder_seq::type_id::create("seq");
        phase.raise_objection(this);
        seq.start(env.drv.seq_item_port);
        #100;  // 等待最后一个事务完成
        phase.drop_objection(this);
    endtask
endclass

// 8. 顶层模块:连接接口并启动UVM
module tb_top;
    logic clk = 0;
    always #5 clk = ~clk;

    adder_if vif(clk);
    adder dut(.a(vif.a), .b(vif.b), .sum(vif.sum));

    initial begin
        uvm_config_db#(virtual adder_if)::set(null, "uvm_test_top.env.*", "vif", vif);
        run_test("adder_test");
    end
endmodule

接口定义

// adder_if.sv - 接口定义
interface adder_if(input logic clk);
    logic [7:0] a;
    logic [7:0] b;
    logic [8:0] sum;
endinterface

运行方式

# 使用QuestaSim运行
vlib work
vlog adder.sv adder_if.sv test_adder.sv
vsim -c work.tb_top -do "run -all; quit"

验证方法与通过标准

运行仿真后,观察UVM报告。通过标准为:

  • UVM_INFO消息中显示5条PASS记录,每条包含a、b和sum值;
  • UVM_ERROR计数为0,即没有出现Mismatch错误;
  • 仿真正常结束,UVM报告显示UVM_ERROR : 0。

执行流程说明

UVM测试的执行遵循以下固定流程:

  1. run_test("adder_test")创建测试实例并执行build_phase,此时构建环境组件;
  2. 进入run_phase后,测试用例通过raise_objection阻止仿真退出;
  3. 启动序列,序列随机生成事务并通过start_item/finish_item发送给驱动器;
  4. 驱动器将事务驱动到DUT接口,监视器采样输入输出并发送给计分板;
  5. 计分板比较期望值与实际输出,打印PASS或ERROR;
  6. 所有激励发送完毕后,测试用例调用drop_objection,仿真结束。

本案例中计分板直接使用DUT输入计算期望值,因此能自动验证加法器功能。若需验证更复杂的DUT,可在事务中增加约束或参考模型来计算期望值。