1. 寄存器建模的要点和顺序
1.1 寄存器创建
定义单个uvm_reg,各个域的确定,并利用configure函数来配置属性;
class ctrl_reg extends uvm_reg;'uvm_object_utils(ctrl_reg)/*uvm_reg;uvm_mem;uvm_reg_map;uvm_reg_block都继承于uvm_object类*/uvm_reg_field reserved;rand uvm_reg_field pkt_len;rand uvm_reg_field prio_level;rand uvm_reg_field chnl_en;function new(string name = "ctrl_reg");super.name(name, 32, UVM_NO_COVERAGE);endfunction virtual function void build();reserved = uvm_reg_field::type_id::create("reserved" );pkt_len = uvm_reg_field::type_id::create("pkt_len ");prio_level = uvm_reg_field::type_id::create("prio_level");chnl_en = uvm_reg_field::type_id::create("chnl_en ");reserved .configure(this, 26, 6, "RO", 0, 26'h0, 1, 0, 0);pkt_len .configure(this, 3 , 2, "RW", 0, 3'h0, 1, 1, 0);prio_level.configure(this, 2 , 1, "RW", 0, 2'h3, 1, 1, 0);chnl_en .configure(this, 1 , 0, "RW", 0, 1'h1, 1, 1, 0);endfunction
endclass
- 在new()函数中,第二个参数是寄存器的宽度,一般为32位,第三个参数用于指定是否需要进行覆盖率收集。不需要则写为UVM_NO_COVERAGE, 需要则写为UVM_CVR_ALL
- uvm_reg;uvm_mem;uvm_reg_map;uvm_reg_block都继承于uvm_object类
- uvm_reg类中的
build()函数
,不同于UVM_component的bulid_phase,不会自动执行,需要手动调用。 - configure配置所需要的参数如下
field_name.configure( .parent ( this ), // 参数一是此域的父辈,也就是此域位于哪个寄存器中,即是this;
.size ( 1 ), // 参数二是此域的宽度;
.lsb_position ( 0 ), // 参数三是此域的最低位在整个寄存器的位置,从0开始计数;
.access ( "RW" ), // 参数四表示此域段的存取方式(属性);
.volatile ( 0 ), // 参数五表示是否是易失的(volatile),这个参数一般不会使用;
.reset ( 0 ), // 参数六表示此域上电复位后的默认值;
.has_reset ( 1 ), // 参数七表示此域可复位;
.is_randomize ( 1 ), // 参数八表示这个域是否可以随机化;
.individually_accessible( 0 ) // 参数九表示这个域是否可以单独存取。);
1.2 寄存器映射
定义uvm_reg_block,在里面例化定义的uvm_reg,使用configure做配置;通过add_reg()函数添加各个uvm_reg对应的偏移地址和访问属性。
class mcdf_rgm extends uvm_reg_block;'uvm_object_utils(uvm_rgm)ctrl_reg chnl0_ctrl_reg;ctrl_reg chnl1_ctrl_reg; ctrl_reg chnl2_ctrl_reg; function new(string name = "mcdf_rgm");super.new(name, UVM_NO_COVERAGE);endfunctionvirtual function void build();chnl0_ctrl_reg = ctrl_reg::type_id::create("chnl0_ctrl_reg");chnl0_ctrl_reg.configure(this);chnl0_ctrl_reg.build();chnl1_ctrl_reg = ctrl_reg::type_id::create("chnl1_ctrl_reg");chnl1_ctrl_reg.configure(this);chnl1_ctrl_reg.build(); chnl2_ctrl_reg = ctrl_reg::type_id::create("chnl2_ctrl_reg");chnl2_ctrl_reg.configure(this);chnl2_ctrl_reg.build(); map = create_map("map", 'h0, 4, UVM_LITTLE_ENDING);//给基地址map.add_reg(chnl0_ctrl_reg, 32'h0000 0000, "RW");//给偏移地址map.add_reg(chnl1_ctrl_reg, 32'h0000 0000, "RW"); map.add_reg(chnl2_ctrl_reg, 32'h0000 0000, "RW"); chnl0_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", 'SLV0_RW_REG), 0, 32);chnl1_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", 'SLV0_RW_REG), 0, 32); chnl2_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", 'SLV0_RW_REG), 0, 32); add_hdl_path("tb.dut.ctrl_regs_inst");lock_model();endfunction
endclass
在build函数中需要做以下几步,
- 创建各个寄存器,配置寄存器实例的属性
- 并调用寄存器的build()函数,实现对各个域的配置
- 创建map,create_map第一个参数是名字,第二个参数是该reg block的基地址,第三个参数是寄存器所映射到的总线宽度(单位是byte),第四个参数是大小端,用于指定字节排序,一般有大端序(UVM_BIG_ENDING,高位字节存储在低地址处,低位字节存储在高地址处)和小端序(UVM_LITTLE_ENDING,低位字节存储在低地址处,高位字节存储在高地址处 )第五个参数指定连续地址是否相隔1个字节 (TRUE; 默认值) 或n_bytes (FALSE)。,一般为默认值1。
- 通过add_reg来添加各个reg的偏移地址和访问属性。第一个参数是要添加的寄存器名,第二个是地址,第三个是寄存器的读写属性。
- 关联寄存器模型和HDL硬件,用于将寄存器字段的一部分(即切片)与HDL路径绑定。从而使得UVM寄存器模型能够访问和操纵实际的硬件寄存器。第一个参数是hdl的地址,第二个参数是寄存器字段中切片的起始位,第三个参数是寄存器字段切片的大小。
- lock_model()锁定该寄存器模型,不能进行诸如添加寄存器或存储器之类的结构改变。因此必须确保在调用lock_model之前已经创建了所有子块、映射和寄存器。
1.3 Adapter实现
adapter具有桥接功能,实现uvm_reg_bus_op和transaction之间的转换,实现前门访问和DUT返回值对寄存器模型的修改.
class reg2mcdf_adapter extends uvm_reg_adapter;'uvm_object_utils(reg2mcdf_adapter)function new(string name = "reg2mcdf_adapter");super.new(name);provides_response = 1;endfunctionfunction uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);reg_trans t = reg_trans::type_id::create("t");t.cmd = (rw.kind == UVM_WRITE)? UVM_WRITE: UVM_READ;t.addr = rw.addr;t.data = rw.data;return t;endfunctionfunction void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);reg_trans t;if(!$cast(t,bus_item))begin'uvm_fatal("CASTFALL","provide bus_item is not of the correct type")return;endrw.kind = (t.cmd == 'WRITE)? UVM_WRITE: UVM_READ;rw.addr= t.addr;rw.data = t.data;rw.status = UVM_IS_OK;endfunction
endclass
通过reg2bus和bus2reg函数来实现数据类型的转换。
bus2reg()函数将收集到的transaction转换成寄存器模型使用的uvm_reg_bus_op类型变量,用以更新寄存器模型中相应寄存器的值;
virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
1.4 顶层集成block, adapter和predictor
1.4.1 例化并创建
class mcdf_env extends uvm_env;chnl_agent chnl_agts[3];reg_agent reg_agt;fmt_agent fmt_agt;mcdf_checker chker;mcdf_coverage cvrg;mcdf_virtual_sequencer virt_sqr;//例化adapter,predictor和register model句柄mcdf_rgm rgm;reg2mcdf_adapter adapter;uvm_reg_predictor #(reg_trans) predictor;'uvm_component_ultis(mcdf_env)function new(string name = "mcdf_env", uvm_component parent);super.new(name);endfunction function void build_phase(uvm_phase phase)...//创建adapter,predictor和register model句柄rgm = mcdf_rgm::type_id::create("rgm", this);rgm.build();adapter = reg2mcdf_adapter::type_id::create("adapter", this);predictor = uvm_reg_predictor#(reg_trans)::type_id::create("predictor", this);endfunction...endclass
1.4.2 句柄连接
/*将 reg_agt.sequencer 关联到 rgm.map,并使用 adapter 进行适配*/rgm.map.set_sequencer(reg_agt.sequencer, adapter);/*将monitor的analysis_port连接到predictor的输入端口,使predictor能够接收monitor捕获的总线事务。*/reg_agt.monitor.mon_ana_port.connect(predictor.bus_in);predictor.map = rgm.map;predictor.adapter = adapter;virt_sqr.rgm = rgm;
1.4.2 在test层运行
top_seq.start(env.virt_sqr);