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()中按同样方式添加并映射地址即可。