寄存器模型的优势
- sequence复用性高,方便对 DUT 中寄存器进行读写;
- 提供了后门访问方式,可以不耗时的获取寄存器的值;
- 可以很方便的对寄存器的 coverage 验证点的收集
寄存器模型基本概念
寄存器模型概念 | 作用 |
---|---|
uvm_reg_field | 寄存器模型中的最小单位 |
uvm_reg | 比uvm_reg_field高一个级别,一个寄存器中至少包含一个uvm_reg_field |
uvm_reg_block | 比uvm_reg高一个级别,可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block。一个寄存器模 型中至少包含一个uvm_reg_block。 |
uvm_reg_map | 存储寄存器的偏移地址,并将其转换成可以访问的物理地址 在每个reg_block内部,至少有一个(通常也只有一个)uvm_reg_map。 |
uvm_reg_file | 用于区分不同的hdl路径 |
简单的寄存器模型
实例
- 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
- 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
步骤总结
- 从uvm_reg派生一个寄存器类:
- 声明build函数,并在其实例化uvm_reg_field类;
build函数负责所有uvm_reg_field的实例化
- 调用configure函数,配置上述域
configure字段的含义:
参数一,是此域的父辈,也就是此域位于哪个寄存器中,即是 this;
参数二,是此域的宽度;
参数三,是此域的最低位在整个寄存器的位置,从0开始计数;
参数四,表示此字段的存取方式,共支持25种;
参数五,表示是否是易失的(volatile),这个参数一般不会使用;
参数六,表示此域上电复位后的默认值;
参数七,表示此域时都有复位;
参数八,表示这个域是否可以随机化;
参数九,表示这个域是否可以单独存取
- 声明build函数,并在其实例化uvm_reg_field类;
- 在一个由reg_block派生的类中将上述寄存器配置:
- 声明一个build函数
build函数作用:实现所有寄存器的实例化
- default_map实例化;
调用
create_map
函数完成default_map
的实例化,default_map = create_map(“default_map”, 0, 2, UVM_LITTLE_ENDIAN, 0)
;
第一个参数,表示名字;
第二个参数,表示该 reg block 的基地址;
第三个参数,表示寄存器所映射到的总线的宽度(单位是 byte,不是 bit );
第四个参数,表示大小端模式;
第五个参数,表示该寄存器能否按 byte 寻址。 - 调用configure函数;
configure ( uvm_reg_block blk_parent, uvm_reg_file regfile_parent = null, string hdl_path = "" )
第一个参数,表示所在 reg block 的指针;
第二个参数,表示 reg_file 指针;
第三个参数,表示寄存器后面访问路径 - string 类型 - 调用build函数将域实例化;
- 将此寄存器加入default_map
default_map.add_reg (version, 16'h47, "RW") ;
第一个参数,表示要添加的寄存器名;
第二个参数,表示地址;
第三个参数,表示寄存器的读写属性。
- 声明一个build函数
复杂的寄存器模型
实例
class global_blk extends uvm_reg_block;
...
endclass
class buf_blk extends uvm_reg_block;
...
endclass
class mac_blk extends uvm_reg_block;
...
endclass
class reg_model extends uvm_reg_block;
rand global_blk gb_ins;
rand buf_blk bb_ins;
rand mac_blk mb_ins;
virtual function void build();
default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
gb_ins = global_blk::type_id::create("gb_ins");
gb_ins.configure(this, "");
gb_ins.build();
gb_ins.lock_model();
default_map.add_submap(gb_ins.default_map, 16'h0);
bb_ins = buf_blk::type_id::create("bb_ins");
bb_ins.configure(this, "");
bb_ins.build();
bb_ins.lock_model();
default_map.add_submap(bb_ins.default_map, 16'h1000);
mb_ins = mac_blk::type_id::create("mb_ins");
mb_ins.configure(this, "");
mb_ins.build();
mb_ins.lock_model();
default_map.add_submap(mb_ins.default_map, 16'h2000);
endfunction
`uvm_object_utils(reg_model)
function new(input string name="reg_model");
super.new(name, UVM_NO_COVERAGE);
endfunction
endclass
步骤总结
-
第一步是先实例化子reg_block。
-
第二步是调用子reg_block的configure函数(如果需要使用后门访问,则在这个函数中要说明子reg_block的路径,这个路径不是绝对路径,而是相对于父reg_block来说的路径)
-
第三步是调用子reg_block的build函数。
-
第四步是调用子reg_block的lock_model函数。
-
第五步则是将子reg_block的default_map以子map的形式加入父reg_block的default_map中
寄存器模型中加入存储器
- 存储器同样支持read/write/peek/poke进行读写,不过会额外多一个offset参数,表示存储器的哪个地址
/*从uvm_mem中派生存储器类,深度1024,宽度16*/
class my_memory extends uvm_mem;
function new(string name="my_memory");
super.new(name, 1024, 16);
endfunction
`uvm_object_utils(my_memory)
endclass
class reg_model extends uvm_reg_block;
rand my_memory mm;
virtual function void build();
default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
mm = my_memory::type_id::create("mm", , get_full_name());
mm.configure(this, "stat_blk.ram1024x16_inst.array");
default_map.add_mem(mm, 'h100);
endfunction
endclass
寄存器模型集成到环境
寄存器模型转换到总线上操作
- adapter的作用:
- reg2bus:其作用为将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式,
- bus2reg:其作用为当监测到总线上有操作时,它将收集来的transaction转换成寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器的值
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
在验证平台中使用寄存器模型
实例
- base_test:加入REG_MODEL
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
步骤总结
- 定义 reg_model和reg_sqr_adapter;
- 在build_phase中将其实例化
- 配置reg_model
第一是调用configure函数,其第一个参数是parent block,由于是最顶层的reg_block,因此填写null,第二个参数是后门访问路径
第二是调用build函数,将所有的寄存器实例化。
第三是调用lock_model函数,调用此函数后,reg_model中就不能再加入新的寄存器了
第四是调用reset函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0,调用此函数后,所有寄存器的值都将变为设置的复位值 -
在connect_phase中,通过set_sequencer函数设置adapter的bus_sequencer,并将default_map设置为自动预测状态
寄存器模型中的访问
前门访问
定义
- 前门访问操作就是通过寄存器配置总线(如APB协议、OCP协议、I2C协议等)来对DUT进行操作。前门访问操作只有两种:读操作和写操作。
方法函数
- 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);
- write
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);
- 参数说明:
- 第一个是uvm_status_e型的变量,这是一个输出,用于表明操作是否成功;
- 第二个是读/写的数值;
- 第三个是读/写的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR
后门访问
定义
不消耗仿真时间(即$time打印的时间)而只消耗运行时间的操作为后门访问。
实现方式
- 使用interface进行后门访问操作
- DPI+VPI实现后门访问
函数 作用 uvm_hdl_read(string path, uvm_hdl_data_t value); 后门读 uvm_hdl_deposit(string path, uvm_hdl_data_t value); 后门写
可被信值覆盖
uvm_hdl_force(string path, uvm_hdl_data_t value); force信号
在release前不能被覆盖
uvm_hdl_release(string path); release信号 uvm_hdl_check_path(string path); 检查HDL路径是否存在 uvm_hdl_release_and_read(string path, uvm_hdl_data_t value); 更新信号release后的值 uvm_hdl_force_time(string path, uvm_hdl_data_t value, time force_time); force某个信号为特定值之后一段时间后再释放 - 寄存器模型中的后门访问函数
- 实现前提:设置好configure函数的第三个路径参数;设置好根路径hdl_root
class reg_model extends uvm_reg_block; … virtual function void build(); counter_high.configure(this, null, "counter[31:16]"); counter_low.configure(this, null, "counter[15:0]"); … endfunction … endclass function void base_test::build_phase(uvm_phase phase); 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"); endfunction
- UVM_BACKDOOR形式的read和write:会在进行操作时模仿DUT的行为
- peek/pook:完全不管DUT的行为
task uvm_reg::poke(output uvm_status_e status,
input uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
task uvm_reg::peek(output uvm_status_e status,
output uvm_reg_data_t value,
input string kind = "",
input uvm_sequence_base parent = null,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
前后门访问区别
区别 | 前门访问 | 后门访问 |
---|---|---|
时间上 | 由于是真实的物理操作,因此会 消耗仿真时间 | 不消耗仿真时间 |
调试上 | 任何前门访问都会 有波形文件,方便调试 | 没有波形文件 的记录,调试难度增加 |
应用上 | 能验证 总线协议本身 是否正确 | 大规模寄存器的 快速初始化 能够操作 只读寄存器 注入故障 |
寄存器模型对DUT的模拟
期望值与镜像值
- 镜像值(mirrored value):与DUT保持同步的值
- 期望值(desired value):期望DUT改变的值
/*设置期望值*/
p_sequencer.p_rm.invert.set(16'h1);
/*获取期望值*/
value = p_sequencer.p_rm.invert.get();
/*获取镜像值*/
value = p_sequencer.p_rm.invert.get_mirrored_value();
/*检查1镜像值和期望值是否一致,不一致则更新*/
p_sequencer.p_rm.invert.update(status,UVM_FRONTDOOR);
常用操作及其对期望值和镜像值的影响
操作 | 影响 |
---|---|
read&write | 更新期望值和镜像值(二者相等) |
peek&poke | 更新期望值和镜像值(二者相等) |
get&set | set操作会更新期望值,但是镜像值不会改变 get操作会返回寄存器模型中当前寄存器的期望值 |
update | 检查寄存器的期望值和镜像值是否一致,如果不一致,那么就会将期望值写入DUT中,并且更新镜像值,使其与期望值一致 |
randomize | 期望值将会变为随机出的数值,镜像值不会改变。一般和update一起使用 只有当configure时将其第八个参数设置为1时支持此功能 |
mirror | 更新期望值和镜像值 |
predict | 更新镜像值和期望值 |
寄存器模型的高级用法
- auto predict:寄存器模型会更新寄存器的镜像值和期望值
rm.default_map.set_auto_predict(1);
- mirror:读取DUT中寄存器的值并将它们更新到寄存器模型;可以在uvm_reg和uvm_reg_block级别被调用
task uvm_reg::mirror(output uvm_status_e status, input uvm_check_e check = UVM_NO_CHECK, input uvm_path_e path = UVM_DEFAULT_PATH, …); //第二个参数可选:UVM_CHECK和UVM_NO_CHECK,代表若镜像值与期望值不一致,是否更新前给出错误提示
- predict:更新镜像值,但是同时又不对DUT进行任何操作
function bit uvm_reg::predict(uvm_reg_data_t value, uvm_reg_byte_en_t be=-1, uvm_predict_e kind=UVM_PREDICT_DIRECT, uvm_path_e path=UVM_FRONTDOOR, ); //第一个参数表示要预测的值 //第二个参数是byte_en,默认-1的意思是全部有效 //第三个参数是预测的类型,可选参数: typedef enum { UVM_PREDICT_DIRECT, UVM_PREDICT_READ, UVM_PREDICT_WRITE } uvm_predict_e; //第四个参数是后门访问或者是前门访问
- 扩展位宽
/*扩展位宽*/ `ifndef UVM_REG_DATA_WIDTH `define UVM_REG_DATA_WIDTH 64 `endif /*地址位宽*/ `ifndef UVM_REG_ADDR_WIDTH `define UVM_REG_ADDR_WIDTH 64 `endif /*字选择信号的位宽*/ `ifndef UVM_REG_BYTENABLE_WIDTH `define UVM_REG_BYTENABLE_WIDTH ((`UVM_REG_DATA_WIDTH-1)/8+1) `endif
- get_root_blocks
- get_reg_by_offset
寄存器模型内建sequence
内建reg测试seq:
- uvm_reg_hw_reset_seq:
- 功能:检查reg_model中寄存器的复位值与实际RTL是否一致
- 测试级别:uvm_reg_block/uvm_reg
- 原理:
- 先reset reg_modle 。将reg_modle中的镜像值和期望值复位
- 判断是否在外部设置了哪些 reg_block /reg不需要进行 reset 测试
- 对所有需要进行测试的 reg 通过前门的方式读回DUT的硬件值,再与 reg_model 的 mirror 值进行对比,如果不一致,证明DUT的硬件复位值与reg_modle 的寄存器复位值不一致
- 使用方式:
uvm_reg_hw_reset_seq hw_reset_seq; //声明 hw_reset_seq = new("hw_reset_seq"); //例化 hw_reset_seq.model = XXX_RAL.xxx_reg; //与要被检查的寄存器模型相连 hw_reset_seq.start(null); //启动 /*这些内建sequence本质上并没有放到某个具体的sequencer执行,它只是用了sequence执行时调用body()的机制而已,因而我们传递任何的参数给sequence的start()只需要满足参数类型需求就行了,具体传递的值是多少并不重要*/
- 设置reg不测试:
uvm_resource_db#(bit)::set({"REG::",regmodel.blk.get_full_name(),".*"}, "NO_REG_TESTS", 1, this); uvm_resource_db#(bit)::set({"REG::",regmodel.blk.reg.get_full_name(),".*"}, "NO_REG_HW_RESET_TEST", 1, this); //reg_block/reg均可使用NO_REG_TESTS和NO_REG_HW_RESET_TEST //NO_REG_TESTS和NO_REG_HW_RESET_TEST区别在于前者针对所有的内建sequence都排除,而NO_REG_HW_RESET_TEST仅仅针对的是uvm_reg_hw_reset_seq这一单一sequence
- uvm_reg_single_bit_bash_seq
- 功能:通过前门读写实现对寄存器读写访问域每个bit的遍历操作
- 测试级别:uvm_reg
- 原理:对每个可读可写域分别写入1和0并读出后座比较
- 设置reg不测试:
- uvm_reg_bit_bash_seq
- 功能:通过前门读写实现对所有寄存器读写访问域每个bit的遍历操作
- 测试级别:uvm_reg_block
- 原理:对包含所有uvm_reg都执行 <uvm_reg_bit_bash_seq> 序列
- 注意事项:仅支持RW类型,对于其他类型如RO,RC这些需要exclude掉
- 设置reg不测试:
- uvm_reg_single_access_seq
- 功能:用来检查寄存器映射的有效性
- 测试级别:uvm_reg
- 原理:先从前门写入寄存器,而后从后门读回值对比;然后反过来后门写入再用前门读回,确保得到的结果值与镜像值匹配
- 注意事项:
- 要求寄存器的hdl路径完成映射
- 没有可用后门的寄存器,或者只包含只读字段的寄存器,或者字段具有未知访问策略的寄存器,都不能被测试
- 设置reg不测试:
- uvm_reg_access_seq
- 功能:检查寄存器的读写
- 测试级别:uvm_reg_block
- 原理:对包含所有uvm_reg都执行 <uvm_reg_single_access_seq> 序列,来验证一个块内所有寄存器的可访问性
- 注意事项: 需要在配置ral时,添加寄存器的层级路径,方便seq找到后门。
//二者地址拼接 XXX_RAL.set_hdl_path_root("xxx") xxx.configure(this,"")
-
设置reg不测试:
- uvm_reg_share_access_seq
- 功能:用来检查所有可能访问寄存器路径的有效性
- 测试级别:uvm_reg
- 原理:先在一个地址映射中写入共享寄存器,然后通过其他所有可读的地址映射以及后门接口读取它,确保得到的值与镜像值一致
- 设置reg不测试:
- uvm_reg_mem_hdl_paths_seq
- 功能:检查hdl路径正确性
内建mem测试seq:
- uvm_mem_single_walk_seq:
- uvm_mem_walk_seq:
- uvm_mem_single_access_seq:
- 注意事项:需要存储模型的hdl路径已经指定
- uvm_mem_access_seq:
- 功能:
- 设置mem不测试:
uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(),".*"}, "NO_REG_TESTS", 1, this); uvm_resource_db#(bit)::set({"REG::",rm.get_full_name(),".*"}, "NO_MEM_TESTS", 1, this); uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},"NO_MEM_ACCESS_TEST", 1, this);
- uvm_mem_shared_access_seq:
- uvm_mem_mam:动态内存分配
- uvm_mem_mam介绍
- Memory Allocation Manager