UVM中的寄存器模型(上) | UVM笔记 6

Posted by Kion on January 31, 2020

寄存器模型

对于包含寄存器的DUT,就需要在验证时建立寄存器模型。因为,参考模型想要读取寄存器信息,就需要启动sequence来读取寄存器的值,如何启动,如何获得值都是要解决的问题。所以,采用寄存器模型,让参考模型与寄存器模型的通信代替与DUT中寄存器的通信,可以方便地解决这些问题。一个寄存器模型里,包含以下几个部分。

/*
uvm_reg_field:寄存器模型的最小单位
uvm_reg:寄存器模型中的一个单位,由uvm_reg_field组成
uvm_reg_block:寄存器模型中的一个单位,由uvm_reg组成,模型中至少需要有一个uvm_reg_block
*/

/*创建一个简单寄存器模型*/
class reg_invert extends uvm_reg;
    rand uvm_reg_field reg_data;
    
    /*不会自动执行,需要手工调用*/
    virtual function void build();
        reg_data = uvm_reg_field::type_id::create("reg_data");
        /*parent,size,此域最低位在整个寄存器的位置,存取方式,是否易失,复位后默认值,是否有复位,是否可以随机化,是否可以单独存取*/
        reg_data.configure(this,1,0,"RW",1,0,1,1,0);
    endfunction
    
    `uvm_object_utils(reg_invert)
    
    function new(input string name="reg_invert");
        /*16一般为系统的总线宽度,第三个参数是是否添加覆盖率支持*/
        super.new(name,16,UVM_NO_COVERAGE);
    endfunction
endclass

/*在reg_block类中实例化寄存器*/
class reg_model extends uvm_reg_block;
    rand reg_invert invert;
    
    virtual function void build();
        /*uvm_reg_block要对应uvm_reg_map,map内储存了寄存器的地址信息;
        第二个参数为基地址;第三个参数为系统总线宽度,byte;第四个为大小端(大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中);
        第五个是能否按照byte寻址*/
        default_map = create_map("default_map",0,2,UVM_BIG_ENDIAN,0);
        
        invert = reg_invert::type_id::create("invert",,get_full_name());
        /*主要功能是指定寄存器进行后门访问操作时的路径;
        第一个参数是所在block指针;
        第二个参数是reg_file指针;
        第三个参数是后门访问路径*/
        invert.configure(this,null,"");
        invert.build();
        /*将寄存器加入default_map中;第二个参数是寄存器地址*/
        default_map_add_reg(invert,'h9,"RW");
    endfunction
    
    `uvm_object_utils(reg_model)
    
    function new(input string name="reg_model");
        super.new(name,UVM_NO_COVERAGE);
    endfunction
endclass

寄存器模型的访问方式有前门访问和后门访问。前门访问需要借助sequence,通过sequence产生一个uvm_reg_bus_op的变量,然后利用adapter将uvm_reg_bus_op和transaction互相转化,随后交给bus_sequencer以及bus_driver进行读写操作。后门操作则不借助总线,直接通过层次化的引用改变寄存器的值。

/*adapter*/
class my_adapter extends uvm_reg_adapter;
    `uvm_object_utils(my_adapter)
    
    function new(string name="my_adapter");
        super.new(name);
    endfunction
    
    /*转换器内部应该声明两个函数,用于将uvm_reg_bus_op转换为transaction以及
    transaction转换为uvm_reg_bus_op*/
    function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        bus_transaction tr;
        tr = new("tr");
        tr.addr = rw.addr;
        tr.bus_op = (rw.kind==UVM_READ)?BUS_RD:BUS_WR;
        if(tr.bus_op==BUS_WR) tr.wr_data = rw.data;
        return tr;
    endfunction
    
    function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
        bus_transaction tr;
        if (!$cast(tr,bus_item)) begin
            `uvm_fatal(tID,"Provided bus_item is not the correct type.") ;
            return;
        end
        rw.kind = (tr.bus_op==BUS_RD)?UVM_READ:UVM_WRITE;
        rw.addr = tr.addr;
        rw.byte_en = 'h3;
        rw.data = (tr.bus_op==BUS_RD)?tr.rd_data:tr.wr_data;
        rw.status = UVM_IS_OK;
    endfunction
endclass

