uvm(六)寄存器模型

简介

加入带总线的DUT后,我们就需要使用寄存器模型加入一个总线的driver了

UVM寄存器模型的本质就是重新定义了验证平台与DUT的寄存器接口, 使验证人员更好地组织及配置寄存器, 简化流程、 减少工作量。
 

如何定义

上述是reg field 、 uvm_reg 、

uvm_reg_block:

它是一个比较大的单位, 在其中可以加入许多的uvm_reg, 也可以加入其他的uvm_reg_block。 一个寄存器模型中至少包含一个uvm_reg_block。

uvm_reg_map:

用于存储寄存器地址, 并转换成物理地址( 因为加入寄存器模型中的寄存器地址一般都是偏移地址, 而不是绝对地址) 。

当寄存器模型使用前门访问方式来实现读或写操作时, uvm_reg_map就会将地址转换成绝对地址, 启动一个读或写的sequence, 并将读或写的结果返回。

在每个reg_block内部, 至少有一个( 通常也只有一个) uvm_reg_map

构建一个叫nvert的寄存器

super.new(寄存器名字,寄存器位数(一般与系统总线宽度一致),是否覆盖)

当reg_data实例化后, 要调用data.configure函数来配置这个字段,在build中配置

config(属于谁,位域宽度,最低位在reg的下标,存取方式,是否易失,复位值,是否存在复位,可否随机化,可否单独存取)

UVM共支持如下25种存取方式

  1.  RO: 读写此域都无影响。
  2.  RW: 会尽量写入, 读取时对此域无影响。
  3.  RC: 写入时无影响, 读取时会清零。
  4.  RS: 写入时无影响, 读取时会设置所有的位。
  5.  WRC: 尽量写入, 读取时会清零。
  6.  WRS: 尽量写入, 读取时会设置所有的位
  7.  WC: 写入时会清零, 读取时无影响。
  8. WS: 写入时会设置所有的位, 读取时无影响。
  9. WSRC: 写入时会设置所有的位, 读取时会清零。
  10. WCRS: 写入时会清零, 读取时会设置所有的位。
  11.  W1C: 写1清零, 写0时无影响, 读取时无影响。
  12. W1S: 写1设置所有的位, 写0时无影响, 读取时无影响。
  13.  W1T: 写1入时会翻转, 写0时无影响, 读取时无影响。
  14. W0C: 写0清零, 写1时无影响, 读取时无影响。
  15. W0S: 写0设置所有的位, 写1时无影响, 读取时无影响。
  16. W0T: 写0入时会翻转, 写1时无影响, 读取时无影响。
  17. W1SRC: 写1设置所有的位, 写0时无影响, 读清零。
  18. W1CRS: 写1清零, 写0时无影响, 读设置所有位
  19. W0SRC: 写0设置所有的位, 写1时无影响, 读清零。
  20. W0CRS: 写0清零, 写1时无影响, 读设置所有位。
  21. WO: 尽可能写入, 读取时会出错。
  22. WOC: 写入时清零, 读取时出错。
  23. WOS: 写入时设置所有位, 读取时会出错。
  24. W1: 在复位( reset) 后, 第一次会尽量写入, 其他写入无影响, 读取时无影响。
  25. WO1: 在复位后, 第一次会尽量写入, 其他的写入无影响, 读取时会出错
     

定义好此寄存器后, 需要在一个由reg_block派生的类中将其实例化:

在block中定义了map,实现了reg,build,并添加到map

config:第一个参数是此寄存器所在uvm_reg_block的指针, 这里填写this, 第二个参数是reg_file的指针  这里暂时填写null, 第三个参数是此寄存器的后门访问路径

map定义:第一个参数是名字, 第二个参数是基地址, 第三个参数则是系统总线的宽度, 这里的单位是byte而不是bit, 第四个参数是大小端, 最后一个参数表示是否能够按照byte寻址

集成


首先寄存器模型都会通过sequence产生一个uvm_reg_bus_op的变量, 此变量中存储着操作类型( 读还是写) 和操作的地址

adapter起到转换数据类型的作用

作用是uvm_reg_bus_op到bus trans的转化

在test实现

寄存器模型的前门访问操作最终都将由uvm_reg_map完成

因此在connect_phase中, 需要将adapter和bus_sequencer通过set_sequencer函数告知reg_model的default_map, 并将default_map设置为自动预测状态。

