写过 verilog 硬件代码的同学应该都知道 DUT 会包含很多寄存器,它们是模块间交互的接口,其用途大致可以分为两类:

a. 通过读出寄存器当前的值获取 DUT 当前的状态,该类寄存器称为状态寄存器;

b. 通过对寄存器进行配置,可以使得 DUT 工作在一定模式下,该类寄存器称为配置寄存器。

在验证过程中,寄存器的验证是最新开始的,只有保证寄存器的配置正确,才能使得硬件之间的“交互”正确。在验证寄存器配置是否正确的过程中,我们需要频繁的对 DUT 内部的寄存器进行读写操作,如 reference model 需要获取指定 reg 的参数值,在验证平台中我们获取 DUT 内部寄存器的值的方式主要有两种:

前门访问(FRONTDOOR):启动 sequence 产生待操作寄存器的读写控制和地址,在 driver 中通过总线(Bus)驱动至 DUT,并在 monitor 中采集 Bus 输出数据,该方式需要消耗仿真时间 ;

后门访问(BACKDOOR):在仿真环境中通过 DUT 实例名进行点操作,直接访问 DUT 内部的寄存器,该方式的缺点是,点操作需要通过绝对路径操作,如果寄存器数量庞大,会导致验证环境臃肿繁杂,容易出错。

因为上述操作的不利因素,才导致寄存器模型 ( RAL Model ) 产生。

1. 什么是寄存器模型

RAL Model 对应于 DUT 中的寄存器,RAL Model 中有 DUT 每一个 寄存器的副本,它是真实硬件寄存器在软件环境中的行为模型;硬件寄存器的一个或多个 bit 的拼接称为一个域 ( field );多一个 field 形成一个 reg;多个 reg 构成一个块 ( block )。uvm library 已经为我们定义好了上述几个概念,我们在使用时只需继承即可。

uvm_reg_field:这是寄存器模型中的最小单位。

uvm_reg:它比 uvm_reg_field 高一个级别,但是依然是比较小的单位。一个寄存器中至少包含一个 uvm_reg_field。

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

uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map 就是存储这些地址,并将其转换成可以访问的物理地址(因为加入寄存器模型中的寄存器地址一般都是偏移地址,而不是绝对地址)。当寄存器模型使用前门访问方式来实现读或写操作时,uvm_reg_map 就会将地址转换成绝对地址,启动一个读或写的 sequence,并将读或写的结果返回。在每个 reg_block 内部,至少有一个(通常也只有一个)uvm_reg_map。

如下图所示,RAL Model 中包含 MEM 和 block,它们分别用于对 DUT 中的寄存器和 memory 进行建模,其行为和硬件行为保持一致(其实是尽可能保持一致),ADDR MAP 用于实现访问寄存器的相对地址和绝对地址的转换。

寄存器模型注意有以下优势:

a.方便对 DUT 中寄存器进行读写;

b.在软件仿真时,可以不耗时的获取寄存器的值(直接从 RAL Model 中获取);

c.可以很方便的正对寄存器的 coverage 验证点的收集。

如果有了寄存器模型,那么寄存器访问过程就可以简化为:

RAL Model

task my_model::main_phase(uvm_phase phase);

reg_model.version.read(status, value, UVM_FRONTDOOR);

reg_model.version.write(status, value, UVM_FRONTDOOR);

endtask

只要一条语句就可以实现上述复杂的过程。像启动 sequence 及将读取结果返回这些事情,都会由寄存器模型来自动完成。在没有寄存器模型之前,只能启动 sequence 通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在 scoreboard(或者其他 component )中难以控制。而有了寄存器模型之后,scoreboard 只与寄存器模型打交道,无论是发送读的指令还是获取读操作的返回值,都可以由寄存器模型完成。有了寄存器模型后,可以在任何耗费时间的phase中使用寄存器模型以前门访问或后门(BACKDOOR)访问的方式来读取寄存器的值,同时还能在某些不耗费时间的 phase(如 check_phase)中使用后门访问的方式来读取寄存器的值。

