一、 UVM_PREDICT_DIRECT功能与mirror操作
UVM提供mirror操作,用于读取DUT中寄存器的值并将它们更新到寄存器模型中。它的函数原型为:
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,
…);
它有多个参数,但是常用的只有前三个。其中第二个参数指的是如果发现DUT中寄存器的值与寄存器模型中的镜像值不一致,那么在更新寄存器模型之前是否给出错误提示。其可选的值为UVM_CHECK和UVM_NO_CHECK。它有两种应用场景,一是在仿真中不断地调用它,使得到整个寄存器模型的值与DUT中寄存器的值保持一致,此时check选项是关闭的。二是在仿真即将结束时,检查DUT中寄存器的值与寄存器模型中寄存器的镜像值是否一致,这种情况下,check选项是
打开的。
mirror操作会更新期望值和镜像值。同update操作类似,mirror操作既可以在uvm_reg级别被调用,也可以在uvm_reg_block级别被调用。当调用一个uvm_reg_block的mirror时,其实质是调用加入其中的所有寄存器的mirror。
前文已经说过,在通信系统中存在大量的计数器。当网络出现异常时,借助这些计数器能够快速地找出问题所在,所以必须要保证这些计数器的正确性。一般的,会在仿真即将结束时使用mirror操作检查这些计数器的值是否与预期值一致。
在DUT中的计数器是不断累加的,但是寄存器模型中的计数器则保持静止。参考模型会不断统计收到了多少包,那么怎么将这些统计数据传递给寄存器模型呢?
前文中介绍的所有的操作都无法完成这个事情,无论是set,还是write,或是poke;无论是后门访问还是前门访问。这个问题的实质是想人为地更新镜像值,但是同时又不要对DUT进行任何操作。
UVM提供predict操作来实现这样的功能:
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;
read/peek和write/poke操作在对DUT完成读写后,也会调用此函数,只是它们给出的参数是UVM_PREDICT_READ和UVM_PREDICT_WRITE。要实现在参考模型中更新寄存器模型而又不影响DUT的值,需要使用UVM_PREDICT_DIRECT,即默认值:
在测试用例中,仿真完成后可以检查DUT中counter的值是否与寄存器模型中的counter值一致:
二、寄存器模型的随机化与update操作
前文中在向uvm_reg中加入uvm_reg_field时,是将加入的uvm_reg_field定义为rand类型:
class reg_invert extends uvm_reg;
rand uvm_reg_field reg_data;
…
endclass
在将uvm_reg加入uvm_reg_block中时,同样定义为rand类型:
class reg_model extends uvm_reg_block;
rand reg_invert invert;
…
endclass
由此可以判断,对register_model来说,支持randomize操作。可以在uvm_reg_block级别调用randomize函数,也可以在uvm_reg级别,甚至可以在uvm_reg_field级别调用:
assert(rm.randomize());
assert(rm.invert.randomize());
assert(rm.invert.reg_data.randomize());
但是,要使某个field能够随机化,只是将其定义为rand类型是不够的。在每个reg_field加入uvm_reg时,要调用其configure函数:
// parameter: parent, size, lsb_pos, access, volatile, reset value, has_reset,
is_rand, individually accessible
reg_data.configure(this, 1, 0, "RW", 1, 0, 1, 1, 0);
这个函数的第八个参数即决定此field是否会在randomize时被随机化。但是即使此参数为1,也不一定能够保证此field被随机化。当一个field的类型中没有写操作时,此参数设置是无效的。换言之,此参数只在此field类型为RW、WRC、WRS、WO、W1、WO1时才有效。
因此,要避免一个field被随机化,可以在以下三种方式中任选其一:
1)当在uvm_reg中定义此field时,不要设置为rand类型。
2)在调用此field的configure函数时,第八个参数设置为0。3)设置此field的类型为RO、RC、RS、WC、WS、W1C、W1S、W1T、W0C、W0S、W0T、W1SRC、W1CRS、W0SRC、W0CRS、WSRC、WCRS、WOC、WOS中的一种。
其中第一种方式也适用于关闭某个uvm_reg或者某个uvm_reg_block的randomize功能。
既然存在randomize,那么也可以为它们定义constraint:
class reg_invert extends uvm_reg;
rand uvm_reg_field reg_data;
constraint cons{
reg_data.value == 0;
}
…
endclass
在施加约束时,要深入reg_field的value变量。
randomize会更新寄存器模型中的预期值:
function void uvm_reg_field::post_randomize();
m_desired = value;
endfunction: post_randomize
这与set函数类似。因此,可以在randomize完成后调用update任务,将随机化后的参数更新到DUT中。这特别适用于在仿真开始时随机化并配置参数。