UVM知识点总结-寄存器模型

UVM中的寄存器模型

寄存器模型的优势:在没有寄存器模型之前,只能启动 sequence 通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在 scoreboard(或者其他 component )中难以控制。而有了寄存器模型之后,scoreboard 只与寄存器模型打交道,无论是发送读的指令还是获取读操作的返回值,都可以由寄存器模型完成。有了寄存器模型后,可以在任何耗费时间的phase中使用寄存器模型以前门访问或后门(BACKDOOR)访问的方式来读取寄存器的值,同时还能在某些不耗费时间的 phase(如 check_phase)中使用后门访问的方式来读取寄存器的值。

1.寄存器模型(adapter的实现,adapter相当于一个转化器/复杂寄存器模型的实现,考虑层次化/在uvm_reg_block中加入uvm_reg_map/寄存器模型继承到验证平台中去)

寄存器模型的构建步骤包括

**声明寄存器

**对default_map的创建;注意这里不需要声明仅仅需要创建(系统已经声明过一个default map,当然也可以通过uvm_reg_map自己进行声明一个map,随后利用该map进行创建),通过default_map=create_map(“default_map”,0,2,UVM_BIG_ENDIAN,0);第一个参数表示map的名称,第二个参数表示基地址,第三个参数表示系统总线的宽度(单位是byte),第四个参数表示大小端,第五个参数表示能否按照byte进行寻址。

**创建寄存器,create创建

**针对寄存器的configure函数的调用

**build函数的调用

**寄存器加入default_map,比如操作default_map.add_reg(xx,xx,xx);第一个参数表示寄存器的名称,第二个参数表示偏移地址,第三个参数表示寄存器的存取方式(这个存取方式是否类似于configure针对域的情况下的第四个参数)。

注意在寄存器模型构架过程有如下几点需要注意:

**对域的声明需要限定其为rand,rand的目的在于后期对reg_block进行随机化时实际上对reg进行随机化,而对reg进行随机化实际上就是对reg_field进行随机化

//构建寄存器模型

