目录
寄存器模型对DUT的模拟
期望值与镜像值
由于DUT中寄存器的值可能是实时变更的,寄存器模型不能实时知道变更,因此寄存器模型中寄存器的值有时与DUT中相关寄存器的值并不一致。对于任意一个寄存器,寄存器模型中都会有一个专门的变量用于最大可能地与DUT保持同步,这个变量在寄存器模型中称为DUT的镜像值(mirrored value)。
寄存器模型中还有期望值(desired value)。如目前DUT中invert的值为’h0,寄存器模型中的镜像值也为’h0,但希望向此寄存器中写入一个’h1,一种方法是直接调用write任务将’h1写入,期望值与镜像值都更新为’h1;另外一种方法是通过set函数将期望值设置为’h1(此时镜像值依然为0),之后调用update任务,update任务会检查期望值和镜像值是否一致,如果不一致,那么将会把期望值写入DUT中并更新镜像值。
class case0_cfg_vseq extends uvm_sequence;
…
virtual task body();
…
p_sequencer.p_rm.invert.set(16'h1);
value = p_sequencer.p_rm.invert.get();
`uvm_info("case0_cfg_vseq", $sformatf("invert's desired value is %0h ",
value), UVM_LOW)
value = p_sequencer.p_rm.invert.get_mirrored_value();
`uvm_info("case0_cfg_vseq", $sformatf("invert's mirrored value is %0h ",
value), UVM_LOW)
p_sequencer.p_rm.invert.update(status, UVM_FRONTDOOR);
value = p_sequencer.p_rm.invert.get();
`uvm_info("case0_cfg_vseq", $sformatf("invert's desired value is %0h ",
value), UVM_LOW)
value = p_sequencer.p_rm.invert.get_mirrored_value();
`uvm_info("case0_cfg_vseq", $sformatf("invert's mirrored value is %0h ",
value), UVM_LOW)
p_sequencer.p_rm.invert.peek(status, value);
`uvm_info("case0_cfg_vseq", $sformatf("invert's actual value is %0h",
value), UVM_LOW)
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask
通过get函数可以得到寄存器的期望值,通过get_mirrored_value可以得到镜像值。
存储器并不存在期望值和镜像值。寄存器模型不对存储器进行任何模拟。若要得到存储器中某个存储单元的值,只能使用read、write、peek、poke四种操作。
常用操作及其对期望值和镜像值的影响
- read&write操作:无论通过后门访问还是前门访问从DUT中读取或写入寄存器的值,操作完成后寄存器模型都会根据读写的结果更新期望值和镜像值(二者相等)。
- peek&poke操作:操作完成后寄存器模型会根据操作的结果更新期望值和镜像值(二者相等)。
- get&set操作:set操作会更新期望值,但镜像值不会改变。get操作会返回寄存器模型中当前寄存器的期望值。
- update操作:这个操作会检查寄存器的期望值和镜像值是否一致,不一致就会将期望值写入DUT中并且更新镜像值,使其与期望值一致。每个由uvm_reg/uvm_reg_block派生来的类都会有update操作,从uvm_reg_block派生的类会递归调用所有加入此reg_block的寄存器的update任务。
- randomize操作:寄存器模型提供randomize接口。randomize后期望值会变为随机的数值,镜像值不会改变。并非寄存器模型中所有寄存器都支持此函数,如果不支持则randomize调用后其期望值不变。要关闭随机化功能,如在reg_invert的build中调用reg_data.configure时将其第八个参数设置为0即可。
- 一般randomize和update一起使用。如在DUT上电复位后需要配置一些寄存器的值。这些寄存器的值通过randomize获得,并使用update任务配置到DUT中。
寄存器模型中一些内建的sequence
*检查后门访问中hdl路径的sequence
UVM提供一系列sequence用于检查寄存器模型及DUT中的寄存器。其中uvm_reg_mem_hdl_paths_seq用于检查hdl路径的正确性。这个sequence的原型为:
class uvm_reg_mem_hdl_paths_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item))
这个sequence的运行依赖于在基类uvm_sequence中定义的一个变量:uvm_reg_block model
启动此sequence时必须给model赋值。在任意sequence中可以启动此sequence:
class case0_cfg_vseq extends uvm_sequence;
…
virtual task body();
..
uvm_reg_mem_hdl_paths_seq ckseq;
…
ckseq = new("ckseq");
ckseq.model = p_sequencer.p_rm;
ckseq.start(null);
…
endtask
endclass
调用该sequence的start任务时,传入的sequencer参数为null。因为它正常工作不依赖于这个sequencer,而依赖于model变量。这个sequence会试图读取hdl所指向的寄存器,如果无法读取则给出错误提示。由这个sequence的名字也可以看出,它除了检查寄存器外,还检查存储器。如果某个寄存器/存储器在加入寄存器模型时没有指定其hdl路径,那么此sequence在检查时会跳过这个寄存器/存储器。
*检查默认值的sequence
uvm_reg_hw_reset_seq用于检查上电复位后寄存器模型与DUT中寄存器的默认值是否相同,它的原型为:
class uvm_reg_hw_reset_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_item));
对于DUT来说在复位完成后其值就是默认值。但对于寄存器模型来说,如果只是将它集成在验证平台上而不做任何处理,那么它所有寄存器的值为0,此时需要调用reset函数来使其内寄存器的值变为默认值(复位值):
function void base_test::build_phase(uvm_phase phase);
…
rm = reg_model::type_id::create("rm", this);
…
rm.reset();
…
endfunction
这个sequence在其检查前会调用model的reset函数,所以即使在集成到验证平台时没有调用reset函数,这个sequence也能正常工作。除了复位(reset)外,这个sequence所做的事情就是使用前门访问的方式读取所有寄存器的值,并将其与寄存器模型中的值比较。这个sequence在启动时也需要指定其model变量。
如果想跳过某个寄存器的检查,可以在启动此sequence前使用resource_db设置不检查此寄存器。resource_db机制与config_db机制的底层实现是一样的,uvm_config_db类就是从uvm_resource_db类派生而来的。由于在寄存器模型的sequence中get操作是通过resource_db来进行的,所以这里使用resource_db来进行设置:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},
"NO_REG_TESTS", 1, this);
…
endfunction
或者使用:
function void my_case0::build_phase(uvm_phase phase);
…
uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},
"NO_REG_HW_RESET_TEST", 1, this);
endfunction
*检查读写功能的sequence
UVM提供两个sequence分别用于检查寄存器和存储器的读写功能:uvm_reg_access_seq用于检查寄存器的读写,它的原型为:
class uvm_reg_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_it em))
使用此sequence也需要指定其model变量。
此sequence使用前门访问的方式向所有寄存器写数据,然后使用后门访问的方式读回并比较结果。最后反过来使用后门访问的方式写入数据,再用前门访问读回。这个sequence要正常工作必须为所有寄存器设置好hdl路径。
如果要跳过某个寄存器的读写检查,可以在启动sequence前使用如下的两种方式之一进行设置:
function void my_case0::build_phase(uvm_phase phase);
…
//set for reg access sequence
uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},
"NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::",rm.invert.get_full_name(),".*"},
"NO_REG_ACCESS_TEST", 1, this);
…
endfunction
uvm_mem_access_seq用于检查存储器的读写,它的原型为:
class uvm_mem_access_seq extends uvm_reg_sequence #(uvm_sequence #(uvm_reg_it em)
启动此sequence同样需要指定其model变量。这个sequence会通过使用前门访问的方式向所有存储器写数据,然后使用后门访问的方式读回并比较结果。最后反过来使用后门访问的方式写入数据,再用前门访问读回。这个sequence要正常工作必须为所有的存储器设置好HDL路径。
如果要跳过某块存储器的检查,可使用如下的三种方式之一进行设置:
function void my_case0::build_phase(uvm_phase phase);
…
//set for mem access sequence
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);
endfunction