寄存器模型
对于包含寄存器的DUT,就需要在验证时建立寄存器模型。因为,参考模型想要读取寄存器信息,就需要启动sequence来读取寄存器的值,如何启动,如何获得值都是要解决的问题。所以,采用寄存器模型,让参考模型与寄存器模型的通信代替与DUT中寄存器的通信,可以方便地解决这些问题。一个寄存器模型里,包含以下几个部分。
/*
uvm_reg_field:寄存器模型的最小单位
uvm_reg:寄存器模型中的一个单位,由uvm_reg_field组成
uvm_reg_block:寄存器模型中的一个单位,由uvm_reg组成,模型中至少需要有一个uvm_reg_block
*/
/*创建一个简单寄存器模型*/
class reg_invert extends uvm_reg;
rand uvm_reg_field reg_data;
/*不会自动执行,需要手工调用*/
virtual function void build();
reg_data = uvm_reg_field::type_id::create("reg_data");
/*parent,size,此域最低位在整个寄存器的位置,存取方式,是否易失,复位后默认值,是否有复位,是否可以随机化,是否可以单独存取*/
reg_data.configure(this,1,0,"RW",1,0,1,1,0);
endfunction
`uvm_object_utils(reg_invert)
function new(input string name="reg_invert");
/*16一般为系统的总线宽度,第三个参数是是否添加覆盖率支持*/
super.new(name,16,UVM_NO_COVERAGE);
endfunction
endclass
/*在reg_block类中实例化寄存器*/
class reg_model extends uvm_reg_block;
rand reg_invert invert;
virtual function void build();
/*uvm_reg_block要对应uvm_reg_map,map内储存了寄存器的地址信息;
第二个参数为基地址;第三个参数为系统总线宽度,byte;第四个为大小端(大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中);
第五个是能否按照byte寻址*/
default_map = create_map("default_map",0,2,UVM_BIG_ENDIAN,0);
invert = reg_invert::type_id::create("invert",,get_full_name());
/*主要功能是指定寄存器进行后门访问操作时的路径;
第一个参数是所在block指针;
第二个参数是reg_file指针;
第三个参数是后门访问路径*/
invert.configure(this,null,"");
invert.build();
/*将寄存器加入default_map中;第二个参数是寄存器地址*/
default_map_add_reg(invert,'h9,"RW");
endfunction
`uvm_object_utils(reg_model)
function new(input string name="reg_model");
super.new(name,UVM_NO_COVERAGE);
endfunction
endclass
寄存器模型的访问方式有前门访问和后门访问。前门访问需要借助sequence,通过sequence产生一个uvm_reg_bus_op的变量,然后利用adapter将uvm_reg_bus_op和transaction互相转化,随后交给bus_sequencer以及bus_driver进行读写操作。后门操作则不借助总线,直接通过层次化的引用改变寄存器的值。
/*adapter*/
class my_adapter extends uvm_reg_adapter;
`uvm_object_utils(my_adapter)
function new(string name="my_adapter");
super.new(name);
endfunction
/*转换器内部应该声明两个函数,用于将uvm_reg_bus_op转换为transaction以及
transaction转换为uvm_reg_bus_op*/
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
bus_transaction tr;
tr = new("tr");
tr.addr = rw.addr;
tr.bus_op = (rw.kind==UVM_READ)?BUS_RD:BUS_WR;
if(tr.bus_op==BUS_WR) tr.wr_data = rw.data;
return tr;
endfunction
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
bus_transaction tr;
if (!$cast(tr,bus_item)) begin
`uvm_fatal(tID,"Provided bus_item is not the correct type.") ;
return;
end
rw.kind = (tr.bus_op==BUS_RD)?UVM_READ:UVM_WRITE;
rw.addr = tr.addr;
rw.byte_en = 'h3;
rw.data = (tr.bus_op==BUS_RD)?tr.rd_data:tr.wr_data;
rw.status = UVM_IS_OK;
endfunction
endclass
/*在base_test中加入寄存器模型*/
class base_test extends uvm_test;
my_env env;
my_vsqr v_sqr;
/*声明寄存器模型以及adapter*/
reg_model rm;
my_adapter reg_sqr_adapter;
function void base_test(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env",this);
v_sqr = my_vsqr::type_id::create("v_sqr",this);
rm = reg_model::type_id::create("rm",this);
/*寄存器配置,parent,后门访问路径*/
rm.configure(null,"");
rm.build();
/*lcok后寄存器模型不能加入新的寄存器*/
rm.lock_model();
/*更新寄存器值为复位值,否则都为0*/
rm.reset();
reg_sqr_adapter = new("reg_sqr_adapter");
env.p_rm = this.rm;
endfunction
function void base_test::connect_phase(uvm_phase phase);
super.connect_phase(phase);
v_sqr.p_my_sqr = env.i_agt.sqr;
v_sqr.p_bus_sqr = env.bus_agt.sqr;
v_sqr.p_rm = this.rm;
/*告知寄存器模型相应的sequencer以及adapter*/
rm.default_map.set_sequencer(env.bus_agt.sqr,reg_sqr_adapter);
rm.default_map.set_auto_predict(1);
endfunction
endclass
/*在验证平台使用寄存器模型*/
class my_model extends uvm_component;
/*在参考模型中建立寄存器指针*/
reg_model p_rm;
endclass
/*将env中的p_rm传递给参考模型*/
function void my_env::connect_phase(uvm_phase phase);
mdl.p_rm = this.p_rm;
endfunction
/*寄存器模型提供两个基本任务read write;若在参考模型中读取寄存器,使用read*/
task my_model::main_phase(uvm_phase phase);
my_transaction tr;
my_transaction new_tr;
uvm_status_e status;
uvm_reg_data_t value;
super.main_phase(phase);
/*第一个参数,表示操作是否成功;第二个参数为读取值;第三个为读取方式*/
p_rm.invert.read(status,value,UVM_FRONTDOOR);
while(1) begin
port.get(tr);
new_tr = new("new_tr");
new_tr.copy(tr);
if(value)/*为1,取反*/
invert_tr(new_tr);
ap.write(new_tr);
end
endtask
/*写寄存器*/
class case0_cfg_vseq extends uvm_sequence;
virtual task body();
uvm_status_e status;
uvm_reg_data_t value;
/*第二个参数为要写的值*/
p_sequencer.p_rm.invert.write(status,1,UVM_FRONTDOOR);
endtask
endclass
/*使用前门操作时,寄存器模型会自动生成sequence,并通过adapter进行转换,
发送给driver*/
/*因此要创建一个读写的sequence*/
class reg_access_sequence extends uvm_sequence#(bus_transaction);
string tID = get_type_name();
bit[15:0] addr;
bit[15:0] rdata;
bit[15:0] wdata;
bit is_wr;
`uvm_object_utils(reg_access_sequence)
function new(string name = "reg_access_sequence");
super.new(name);
endfunction
virtual task body();
bus_transaction tr;
tr = new("tr");
tr.addr = this.addr;
tr.wr_data = this.wdata;
tr.bus_op = (is_wr ? BUS_WR : BUS_RD);
`uvm_info(tID, $sformatf("begin to access register: is_wr = %0d, addr = %0h", is_wr, addr), UVM_MEDIUM)
`uvm_send(tr)
`uvm_info(tID, "successfull access register", UVM_MEDIUM)
this.rdata = tr.rd_data;
endtask
endclass
因此,对于寄存器模型的读操作,完整流程为:
- 参考模型调用寄存器模型的读任务
- 寄存器模型产生sequence,并产生uvm_reg_item,rw
- 通过adapter把rw转换成driver能够接收的transaction,bus_req
- 把bus_req发送给sequencer
- driver得到bus_req并驱动,得到读取的值,调用item_done
- 寄存器模型调用adapter把bus_req转换成rw
- 将rw读到的值返回给参考模型
后门访问
前门操作要通过总线进行操作寄存器,后门操作与其相对。后门操作不消耗仿真时间,可以完成一些前门操作无法完成的事情。
对于一些验证情况,在C/C++代码中有着对寄存器读写的需求,这时就要用一些接口,提供给这些代码。
/*VPI接口*/
vpi_get_value(obj,p_value);
vpi_put_value(obj,p_value,p_time,flags);
/*DPI接口*/
int uvm_hdl_read(char *path, p_vpi_vecval value);/*先在C/C++中定义*/
import "DPI-C" context function int uvm_hdl_read(string path,output uvm_hdl_data_t value);/*SystemVerilog中先导入DPI接口,读操作*/
import "DPI-C" context function int uvm_hdl_deposit(string path,uvm_hdl_data_t value);/*SystemVerilog中先导入DPI接口,写操作*/
uvm_hdl_read("top_tb.my_dut.counter",value);
uvm_hdl_deposit("top_tb.my_dut.counter",value);
在寄存器模型中使用后门访问功能,需要先在reg_model中修改configure函数,加入第三个路径参数。在验证平台集成时,要设置好根路径hdl_root。
counter_low.configure(this, null, "counter[15:0]");
rm.set_hdl_path_root("top_tb.my_dut");