class reg_invert extends uvm_reg;

  rand uvm_reg_field reg_data;

  virtual function void build();

    reg_data = uvm_reg_field::type_id::create("reg_data");

       reg_data.configure(this,)//??

  endfunction

 

  `uvm_object_utils(reg_invert)

 

  function new(input string name = "reg_invert");

    super.new(name,16,UVM_NO_COVERAGE);

  endfunction

endclass

class reg_model extends uvm_reg_block;

  rand reg_invert invert;

  virtual function void build();

    default_map = create_map();

       invert = reg_invert::type_id::create("invert");

       invert.configure(this,);//??

       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

  一些复杂寄存器模型的构建:

(1)嵌套寄存器的构建

(2)多个寄存器在同一个hdl路径下

(3)多个域的寄存器

(4)多个地址的寄存器

2.利用寄存器模型对寄存器进行访问:前门访问/后门访问

前门访问:通过寄存器配置的总线对DUT进行访问。

具体的流程:

前门访问中adapter的作用在于通过adapter的bus2reg和reg2bus实现uvm_reg_item与目标transaction之间的转化。

下面以读操作为例完成整个前门访问的流程:

(1)参考模型调用寄存器模型中的读任务

(2)寄存器模型产生sequence,并且产生uvm_reg_item:rw

(3)rw通过adapter进行数据类型的转化,转化为driver能够接收的transaction。bus_req=adapter.reg2bus(rw)

(4)把bus_req交给bus_sequencer

(5)driver得到bus_req后,驱动,得到读取的数值,将其放入rsp中,调用item_done

(这一步操作的前提是在adapter中存在语句provides_responses=1,如果没有该语句的化,实际上是将读取的值继续放入bus_req中;除此以外还存在suports_byte_enable=1语句(支持byte访问),上面的两个语句在实际应用过程中需要根据总线是否支持来判断是否写)

(6)寄存器模型调用adapter.bus2reg(rsp,rw)将rsp读取的值传递给rw

(7)将rw中读取的数据返回参考模型

基于这个要求adapter(是一个object)提出如下要求

1.包含bus2reg和reg2bus两个函数,需要注意这两函数相关参数的传递,对于reg2bus,其函数类型需要指定为uvm_sequence_item,以保证返回句柄时进行一个隐式的转化,将transaction类型数据的子类(用子类的目的在于进行值的访问)隐式的转化为父类。而对于后者无需指定函数类型,用void修饰即可。

2.adapter完成后需要将其集成在顶层环境中,需要有如下步骤:

在base_test中,(1)build_phase定义变量阶段至少定义register modle和adapter(除此以外还可以例化环境,然后通过config机制将register modle传递到环境中去)。实例化register modle后在build_phase阶段针对register modle有四个步骤:调用configeure函数;调用build函数,将所有寄存器实例化;调用lock_model函数,锁住寄存器防止新加寄存器;调用reset函数,复位。(2)connect_phase阶段除了virtual sequencer和底层的sequencer连接以外,还需要实现通过set_sequencer函数将adapter和bus_sequencer告知register modle中的default_map,并且设置default_map的状态为自动预测状态。

xx. default_map.set_sequencer(bus_sequencer,adapter);

xx. default_map.set_auto_predict(1);

在顶层环境中,也可以进行上述base_test的操作,比如set_sequencer/set_auto_predict等待,除此以外还可以对应前面的config机制,在这里get到寄存器的模型,当然也可以自己例化。此外,在顶层环境中针对register block的build操作实际上是reg block中的build函数操作,主要进行创建各个uvm_reg,进一步对其进行configure,还要调用各个uvm_reg中的build,uvm_reg中的各个build会去例化和配置相应uvm_reg中的reg_field

//adapter的应用

class adapter extends uvm_reg_adapter;

  `uvm_object_utils(adapter)

  function new(string name = "mcdf_bus_trans");

    super.new(name);

       provides_responses = 1;

  endfunction

 

  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);//仅有reg一侧的数据类型

    mcdf_bus_trans t = mcdf_bus_trans::type_id::create("t");

       t.cmd  = (rw.kind == UVM_WRITE)?`write:`read

       t.addr = rw.addr;

       t.wdata = rw.data

       return t;

  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();

        return;

       end

       rw.kind = (t.cmd == `WRITE)? UVM_WRITE:UVM_READ;

       rw.addr = t.addr;

       rw.data = (t.cmd == `WRITE)? wdata:rdata;

       rw.status = UVM_IS_OK;

  endfunction

endclass

注意,rw一侧的数据的成员变量有addr/data/kind/n_bits/byte_en/status

*********************************************************************************************

configure函数的应用:

