学习目标
UVM入门和进阶部分9、10
学习内容
1.寄存器是模块之间相互交谈的窗口:
通过读出寄存器的状态,获取硬件当前的状况;
通过配置寄存器,使得寄存器工作在一定的模式下
2.将单个寄存器拆分之后,又可以分为多个域,不同的域往往代表某一项独立的功能,单个域可能由多个/单一比特位构成
类名 | 功能 |
---|---|
uvm_reg_field | 用来针对寄存器功能域来构建对应的比特位 |
uvm_reg | 与寄存器相匹配,其内部可以例化和配置多个uvm_reg_field对象 |
uvm_mem | 匹配硬件存储模型 |
uvm_reg_map | 用来指定寄存器列表中各个寄存器的偏移地址、访问属性以及对应的总线 |
uvm_reg_block | 可以容纳多个寄存器(uvm_reg)、存储器(uvm_mem)和寄存器列表(uvm_reg_map ) |
4.MCDF寄存器模型
class ctrl_reg extends uvm_reg; //控制寄存器
'uvm_object_utils(ctrl_reg)
uvm_reg_field reserved;
rand uvm_reg_field pkt_len; //多个域
rand uvm_reg_field prio_level; //为什么是rand?
rand uvm_reg_field chnl_en;
function new(string name="ctrl_reg"); //new函数
super.new(name,32,UVM_NO_COVERAGE);
endfunction
virtual function build(); //不要跟build_phase()混淆,没有关系;其是object没有build_phase();预定义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,3,"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'h0,1,1,0);
endfunction
endclass
class stat_reg extends uvm_reg; //状态寄存器
'uvm_object_utils(stat_reg)
uvm_reg_field reserved;
rand uvm_reg_field fifo_avail;
function new(string name="stat_reg");
super.new(name,32,UVM_NO_COVERAGE);
endfunction
virtual function build();
reserved=uvm_reg_field::type_id::create("reserved");
fifo_avail=uvm_reg_field::type_id::create("fifo_avail");
reserved.configure(this,24,8,"RO",0,24'h0,1,0,0);
fifo_avail.configure(this,8,0,"RO",0,8'h0,1,1,0);
endfunction
endclass
class mcdf_rgm extends uvm_reg_block;
'uvm_object_utils(mcdf_rgm)
rand ctrl_reg chnl0_ctrl_reg; //uvm_reg_block的rand声明就是对uvm_reg_field的随机化声明
rand ctrl_reg chnl1_ctrl_reg;
rand ctrl_reg chnl2_ctrl_reg;
rand stat_reg chnl0_stat_reg;
rand stat_reg chnl1_stat_reg;
rand stat_reg chnl2_stat_reg;
uvm_reg_map map;
function new(string name="mcdf_rgm");
super.new(name,UVM_NO_COVERAGE);
endfunction
virtual function build();
chnl0_ctrl_reg=ctrl_reg::type_id::create("chnl0_ctrl_reg");
chnl0_ctrl_reg.configure(this);
chnl0_ctrl_reg.build(); //object没有自动化调用的关系;手动层次调用
chnl1_ctrl_reg=ctrl_reg::type_id::create("chnl1_ctrl_reg");
chnl1_ctrl_reg.configure(this);
chnl1_ctrl_reg.build();
chnl0_ctrl_reg=ctrl_reg::type_id::create("chnl2_ctrl_reg");
chnl0_ctrl_reg.configure(this);
chnl0_ctrl_reg.build();
chnl0_stat_reg=ctrl_reg::type_id::create("chnl0_stat_reg");
chnl0_stat_reg.configure(this);
chnl0_stat_reg.build();
chnl1_stat_reg=ctrl_reg::type_id::create("chnl1_stat_reg");
chnl1_stat_reg.configure(this);
chnl1_stat_reg.build();
chnl2_stat_reg=ctrl_reg::type_id::create("chnl2_stat_reg");
chnl2_stat_reg.configure(this);
chnl2_stat_reg.build();
//映射名称、偏移量、字节数、端性
map=create_map("map",'h0,4,UVM_LITTLE_ENDIAN);
map.add_reg(chnl0_ctrl_reg,32'h00000000,"RW");
map.add_reg(chnl1_ctrl_reg,32'h00000004,"RW");
map.add_reg(chnl2_ctrl_reg,32'h00000008,"RW");
map.add_reg(chnl0_stat_reg,32'h00000010,"RO");
map.add_reg(chnl1_stat_reg,32'h00000014,"RO");
map.add_reg(chnl2_stat_reg,32'h00000018,"RO");
lock_model(); //锁定,不允许访问
endfunction
endclass:mcdf_rgm
5.寄存器建模的基本要点和顺序:
定义单个寄存器时,需要将各个域整理并创建,创建之后通过uvm_reg_field::configure()函数来进一步配置各自属性;
uvm_reg_block可以用来模拟一个功能模块的寄存器模型,包含uvm_mem、uvm_reg以及uvm_reg_map,一个uvm_reg_block可以包含多个uvm_reg_map,各个map可以分别对应不同总线或者不同地址段,在uvm_reg_block中创建各个uvm_reg之后,也需要通过uvm_reg::configure()函数来配置各个uvm_reg实例的属性;
uvm_re_map在uvm_reg_block中例化后,需要通过uvm_reg_map::add_reg()函数来添加各个uvm_reg对应的偏移地址和访问属性等,只有规定了这些属性,才可以在前门访问(FRONTDOOR)中给出正确的地址;
uvm_reg_block可以对更大的系统做寄存器建模,这说明uvm_reg_block之间也可以存在层次关系,上层uvm_reg_block的uvm_reg_map可以添加子一级uvm_reg_block的uvm_reg_map,用来构建更全局的版图,继而通过uvm_reg_block和uvm_reg_map之间的层次关系来构建更系统的寄存器模型
6.用户在开发某一个总线adapter类型时,需要实现下面几点:
uvm_reg_bus_op与总线transaction中各自的数据映射;
实现reg2bus()和bus2reg()两个函数,这两个函数即实现了两种transaction的数据映射;
如果总线支持byte访问,可以使能supports_byte_enable;如果总线UVC要返回response数据,则应当使能provides_responses;
7.adapter实例
class reg2mcdf_adapter extends uvm_reg_adapter;
'uvm_object_utils(reg2mcdf_adapter)
function new(string new = "mcdf_bus_trans");
super.new(name);
provides_responses=1; //使能返回
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
mcdf_bus_trans t=mcdf_bus_trans::type_id::create("t");
t.cmd=(rw.kind==UVM_WRITE)?'WRITE:'READ; //kind---UVM_WRITE/UVM_READ
t.addr=rw.addr;
t.wdata=rw.data;
return t; //返回mcdf_bus_trans之后,该实例将通过mcdf_bus_sequencer传入到driver
endfunction
function void bus2reg(uvm_sequence_item bus_item,ref uvm_reg_bus_op rw);
mcdf_bus_trans t;
if(!$cast(t,bus_item)) begin
'uvm_fatal("NOT_MCDF_BUS_TYPE","Provided bus_item is not of the correct type")
return;
end
rw.kind=(t.cmd=='WRITE)?UVM_WRITE:UVM_READ;
rw.addr=t.addr;
rw.data=(t.cmd=='WRITE)?t.wdata:t.rdata;
rw.status=UVM_IS_OK;
endfunction
endclass
对于寄存器操作,无论读操作还是写操作,都需要经历调用reg2bus(),继而发起总线事务,而在完成事务发回反馈之后,又需要调用bus2reg(),将总线的数据返回至寄存器操作层面
8.前门访问:在寄存器模型上做的读写操作,最终通过总线UVC来实现总线上的物理时序访问,因此是真实的物理操作;
后门访问:指的是利用UVM DPI(uvm_hdl_read())、uvm_hdl_deposit(),将寄存器的操作直接作用到DUT内的寄存器变量而不通过物理总线访问
9.后门访问要先将寄存器模型关联到DUT一端(uvm_reg_block::add_hdl_path()),再通过uvm_reg::add_hdl_path_slice完成寄存器模型各个寄存器成员与HDL一侧的地址映射
前门访问 | 后门访问 |
---|---|
通过总线协议访问需要耗时,且在总线访问结束时才能结束前门访问 | 通过UVM DPI关联硬件寄存器信号路径,直接读取或修改硬件,不需要访问时间,零时刻响应 |
一般读写只能按字读写,无法直接读写寄存器域 | 可以对寄存器或寄存器域直接做读写 |
依靠监测总线来对寄存器模型内容做预测 | 依靠auto prediction方式自动对寄存器内容做预测 |
正确反映了时序关系 | 不受硬件时序控制,对硬件做的后门访问可能发生时序冲突 |
通过总线协议,可以有效捕捉总线错误,继而验证总线访问路径 | 不受总线时序功能影响 |
11.寄存器中的每一个寄存器,都应该有两个值,一个是镜像值(mirrored value),一个是期望值(desired value)
期望值是先利用寄存器模型修改软件对象值,而后利用该值更新硬件值;
镜像值是表示当前硬件的已知状态值;
镜像值往往由模型预测给出,即在前门访问时通过观察总线或者在后门访问时通过自动预测等方式来给出镜像值;
镜像值有可能与硬件实际值(actual value)不一致
12.UVM提供了两种用来跟踪寄存器值的方式:自动预测和显式预测(更准确)
如果一些sequence直接在总线层面上对寄存器进行操作(跳过寄存器级别的write()/read()操作),或者通过对其他总线来访问寄存器等这些额外情况,都无法自动得到寄存器的镜像值和预期值。
13.uvm_reg_block、uvm_reg、uvm_reg_field三个类提供的用于访问寄存器的方法:
14.uvm_reg_sequence提供的方法:
15.寄存器检查的几种可行方式:
从前门写,并且从前门读。最为常见,但无法检查地址是否正确映射;
从前门写,再从后门读。利用write()或者poke()从后门写,再利用read()从前门读;
从后门写,再从前门读。利用write()或者poke()从后门写,再利用read()从前门读;
对于一些状态寄存器(硬件自身信号会驱动更新其实际值),先用peek()来获取(并且会调用predict()方法来更新镜像值),再调用mirror()方法来从前门访问并且与之前更新的镜像值比较;