3. 使用UVM寄存器模型

任务说明

本案例在一个最小UVM验证环境中集成寄存器模型(RAL),通过前门(FIFO接口)和后门(HDL路径)两种方式对DUT寄存器执行读写,并自动比对期望值与实际值,验证寄存器访问正确性。DUT为一个简单的8位控制寄存器,支持读改写。

DUT与接口定义

// dut_reg.sv - 最小DUT:单寄存器,支持读写
module dut_reg(
    input        clk,
    input        rst_n,
    input        cs,
    input        we,
    input  [7:0] addr,
    input  [7:0] wdata,
    output [7:0] rdata
);
    reg [7:0] reg_ctrl;
    assign rdata = (cs && !we && addr == 8'h00) ? reg_ctrl : 8'h00;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)      reg_ctrl <= 8'h00;
        else if (cs && we && addr == 8'h00) reg_ctrl <= wdata;
    end
endmodule

UVM寄存器模型实现

// ral_env_pkg.sv - 寄存器模型、适配器、测试与env
package ral_env_pkg;
    import uvm_pkg::*;
    `include "uvm_macros.svh"

    // ---------- 寄存器模型 ----------
    class reg_ctrl extends uvm_reg;
        `uvm_object_utils(reg_ctrl)
        rand uvm_reg_field f_ctrl;
        function new(string name = "reg_ctrl");
            super.new(name, 8, UVM_NO_COVERAGE);
        endfunction
        function void build();
            f_ctrl = uvm_reg_field::type_id::create("f_ctrl");
            f_ctrl.configure(this, 8, 0, "RW", 0, 8'h00, 1, 0, 0);
        endfunction
    endclass

    class reg_model extends uvm_reg_block;
        `uvm_object_utils(reg_model)
        rand reg_ctrl ctrl;
        function new(string name = "reg_model");
            super.new(name, UVM_NO_COVERAGE);
        endfunction
        function void build();
            default_map = create_map("default_map", 0, 1, UVM_LITTLE_ENDIAN);
            ctrl = reg_ctrl::type_id::create("ctrl");
            ctrl.configure(this, null, "");
            ctrl.build();
            default_map.add_reg(ctrl, 8'h00, "RW");
        endfunction
    endclass

    // ---------- 前门适配器 ----------
    class reg_adapter extends uvm_reg_adapter;
        `uvm_object_utils(reg_adapter)
        function new(string name = "reg_adapter");
            super.new(name);
        endfunction
        function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
            bus_trans t = bus_trans::type_id::create("t");
            t.addr = rw.addr;
            t.we   = (rw.kind == UVM_WRITE);
            t.wdata = rw.data;
            return t;
        endfunction
        function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
            bus_trans t;
            if (!$cast(t, bus_item)) `uvm_fatal("ADAPTER", "bus2reg cast failed")
            rw.kind = t.we ? UVM_WRITE : UVM_READ;
            rw.addr = t.addr;
            rw.data = t.we ? t.wdata : t.rdata;
        endfunction
    endclass

    // ---------- 总线事务 ----------
    class bus_trans extends uvm_sequence_item;
        `uvm_object_utils(bus_trans)
        rand bit [7:0] addr;
        rand bit       we;
        rand bit [7:0] wdata;
        bit [7:0] rdata;
        function new(string name = "bus_trans");
            super.new(name);
        endfunction
    endclass

    // ---------- 驱动DUT接口 ----------
    class dut_driver extends uvm_driver #(bus_trans);
        `uvm_component_utils(dut_driver)
        virtual dut_if vif;
        function new(string name, uvm_component parent);
            super.new(name, parent);
        endfunction
        function void build_phase(uvm_phase phase);
            if (!uvm_config_db#(virtual dut_if)::get(this, "", "vif", vif))
                `uvm_fatal("DRV", "No vif")
        endfunction
        task run_phase(uvm_phase phase);
            bus_trans t;
            vif.cs = 0; vif.we = 0; vif.wdata = 0;
            forever begin
                seq_item_port.get(t);
                @(posedge vif.clk);
                vif.cs <= 1; vif.we <= t.we; vif.addr <= t.addr; vif.wdata <= t.wdata;
                @(posedge vif.clk);
                if (!t.we) t.rdata = vif.rdata;
                vif.cs <= 0;
                seq_item_port.item_done();
            end
        endtask
    endclass

    // ---------- 寄存器测试序列 ----------
    class reg_test_seq extends uvm_sequence #(bus_trans);
        `uvm_object_utils(reg_test_seq)
        reg_model rm;
        function new(string name = "reg_test_seq");
            super.new(name);
        endfunction
        task body();
            uvm_status_e st;
            uvm_reg_data_t exp, got;
            // 前门写/读
            rm.ctrl.write(status(st), 8'hA5);
            rm.ctrl.read(status(st), got);
            if (got !== 8'hA5) `uvm_error("RAL", $sformatf("前门读回 %02h, 期望 A5", got))
            else `uvm_info("RAL", "前门读写一致", UVM_LOW)
            // 后门写/读
            rm.ctrl.mirror(status(st), UVM_CHECK, UVM_FRONTDOOR);
            rm.ctrl.peek(status(st), got);
            if (got !== 8'hA5) `uvm_error("RAL", $sformatf("后门读回 %02h, 期望 A5", got))
            else `uvm_info("RAL", "后门读写一致", UVM_LOW)
            // 后门直接写,再前门读验证
            rm.ctrl.poke(status(st), 8'h3C);
            rm.ctrl.read(status(st), got);
            if (got !== 8'h3C) `uvm_error("RAL", $sformatf("poke后前门读回 %02h, 期望 3C", got))
            else `uvm_info("RAL", "后门写-前门读一致", UVM_LOW)
        endtask
    endclass

    // ---------- 测试顶层 ----------
    class test_ral extends uvm_test;
        `uvm_component_utils(test_ral)
        reg_model rm;
        reg_adapter adapter;
        dut_driver drv;
        function new(string name, uvm_component parent);
            super.new(name, parent);
        endfunction
        function void build_phase(uvm_phase phase);
            rm = reg_model::type_id::create("rm");
            rm.build();
            adapter = reg_adapter::type_id::create("adapter");
            rm.default_map.set_sequencer(m_sequencer, adapter);
            drv = dut_driver::type_id::create("drv", this);
        endfunction
        task run_phase(uvm_phase phase);
            reg_test_seq seq;
            phase.raise_objection(this);
            seq = reg_test_seq::type_id::create("seq");
            seq.rm = rm;
            seq.start(m_sequencer);
            #100ns;
            phase.drop_objection(this);
        endtask
    endclass
endpackage

接口与顶层连接

// dut_if.sv
interface dut_if(input logic clk);
    logic rst_n, cs, we;
    logic [7:0] addr, wdata, rdata;
endinterface

// top.sv
module top;
    logic clk = 0;
    always #5 clk = ~clk;
    dut_if dif(clk);
    dut_reg dut(.clk(clk), .rst_n(dif.rst_n), .cs(dif.cs), .we(dif.we),
                .addr(dif.addr), .wdata(dif.wdata), .rdata(dif.rdata));

    initial begin
        dif.rst_n = 0;
        #20 dif.rst_n = 1;
        uvm_config_db#(virtual dut_if)::set(null, "uvm_test_top.drv", "vif", dif);
        run_test("ral_env_pkg::test_ral");
    end
endmodule

运行与验证

# 编译(以Questa/ModelSim为例)
vlib work
vlog dut_reg.sv dut_if.sv ral_env_pkg.sv top.sv
vsim -c top +UVM_TESTNAME=ral_env_pkg::test_ral
run -all

通过条件:仿真结束时UVM报告无UVM_ERROR,且日志中出现以下三条信息即表示寄存器模型前门/后门读写均正确:

RAL: 前门读写一致
RAL: 后门读写一致
RAL: 后门写-前门读一致

关键说明

  • 前门访问通过default_map.set_sequencer将寄存器操作转换为bus_trans,由dut_driver驱动DUT接口;后门访问(peek/poke)直接通过HDL路径操作寄存器,不经过总线时序。
  • 寄存器模型中的mirror调用使用UVM_FRONTDOOR,实际执行前门读并与期望值(模型内镜像值)比对,用于验证DUT寄存器与模型同步。
  • 若需扩展更多寄存器,在reg_model::build()中按同样方式添加并映射地址即可。