针对寄存器来说,设置寄存器后门访问的路径,第一个参数时该register所在的uvm_reg_block的指针,一般会填this,第二个参数时reg_file的指针,第三个参数时寄存器进行后门访问的路径。(问题这里的第三个参数能否通过set_hdl_path_root进行覆盖??

那么reg_file是什么?reg_file是避免同一路径下出现多个寄存器时,而实际情况下因为某种原因修改了这一路径的名称,导致新修改的路径下需要修改多个寄存器的hdl路径问题。对于其应用参考白皮书p240.

针对域来说,uvm_reg_field要在创建和实例化之后,还要在 uvm_reg 中调用configure( )函数对该域进行参数配置。其中:第一个参数指定关联到的寄存器uvm_reg;第二个参数是位数长,第三个是起点位,第四个为访问属性(RW、RO等),第五个为是否volatile,第六个为上电复位后的默认值,第七个为是否可以复位(一般都可以复位,为1),第八个表示是否可以随机化(可读写寄存器及对应的域声明位rand类型,并且配置此参数为1可以保证域的随机化成功,比如课件中的reserved不可读写,因此也没有进行rand的声明并且confige配置时此参数也为0;在随机化过程实际上是随机desired value),第九个表示是否可以单独存取。

那么如何针对uvm_reg_field进行配置其后门访问路径呢(也就是多个域的寄存器如何配置不同域的后门访问路径),具体操作如下:

reg.configure(this,null,“”),首先针对这个寄存器进行配置,但是此时需要将第三个表示后门访问路径的参数设为空

reg.field.configure(xxx),九个参数配置域的相关属性

reg.field.add_hdl_path_slice(xx,xx,xx)配置域的路径,第一个参数表示路径,第二个参数表示此路径在寄存器中的起始位数,第三个参数表示域的位宽

后续将寄存器加入map等操作

************************************************************************************************

方法:read/write等,分别是读回寄存器的实际值和修改寄存器的实际值(针对硬件)

前门访问的具体例子(在sequence中进行操作):

(1)通过寄存器的句柄调用相关函数:rgm.chnl0_ctrl_reg.read/write(status,data,UVM_FRONTDOOR,.parent(this))  参数依次是状态/数据/前后门访问/parent    这里有问题,第四个参数说的是parent具体指的什么意思

(2)uvm_reg_sequence的方法,必须要继承于uvm_reg_sequence才可以使用这些方法,read_reg(rgm.chnl0_ctrl_reg,status,data, UVM_FRONTDOOR)

后门访问

(1)使用interface进行后门访问

可以使用绝对路径对寄存器进行后门访问,但是需要修改顶层文件。可以通过interface的方式进行访问。在interface中写poke_counter/peek_counter函数(前者为后门写,后者为后门读)。在后续使用过程中可以直接调用poke_counter/peek_counter函数进行写和读操作。

适用与没有寄存器模型或者是不想用寄存器模型的后门访问时对一个寄存器或存储器进行的访问。

(2)DPI+VPI(参见讲义)

(3)后门访问操作接口

在进行后门访问前需要在register modle一侧设置好相关路径。设置路径的方式:set_hdl_path_root/configure的第三个参数/绝对路径+具体路径:adl_hdl_path以及xx. adl_hdl_path_slice()  (第一个参数是加入的路径,第二个参数是此路径对应的域在此寄存器中的起始位,第三个参数则表示的是此路径对应的域的位宽)

例子如下

访问函数:

read/write

peek(读)/poke(写),此函数常用的参数仅有两个,分别是读写操作是否成功和读写操作的数据值。在sequence中的task body中可以通过p_sequencer.p_rm.counter_low.peek/poke(status,16’hFFFD)调用

这两类函数的区别在于read/write在进行操作的时候会模仿DUT的行为,而peek/poke函数在进行读写操作的时候不会管DUT操作。如果对只读寄存器进行一个写操作,那么调用write函数实际上是写不进去的,而使用poke函数可以写进去。

后门访问的具体例子:类似于前门访问,可以通过寄存器句柄访问或者uvm_reg_sequence的方法进行访问,uvm_reg_sequence的方法有read_reg/write_reg/peek_reg/poke_reg,相关参数意义类似于前门访问。

后门访问中hdl路径的设置(设置时需要注意configure函数的第三个参数设置为空)

两种方式:

addl_hdl_path()将寄存器模型关联到DUT一侧+ add_hdl_path_slice()将寄存器模型中的各个寄存器成员与HDL一侧的地址进行映射。第一函数参数是放路径,第二个函数第一个参数是要加入的路径,第二个参数则是此路径对应的域在此寄存器中的起始位数,如fieldA是从0开始的,而fieldB是从2开始的,第三个参数则是此路径对应的域的位宽

比如:

chnl0_ctrl_reg.configure(this);

chnl0_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV0_RW_REG), 0, 32);//有问题

add_hdl_path("tb.dut.ctrl_regs_inst");

另一种方式:set_hdl_path_root():This sets the specified path as the absolute HDL path to the block instance for the specified design abstraction kind. This absolute root path is prepended to all hierarchical paths under this block. The HDL path of any ancestor block is ignored. This method overrides any incremental path for the same design abstraction specified using add_hdl_path. The default value of kind shall be "RTL".起到一个覆盖作用

3.寄存器及寄存器模型中一些值:mirror value/desired value/actual value;这些值的追踪方法:自动预测/显示预测

mirror value/desired value/actual value

mirror value主要涉及值的更新问题,仅仅在predict时才能进行该值的预测,对镜像值的写入可以通过write,写入后期望值和镜像值均更新;也可以通过先set,此时仅仅修改了desired value,在调用update,检查期望值和镜像值是否相同,不同的化会将期望值写入actual value,通过actual value更新期望值。

调用上述函数的格式为在sequence中进行p_sequencer.block.register.函数

