UVM中的寄存器模型(下) | UVM笔记 7

Posted by Kion on February 1, 2020

复杂的寄存器模型

在实际构建寄存器模型中,通常构建层次化的模型,比如将uvm_reg_block加入uvm_reg_block中。要实现这种模型,一般要遵循以下步骤:

class global_blk extends uvm_reg_block;
   /*子block*/
endclass

class reg_model extends uvm_reg_block;

   rand global_blk gb_ins;
   
   virtual function void build();
      default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
      gb_ins = global_blk::type_id::create("gb_ins");
      gb_ins.configure(this, "");
      gb_ins.build();
      gb_ins.lock_model();
      default_map.add_submap(gb_ins.default_map, 16'h0);/*加入子map*/
	endfunction

   `uvm_object_utils(reg_model)

    function new(input string name="reg_model");
        super.new(name, UVM_NO_COVERAGE);
    endfunction 

endclass

UVM寄存器模型中的reg_file概念,可以用来区分不同的hdl路径。

/*首先从uvm_reg_file派生出一个类regfile*/

class regfile extends uvm_reg_file;
   function new(string name = "regfile");
      super.new(name);
   endfunction

   `uvm_object_utils(regfile)
endclass

class mac_blk extends uvm_reg_block;
/*定义file_a,file_b*/
   rand regfile file_a;
   rand regfile file_b;
   rand reg_regA regA;
   rand reg_regB regB;
   
   virtual function void build();
      default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);

      file_a = regfile::type_id::create("file_a", , get_full_name());
      file_b = regfile::type_id::create("file_b", , get_full_name());
      /*第一个参数为所在block指针,第二个参数是父regfile指针,第三个参数是regfile路径*/
      file_a.configure(this, null, "fileA");
      file_b.configure(this, null, "fileB");
      
      regA = reg_regA::type_id::create("regA", , get_full_name());
      regB = reg_regB::type_id::create("regB", , get_full_name());
      /*所以只要声明了regfile,后续file_a即使改变,也能方便地修改路径*/
      /*直接修改file_a即可*/
      regA.configure(this, file_a, "regA");
      regB.configure(this, file_b, "regB");
   endfunction

    `uvm_object_utils(mac_blk)

    function new(input string name="mac_blk");
        super.new(name, UVM_NO_COVERAGE);
    endfunction 
   
endclass

构建多个域的寄存器。

/*首先定义一个寄存器uvm_reg*/
class three_field_reg extends uvm_reg;
    rand uvm_reg_field fieldA;
    rand uvm_reg_field fieldB;
    rand uvm_reg_field fieldC;

    virtual function void build();
        fieldA = uvm_reg_field::type_id::create("fieldA");
        fieldB = uvm_reg_field::type_id::create("fieldB");
        fieldC = uvm_reg_field::type_id::create("fieldC");
    endfunction
endclass
/*然后在uvm_block中进行build,configure*/
class mac_blk extends uvm_reg_block;

   rand three_field_reg tf_reg;
   
   virtual function void build();
      tf_reg = three_field_reg::type_id::create("tf_reg", , get_full_name());
      tf_reg.configure(this, null, "");
      tf_reg.build();
      tf_reg.fieldA.configure(tf_reg, 2, 0, "RW", 1, 0, 1, 1, 1);
      tf_reg.add_hdl_path_slice("fieldA", 0, 2);/*划分域*/
      tf_reg.fieldB.configure(tf_reg, 3, 2, "RW", 1, 0, 1, 1, 1);
      tf_reg.add_hdl_path_slice("fieldA", 2, 3);
      tf_reg.fieldC.configure(tf_reg, 4, 5, "RW", 1, 0, 1, 1, 1);
      tf_reg.add_hdl_path_slice("fieldA", 5, 4);
      default_map.add_reg(tf_reg, 'h41, "RW");
   endfunction
endclass

可以在寄存器模型中加入存储器。存储器同样支持read/write/peek/poke进行读写,不过会额外多一个offset参数,表示存储器的哪个地址。

/*从uvm_mem中派生存储器类,深度1024,宽度16*/
class my_memory extends uvm_mem;
   function new(string name="my_memory");
      super.new(name, 1024, 16);
   endfunction

   `uvm_object_utils(my_memory)
