数字验证--uvm寄存器模型常用函数介绍
DUT中寄存器的值可能是实时变更的, 寄存器模型并不能实时地知道这种变更, 因此, 寄存器模型中的寄存器的值有时与DUT中相关寄存器的值并不一致。 对于任意一个寄存器, 寄存器模型中都会有一个专门的变量用于最大可能地与DUT保持同步, 这个变量在寄存器模型中称为DUT的镜像值( mirrored value) 。寄存器模型中还有一个值叫期望值(respected value),这个值保存我们希望写入寄存器的值。比如希望向DUT某个寄存器写入'h1,用set函数设置期望值,然后用update任务(update会比较镜像值和期望值,如果不一样,将期望值写入DUT,并更新镜像值)。
寄存器模型的使用的优点:
1.可进行后门方式访问dut中的寄存器;
2.在参考模型中不必引入全局变量可得到寄存器模型中值的变化;
3.提供很多函数使用方便;
4.可以很方便的对寄存器的 coverage 验证点的收集。
寄存器模型中的单位
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。
一个简单的寄存器模型,每个派生自uvm_reg的类都有一个build()函数,它只能手工调用.(可直接通过脚本将excel生成寄存器模型。脚本地址 https://download.csdn.net/download/qq_38620826/18198642)
class reg_invert extends uvm_reg; rand uvm_reg_field reg_data; //在reg中定义field virtual function void build(); //build reg_data = uvm_reg_field::type_id::create("reg_data"); // 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); //上面一行是参数介绍 endfunction function new(input string name="reg_invert"); //parameter: name, size, has_coverage super.new(name, 16, UVM_NO_COVERAGE); //16是这个寄存器的宽度, endfunction endclass
寄存器模型中常用的函数
read:函数的原型(将根据从dut寄存器中读取的结果同时更新寄存器模型的期望值和镜像值),有多个参数, 常用的是其前三个参数。 其中第一个是uvm_status_e型的变量, 这是一个输出, 用于表明读操作是否成功;第二个是读取的数值, 也是一个输出; 第三个是读取的方式, 可选UVM_FRONTDOOR和UVM_BACKDOOR。
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);
使用示例
p_sequencer.p_rm.invert.read(status, value, UVM_FRONTDOOR);
write:函数的原型(将值写入到dut的寄存器中同时更新寄存器模型的期望值和镜像值),参数也有很多个, 但是与read类似, 常用的也只有前三个。 其中第一个为uvm_status_e型的变量, 这是一个输出, 用于表明写操作是否成功。 第二个要写的值, 是一个输入, 第三个是写操作的方式, 可选UVM_FRONTDOOR和UVM_BACKDOOR。
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);
使用示例
p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);
peek:后门读函数(将根据从dut寄存器中读取的结果同时更新寄存器模型的期望值和镜像值),虽然read函数也能选择后门访问的方式读取寄存器,但是dut中的寄存器如果是只写的则使用呢read不能读取出来,但是使用peek是不管dut中寄存器的类型的,一定可以读取出来。
extern virtual task 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);
使用示例
p_sequencer.p_rm.invert.peek(status, value);
poke:后门写函数(将值写入到dut的寄存器中同时更新寄存器模型的期望值和镜像值)。虽然write函数也能选择后门访问的方式写寄存器,但是dut中如果是一个只读寄存器,使用write则不能写入成功,但poke是不管dut中寄存器的类型的,可以完成对只读寄存器的写入。
extern virtual task 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);
使用示例
set:将值写入到寄存器模型的期望值中
extern virtual function void set (uvm_reg_data_t value, string fname = "", int lineno = 0);
使用示例
p_sequencer.p_rm.invert.set(16'h1);
update:将期望值和镜像值比对,如果不一致则吧期望值写入到dut的寄存器中,同时更新寄存器模型镜像值。uvm_reg级别被调用, 也可以在uvm_reg_block级别被调用。
extern virtual task update(output uvm_status_e status, 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);
使用示例
p_sequencer.p_rm.invert.update(status, UVM_FRONTDOOR);
get:得到寄存器模型的期望值
extern virtual function uvm_reg_data_t get(string fname = "", int lineno = 0);
使用示例
value = p_sequencer.p_rm.invert.get();
get_mirrored_value:得到寄存器模型的镜像值
extern virtual function uvm_reg_data_t get_mirrored_value(string fname = "", int lineno = 0);
使用示例
value = p_sequencer.p_rm.invert.get_mirrored_value();
mirror:将dut中的寄存器值更新到寄存器模型的镜像值值和期望值,其中第二个参数指的是如果发现DUT中寄存器的值与寄存器模型中的镜像值不一致, 那么在更新寄存器模型之前是否给出错误提示。 其可选的值为UVM_CHECK和UVM_NO_CHECK。它有两种应用场景, 一是在仿真中不断地调用它, 使得到整个寄存器模型的值与DUT中寄存器的值保持一致, 此时check选项是关闭的。 二是在仿真即将结束时, 检查DUT中寄存器的值与寄存器模型中寄存器的镜像值是否一致, 这种情况下, check选项是打开的。mirror操作既可以在uvm_reg级别被调用, 也可以在uvm_reg_block级别被调用。
extern virtual task mirror(output uvm_status_e status, input uvm_check_e check = UVM_NO_CHECK, 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);
使用示例
p_sequencer.p_rm.counter.mirror(status, UVM_CHECK, UVM_FRONTDOOR);
predict:在DUT中的计数器是不断累加的, 但是寄存器模型中的计数器则保持静止。参考模型会不断统计收到了多少包。如果想将参考模型中的数据传递给寄存器模型又不对dut进行操作,可以通过precict,predict操作会更新镜像值和期望值。
其中第一个参数表示要预测的值, 第二个参数是byte_en, 默认-1的意思是全部有效, 第三个参数是预测的类型, 第四个参数是后门访问或者是前门访问。 要实现在参考模型中更新寄存器模型而又不影响DUT的值, 第三个参数需要使用UVM_PREDICT_DIRECT
,第三个参数预测类型有如下几种可以选择
UVM_PREDICT_DIRECT :Predicted value is as-is
UVM_PREDICT_READ :Predict based on the specified value having been read
UVM_PREDICT_WRITE :Predict based on the specified value having been written
extern virtual function bit 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, uvm_reg_map map = null, string fname = "", int lineno = 0);
使用示例
p_rm.counter.predict(counter);
randomize:寄存器模型期望值随机化,可以在uvm_reg_block级别调用randomize函数, 也可以在uvm_reg级别, 甚至可以在uvm_reg_field级别调用:可通过下面调用示例完成寄存器随机化
assert(rm.randomize());
assert(rm.invert.randomize());
assert(rm.invert.reg_data.randomize());
只使用randomize是不能完成寄存器值随机化的还要保证下面三点:
1.当在uvm_reg中定义此field时, 设置为rand类型。
2.在调用此field的configure函数时, 第八个参数设置为1。
3.寄存器类型是可写的