而desire value主要指的是如下情况的应用:先把register modle中的所有field进行随机化,随后将一些关心的值进行更新(也就是先更新软件中的值),随后在根据desire value更新硬件的actual value(硬件的actual value能够和软件一侧的desire value进行匹配),硬件的actual value更新完后再进行更新软件一侧的mirror value(由actual value在总线上进行捕捉)。

使用mirror value/desired value值的优势在于:首先寄存器模型构建至少需要一个值,这个值用来预测寄存器的值,进而对硬件做出判断(假设是mirror value,在对DUT写入操作后进行读操作,此时可以将mirror value和实际的actual value进行比较)。那么为什么需要desired value呢,desired value的优势在于在mirror value的基础上能够知道当前寄存器中的值那么此时就不需要再去访问寄存器而可以直接访问mirror value就可以得到寄存器的值,此外在可以通过desire value对某个具体的域进行set操作后(对某个具体的域进行set),在随后对寄存器的update的更新过程中,他会自动检查desire value和mirror value哪里不同,如果不同的话就会对硬件寄存器的总线上对应的地址上进行一次写操作,通过这种方式可以节约人力。

mirro value和desired value的更新(什么时候两者均更新什么时候只更新一个)

有两种情况,第一种就是上述的set操作,然后调用update。此时实际上仅仅需要更新mirror value,mirror value实际上是根据desired value进行变化(实际上这种更新是desire value发现和mirro value值不同,此时会促成一个前门访问去更新dut一侧的actual value,而此时predictor捕捉到总线上的行为,会去更新mirror value),这种操作的数据流向是由寄存器模型一侧的数据变化引起硬件中寄存器中的actual value的值的变化。另一种情况,是由硬件寄存器中的值变化驱动软件一侧的寄存器模型的值的变化,这种情况下mirror value和desired value的值均要根据actual value的值进行变化。比如状态寄存器,假设一开始没有读状态寄存器,此时mirror value和desire value均是复位值,如果接下来对状态寄存器的操作不涉及读状态寄存器,那么此时如果状态寄存器中的actual value会发生改变,在寄存器模型中的mirror value和desire value由于没有捕捉到总线的行为,因此任然保持复位值,不会更新。但是如果对寄存器进行了read/mirro操作(均是前门访问,读一个数值回来),那么此时都回来的值更新desire value值不同,此时predictor又检测到总线的行为,又会主动去更新mirro value的值,最终保持三种值均相同。

总的来说,寄存器模型的目的就是保持这三个值相同。

另外,针对这三个值有如下几点需要注意:

***上面这三个值实际是uvm_reg_field的成员变量,而不在uvm_reg中。

***不管是前门访问还是后门访问得到的都是一个硬件的actual value

***在进行任何操作前,比如set操作前都需要对寄存器模型进行reset,现实测试过程中DUT已经进行reset了,那么此时也需要对寄存器模型进行reset以保证三个值一开始的时候均是相同的。

***下面的status表示总线返回给我们这次操作是写成功了还是失败了

***相较于直接对寄存器进行write(每次修改的都是32位的数据),这种先set在进行update的操作可以节约资源(针对某个域进行操作)

***下面get操作get到的是desired value

上面mirror/get的操作是不是先将寄存器的值读到寄存器模型中,在直接从寄存器模型中拿到值??

自动预测和显示预测:

(1)显示预测,实际上一方面是因为predictor(是一个component)可以拿到monitor从总线上获取的bus_transaction,其内容与bus_seq_item内容完全一致;另一方面predictor可以拿到adapter的句柄,因此predictor可以调用adapter的bus2reg函数,使得bus_transaction转化为uvm_reg_item的类型,然后再利用register modle更新每一个register field的值。因此内置的predictor的实现需要三部分,分别是monitor送到数据,predictor拿到adapter的句柄,register modle实现对每个field的更新。

加入predictor的显示预测需要注意以下几个方面:

在环境中需要加入:map和adapter与predicater之间通过句柄连接:predictor.map=rgm.map;predictor.adapter=rgm.adapter;这两个句柄的连接意义在于从总线一侧传来transation,通过monitor给到predictor,predictor通过句柄调用adapter的bus2reg函数,将transaction数据进行转化,再通过map的句柄更新rgeister block中的各个寄存器中的field。

