3. 使用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
运行与验证
- 将上述代码与DUT的
bus_transaction(含BUS_READ/BUS_WRITE枚举)及总线驱动器集成到UVM环境中。 - 编译命令示例(VCS):
vcs -sverilog -ntb_opts uvm-1.2 -f filelist.f - 运行仿真:
./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实现一致,否则前门读写会因地址或位宽不匹配而失败。