2. 寄存器模型实例

假设有如下的 DUT:

DUT

module dut (clk, data, addr, we_n, cs);

input clk;

inout[15:0] data;

input[15:0] addr;

input we_n;

input cs;

reg [16:0] version;

initial begin

version <= 16'h0000;

end

endmodule

这个 DUT 比较的简单,它只有一个寄存器 version, 我们依据上述基础理论为其建造 RAL model。首先要从 uvm_reg 派生一个通用的寄存器类:

reg uvm_reg

class my_reg extends uvm_reg;

rand uvm_reg_field data;

`uvm_object_utils(my_reg)

virtual function void build();

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

//parameter: parent, size, lsb_pos, access, volatile, reset value, has_reset, is_rand, individually accessible

data.configure(this, 16, 0, "RW", 1, 0, 1, 1, 0);

endfunction

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

//parameter: name, size, has_coverage

super.new(name, 16, UVM_NO_COVERAGE);

endfunction

endclass

data.configure 的 9 个parameter: parent, size, lsb_pos, access, volatile, reset value, has_reset, is_rand, individually accessible

参数一,是此域的父辈,也就是此域位于哪个寄存器中,即是 this;

参数二,是此域的宽度;

参数三,是此域的最低位在整个寄存器的位置,从0开始计数;

参数四,表示此字段的存取方式;

参数五,表示是否是易失的(volatile),这个参数一般不会使用;

参数六,表示此域上电复位后的默认值;

参数七,表示此域时都有复位;

参数八,表示这个域是否可以随机化;

参数九,表示这个域是否可以单独存取。

定义好了此通用寄存器后,我们需要在一个由 reg_block 派生的类中把其实例化:

reg uvm_reg_block

class my_regmodel extends uvm_reg_block;

rand my_reg version;

function void build();

default_map = create_map("default_map", 0, 2, UVM_LITTLE_ENDIAN, 0);

version = my_reg::type_id::create("version", , get_full_name());

version.configure(this, null, "version");

version.build();

default_map.add_reg(version, 16'h47, "RW");

endfunction

`uvm_object_utils(my_regmodel)

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

super.new(name, UVM_NO_COVERAGE);

endfunction

endclass

reg block中也有 build 函数,在其中要做如下事情:

调用 create_map 函数完成 default_map 的实例化,default_map = create_map(“default_map”, 0, 2, UVM_LITTLE_ENDIAN, 0);

第一个参数,表示名字;

第二个参数,表示该 reg block 的基地址;

第三个参数,表示寄存器所映射到的总线的宽度(单位是 byte,不是 bit );

第四个参数,表示大小端模式;

第五个参数,表示该寄存器能否按 byte 寻址。

完成每个寄存器的 build 及 configure 操作,uvm_reg 的 configure 函数原型: function void configure ( uvm_reg_block blk_parent, uvm_reg_file regfile_parent = null, string hdl_path = "" );

第一个参数,表示所在 reg block 的指针;

第二个参数,表示 reg_file 指针;

第三个参数,表示寄存器后面访问路径 - string 类型。

