概述
寄存器
我们知道寄存器是模块之间交谈的窗口,一方面可以通过读出寄存器的状态,获取硬件当前状态,另一方面可以配置寄存器,使寄存器工作在一定的模式之下,验证寄存器,即保证了硬件之间通信的正确性。通常来说,DUT中会有一组控制端口,通过控制端口,可以配置DUT中的寄存器,DUT可以通过寄存器的值来改变其行为。这组控制端口就是寄存器的配置总线。
寄存器模型作用
UVM的寄存器模型是一组高级抽象的类,用来对DUT中具有地址映射的寄存器和存储器进行建模。它非常贴切的反映DUT中寄存器的各种特性,可以产生激励作用于DUT并进行寄存器功能检查。通过UVM的寄存器模型,可以简单高效的实现对DUT的寄存器进行前门或后门操作。它本身也提供了一些寄存器测试的sequence,方便用户直接使用。UVM寄存器模型的本质就是重新定义了验证平台与DUT的寄存器接口,使验证人员更好地组织以及配置寄存器,简化流程,减少工作量。
寄存器模型的层次结构
类名 | 功能 |
uvm_reg_field | 寄存器模型中最小的单位,用来针对寄存器功能域来构建对应的比特位 |
uvm_reg | 与寄存器相匹配,一个寄存器中至少包含一个uvm_reg_field |
uvm_reg_block | 可以加入许多uvm_reg,也可以加入其他uvm_reg_map,一个寄存器模型至少包含一个uvm_reg_block |
uvm_reg_map | 存储加入进来的寄存器的偏移地址,并转换成可以访问的物理地址 |
简单寄存器模型
step1:定义一个寄存器模型
class reg_invert extends uvm_reg;
rand uvm_reg_field reg_data;
virtual function void build(); //记住是函数而不是phase
reg_data=uvm_reg_field::type_id::creat("reg_data");
reg_data.configure(this,1,0,"RW",1,0,1,1,0);
endfunction
`uvm_object_utiles(reg_invert)
function new(input string name="reg_invert");
super.new(name,16,UVM_NO_COVERAGE);
endfunction
endclass
1.在new函数中,要把invert寄存器中总共的位数作为参数传递给super.new函数。
2.每个派生自uvm_reg类都有一个build函数(不是phase)需要手动调用,所有uvm_reg_field都在此例化,并且要调用configure函数来配置这个字段。解释一下configure的这么多参数:第一个参数是此域的父辈,即此域位于哪一个寄存器之中;第二个参数是此域的宽度;第三个是此域最低位在整个寄存器中的位置;第四个是此字段的存取方式;第五个是表示是否是易失的,一般不会使用;第六个是 上电复位后的默认值,第七个表示此域是否有复位,1为有;第八个表示此域是否可以随机化;第九个是此域是否可以单独存取。
step2:在reg_block中例化寄存器
class reg_model extends uvm_reg_block;
rand reg_invert invert; //寄存器的类
virtual function void build();
default_map=creat_map("default_map",0,2,UVM_BIG_ENDIAN,0);
invert=reg_invert::type_id::creat("invert",,get_full_name());
invert.configure(this,null,'');
invert.build();
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
uvm_reg_block派生的类也有build函数,寄存器在此实例化,随后调用configure函数,这个函数主要功能是指定寄存器进行后门访问操作的路径。第一个参数是指定寄存器所在的uvm_reg_block的指针 ,第二个参数是reg_file指针,第三个参数是此寄存器后门访问路径。一个uvm_reg_block一定要对应一个uvm_reg_map,通过creat_map来例化,creat_map的参数从左往右为:名字,基地址,系统总线宽度,大小端,是否能按照byte寻址。最后一步是将寄存器加入default_map之中,否则无法进行前门访问操作。add_reg函数第一个参数是要加入的寄存器,第二个参数是寄存器的地址,第三个是此寄存器的存取方式。
step3:将寄存器模型集成到验证平台之中
进行前门操作时,寄存器模型都会通过sequence产生一个uvm_reg_bus_op的变量,此变量信息要通过一个转换器(adapter)转换后给bus_sequencer,而从bus_driver返回的rsp也需要adapter转化成uvm_reg_bus_op类型变量返回寄存器模型更新内部的值。所以要先定义一个转换器。一个转换器主要是这两个函数:reg2bus:寄存器模型通过sequence发出的uvm_reg_bus_op型变量转化为bus_sequencer能接受的形式,第二个是bus2reg,其作用是检测到总线上有操作时,它将收集来的transaction转化成寄存器模型能接受的形式,以便寄存器模型能更新响应的寄存器值。
转化器写好之后就可以在base_test加入寄存器模型
class base_test extends uvm_test;
my_env env;
my_vsqr v_sqr;
reg_model rm;
my_adapter reg_sqr_adapter;
...
endclass
function void base_test::build_phase(uvm_phase);
super.build_phase(phase);
env=my_env::type_id::creat("env",this);
v_sqr=my_vsqr::type_id::creat(v_sqr,this);
rm=reg_model::type_id::creat("rm",this);
rm.configure(null,"");
rm.build();
rm.lock_model();
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;
rm.default_map.set_sequencer(env.bus_agt.sqr,reg_sqr_adapter);
rm.default_map.set_aauto_predict(1);
endfunction
实例化reg_model之后,要调用configure函数,第一个参数是parent block,第二个参数后门访问路径,之后调用build函数,将所有寄存器实例化,接着调用lock_model函数,这样reg_model中不能加入新的寄存器,最后调用reset函数,调用此函数之后所有寄存器的值将变为设置的复位值。
step4:在验证平台中使用寄存器模型
寄存器模型提供了两个基本的任务:read和write
//在my_model的main_phase之中
p_rm.invert.read(status,value,UVM_FRONTDOOR);
read有许多参数,最常用的是前三种,第一个是uvm_status_e型变量,这是一个输出,表明读操作是否成功;第二个是读取的数值,也是输出,第三个是读取的方式,分前门和后门。
//在sequence的task body()函数之中
p_sequencer.p_rm.inverrt.write(status,1,UVM_FRONTDOOR);
write函数的参数也有很多个,最常用的也是前三个,第一个是uvm_status_e型变量,这是一个输出,表示写操作是否成功。第二个是要写的值,第三个是写操作的方式。
寄存器访问方式
(1)前门访问:前门访问操作就是通过寄存器配置总线(I2C,APB协议)来对DUT进行操作,前门访问操作只有读操作和写操作,是真实的物理操作,消耗仿真时间。操作流程为:
1)参考模型调用寄存器模型的读任务
2)寄存器模型产生sequence,并产生uvm_reg_item:rw
3)产生driver能接受的transaction,通过adapter转换
4)将转换后的bus_req交给sequencer
5)driver得到bus_req并驱动,将读取的值放入rsp中,调用item_done
6)寄存器模型再调用adapter,将rsp转化回rw,并返回参考模型
(2)后门访问:不通过DUT总线而对DUT内部的寄存器或者存储器进行的存取操作,不耗费仿真时间
前门访问 | 后门访问 |
通过总线协议访问需要耗时,且在总线访问结束时才能结束前门访问 | 通过UVM DPI关联硬件寄存器信号路径,直接读取或者修改硬件,不需要访问时间 |
一般读写只能按字读写,无法直接读写寄存器域 | 可以对寄存器或者寄存器域直接读写 |
依靠监测总线来对寄存器模型内容做预测 | 依靠auto prediction方式自动对寄存器内容做预测 |
正确反映了时序关系 | 不受硬件时序控制 |
通过总线协议,可以有效捕捉总线错误,继而验证总线访问路径 | 不受总线时序功能影响 |
UVM中使用DPI+VPI的方式进行后门访问操作,大体的流程为
1)在建立寄存器模型时将路径参数设置好
2)在进行后门访问的写操作时,寄存器模型调用uvm_hdl_deposit函数,在C/C++侧,此函数内部会调用vpi_put_value函数对DUT中的寄存器进行写操作。
3)进行读操作时,调用uvm_hdl_read函数,在C/C++侧此函数内部会调用vpi_get_value函数来对DUT中寄存器进行读操作并将取值返回。