monitor需要具有analysis port以便于predictor的analysi import连接,连接时需要注意monitor一侧的数据类型需要和predictor一侧的数据类型相同(也就是在predictor创建过程中需要指定和monitor一侧相同的数据类型),具体操纵是agent.monitor.ap.connect(predictor.bus_in),bus_in的默认端口类型是analysi import.

(2)自动预测(不依赖predictor,monitor,也就是set_auto_predict操作):不关心总线上是否存在读写,只要通过寄存器进行了一个配置的操作或者只要bus_seq_item发送出来,就会自动对register modle中的每一个寄存器进行一个映射。但是往往不用,因为不准确,一方面在于不知道在总线上数据是否发送成功;另一方面在于读状态寄存器时不知带能否进行自动预测。

4.uvm_reg_block/uvm_reg/uvm_reg_field的方法

除了前面阐述的read/write,peek/poke以外,还存在一些其他的方法。

对于mirror来说有如下应用场景(针对reg_block和reg,前面访问后门访问均可):

***类似于read操作,读硬件的actual value而更新mirror value,但是需要注意mirror操作不会返回相应的读取值,但是会将镜像值修改;

***此外在uvm_reg_block进行mirror操作时,相当于对block的六个寄存器依次进行读操纵;

***对镜像值的比较操作:体现在对读写寄存器在先进行一次写操作,写操作后mirror value已经更新过,在进行mirror操作后,这时可以进行检查,检查硬件读回来的值和上次写进去的值是否相同。

对于updata(前后门访问均可)的使用场景往往是在默些情况下通过set操作将design value的值改变(原本状态下三个value的值均是相同的),此时使用updata,发现design value和mirror value的值不同,会自动将actual value的值更新为design value,随后actual value又会驱动mirror value使得三者的值均相同,block操作相当于检查每一个寄存器

类似于read/write,uvm_reg_sequence中还内置了mirror_reg和upadta_reg,但是需要注意的是所有uvm_reg_sequence的函数都是针对reg而无法针对block或field。

上述针对寄存器的方法中(非uvm_reg_sequence的方法),read/write/peek/poke均是针对硬件值,mirror/updata针对mirror value(仅仅支持block和register),除此以外还存在一些针对desired value的操作(这些操作均是前门访问),分别是reset(),针对寄存器的三种结构进行期望值和镜像值进行复位;get(),获取register/field的期望值;set,修改register/field的期望值;get_reset, 获取register/field的复位值。相关函数的参数类型类似于前面的例子,如果没有参数的传入的化,实际仅需三个参数即可。

5.reg和mem的比较(uvm_men)

加入存储器的背景在于实际上DUT中会存在大量的存储器,这些存储器往往在默写操作之间存放数据。在一些情况下需要得到这部分操作之间的存储器的数据进行比较,这样做的目的在于确定问题究竟是在存储器上游的操作还是存储器下游的操作上。寄存器模型中加入存储器类似于加入寄存器。

具体的应用步骤如下:

(1)创建一个存储器的类(object类),class my_memory extends uvm_mem,在这个类中的function函数中需要调用super.new函数,此函数有三个参数,分别是存储器的名字/存储器的深度/存储器的宽度。

(2)在寄存器模型中例化一个存储器,注意这个存储器需要通过rand修饰。rand my_memory mm。

(3)在寄存器模型中的build函数中通过create创建该存储器/configure函数配置该存储器(一般写两个参数,第一个参数是这个存储器所在的block的指针,如果就在当前block中可以写this,第二个参数是此存储器的hdl路径)/default_map.add_mem(xx,xx);第一个参数表示存储器的名称,第二个参数表示偏移地址

对于uvm_mem需要注意如下几点:

(1)没有mirror value/desire value(不支持预测和影子存储),但是支持前门访问和后门访问。

(2)除了read/write/peek/poke以外,还支持burst_read和burst_write(连续的读和写)

存储器的读操作需要加offset参数/存储器的位宽大于总线位宽的情况??(这两个点任然有问题)

6.内建sequence及相关方法

具体操作如下:(注意这些操作是在sequence进行的)

mcdf_rgm rgm;