endclass

class reg_model extends uvm_reg_block;
   rand my_memory mm;

   virtual function void build();
      default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);

      mm = my_memory::type_id::create("mm", , get_full_name());
      mm.configure(this, "stat_blk.ram1024x16_inst.array");
      default_map.add_mem(mm, 'h100);
   endfunction
endclass

期望值与模拟值

DUT中寄存器的值可能随时会改变,寄存器模型的值也应当同步。所以寄存器模型内部有mirrored value概念,用来同步寄存器的值。同时也有desired valure。要更新镜像值,一来可以直接write,二来可以set期望值,然后update。update任务会检查期望值镜像值是否一致,如若不一致,会把期望值写入DUT中,然后更新镜像值。也可以使用mirror操作,读取寄存器中的值,并更新到寄存器模型中去。

p_sequencer.p_rm.invert.set(16'h1);/*设置期望值*/
value = p_sequencer.p_rm.invert.get();/*获取期望值*/
value = p_sequencer.p_rm.invert.get_mirrored_value();/*获取镜像值*/
p_sequencer.p_rm.invert.update(status,UVM_FRONTDOOR);/*更新*/

寄存器模型中的一些内建sequence

/*检查后门访问中hdl路径的sequence*/
class uvm_reg_mem_hdl_paths_seq extends uvm_reg_sequence#(uvm_sequence #(uvm_reg_item))
/*检查默认值的sequence*/
class uvm_reg_hw_reset_seq extends uvm_reg_sequence#(uvm_sequence #(uvm_reg_item))
/*检查读写功能的sequence*/
class uvm_reg_access_seq extends uvm_reg_sequence#(uvm_sequence #(uvm_reg_item))

寄存器模型的高级用法

在上一篇中,建立寄存器模型时打开了自动更新。

rm.default_map.set_auto_predict(1);

这会使得,driver读取到值并返回后,寄存器模型会相应更新镜像值和期望值。除此之外,还能借助monitor,将从总线收集的transaction交给寄存器模型,进行更新。这样需要建立reg_predicter,并为其连接另外一个adapter。

class base_test extends uvm_test;
   reg_model      rm;
   my_adapter     reg_sqr_adapter;
   my_adapter     mon_reg_adapter;

   uvm_reg_predictor#(bus_transaction) reg_predictor;
endclass
       
function void base_test::build_phase(uvm_phase phase);
   super.build_phase(phase);
   rm = reg_model::type_id::create("rm", this);
   rm.configure(null, "");
   rm.build();
   rm.lock_model();
   rm.reset();
   reg_sqr_adapter = new("reg_sqr_adapter");
   mon_reg_adapter = new("mon_reg_adapter");
   reg_predictor = new("reg_predictor", this);
   env.p_rm = this.rm;
endfunction

function void base_test::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   rm.default_map.set_sequencer(env.bus_agt.sqr,reg_sqr_adapter);
   rm.default_map.set_auto_predict(1);
   reg_predictor.map = rm.default_map;
   reg_predictor.adapter = mon_reg_adapter;/*关联寄存器模型*/
   env.bus_agt.ap.connect(reg_predictor.bus_in);
endfunction

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,
                             );
/*第一个参数即目标值,第二个默认为-1,意为全部有效,第三个为预测类型,第四为访问方式*/

UVM中给扩展位宽定义了默认最大值,可以根据需要修改。

/*扩展位宽*/
`ifndef UVM_REG_DATA_WIDTH
`define UVM_REG_DATA_WIDTH 64
`endif
/*地址位宽*/
`ifndef UVM_REG_ADDR_WIDTH
`define UVM_REG_ADDR_WIDTH 64
`endif
/*字选择信号的位宽*/
`ifndef UVM_REG_BYTENABLE_WIDTH
`define UVM_REG_BYTENABLE_WIDTH ((`UVM_REG_DATA_WIDTH-1)/8+1)
`endif