/*在base_test中加入寄存器模型*/
class base_test extends uvm_test;
    my_env env;
    my_vsqr v_sqr;
    /*声明寄存器模型以及adapter*/
    reg_model rm;
    my_adapter reg_sqr_adapter;
    
    function void base_test(uvm_phase phase);
        super.build_phase(phase);
        env = my_env::type_id::create("env",this);
        v_sqr = my_vsqr::type_id::create("v_sqr",this);
        rm = reg_model::type_id::create("rm",this);
        /*寄存器配置,parent,后门访问路径*/
        rm.configure(null,"");
        rm.build();
        /*lcok后寄存器模型不能加入新的寄存器*/
        rm.lock_model();
        /*更新寄存器值为复位值,否则都为0*/
        rm.reset();
        reg_sqr_adapter = new("reg_sqr_adapter");
        env.p_rm = this.rm;
    endfunction
    
    function void base_test::connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        v_sqr.p_my_sqr = env.i_agt.sqr;
        v_sqr.p_bus_sqr = env.bus_agt.sqr;
        v_sqr.p_rm = this.rm;
        /*告知寄存器模型相应的sequencer以及adapter*/
        rm.default_map.set_sequencer(env.bus_agt.sqr,reg_sqr_adapter);
        rm.default_map.set_auto_predict(1);
    endfunction
endclass

/*在验证平台使用寄存器模型*/
class my_model extends uvm_component;
    /*在参考模型中建立寄存器指针*/
    reg_model p_rm;
endclass

/*将env中的p_rm传递给参考模型*/
function void my_env::connect_phase(uvm_phase phase);
    mdl.p_rm = this.p_rm;
endfunction

/*寄存器模型提供两个基本任务read write;若在参考模型中读取寄存器,使用read*/
task my_model::main_phase(uvm_phase phase);
    my_transaction tr;
    my_transaction new_tr;
    uvm_status_e status;
    uvm_reg_data_t value;
    
    super.main_phase(phase);
    /*第一个参数,表示操作是否成功;第二个参数为读取值;第三个为读取方式*/
    p_rm.invert.read(status,value,UVM_FRONTDOOR);
    while(1) begin
        port.get(tr);
        new_tr = new("new_tr");
        new_tr.copy(tr);
        if(value)/*为1,取反*/
            invert_tr(new_tr);
        ap.write(new_tr);
    end
endtask

/*写寄存器*/
class case0_cfg_vseq extends uvm_sequence;
    
    virtual task body();
        uvm_status_e status;
        uvm_reg_data_t value;
        /*第二个参数为要写的值*/
        p_sequencer.p_rm.invert.write(status,1,UVM_FRONTDOOR);
    endtask
endclass

/*使用前门操作时,寄存器模型会自动生成sequence,并通过adapter进行转换,
发送给driver*/
/*因此要创建一个读写的sequence*/
class reg_access_sequence extends uvm_sequence#(bus_transaction);
   string tID = get_type_name();

   bit[15:0] addr;
   bit[15:0] rdata;
   bit[15:0] wdata;
   bit       is_wr;

   `uvm_object_utils(reg_access_sequence)
   function new(string name = "reg_access_sequence");
      super.new(name);
   endfunction

   virtual task body();
      bus_transaction tr;
      tr = new("tr");
      tr.addr = this.addr;
      tr.wr_data = this.wdata;
      tr.bus_op = (is_wr ? BUS_WR : BUS_RD);
      `uvm_info(tID, $sformatf("begin to access register: is_wr = %0d, addr = %0h", is_wr, addr), UVM_MEDIUM)
      `uvm_send(tr)
      `uvm_info(tID, "successfull access register", UVM_MEDIUM)
      this.rdata = tr.rd_data;
   endtask
endclass

因此,对于寄存器模型的读操作,完整流程为:

  1. 参考模型调用寄存器模型的读任务
  2. 寄存器模型产生sequence,并产生uvm_reg_item,rw
  3. 通过adapter把rw转换成driver能够接收的transaction,bus_req
  4. 把bus_req发送给sequencer
  5. driver得到bus_req并驱动,得到读取的值,调用item_done
  6. 寄存器模型调用adapter把bus_req转换成rw
  7. 将rw读到的值返回给参考模型

后门访问

前门操作要通过总线进行操作寄存器,后门操作与其相对。后门操作不消耗仿真时间,可以完成一些前门操作无法完成的事情。

对于一些验证情况,在C/C++代码中有着对寄存器读写的需求,这时就要用一些接口,提供给这些代码。

/*VPI接口*/
vpi_get_value(obj,p_value);
vpi_put_value(obj,p_value,p_time,flags);
/*DPI接口*/
int uvm_hdl_read(char *path, p_vpi_vecval value);/*先在C/C++中定义*/
import "DPI-C" context function int uvm_hdl_read(string path,output uvm_hdl_data_t value);/*SystemVerilog中先导入DPI接口,读操作*/
import "DPI-C" context function int uvm_hdl_deposit(string path,uvm_hdl_data_t value);/*SystemVerilog中先导入DPI接口,写操作*/
uvm_hdl_read("top_tb.my_dut.counter",value);
uvm_hdl_deposit("top_tb.my_dut.counter",value);

在寄存器模型中使用后门访问功能,需要先在reg_model中修改configure函数,加入第三个路径参数。在验证平台集成时,要设置好根路径hdl_root。

counter_low.configure(this, null, "counter[15:0]");

rm.set_hdl_path_root("top_tb.my_dut");