使用

在sequence和其他component中使用,声明之后reg_model p_rm;然后例化即可,可以在env传值例化

寄存器模型提供了两个基本的任务: read和write。

p_rm.invert.read(status, value, UVM_FRONTDOOR);
(是否成功,读取的值,读取方式)

参考模型一般不会写寄存器

在sequence中使用寄存器模型, 通常通过p_sequencer的形式引用,需要首先在sequencer中有一个寄存器模型的指针
p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);

前门 后门访问

前门

举例读操作

如果driver一直发送应答而sequence不收集应答, 那么将会导致sequencer的应答队列溢出。 UVM考虑到这种情况, 在adapter中设置了provide_responses选项:
 

在设置了此选项后, 寄存器模型在调用bus2reg将目标transaction转换成uvm_reg_item时, 其传入的参数是rsp, 而不是req。 使用应答机制的操作流程为:

后门

直接操作寄存器,不消耗仿真时间,消耗运行时间

一般使用接口,新建一个后门interface:

缺点是只能单独调用,操作一个寄存器

我们可以用c+dpi

DPI VPI

用于对RTL写值

我们要在C定义函数

int uvm_hdl_read(char *path, p_vpi_vecval value);
此函数要调用VPI读

然后在C

import "DPI-C" context function int uvm_hdl_read(string path, output uvm_hdl_d ata_t value);


以后就可以在SystemVerilog中像普通函数一样调用uvm_hdl_read函数了

然后要操作的寄存器的路径被抽像成了一个字符串, 而不再是一个绝对路径。

从而可以以参数的形式传递, 并可以存储,这为建立寄存器模型提供了可能。 一个单纯的Verilog路径, 如top_tb.my_dut.counter, 它是不能被传递的, 也是无法存储的。

注意还要在reg_block中调用uvm_reg的configure函数时, 设置好第三个路径参数:

设置好根路径hdl_root:
rm.set_hdl_path_root("top_tb.my_dut");

UVM提供两类后门访问的函数:

一是UVM_BACKDOOR形式的read和write:操作时模仿DUT的行为

二是peek和poke :完全不管DUT的行为

 第 如对一个只读的寄存器进行写操作, 那么第一类由于要模拟DUT的只读行为, 所以是写不进去的, 但是使用第二类可以写进去。

peek还是poke, 其常用的参数都是前两个。 各自的第一个参数表示操作是否成功, 第二个参数表示读写的数据。
如此调用

复杂的rm

一般使用两级reg block

在第一级的uvm_reg_block中加入寄存器, 而第二级的uvm_reg_block通常只添加uvm_reg_block
 

uvm file

用于区分不同的hdl路径。

多域寄存器

物理上来看, 它的DUT实现中是三个寄存器, 因此这一个寄存器实际上对应着三个不同的hdl路径

首先定义一个reg包含三个域

然后在block中

占多个地址的reg

第一个方法是分割成两个寄存器

reg_data.configure(this, 32, 0, "W1C", 1, 0, 1, 1, 0);
直接定义两个地址长度就行了

加入存储器

在寄存器模型中加入存储器如下  深度1024,宽度16

config第二个参数是真实的dut存储器地址

对此存储器进行读写, 可以通过调用read、 write、 peek、 poke实现

当指定一个offset, 使用前门访问操作读写时, 由于一个offset对应的是两个物理地址, 所以寄存器模型会在总线上进行两次读写操作。


寄存器模型对DUT的模拟

在寄存器模型存在期望值以及镜像值,期望值是提前预判dut即将变化的值

read&write操作: 无论通过后门访问还是前门访问的方式, 操作完成后, 寄存器模型都会根据读写的结果更新期望值和镜像值( 二者相等) 。

peek&poke操作: 在操作完成后, 寄存器模型会根据操作的结果更新期望值和镜像值( 二者相等) 。

get&set操作: set操作会更新期望值, 但是镜像值不会改变。 get操作会返回寄存器模型中当前寄存器的期望值

update操作: 这个操作会检查寄存器的期望值和镜像值是否一致, 如果不一致, 那么就会将期望值写入DUT中, 并且更新镜像值, 使其与期望值一致。 每个由uvm_reg_block派生来的类也有update操作, 它会递归地调用所有加入此reg_block的寄存器的update任务。