uvm_reg_hw_reset_seq reg_rst_seq=new();uvm_reg_hw_reset_seq检查寄存器模型的复位值是否于硬件中寄存器的复位值相同

reg_rst_seq.modle=rgm; 从顶层拿到的register modle给到每一个sequence。

reg_rst_seq.start(m_sequencer);挂载操作

除了上述的uvm_reg_hw_reset_seq检查复位值是否相同以外,还有如下内置的sequence,

比如uvm_reg_access_seq,他的意思是对包含所有的uvm_reg进行uvm_reg_single_access_seq, uvm_reg_single_access_seq表示先进行前门写在进行后门读,比较结果,然后进行后门写前门读,再比较结果,这个内置sequence正常工作的前提是所有寄存器的hdl路径均设置好了。

比如uvm_reg_shared_acess_seq,表达的语境是AXI中的map进行写入操作时,使用该语句后会检查其他map,也就是AHB的map能否读到AXI的map写入的数据。

比如uvm_reg_bit_bash_seq,是对所有uvm_reg或者子一级的uvm_block进行uvin_reg_single_bit_bash_seq操作,表示的是检查所有支持读写访问的域,对每一个可读写域分别写入1和0并且读出后做比较,用来检查寄存器域属性的有效性。

如果想再检查过程中跳过对某个寄存器/存储器的检查,可以在启动sequence之前通过uvm_resource_db#(bit)::set({“REG::”,xx.xx.get_full_name()},”NO_REG_ACCESS_TESTS”,1),两个xx分别是block和register。

7.一些其他的应用场景:覆盖率的自动收集or手动收集

自动收集(在寄存器中进行自动收集):

具体的例子如下:

class ctrl_reg extends uvm_reg;

covergroup value_cg;

  pkt_len: coverponit pkt_len.value[2:0];//表示的是pkt_len这个域所关心的范围

endgroup

function new(string name = ctrl_reg);

  super.new(name,32,UVM_CVR_ALL);

  set_coverage(xx);

  has_coverage(xx); has_coverage表示当前的covergroup是否需要进行例化,这里是通过前面的set_coveragre操作默认例化covergroup, has_coverage放在new函数中

endfunction

function void sample(xx);固定形式,参见课件

**********

protected virtual function void sample( uvm_reg_data_t data, uvm_reg_data_t byte_en, bit is_read, uvm_reg_map map )

********

super.sampel(xxx);

sample_values();

endfunction

function void sample_values();

  super.sample_values();

  if(get_coverage(xx)) begin;// get_coverage表示是否允许进行采样,get_coverage放在sample相关的函数中

    value_cg.sample()

  end

endfunction

sampel在执行完read/write后就会执行,sample进一步调用sample_value,sample_value会进一步对covergroup进行采样

手动收集

手动收集的covergroup需要继承于uvm_subscriber,uvm_subscriber也是继承于uvm_component,但是其添加了一个analysis import(一般于monitor的port连接,通过import捕捉到相关参数进行分析,uvm_subscriber用于监听monitor的数据)。

手动收集的coverponit可以做出如下修改:

(1)pkt_len: coverponit pkt_len.value[2:0]{bins len[]=0,1,2,3,[4:7]}, 这里拿到的value是mirror value, 一共五个bin,对于[4:7]表示的是4-7覆盖一个即可(也就是len的数值位关心的数值是0/1/2/3/4-7中的一个)。如果不写{}的内容表示要覆盖所有值。

(2)可以在coverpoint声明的基础上进行cross,cross A,B

(3)由于存在analysi端口因此可以调用write函数

其他总结

field中存在三个value,分别是desire value/mirror value/value

对于value来说,他表示硬件接下里要变成了某个值或者硬件已经变成的某个值,在set函数中于desire value相同,而在predict函数中,这三者均和硬件的值相同;对于采样过程中由于desire value/mirror value都是local类型,因此一般采样用这个value进行采样,而在代码中对这个value进行赋值,在sampel采样时机合适时会保证这个value和mirro value相同,以保证采样正确(本质上还是采样的是mirror value)

在do_predict函数(predict就是从总线上拿到值)中将这三个值均更新了,三者和硬件上的值均相同

对寄存器的检查总结

(1)通过sequence的宏