把每个寄存器加入到 default_map 中,uvm_reg_map 存有各个寄存器的地址信息,default_map.add_reg (version, 16'h47, "RW") ;

第一个参数,表示要添加的寄存器名;

第二个参数,表示地址;

第三个参数,表示寄存器的读写属性。

到此为止,一个简单的 register model 已经完成。

3. 寄存器模型应用

3.1寄存器模型读写操作

a.读操作

reg read

extern virtual task read(output uvm_status_e status,

output uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input uvm_sequence_base parent = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0

);

b.写操作

reg read

extern virtual task write(output uvm_status_e status,

input uvm_reg_data_t value,

input uvm_path_e path = UVM_DEFAULT_PATH,

input uvm_reg_map map = null,

input uvm_sequence_base parent = null,

input int prior = -1,

input uvm_object extension = null,

input string fname = "",

input int lineno = 0

);

read/write有多个参数,常用的是其前三个参数:

输出型,uvm_status_e 型的变量,表示读/写操作是否成功;

输出/入型, 读/写取的数值;

读/写取的方式,可选 UVM_FRONTDOOR 和 UVM_BACKDOOR。

register model 的 FRONTDOOR 方式工作流程如上图所示,其中左图为读操作,右图为写操作。无论是读或写,RAL model 在调用 read/write函数时都会通过启动一个 sequence(用户不可见) 产生一个 uvm_reg_bus_op 的变量,此变量中存储着操作类型(读还是写),操作的地址,如果是写操作,还会有要写入的数据。此变量中的信息要经过一个转换器( adapter )转换之后,交给 bus_sequencer,之后 bus_sequencer 交给 bus_driver,bus_driver 实现最终的 FRONTDOOR 读写操作。因此,必须要定义好一个转换器。

adapter

class adapter extends uvm_reg_adapter;

string tID = get_type_name();

`uvm_object_utils(my_adapter)

function new(string name="my_adapter");

super.new(name);

endfunction : new

function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);

bus_transaction tr;

tr = new("tr");

tr.addr = rw.addr;

tr.bus_op = (rw.kind == UVM_READ) BUS_RD: BUS_WR;

if (tr.bus_op == BUS_WR)

tr.wr_data = rw.data;

return tr;

endfunction : reg2bus

function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);

bus_transaction tr;

if(!$cast(tr, bus_item)) begin

`uvm_fatal(tID,"Provided bus_item is not of the correct type. Expecting bus_trans action")

return;

end

rw.kind = (tr.bus_op == BUS_RD) UVM_READ : UVM_WRITE;

rw.addr = tr.addr;

rw.byte_en = 'h3;

rw.data = (tr.bus_op == BUS_RD) tr.rd_data : tr.wr_data;

rw.status = UVM_IS_OK;

endfunction : bus2reg

endclass : adapter

一个转换器要定义好两个函数,一是 reg2bus,其作用为将寄存器模型通过 sequence 发出的 uvm_reg_bus_op 型的变量转换成 bus_sequencer 能够接受的形式,二是 bus2reg,其作用为当监测到总线上有操作时,它将收集来的 transaction 转换成寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器的值。

说到这里,不得不考虑寄存器模型发起的读操作的数值是如何返回给寄存器模型的?由于总线的特殊性,bus_driver 在驱动总线进行读操作时,它也能顺便获取要读的数值,如果它将此值放入从 bus_sequencer 获得的 bus_transaction 中时,那么bus_transaction 中就会有读取的值,此值经过 adapter 的 bus2reg 函数的传递,最终被寄存器模型获取,这个过程如上图读操作虚线所示。

寄存器模型的读/写过程类似,现在以读操作为例,其完成流程为:

a.参考模型调用寄存器模型的读任务。

b.寄存器模型产生 sequence,并产生 uvm_reg_item:rw。

c.产生 driver 能够接受的transaction:bus_req=adapter.reg2bus(rw)。

d.把 bus_req 交给bus_sequencer。

e.driver 得到 bus_req 后驱动它,得到读取的值,并将读取值放入 bus_req 中,调用 item_done。

f.寄存器模型调用 adapter.bus2reg(bus_req,rw)将 bus_req 中的读取值传递给 rw。

g.将 rw 中的读数据返回参考模型。

3.2 寄存器模型例化

一般在 env 或者 test 中加入 RALmodel :

base_test

class base_test extends uvm_test;

my_env env;

my_vsqr v_sqr;

reg_model rm;

adapter reg_sqr_adapter;

endclass

function void base_test::build_phase(uvm_phase phase);

super.build_phase(phase);

env = my_env::type_id::create("env", this);

v_sqr = my_vsqr::type_id::create("v_sqr", this);

rm = reg_model::type_id::create("rm", this);

rm.configure(null, "");

rm.build();

rm.lock_model();

rm.reset();

rm.set_hdl_path_root("top_tb.my_dut");

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

endfunction

要将一个寄存器模型集成到 base_test 中,那么至少需要在 base_test 中定义两个成员变量,一是 reg_model,另外一个就是 adapter 。将所有用到的类在 build_phase 中实例化。在实例化后 reg_model 还要做四件事:

第一是调用 configure 函数,其第一个参数是parent block,由于是最顶层的reg_block,因此填写null,第二个参数是后门访问路径,这里传入一个空的字符串。

第二是调用 build 函数,将所有的寄存器实例化。

第三是调用 lock_model 函数,调用此函数后,reg_model 中就不能再加入新的寄存器了。

第四是调用 reset 函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0,调用此函数后,所有寄存器的值都将变为设置的复位值。

寄存器模型的前门访问操作最终都将由 uvm_reg_map 完成,因此在 connect_phase 中,需要将转换器和 bus_sequencer 通过 set_sequencer 函数告知 reg_model 的 default_map,并将 default_map 设置为自动预测状态。

3.3 寄存器模型应用

寄存器模型定义好后,可以在 reference model 和 sequence,因为 uvm_component 和 uvm_object 的区别,其在 uvm_component 中的应用和在 uvm_object 类中存在些微差异。

refencer model 场景使用

reference model 为 component 类型,为了使用在 base_test 中例化的 reg model ,需要在参考模型中定义一个寄存器模型的指针,并在env 的 connect_phase 中实现指针赋值:

base_test

class my_model extends uvm_component;

reg_model p_rm;

endclass

function void my_env::connect_phase(uvm_phase phase);

mdl.p_rm = this.p_rm;

endfunction

read/write实例如下:

my_model 应用

task my_model::main_phase(uvm_phase phase);

my_transaction tr;

my_transaction new_tr;

uvm_status_e status;

uvm_reg_data_t value;

super.main_phase(phase);

p_rm.version.read(status, value, UVM_FRONTDOOR);

while(1) begin

port.get(tr);

new_tr = new("new_tr");

new_tr.copy(tr);

//`uvm_info("my_model", "get one transaction, copy and print it:", UV M_LOW)

//new_tr.print();

if(value)

invert_tr(new_tr);

ap.write(new_tr);

end

endtask

sequence 场景使用

reg model 的使用,关键在于获取 reg model 的实例,在 sequence 为了获取一个指向 reg model 的指针,我们可以借助于 sequencer。实例如下:

my_model 应用

class case0_cfg_vseq extends uvm_sequence;

virtual task body();

p_sequencer.p_rm.version.read(status, value, UVM_FRONTDOOR);

p_sequencer.p_rm.version.write(status, value, UVM_FRONTDOOR);

endtask

endclass

因为在base_test 的 connect_phase 中,对 `v_sqr.p_rm = this.rm;`,所以我们在 case0_cfg_vseq 挂载在 vseqr 后,我们间接获得了 rm 的指针。所以可以直接使用。

uvm 形式验证_6.小白学uvm验证 - 寄存器模型相关推荐

  1. 【小白学PyTorch】6.模型的构建访问遍历存储(附代码)

    <<小白学PyTorch>> 小白学PyTorch | 5 torchvision预训练模型与数据集全览 小白学PyTorch | 4 构建模型三要素与权重初始化 小白学PyT ...

  2. pytorch默认初始化_小白学PyTorch | 9 tensor数据结构与存储结构

    [机器学习炼丹术]的学习笔记分享<> 小白学PyTorch | 8 实战之MNIST小试牛刀 小白学PyTorch | 7 最新版本torchvision.transforms常用API翻 ...

  3. 【小白学PyTorch】扩展之Tensorflow2.0 | 21 Keras的API详解(上)卷积、激活、初始化、正则...

    [机器学习炼丹术]的学习笔记分享 <<小白学PyTorch>> 扩展之Tensorflow2.0 | 20 TF2的eager模式与求导 扩展之Tensorflow2.0 | ...

  4. 【小白学PyTorch】17.TFrec文件的创建与读取

    [机器学习炼丹术]的学习笔记分享 <<小白学PyTorch>> 小白学PyTorch | 16 TF2读取图片的方法 小白学PyTorch | 15 TF2实现一个简单的服装分 ...

  5. 【小白学PyTorch】16.TF2读取图片的方法

    <<小白学PyTorch>> 扩展之tensorflow2.0 | 15 TF2实现一个简单的服装分类任务 小白学PyTorch | 14 tensorboardX可视化教程 ...

  6. 【小白学PyTorch】14.tensorboardX可视化教程

    <<小白学PyTorch>> 小白学PyTorch | 13 EfficientNet详解及PyTorch实现 小白学PyTorch | 12 SENet详解及PyTorch实 ...

  7. 【小白学PyTorch】9.tensor数据结构与存储结构

    [机器学习炼丹术]的学习笔记分享 <<小白学PyTorch>> 小白学PyTorch | 8 实战之MNIST小试牛刀 小白学PyTorch | 7 最新版本torchvisi ...

  8. 【小白学PyTorch】8.实战之MNIST小试牛刀

    <<小白学PyTorch>> 小白学PyTorch | 7 最新版本torchvision.transforms常用API翻译与讲解 小白学PyTorch | 6 模型的构建访 ...

  9. 【小白学PyTorch】7.最新版本torchvision.transforms常用API翻译与讲解

    机器学习炼丹术]的学习笔记分享 <<小白学PyTorch>> 小白学PyTorch | 6 模型的构建访问遍历存储(附代码) 小白学PyTorch | 5 torchvisio ...

