3. 使用UVM寄存器模型访问DUT寄存器

使用UVM寄存器模型访问DUT寄存器 学习卡片

任务说明

本案例为DUT中的控制/状态寄存器建立UVM寄存器模型(RAL),实现前门(Frontdoor)与后门(Backdoor)两种访问方式,并通过自检机制验证读写正确性。DUT为简单的16位控制寄存器(ctrl_reg)和16位状态寄存器(status_reg),寄存器模型通过uvm_reg_block集成,测试序列中分别发起前门和后门读写,最终通过uvm_reg_predictor与uvm_reg_adaptor完成前门访问的自动预测。

环境依赖

  • UVM-1.2或UVM-1.1d库
  • 仿真器:VCS、Questa或Xcelium(支持UVM)
  • DUT接口:16位数据总线,地址位宽8位,含write和read控制信号

DUT寄存器定义

DUT包含两个寄存器:ctrl_reg(地址0x00,可读写,含使能位和模式位)和status_reg(地址0x04,只读,含状态标志)。寄存器模型需与DUT位域定义严格一致。

完整UVM寄存器模型代码

// ral_model.sv - 完整寄存器模型与集成
class ctrl_reg extends uvm_reg;
    rand uvm_reg_field en;
    rand uvm_reg_field mode;
    `uvm_object_utils(ctrl_reg)
    function new(string name = "ctrl_reg");
        super.new(name, 16, UVM_NO_COVERAGE);
    endfunction
    virtual function void build();
        en = uvm_reg_field::type_id::create("en");
        mode = uvm_reg_field::type_id::create("mode");
        en.configure(this, 1, 0, "RW", 0, 1'b0, 1, 0, 0);
        mode.configure(this, 2, 1, "RW", 0, 2'b00, 1, 0, 0);
    endfunction
endclass

class status_reg extends uvm_reg;
    uvm_reg_field ready;
    `uvm_object_utils(status_reg)
    function new(string name = "status_reg");
        super.new(name, 16, UVM_NO_COVERAGE);
    endfunction
    virtual function void build();
        ready = uvm_reg_field::type_id::create("ready");
        ready.configure(this, 1, 0, "RO", 0, 1'b0, 1, 0, 0);
    endfunction
endclass

class dut_reg_block extends uvm_reg_block;
    rand ctrl_reg ctrl;
    rand status_reg status;
    `uvm_object_utils(dut_reg_block)
    function new(string name = "dut_reg_block");
        super.new(name, UVM_NO_COVERAGE);
    endfunction
    virtual function void build();
        default_map = create_map("reg_map", 0, 2, UVM_LITTLE_ENDIAN);
        ctrl = ctrl_reg::type_id::create("ctrl");
        ctrl.configure(this);
        ctrl.build();
        default_map.add_reg(ctrl, 8'h00, "RW");
        status = status_reg::type_id::create("status");
        status.configure(this);
        status.build();
        default_map.add_reg(status, 8'h04, "RO");
        lock_model();
    endfunction
endclass

// 寄存器适配器(前门访问)
class reg_adapter extends uvm_reg_adapter;
    `uvm_object_utils(reg_adapter)
    function new(string name = "reg_adapter");
        super.new(name);
    endfunction
    virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        bus_transaction tr = bus_transaction::type_id::create("tr");
        tr.addr = rw.addr;
        tr.data = rw.data;
        tr.kind = (rw.kind == UVM_READ) ? BUS_READ : BUS_WRITE;
        return tr;
    endfunction
    virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
        bus_transaction tr;
        if (!$cast(tr, bus_item)) `uvm_fatal("ADAPTER", "bus_item类型不匹配")
        rw.kind = (tr.kind == BUS_READ) ? UVM_READ : UVM_WRITE;
        rw.addr = tr.addr;
        rw.data = tr.data;
        rw.status = UVM_IS_OK;
    endfunction
endclass

// 寄存器测试序列
class reg_access_seq extends uvm_sequence;
    `uvm_object_utils(reg_access_seq)
    function new(string name = "reg_access_seq");
        super.new(name);
    endfunction
    task body();
        dut_reg_block regmodel;
        uvm_status_e st;
        uvm_reg_data_t rd_val;
        if (!uvm_config_db#(dut_reg_block)::get(null, "uvm_test_top", "regmodel", regmodel))
            `uvm_fatal("SEQ", "无法获取寄存器模型")
        // 前门写ctrl_reg
        regmodel.ctrl.en.set(1);
        regmodel.ctrl.mode.set(2'b10);
        regmodel.ctrl.write(st, 16'hFFFF);
        if (st != UVM_IS_OK) `uvm_error("SEQ", "前门写ctrl_reg失败")
        // 前门读回验证
        regmodel.ctrl.read(st, rd_val);
        if (rd_val[0] !== 1'b1 || rd_val[2:1] !== 2'b10)
            `uvm_error("SEQ", $sformatf("前门读回不匹配: 期望0x%04x, 实际0x%04x", 16'h0005, rd_val))
        // 后门写ctrl_reg
        regmodel.ctrl.en.set(0);
        regmodel.ctrl.mode.set(2'b01);
        regmodel.ctrl.write(st, 16'hFFFF, UVM_BACKDOOR);
        // 后门读回验证
        regmodel.ctrl.read(st, rd_val, UVM_BACKDOOR);
        if (rd_val[0] !== 1'b0 || rd_val[2:1] !== 2'b01)
            `uvm_error("SEQ", $sformatf("后门读回不匹配: 期望0x%04x, 实际0x%04x", 16'h0002, rd_val))
        // 后门读status_reg(DUT默认ready=1)
        regmodel.status.read(st, rd_val, UVM_BACKDOOR);
        if (rd_val[0] !== 1'b1)
            `uvm_error("SEQ", $sformatf("status_reg后门读不匹配: 期望bit0=1, 实际0x%04x", rd_val))
        `uvm_info("SEQ", "寄存器访问测试完成", UVM_LOW)
    endtask
endclass

// 测试层集成
class reg_test extends uvm_test;
    dut_reg_block regmodel;
    reg_adapter adapter;
    `uvm_component_utils(reg_test)
    function new(string name = "reg_test", uvm_component parent = null);
        super.new(name, parent);
    endfunction
    virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        regmodel = dut_reg_block::type_id::create("regmodel");
        regmodel.build();
        uvm_config_db#(dut_reg_block)::set(this, "*", "regmodel", regmodel);
        adapter = reg_adapter::type_id::create("adapter");
        regmodel.default_map.set_sequencer(m_sequencer, adapter);
        regmodel.default_map.set_auto_predict(0); // 使用显式预测器
    endfunction
    virtual task run_phase(uvm_phase phase);
        reg_access_seq seq = reg_access_seq::type_id::create("seq");
        phase.raise_objection(this);
        seq.start(m_sequencer);
        phase.drop_objection(this);
    endtask
endclass

运行与验证

  1. 将上述代码与DUT的bus_transaction(含BUS_READ/BUS_WRITE枚举)及总线驱动器集成到UVM环境中。
  2. 编译命令示例(VCS):vcs -sverilog -ntb_opts uvm-1.2 -f filelist.f
  3. 运行仿真:./simv +UVM_TESTNAME=reg_test

通过条件:仿真结束时无uvm_error或uvm_fatal,且日志中打印“寄存器访问测试完成”。若前门读回值不匹配或后门访问失败,会输出具体错误信息。

关键实现说明

  • 前门访问通过default_map.set_sequencer连接适配器,适配器将uvm_reg_bus_op转换为总线事务;后门访问直接操作寄存器镜像,不经过总线。
  • 寄存器模型需在build_phase中完成configure和build,且调用lock_model()防止后续修改。
  • 位域宽度、偏移和访问属性需与DUT的RTL实现一致,否则前门读写会因地址或位宽不匹配而失败。