一、UVM寄存器学习

  1. 寄存器相关概念

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)等操作属性。

如上图有三个域,reserved不是域。每个域所占位宽不同,属性也可以不同。

uvm_reg:一个寄存器一般由32个比特位构成,由多个域(uvm_reg_field)组成。

uvm_reg_block:有多个uvm_reg或者其他uvm_reg_block组成。

uvm_reg_map:存储每个寄存器地址的,前门访问时,其将地址转化为绝对地址。

(该图引用于寄存器模型理解,非本人创作)

  1. 寄存器模型

2.1搭建uvm_reg

将uvm_reg_field加入uvm_reg中。以以上ctrl_reg寄存器为例进行说明。

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个字节开始传输,前面的数据不传输。(阴影表示没有传输的字节)

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总线端

连接示意图为:

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前门访问和后门访问

寄存器模型访问方式可以分为前门访问和后门访问。

前门方法就是通过在寄存器模型上配置总线来实现总线上物理时序的访问,其波形可见。访问示意图为:

前门访问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

连接示意图为:

6.4寄存器模型的方法

6.4.1uvm_reg的方法

uvm_reg的访问方法由下表所示:

注意:

  • 前门访问无法访问修改和读取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,如下表所示:

6.4.3 寄存器模型内建方法

寄存器模型提供了一些内建的sequence,用于项目初期的快速检验,如下表所示:

示例:

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

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值