-
寄存器相关概念
uvm_reg_field:最小单位
一个寄存器一般由32个比特位构成,将单个寄存器拆分之后,又可以分为多个域(uvm_reg_field),不同的域往往代表着某一项独立的功能。单个的域可能由多个比特位构成,也可能由单一比特位构成。而不同的域,对于外部的读写而言,可分为WO(write-only,只写),RO(read-only,只读)、RW(read and write,读写)、读后擦除模式(clean-on-read,RC),只写一次模式(write-one-to-set,W1S)等操作属性。
![](https://i-blog.csdnimg.cn/blog_migrate/cc691b415e38ea0b3d7084c5f6c5a8c8.png)
如上图有三个域,reserved不是域。每个域所占位宽不同,属性也可以不同。
uvm_reg:一个寄存器一般由32个比特位构成,由多个域(uvm_reg_field)组成。
uvm_reg_block:有多个uvm_reg或者其他uvm_reg_block组成。
uvm_reg_map:存储每个寄存器地址的,前门访问时,其将地址转化为绝对地址。
![](https://i-blog.csdnimg.cn/blog_migrate/919094207d3f51f52ae1ba687e5a2e1b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a0b8503273dd1a1135172e6b43212bcb.png)
(该图引用于寄存器模型理解,非本人创作)
-
寄存器模型
2.1搭建uvm_reg
将uvm_reg_field加入uvm_reg中。以以上ctrl_reg寄存器为例进行说明。
![](https://i-blog.csdnimg.cn/blog_migrate/a8d92b9a97d44007d94681b78a6f2684.png)
class ctrl_reg extends uvm_reg; //继承自uvm_reg基类
`uvm_object_utils(ctrl_reg)//注册
//为该reg中的每个域定义名字
uvm_reg_field reserved;
rand 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.new(name,32,UVM_NO_COVERAGE);
//(该寄存器名字,该寄存器的总位数,是否支持覆盖率)
endfunction
//在该函数下需要对各个域进行创建和配置,但与build phase不同,该build不会自动执行,需要在后面的reg block中手工调用//
virtual function 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);
//域配置属性值为(此域的父辈(即该域位于哪个寄存器中),此域的宽度,此域最低位起始位置在该reg中,此域的存取权限,是否易失,复位值,是否可复位,是否可随机化,是否可单独存取)
endfunction
endclass
2.2搭建uvm_reg_block
将uvm_reg加入到uvm_reg_block中,以以上uvm_reg为例。
class mcdf_rgm extends uvm_reg_block; //继承自uvm_reg_block
`uvm_object_utils(mcdf_rgm) //注册
rand ctrl_reg chnl0_ctrl_reg;
rand ctrl_reg chnl1_ctrl_reg;
function new(string name="mcdf_rgm");
super.new(name,UVM_NO_COVERAGE);
endfunction
//在build函数中创建reg,配置reg
virtual function void build();
chnl0_ctrl_reg=ctrl_reg::type_id::create("chnl0_ctrl_reg", ,get_full_name());
chnl0_ctrl_reg.configure(this,null,””);
//指定寄存器进行后门访问时的路径,参数为:(此寄存器所在reg_block指针,reg file指针,此寄存器的后门访问路径)
chnl0_ctrl_reg.build();//手动调用此reg的build函数,将该寄存器的域实例化
chnl1_ctrl_reg=ctrl_reg::type_id::create("chnl1_ctrl_reg");
chnl1_ctrl_reg.configure(this,””);
chnl1_ctrl_reg.build();
default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
//系统已经声明了一个default_map,只需要在build中例化。用来存储不同uvm_reg在uvm_reg_block中的地址。create_map
//create_map参数:(map名字,基地址,系统总线宽度byte,大小端,能否按照byte寻址)
//把该寄存器加入map中,参数为(名字,寄存器地址,访问权限)
map.add_reg(chnl0_ctrl_reg,32'h00000000,"RW");
map.add_reg(chnl1_ctrl_reg,32'h00000004,"RW");
this.lock_model(); //模型一旦建立后,就不允许外部的任何访问,进一步保证了寄存器模型只可以由寄存器模型的预测组件去改变。
endfunction
endclass:mcdf_rgm
3、转换器adapter
在将reg model引入验证环境之前,需要定义一个adapter;因为一个reg model对DUT进行前门访问的时候,reg model会通过seq产生一个uvm_reg_bus_op的变量,此变量中存储着操作类型(读还是写)和操作的地址,如果是写操作,还会有要写入的数据。
但是这个uvm_reg_bus_op的变量并不能正确的被sqr接收,因为sqr只能接收制定的transaction,所以要在sqr和reg model中间做一个转换,经过转换器将seq转交给seqer,在交给bus_driver,由bus_driver实现前门访问读写操作。
class my_adapter #(parameter BUS_DATA_WIDTH) extends uvm_reg_adapter;
string tID = get_type_name();
`uvm_object_utils(my_adapter#(parameter BUS_DATA_WIDTH))//注册my_adapter
function new(string name = "my_adapter");
super.new(name);
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
//reg model产生的op事务转换到sequencer能接受的事务,即tr.x = rw.x
//uvm_sequence_item该function返回的句柄,即tr,指向为转化后的事务对象
//uvm_reg_bus_op,rw为需要转化的事务对象
int ske = 0; //偏移byte数
bar_item tr; // bar_item为自定义的事务类型
tr = bar_item::type_id::create(“tr”)
bit[BUS_DATA_WIDTH/8-1:0] wstrb; //写开关使能。指示memory里需要更新的字节,1bit wstrb 对应数据总线的1byte数据
ske =rw.addr%( BUS_DATA_WIDTH/8);//根据地址计算数据从哪个byte开始传输
byte_num=rw.n_bits/8;//计算数据byte数
tr.addr = rw.addr;//地址转换
tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD : BUS_WR;//读写操作判断
if (tr.bus_op == BUS_WR)//如果是写操作,data转换
begin
tr.wr_data.push_back(rw.data[BUS_DATA_WIDTH-1:0]<<(ske*8));//将数据进行左移,从第ske个byte开始传输
for(int i=0;i<rw.n_bits/8;i++) wstrb[i+ske]=1;//从第ske个byte开始将memory更新开关使能
tr.wstrb.push_back(wstrb);
end
return tr; //返回转化完成后的事务
endfunction
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
//总线的事务uvm_sequence_item(即tr)转换到reg model的 uvm_reg_bus_op(即rw)变量上,即rw.x = tr.x
bar_item tr; // bar_item为自定义的事务类型
tr = bar_item::type_id::create(“tr”)
int ske = 0; //偏移byte数
if(!$cast(tr, bus_item)) //$cast:将右边值赋值给左边变量。赋值成功返回1
begin//类型转换
`uvm_fatal(tID,"Provided bus_item is not of the correct type.Expecting bus_transaction")
return;
end
ske =tr.addr%( BUS_DATA_WIDTH/8);//根据地址计算数据从哪个byte开始传输
rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;//读写操作转换
rw.addr = tr.addr;//地址转换
rw.byte_en=0;
rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;//读数据转换
rw.data = rw.data >> (ske*8);
rw.status = UVM_IS_OK;//事务结果
endfunction
endclass
ske偏移解释:在axi协议中,如果地址和传输数据非对,需要进行数据传输偏移。如下图,地址是01.则数据从第2个字节开始传输。如地址是07,则从第7个字节开始传输,前面的数据不传输。(阴影表示没有传输的字节)
![](https://i-blog.csdnimg.cn/blog_migrate/658f3f04c2a92999359ff186fbaf4bcf.png)
4、将写的寄存器模型集成到环境中
在环境env中,我们将寄存器块mcdf_rgm,适配器my_adapter例化,同时为寄存器模型补上了最后一块拼图predictor
class mcdf_env extends uvm_env;
my_adapter adapter;
uvm_reg_predictor predictor;
mcdf_regm regm;
reg_agent reg_agt;
extern virtual funcion void build_phase (uvm_phase phase);
extern virtual funcion void connect_phase (uvm_phase phase);
endclass
//build_phase
function void mcdf_env::build_phase(uvm_phase phase);
super.build_phase(phase);
adapter = my_adapter::type_id::create("adapter", ,get_full_name());//实例化my_adapter
reg_agt = reg_agent::type_id::create("predictor", ,get_full_name());//实例化reg_agent
predictor = uvm_reg_predictor::type_id::create("predictor", ,get_full_name());//实例化uvm_reg_predictor
regm = mcdf_regm::type_id::create("regm", ,get_full_name());//实例化mcdf_regm
regm.configure(null, "");//配置reg block,(父辈,后门访问路径)
regm.build();//手动调用reg block的build函数从而build reg和reg field
regm.lock_model();//调用此函数,reg model不能再添加新的寄存器
regm.reset();//复位所有寄存器使其为复位值,如不调用复位函数则寄存器值全0
regm.set_hdl_path_root("");//后门访问的绝对路径
endfunction
//connect_phase
function void mcdf_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
regm.default_map.set_sequencer(reg_agt.sequencer, adapter);//将sqr和adapter连接起来
reg_agt.monitor.mon_out_port.connet(predictor.bus_in);//将monitor输出端口与predictor的bus_in连接
predictor.adapter = adapter;
predictor.map = regm.default_map;//将predictor的adapter和map与对应部分连接,让predictor中可以访问adapter和map的值。
regm.default_map.set_auto_predict(1);//使reg model为自动预测状态,意味着reg model中的镜像值时刻与DUT中对应的reg值一样
endfunction
总结:在env中完成了寄存器模块的声明,创建和连接。连接中将predictor与两端的连接,一端是寄存器模型端,即将default_map和adapter的句柄都传递到predictor中(map和adapter都需要提前一起绑定在sqr上),另一端是bus总线端。
连接示意图为:
![](https://i-blog.csdnimg.cn/blog_migrate/a3ce32ff6f922bfd9588b764ad088fe0.png)
5、使用寄存器模型
5.1编写reg_sequence
class mcdf_regm_seq extends uvm_reg_sequence ;
//mcdf_regm_sequence继承自uvm_reg_sequence
uvm_status_e status;//声明寄存器状态
mcdf_rgm rgm;//声明寄存器模型
`uvm_object_utils_begin(mcdf_regm_seq) //field automation注册机制
`uvm_field_enum (uvm_status_e, status,UVM_ALL_ON) //枚举类型
`uvm_field_object (mcdf_rgm , rgm ,UVM_ALL_ON)//派生自object
`uvm_object_utils_end
//new函数
function new(string name = "host_ral_test_sequence");
super.new(name);
endfunction: new
//build_phase
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
rgm=mcdf_rgm::type_id::create("crgm", ,get_full_name());//实例化参考模型rgm
endfunction
//body任务
virtual tast body();
int data; //寄存器数值
rgm.chnl0_ctrl_reg.read(status, data);//使用API读出寄存器模型中chnl0_ctrl_reg寄存器的数值
`uvm_info("", $sformatf("rgm.chnl0_ctrl_reg_data is %h ", data), UVM_MEDIUM);
data = 6'h1a;
rgm.chnl0_ctrl_reg.write(status, data);//写入data值6'h1a给寄存器模型中chnl0_ctrl_reg寄存器中
endtast
5.2在testcase_sequence中使用reg_sequence
class mcdf_testcase_seq extends uvm_base_sequence ;
//P_sequencer Define
`uvm_declare_p_sequencer(xxxx_test_sequencer)
mcdf_regm_seq test_mcdf_regm_seq; //声明mcdf_regm_seq
`uvm_object_utils_begin(mcdf_testcase_seq) //field automation注册机制
`uvm_field_object (mcdf_regm_seq , test_mcdf_regm_seq ,UVM_ALL_ON)
`uvm_object_utils_end
extern function new ();
extern virtual task body();
endclass:mcdf_testcase_seq
function mcdf_testcase_seq::new();//new函数
super.new(name);
test_mcdf_regm_seq = mcdf_regm_seq::type_id::create("test_mcdf_regm_seq");
endfunction
task test_mcdf_regm_seq:body();//body任务
‘uvm_do_on(test_mcdf_regm_seq,p_sequencer.test_mcdf_regm_seqer);
//使用uvm_do_on显式的指定使用哪个seqer发送seq
endtask
6、寄存器模型其他内容
6.1前门访问和后门访问
寄存器模型访问方式可以分为前门访问和后门访问。
前门方法就是通过在寄存器模型上配置总线,来实现总线上物理时序的访问,其波形可见。访问示意图为:
![](https://i-blog.csdnimg.cn/blog_migrate/5367d785756fbc97ce45f3ff716e09f8.png)
前门访问read:将DUT返回值写入镜像值和期望值;
前门访问write:将期望值和镜像值更新为写入的值。
后门访问是利用UVM_DPI将寄存器模型的操作直接作用于DUT内的寄存器变量而不通过物理总线访问。
后门访问步骤:
(1)设置每个寄存器的后门访问路径。
(2)设置绝对路径。
class mcdf_rgm extends uvm_reg_block;
//(1)block一侧设置后门访问路径
chnl0_ctrl_reg.configure(this,null,””);//指定寄存器进行后门访问时的路径,参数为:(此寄存器所在reg_block指针,reg file指针,此寄存器的后门访问路径)
//(2)设置后门访问时寄存器的绝对路径
chnl0_ctrl_reg.set_hdl_path_root(“top.xxx.xx”);//top.xxx.xx为该寄存器在rtl中的绝对路径
endclass
前门访问和后门访问的区别:
(1)前方访问需要通过总线协议,需要耗时。后门访问通过UVM DPI关联硬件路径,直接读取或修改硬件,不消耗仿时间。
(2)前门访问可以通过predictor监测总线做预测,而后门访问只能通过auto prediction方式对寄存器内容做预测。
(3)前门访问正确反映了时序关系,可以有效捕捉总线错误,而后方访问不受时序约束,可能会发生时序冲突,且无法通过波形文件观测总线变化。
后门访问的优势:
(1)运行时间远小于前门,可以通过后门访问来配置寄存器。
(2)可以给只读寄存器赋初值。
6.2期望值和镜像值
镜像值(mirror value):DUT侧寄存器里的值会不断变更,寄存器模型侧设置了专门用于最大可能与DUT保持同步的值,这个值为镜像值。
镜像值不一定与DUT寄存器值相同。如果DUT通过正常方式内部修改了寄存器值,那么镜像值为过时值。
存储器没有镜像值。
期望值(desired value):想要修改DUT侧寄存器的值,先通过set方法设置期望值,然后通过update方法进行更新。update方法会检验镜像值和期望值是否一致,若不一致,就将期望值写入DUT中,并更新镜像值。
6.3寄存器模型预测
由于镜像值需要尽可能与DUT保持同步,因此需要对寄存器模型的镜像值进行预测。寄存器模型预测可以分为自动预测(auto prediction)和显示预测(通过predictor进行预测)。
(1)自动预测:自动预测会利用寄存器的操作来自动记录每一次寄存器的读写数值,并在后台自动调用predict方法。
(2)显示预测:通过将predictor集成到环境中,通过monitor监测物理总线,再将监测到的事务传递经adapter转换后传递给寄存器模型并更新信息到map中。
自动预测示例:
regm.default_map.set_auto_predict(1);//reg model为自动预测状态,意味着reg model中的镜像值时刻与DUT中对应的reg值一样
显示预测示例:
class mcdf_env extends uvm_env;
……
uvm_reg_predictor predictor; //声明
my_adapter adapter;
mcdf_regm regm;
reg_agent reg_agt;
function void build_phase(uvm_phase phase);
predictor = uvm_reg_predictor#(apb_transfer)::type_id::create("predictor", this);//例化
adapter = my_adapter::type_id::create("adapter", ,get_full_name());//实例化my_adapter
reg_agt = reg_agent::type_id::create("predictor", ,get_full_name());//实例化reg_agent
regm = mcdf_regm::type_id::create("regm", ,get_full_name());//实例化mcdf_regm
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
regm.default_map.set_sequencer(reg_agt.sequencer, adapter);//将sqr和adapter连接
reg_agt.monitor.mon_out_port.connet(predictor.bus_in);//monitor输出与predictor的bus _in连接
predictor.adapter = adapter; //adapter 相连
predictor.map = regm.default_map;//predictor的adapter和map连接,让predictor可以访问adapter和map的值。
regm.default_map.set_auto_predict(0);//使reg model为显示预测状态
endfunction
……
endclass
连接示意图为:
![](https://i-blog.csdnimg.cn/blog_migrate/26e87887f13689a6921c169ff14a0a1b.png)
6.4寄存器模型的方法
6.4.1uvm_reg的方法
uvm_reg的访问方法由下表所示:
![](https://i-blog.csdnimg.cn/blog_migrate/b3e39d3a4f42b9ff7a9624b16530f0ce.png)
注意:
-
前门访问无法访问修改和读取filed的值。
-
reset()、get_reset()、get()以及set()都只针对于寄存器模型而不是硬件一侧的值。
示例:
@(negedge p_sequencer.vif.rstn);
rgm.reset();//reg块的镜像值和期望值的复位
rgm.chnl0_ctrl_reg.reset();//reg级别复位
rgm.chnl0_ctrl_reg.pkt_len.reset();//reg域的复位
-
当reg的镜像值和期望值不相同时,可以通过updata()来将不同reg通过前后门访问做全部修改
示例:
void'(rgm.chnl0_ctrl_reg.randomize());
rgm.chnl0_ctrl_reg.pkt_len.set('h3); //对寄存器模型设置为’h3
rgm.chnl0_ctrl_reg.updata(statusm, UVM_FRONTDOOR, .parent(this));//修改reg实际值
void'(rgm.chnl1_ctrl_reg.randomize());
rgm.chnl0_ctrl_reg.set('h22);//寄存器模型设置为’h22
rgm.updata(statusm, UVM_FRONTDOOR, .parent(this));//修改reg实际值
-
peek()是相当于read的后门操作 ,poke()则相当于write的后门操作。
-
对于前门访问的read()和write(),在总线事务完成后镜像值和期望值才会更新成与总线相同的值,而对于后门访问的peek()和poke()由于不经过总线,只能通过auto_predictor的方式,在调用方法后,期望值和镜像值也立即改变。
-
复位后user可以通过读取rgm的复位值,与前门访问获取的reg复位值比较,以此判断硬件各个reg的复位值是否按照reg描述去实现
示例:
rstval=rgm.chnl0_ctrl_reg.get_reset();//rgm的复位值,不是硬件的
rgm.chnl0_ctrl_reg.read(status, data, UVM_BACKDOOR, .parent(this));//硬件实际值
if(rstval !=data) `uvm_error()
-
mirror()不会返回读回的值,但会将对应的镜像值修改,在修改前user可以选择是否与原镜像值进行对比(UVM_CHECK)。不检查为UVM_NO_CHECK
示例:
rgm.chnl0_ctrl_reg.mirror(status, UVM_CHECK, UVM_FRONTDOOR, .parent(this));
6.4.2 uvm_reg_sequence的方法
uvm_reg_sequence针对reg,而不是reg block或者field,如下表所示:
![](https://i-blog.csdnimg.cn/blog_migrate/d81bbf5c3dcb8a84057bef7cfa69f65e.png)
6.4.3 寄存器模型内建方法
寄存器模型提供了一些内建的sequence,用于项目初期的快速检验,如下表所示:
![](https://i-blog.csdnimg.cn/blog_migrate/f253c27d640961b6c1b1a23f4cfc2432.png)
示例:
class mcdf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm;
`uvm_object_utils()
`uvm_declare_p_sequencer()
...
task body();
uvm_status_e status;
uvm_reg_data_t data;
uvm_reg_hw_reset_seq reg_rst_seq=new();//检查模型复位值是否与硬件的一致
uvm_reg_bit_bash_seq reg_bit_bas_seq=new();//对包含所有uvm_reg的block执行读写检查
uvm_reg_access_seq reg_acc_seq=new();//对包含所有uvm_reg的block执行前门写入,后门读取数值比较
if(!uvm_config_db#(mcdf_rgm)::get(null, get_full_name(), "rgm", rgm))
begin
`uvm_error()
end
@(negedge p_sequencer.vif.rstn);
@(posedge p_sequencer.vif.rstn);
`uvm_info()
reg_rst_seq.model=rgm;//给model赋值
reg_rst_seq.start(m_sequencer);
`uvm_info()
`uvm_info()
reg_bit_bash_seq.model=rgm;
reg_bit_bash_seq.start(m_sequencer);
`uvm_info()
`uvm_info()
reg_acc_seq.model=rgm;
reg_acc_seq.start(m_sequencer);
`uvm_info()
endtask
endclass