复杂的寄存器模型
在实际构建寄存器模型中,通常构建层次化的模型,比如将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