randomize操作: randomize之后, 期望值将会变为随机出的数值, 镜像值不会改变。 但是并不是寄存器模型中所有寄存器都支持此函数。 如果不支持, 则randomize调用后其期望值不变。 若要关闭随机化功能, 如7.2.1节所示, 在reg_invert的build中调用reg_data.configure时将其第八个参数设置为0即可。 一般的, randomize不会单独使用而是和update一起。 如在DUT上电复位后, 需要配置一些寄存器的值。 这些寄存器的值通过randomize获得, 并使用update任务配置到DUT中。

内建seq

检查后门hdl路径

在启动此sequence时必须给model赋值。 在任意的sequence中, 可以启动此sequence

调用start任务时, 传入的sequencer参数为null。

因为它正常工作不依赖于这个sequencer, 而依赖于model变量。

这个sequence会试图读取hdl所指向的寄存器, 如果无法读取, 则给出错误提示。

由这个sequence的名字也可以看出, 它除了检查寄存器外, 还检查存储器。 如果某个寄存器/存储器在加入寄存器模型时没有指定其hdl路径, 那么此sequence在检查时会跳过这个寄存器/存储器

检查默认值


uvm_reg_hw_reset_seq用于检查上电复位后寄存器模型与DUT中寄存器的默认值是否相同

对于寄存器模型来说, 需要调用reset函数来使其内寄存器的值变为默认值( 复位值)


此seq会调用reset,然后用前门访问读取dut值,然后比较。启用此seq要指定rm

可以用resource_db设置不检查此寄存器

或者

检查读写功能
 

使用前门访问的方式向所有寄存器写数据, 然后使用后门访问的方式读回, 并比较结果。然后把这个过程反过来, 使用后门访问的方式写入数据, 再用前门访问读回

必须为所有的寄存器设置好hdl路径


不检查寄存器,这样设置

uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},"NO_REG_TESTS", 1, this);
 

uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},"NO_REG_ACCESS_TEST", 1, this);

还有一个也可以起作用

class uvm_mem_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_it em)

不访问寄存器

高级用法

reg_predictor

左边是driver抓取返回值后,rm更新镜像和期望值,这个叫auto predicate

如此打开此功能rm.default_map.set_auto_predict(1);

右边是直接从真实的总线上获取值,使用monitor抓取然后送到predicator

我们需要做如下操作

bus agent和reg predictor连接在一起,通过ap接口

设置reg predictor的adaptor、map,rm通过map和predictor连接在一起

右图中如果要彻底关掉虚线的更新路径, 则需要

关掉自动预测

UVM_PREDICT_DIRECT功能与mirror操作

常用的只有前三个参数

把dut 的参数映射到rm

应用场景:仿真中调用,使rm和dut的reg值一样。此时check不需要check。要么在结束时调用检查一不一样。这时候需要check

mirror操作既可以在uvm_reg级别被调用, 也可以在uvm_reg_block级别被调用。 当调用一个uvm_reg_block的mirror时, 其实质是调用加入其中的所有寄存器的mirror。
 

设想一个场景,我们要统计cnt的值但是不想访问dut,那么我们就可以在rm使用预测

针对要预测的value,第二个参数是代表全部有效,第三个是预测的类型,具体如下

选择第一个参数实现在参考模型中更新寄存器模型而又不影响DUT的值

举个例子

如此在rm中用predict更新counter。

随机化rm以及update

对rm,支持randomize,可以对reg、field定义成rand类型,然后调用config

第八个参数即决定此field是否会在randomize时被随机化。但是即使定义 为1,也要加上“field存在写操作”才能实现随机化

constraint如下

一般可以randomize后使用update实现dut的更新

扩展位宽

定义地址位宽

默认情况下, 字选择信号的位宽等于数据位宽除以8, 它通过如下的宏来控制
 

其他函数

get_root_blocks


使得可以在不使用指针传递的情况下得到寄存器模型的指针
获取验证平台上所有的根块( root block) 。 根块指最顶层的reg_block。 

使用get_root_blocks函数得到reg_block的指针后, 要用cast将其转化为目标reg_block形式( 示例中为reg_model) 。

以后就可以直接使用p_rm来进行寄存器操作, 而不必使用p_sequencer.p_rm。

get_reg_by_offset

调用最顶层的reg_block的get_reg_by_offset, 即可以得到任一寄存器的指针
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值