最新文章

  1. logback:用slf4j+logback实现多功能日志解决方案
  2. bzoj 2006 [NOI2010]超级钢琴 rmq+堆
  3. xgboost学习率不能大于1的原因
  4. python 全栈开发,Day79(Django的用户认证组件,分页器)
  5. QT的QSoundEffect类的使用
  6. python中的ix是啥_python pandas (ix iloc loc) 的区别
  7. 看到这个密码,我就笑了
  8. 在安卓中对应用进行单元测试
  9. Hello,CNDS!(第一次博客)
  10. 设置div中的背景颜色及div中的字体颜色
  11. 奇迹之剑萌新晋升大神辅助攻略 奇迹之剑游戏脚本挂机工具介绍
  12. mysql 表 忽略大小写_mysql表名忽略大小写配置方法详解
  13. 基于 Sphinx 以纯文本编写富媒体项目文档的方法介绍
  14. 开启Win7快速启动栏
  15. pc station v15 博图_博图的pcstation是做什么的?和wincc有什么区别?怎么使用?-工业支持中心-西门子中国...
  16. 为什么在线性模型中相互作用的变量要相乘
  17. 【面试】Java 并发编程
  18. 思想的对象-The objects of thought
  19. linux mips 时钟中断,Linux内核中地时钟中断.pdf
  20. Hutool - 对于图片的基本处理

热门文章

  1. Perl读写Excel简单操作
  2. ajax同步导致ajax上面的代码不执行?
  3. (转)UIPageControl使亮点直接跳到点击dot上
  4. 好久不写日志了,现在开始,好好写了。。
  5. zabbix-server添加了网络设备模板导致boot.log占满根目录不能ssh登陆
  6. SpringBoot, 启动类,使用「SpringBootApplication」标注
  7. 个人宏工作簿PERSONAL.XLSB 保存位置 启动加载项
  8. Linux 的 复制命令 【 cp 】 (copy)及其 (常用参数 -fp)
  9. 20-21-2网络管理quiz3
  10. 异常问题解决Error:Execution failed for task ‘:app:processDebugManifest‘