知识点
寄存器:是模块之间相互交流的窗口,硬件的各个功能可以通过由处理器配置功能以及访问状态,与处理器之间的通话是通过寄存器的读写来实现的。
寄存器按照地址索引的关系是按字对其的,寄存器有多个域,每个域的属性可以不相同,reserved域表示该域所包含的比特位暂时保留以作日后的功能扩展所用,对保留域的读写不起任何作用。
一个寄存器可以由多个域构成,而多个域可以包含多个比特位,一个功能模块中的寄存器可以组团构成一个寄存器模型(register model),下图中包含了寄存器模块和属于验证环境的寄存器模型。两个模块包含的寄存器信息是高度一致的,属于验证环境的寄存器模型可以抽象出一个层次化的寄存器列表;对于功能验证而言,可以将总线访问寄存器的方式抽象成寄存器模型访问的方式,这种方式便于寄存器后期的地址修改或者域的添加,且不会对已有的激励产生影响,从而提高了测试序列的复用性。
中心化管理方式:采用XML、Excel或者DOC等格式来保存寄存器的描述,因为单一源的管理方式可以尽量降低分歧和错误的可能。
uvm_reg相关概念:
寄存器建模的基本要点和顺序:
1:在定义单个寄存器时,需要将寄存器的各个域整理出来,在创建之后还应当通过uvm_ reg_field::configure()函数来进一步配置各自属性;
2:在定义uvm_reg_block时, 读者需要注意reg_block与uvm_mem、uvm_reg以及uvm_reg_map的包含关系。首先uvm_reg和uvm_mem分别对应着硬件中独立的寄存器或者存储,而一个uvm_reg_block可以用来模拟一个功能模块的寄存器模型,其中可以容纳多个uvm_ reg和uvm_ mem实例;其次map的作用一方面用来表示寄存器和存储对应的偏移地址,同时由于一个reg_ block可 以包含多个map,各个map可以分别对应不同总线或者不同地址段。在reg_block中创建了各个uvm_ reg之后,需要调用uvm_reg:configure()去配置各个uvm_reg实例的属性。
3:考虑到uvm_reg_map也会在uvm_reg_block中例化,在例化之后需要通过uvm_reg_map:add_reg()函数来添加各 个uvm_reg对应的偏移地址和访问属性等。只有规定了这些属性,才可以在稍后的前门访问(frontdoor)中给出正确的地址。:4:uvm_reg_block可以对更大的系统做寄存器建模,这意味着uvm_reg_block之间也可以存在层次关系,上层uvm_reg_block的uvm_reg_ map可以添加子一级uvm_ reg_block的uvm_reg map,用来构建更全局的“版图”,继而通过uvm_reg_block与uvm_reg_map之间的层次关系来构建更系统的寄存器模型。
寄存器模型访问方式:
前门访问:在寄存器模型还是那个做读写操作,最终会通过总线UVC的方式实现总线上的物理时序访问
后门访问:利用UVM DPI(uvm_hdl_read()、uvm_hdl_deposit())将寄存器的操作直接作用到DUT内部的寄存器变量,而不通过物理总线访问。在进行后门访问的时候,首先需要确保寄存器模型在建立的时候,是否将各个寄存器映射到DUT一侧的HDL路径
mirrored value(镜像值) 和desired value(期望值)是寄存器模型的属性,而actual value对应着硬件的真实数值。期望值是先利用寄存器模型修改软件对象值,而后利用该值更新硬件值,镜像值表示当前硬件的已知状态值,镜像值由模型预测给出,在前门访问时通过观察总线或者在后门访问的时候通过自动预测来给出镜像值。
预测的分类:自动预测 和 显示预测,显示预测更为准确,自动预测需要调用uvm_reg_map::set_auto_predict()
自动预测:如果没有在环境中集成独立的predictor,而是利用寄存器的操作来自动的记录每一次寄存器的读写数值,然后再后台自动调用predict() ,
显示预测:在物理总线上通过监视器来捕捉总线事务,然后传递给外部例化的predictor,该predictor是被例化在顶层环境中。
uvm_reg_block、uvm_reg、uvm_reg_field提供的访问寄存器的方法:
mirror()方法与read()方法类似,也可以选择前门门访问后者后门访问,不同的是mirror()不会返回读回的数值,但是会将对应的镜像值修改。在修改镜像值之前,用户还可以选择是否将读回的值与模型中的原镜像值进行比较。
寄存器模型的完善和嵌入
1.1、实现reg2mcdf_adapter的类的方法reg2bus和bus2reg:
adapter所完成的功能是转换uvm_reg_bus_op和总线transaction,uvm_reg_bus_op和总线transaction有各自需要实现的数据映射,实现这种映射是通过reg2bus()和bus2reg()两个函数。
reg2bus()函数:如果用户在寄存器级别做了操作,那么寄存器的操作信息uvm_reg_bus_op就会被记录,然后调用reg2bus()函数,uvm_reg_bus_op的信息映射到mcdf_bus_trans中后,reg2bus()函数将mcdf_bus_trans实例返回,然后通过mcdf_bus_sequencer传入mcdf_bus_driver里面。
bus2reg()函数:功能与reg2bus()函数相反,完成mcdf_bus_trans到uvm_reg_bus_op的内容映射,完成映射以后,更新的uvm_reg_bus_op数据最终会返回在寄存器操作场景层。
如果总线支持byte访问,就可以使能supports_byte_enable,如果总线要返回response数据,则应当使能provides_responses,所以需要provides_responses,在mcdf_bus_driver在发起总线访问的时候将RSP一同返回给sequencer。
class reg2mcdf_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2mcdf_adapter)
function new(string name = "reg2mcdf_adapter");
super.new(name);
provides_responses = 1;
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
reg_trans t = reg_trans::type_id::create("t");
t.cmd = (rw.kind == UVM_WRITE) ? `WRITE : `READ;
t.addr = rw.addr;
t.data = rw.data;
return t;
endfunction
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
reg_trans t;
if (!$cast(t, bus_item)) begin
`uvm_fatal("CASTFAIL","Provided bus_item is not of the correct type")
return;
end
rw.kind = (t.cmd == `WRITE) ? UVM_WRITE : UVM_READ;
rw.addr = t.addr;
rw.data = t.data;
rw.status = UVM_IS_OK;
endfunction
endclass
1.2、在mcdf_env中声明register_block、adapter和predictor,并且完成例化,在connect_phase中完成句柄的连接:
adapter的连接需要mcdf_rgm,也需要sequencer;
寄存器模型的使用
2.1、完成register block句柄的传递,因为很多组件需要这个句柄,因此需要在environment里面传递到其他的层次:
2.2、把mcdf_data_consistance_basic_virtual_sequence原有的由总线sequence实现的寄存器读写,改为寄存器模型操作的寄存器读写方式
2.3、将mcdf_full_random_virtual_sequence原有的由总线sequence实现的寄存器读写,改为由寄存器模型预先设置寄存器值,再统一做总线寄存器更新的方式,并且稍后由后门读取的方式取得寄存器的值,加以比较
寄存器内建序列的应用
3.1、在mcdf_reg_builtin_virtual_sequence类中,使用uvm_reg_hw_reset_seq 、uvm_reg_bit_bash_seq、uvm_reg_access_seq对MCDF进行全方位的测试。
这三个都是寄存器模型的内建序列,uvm_reg_hw_reset_seq检查寄存器模型复位值是否和硬件复位一致;uvm_reg_bit_bash_seq对所有uvm_reg或者子uvm_reg_block执行uvm_reg_bit_bash_seq检查;uvm_reg_access_seq先从前门写入寄存器,而后从后门读回数值比对;接下来从后门写入寄存器,又从前门读回写入值比对。这种方式要求寄存器的HDL路径已经完成映射,用来检查寄存器映射的有效性
class mcdf_reg_builtin_virtual_sequence extends mcdf_base_virtual_sequence;
`uvm_object_utils(mcdf_reg_builtin_virtual_sequence)
function new (string name = "mcdf_reg_builtin_virtual_sequence");
super.new(name);
endfunction
task do_reg();
uvm_reg_hw_reset_seq reg_rst_seq = new();
uvm_reg_bit_bash_seq reg_bit_bash_seq = new();
uvm_reg_access_seq reg_acc_seq = new();
// wait reset asserted and release
@(negedge p_sequencer.intf.rstn);
@(posedge p_sequencer.intf.rstn);
`uvm_info("BLTINSEQ", "register reset sequence started", UVM_LOW)
rgm.reset();
reg_rst_seq.model = rgm;
reg_rst_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register reset sequence finished", UVM_LOW)
`uvm_info("BLTINSEQ", "register bit bash sequence started", UVM_LOW)
// reset hardware register and register model
p_sequencer.intf.rstn <= 'b0;
repeat(5) @(posedge p_sequencer.intf.clk);
p_sequencer.intf.rstn <= 'b1;
rgm.reset();
reg_bit_bash_seq.model = rgm;
reg_bit_bash_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register bit bash sequence finished", UVM_LOW)
`uvm_info("BLTINSEQ", "register access sequence started", UVM_LOW)
// reset hardware register and register model
p_sequencer.intf.rstn <= 'b0;
repeat(5) @(posedge p_sequencer.intf.clk);
p_sequencer.intf.rstn <= 'b1;
rgm.reset();
reg_acc_seq.model = rgm;
reg_acc_seq.start(p_sequencer.reg_sqr);
`uvm_info("BLTINSEQ", "register access sequence finished", UVM_LOW)
endtask
endclass: mcdf_reg_builtin_virtual_sequence
class mcdf_reg_builtin_test extends mcdf_base_test;
`uvm_component_utils(mcdf_reg_builtin_test)
function new(string name = "mcdf_reg_builtin_test", uvm_component parent);
super.new(name, parent);
endfunction
task run_top_virtual_sequence();
mcdf_reg_builtin_virtual_sequence top_seq = new();
top_seq.start(env.virt_sqr);
endtask
endclass: mcdf_reg_builtin_test