`uvm_do_with(write_reg_sequence,p_sequencer.reg_sqr,{ addr == `SLV0_RW_ADDR; data == wr_val;})

(2)通过对寄存器模型读写方式进行检查

rgm.chnl0_ctrl_reg.write(status,wr_val);

rgm.chnl0_ctrl_reg.read(status,re_val);

void’(this,diff_value(wr_val,re_val, "SLV0_WR_REG"));

(3)set后进行update然后在进行mirro检查

ch0_wr_val =($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);

rgm.chnl0_ctrl_reg.set(ch0_wr_val);

rgm.update(status);

rgm.chnl0_ctrl_reg.mirror(status,UVM_CHECK,UVM_BACKDOOR)

(4)内建sequence进行检查

uvm_reg_hw_reset_seq reg_rst_seq=new()   (复位值的检查)、

p_sequencer.intf.rstn <= ‘b0;

repest(5) @(p_sequencer.intf.clk);

psequencer.intf.rstn <= ‘b1;

reg_rst_seq.modle = rgm;

rreg_rst_seq.start(p_sequencer.reg_sqr);

针对寄存器变化导致的sequence变化

(1)各个信息集中在一个寄存器中

covergroup value_cg;

      option.per_instance = 1;

      reserved: coverpoint reserved.value[25:0];

      pkt_len: coverpoint pkt_len.value[2:0];

      prio_level: coverpoint prio_level.value[1:0];

      chnl_en: coverpoint chnl_en.value[0:0];

endgroup

// len={4,8,16,32},  prio={[0:3]}, en={[0:1]}

ch0_wr_val=($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);      ch1_wr_val=($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);

ch2_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);

(2)各个信息分别集中在不同的寄存器中

    covergroup value_cg;

      option.per_instance = 1;

      en: coverpoint en.value[3:0];

      reserved: coverpoint reserved.value[31:4];

endgroup

covergroup value_cg;

      option.per_instance = 1;

      slv0_id: coverpoint slv0_id.value[7:0];

      slv1_id: coverpoint slv1_id.value[15:8];

      slv2_id: coverpoint slv2_id.value[23:16];

      slv3_id: coverpoint slv3_id.value[31:24];

endgroup

covergroup value_cg;

      option.per_instance = 1;

      slv0_len: coverpoint slv0_len.value[7:0];

      slv1_len: coverpoint slv1_len.value[15:8];

      slv2_len: coverpoint slv2_len.value[23:16];

      slv3_len: coverpoint slv3_len.value[31:24];

endgroup

****

void'(std::randomize(wr_val) with {wr_val inside {['b0:'b1111]};});

      rgm.slv_en.write(status, wr_val);

     

      void'(std::randomize(wr_val) with {

        wr_val[ 7: 0] inside {[ 0 : 63]};

        wr_val[15: 8] inside {[64 :127]};

        wr_val[23:16] inside {[128:191]};

        wr_val[31:24] inside {[192:255]};

      });

      rgm.slv_id.write(status, wr_val);

      void'(std::randomize(wr_val) with {

        wr_val[ 7: 0] dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};

        wr_val[15: 8] dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};

        wr_val[23:16] dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};

        wr_val[31:24] dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};

      });

      rgm.slv_len.write(status, wr_val);

***(这一步骤还可以被下面条件替换)

void'(rgm.slv_en.en.randomize() with {value inside {['b0:'b1111]};});

void'(rgm.slv_id.slv0_id.randomize() with {value inside {[ 0 : 63]};});

void'(rgm.slv_id.slv1_id.randomize() with {value inside {[64 :127]};});

void'(rgm.slv_id.slv2_id.randomize() with {value inside {[128:191]};});

void'(rgm.slv_id.slv3_id.randomize() with {value inside {[192:255]};});

void'(rgm.slv_len.slv0_len.randomize() with {value dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};});

void'(rgm.slv_len.slv1_len.randomize() with {value dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};});

void'(rgm.slv_len.slv2_len.randomize() with {value dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};});

void'(rgm.slv_len.slv3_len.randomize() with {value dist {0:/10, [1:7]:/20, [8:63]:/20,[64:254]:/40, 255:/10};});

你可能感兴趣的:(UVM,开发语言)