一、概述
- 通过寄存器模型的常规方法,可以用来检查寄存器,以及协助检查硬件设计逻辑进和比对数据。
- 在软件实现硬件驱动和固件层时,也会实现类似寄存器模型镜像值的方法,即在寄存器配置的底层函数中,同时也声明一些全局的影子寄存器。这些影子寄存器的功能就是暂存当时写入寄存器的值,而在后期使用时,如果这些寄存器是非易失的,那么就可以省略读取寄存器的步骤,转而使用影子寄存器的值。这么做的好处在于响应更迅速,而不再通过若干个时钟周期的总线发起请求和等待响应,但另外一方面这么做的前提同测试寄存器模型的目的是一样的,即寄存器的写入值可以准确地反映到硬件中的寄存器。
- 利用寄存器模型的另一个场景是,在对硬件数据通路做数据比对时,需要及时地知道当时的硬件配置状况,而利用寄存器模型的镜像值可以实现实时读取,而不需要从前门访问。后门访问也可以在零时刻内完成,只是这么做会省略检查寄存器的步骤,即假设寄存器模型的镜像值同硬件中的寄存器真实值保持一致,而这一假设存在验证风险。所以只有这么做,才能为后期软件开发时使用影子寄存器扫清可能的硬件缺陷。
- 寄存器模型不但可以用来检查硬件寄存器,也可以用来配合scoreboard实时检查DUT的功能。
二、寄存器检查
用来检查寄存器时,有以下几种可行的方式:
- 从前门写,并且从前门读。这种方式最为常见,但是无法检查地址是否正确映射,而前门与后门混合操作的方式可以保证地址的映射检查。
- 从前门写,再从后门读。
- 从后门写,再从前门读。
- 对于一些状态寄存器(硬件自身信号会驱动更新其实际值),先用peek()来获取(并且会调用predict()方法来更新镜像值),再调用mirror()方法来从前门访问并且与之前更新的镜像值比较。
上面的这些方法,在寄存器模型的内建序列中都已经实现。与内建序列相比,自建序列可以更灵活,更贴近需求,而内建序列使用简单,是全自动化的方式。
在配合scoreboard
实施检查DUT的功能时,需要注意:
- 无论是将寄存器模型通过
config_db
进行层次化配置,还是间接通过封装在配置对象(configuration object)中的寄存器模型句柄,都需要scoreboard
可以索引到寄存器模型。 - 在读取寄存器或者寄存器域的值时,需要加以区分。
uvm_reg
类中没有类似value
的成语来表征其对应硬件寄存器的值。 uvm_reg
并不是寄存器模型的最小切分单元,uvm_reg_field
才是。uvm_reg
可以理解为uvm_reg_field
的容器,一个uvm_reg
可以包含多个顺序排列的uvm_reg_field
。在取值时,可以使用uvm_reg_field
的成员value
直接访问,最好使用uvm_reg
类和uvm_reg_field
类都具备的接口函数get_mirrored_value()
。
三、功能覆盖率概述
在测试寄存器以及设计的某些功能配置模式时,也需要统计测试过的配置情况。就MCDF寄存器模型来看,除了测试寄存器本身,还需要考虑在不同的配置下,设计的数据处理、仲裁等功能是否正确,所以需要放置功能覆盖率在寄存器模型中。由于寄存器描述文件的结构化,可以通过扩充寄存器模型生成器的功能,使得生成的寄存器模型也可以自动包含各个寄存器域的功能覆盖率。UVM的寄存器模型已经内置了一些方法用来使能对应的covergroup
,同时在调用write()
或者read()
方法时,会自动调用covergroup::sample()
来完成功能覆盖率收集。
四、覆盖率自动收集模式
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
uvm_reg_field reserved;
rand uvm_reg_field pkt_len;
rand uvm_reg_field prio_level;
rand uvm_reg_field chnl_en;
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
function new(string name="ctrl_reg");
super.new(name, 32, UVM_CVR_ALL);
set_coverage(UVM_CVR_FIELD_VALS);
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
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);
endfunction
function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction
function void sample_values();
super.sample_values();
if(get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass
sample()
可以理解为read()
、write()
方法的回调函数,需要填充该方法,使得可以保证自动采样数据。
sample_values()
是提供外部调用的方法,在一些特定事件触发时,例如中断、复位等场景,可以在外部通过监听具体事件来调用该方法。在sample_values()
方法中,可以通过调用get_coverage()方法来判断是否允许进行覆盖率采样。
五、覆盖率外部事件触发收集
更贴合实际的、可作为覆盖率验收标准的covergroup定义还当采取自定义的形式,一方面来限定感兴趣的域和值,一方面来指定感兴趣的采样事件,即使用合适的事件来触发采样,通过这种方式,最后可以完成寄存器功能覆盖率的验证完备性标准。
class mcdf_coverage extends uvm_subscriber #(mcdf_bus_trans);
mcdf_rgm rgm;
`uvm_component_utils(mcdf_coverage)
covergroup reg_value_cg;
option.per_instance = 1;
CH0LEN: coverpoint rgm.chnl0_ctrl_reg.pkt_len.value[2:0] {bins len[] = {0, 1, 2, 3, [4:7]};}
CH0PRI: coverpoint rgm.chnl0_ctrl_reg.prio_level.value[1:0];
CH0CEN: coverpoint rgm.chnl0_ctrl_reg.chnl_en.value[0:0];
CH1LEN: coverpoint rgm.chnl1_ctrl_reg.pkt_len.value[2:0] {bins len[] = {0, 1, 2, 3, [4:7]};}
CH1PRI: coverpoint rgm.chnl1_ctrl_reg.prio_level.value[1:0];
CH1CEN: coverpoint rgm.chnl1_ctrl_reg.chnl_en.value[0:0];
CH2LEN: coverpoint rgm.chnl2_ctrl_reg.pkt_len.value[2:0] {bins len[] = {0, 1, 2, 3, [4:7]};}
CH2PRI: coverpoint rgm.chnl2_ctrl_reg.prio_level.value[1:0];
CH2CEN: coverpoint rgm.chnl2_ctrl_reg.chnl_en.value[0:0];
CH0AVL: coverpoint rgm.chnl0_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0, 1, [2:7], [8:55], [56:61], 62, 63};}
CH1AVL: coverpoint rgm.chnl0_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0, 1, [2:7], [8:55], [56:61], 62, 63};}
CH2AVL: coverpoint rgm.chnl0_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0, 1, [2:7], [8:55], [56:61], 62, 63};}
LEN_COMB: cross CH0LEN, CH1LEN, CH2LEN;
PRI_COMB: cross CH0PRI, CH1PRI, CH2PRI;
CEN_COMB: cross CH0CEN, CH1CEN, CH2CEN;
AVL_COMB: cross CH0AVL, CH1AVL, CH2AVL;
endgroup
function new(string name, uvm_component parent);
super.new(name, parent);
reg_value_cg = new();
endfunction
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(mcdf_rgm)::get(this, "", "rgm", rgm)) begin
`uvm_info("GETRGM", "no top-down RGM handle is assigned", UVM_LOW)
end
function
function void write(T t);
reg_value_cg.sample();
endfunction
endclass