在IC验证领域,寄存器模型是验证工程师与DUT交互的重要桥梁。标准寄存器通常遵循简单的读写行为模型,但在实际项目中,我们经常会遇到具有特殊行为的非标准寄存器。本文将以一个"写后自增"的用户自定义寄存器为例,详细讲解如何在UVM中精准建模这类特殊寄存器行为。
这个案例中的寄存器具有以下特性:
这种非标准行为在硬件设计中并不罕见,比如:
用户自定义寄存器类是建模的基础,我们需要继承uvm_reg基类并重写关键方法:
systemverilog复制class user_acp_reg extends uvm_reg;
local uvm_reg_field value;
`uvm_object_utils(user_acp_reg)
function new(string name = "user_acp_reg");
super.new(name,16,UVM_NO_COVERAGE);
endfunction
virtual function void build();
value = uvm_reg_field::type_id::create("value",,get_full_name());
value.configure(this, 16, 0, "RW", 0, 16'h0000, 1, 0, 0);
value.set_compare(UVM_NO_CHECK);
uvm_resource_db#(bit)::set({"REG::",get_full_name()},
"NO_REG_BIT_BASH_TEST", 1);
uvm_resource_db#(bit)::set({"REG::",get_full_name()},
"NO_REG_ACCESS_TEST", 1);
begin
user_acp_incr_on_write_cbs cb = new;
uvm_reg_field_cb::add(value, cb);
end
endfunction
endclass
关键点解析:
回调机制是实现特殊行为建模的核心:
systemverilog复制class user_acp_incr_on_write_cbs extends uvm_reg_cbs;
virtual function void post_predict(input uvm_reg_field fld,
input uvm_reg_data_t previous,
inout uvm_reg_data_t value,
input uvm_predict_e kind,
input uvm_path_e path,
input uvm_reg_map map);
if (kind != UVM_PREDICT_WRITE) return;
if (path != UVM_FRONTDOOR) return;
value = previous + 1;
endfunction
endclass
回调类工作流程:
注意:回调机制只影响寄存器模型的预测值,不会直接影响DUT行为。DUT行为的正确性仍需通过监测器或断言来验证。
为了确保前后门访问行为一致,需要重写pre_write任务:
systemverilog复制virtual task pre_write(uvm_reg_item rw);
uvm_reg_data_t m_data;
uvm_reg rg;
assert($cast(rg,rw.element));
m_data = rg.get() + 1;
if (rw.path == UVM_BACKDOOR)
rw.value[0] = m_data;
endtask
这段代码确保:
将自定义寄存器集成到寄存器块中:
systemverilog复制class block_B extends uvm_reg_block;
user_acp_reg user_acp;
`uvm_object_utils(block_B)
function new(string name = "B");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
default_map = create_map("", 0, 1, UVM_BIG_ENDIAN);
user_acp = user_acp_reg::type_id::create("user_acp",,get_full_name());
user_acp.configure(this, null, "acp");
user_acp.build();
default_map.add_reg(user_acp, 'h0000, "RW");
endfunction
endclass
验证自增行为的测试序列示例:
systemverilog复制class acp_test_seq extends uvm_sequence;
`uvm_object_utils(acp_test_seq)
rand int num_trans;
task body();
uvm_status_e status;
uvm_reg_data_t rdata;
// 初始化检查
model.user_acp.read(status, rdata);
if(rdata != 0) `uvm_error("INIT", "Register not reset properly")
// 连续写入测试
repeat(num_trans) begin
model.user_acp.write(status, $urandom());
model.user_acp.read(status, rdata);
if(rdata != expected_value)
`uvm_error("VAL_CHK", $sformatf("Expected %0d, got %0d", expected_value, rdata))
expected_value++;
end
endtask
endclass
对于这类特殊寄存器,建议采用以下验证策略:
功能验证聚焦:
标准测试豁免:
systemverilog复制uvm_resource_db#(bit)::set({"REG::",get_full_name()},
"NO_REG_BIT_BASH_TEST", 1);
uvm_resource_db#(bit)::set({"REG::",get_full_name()},
"NO_REG_ACCESS_TEST", 1);
文档与追溯:
在实际项目中可能会遇到以下问题:
前后门行为不一致:
回调未触发:
仿真性能下降:
这种建模方法可以推广到其他特殊寄存器行为:
只写寄存器:
自清除寄存器:
条件性更新寄存器:
在实际项目中,我曾遇到一个状态机控制寄存器,写入特定值会触发状态转换。采用类似的回调机制,我们成功建模了这种复杂行为,并发现了硬件设计中的一个边界条件错误。这再次证明了精准寄存器建模在验证中的重要性。