一、寄存器模型的完善和嵌入
定义uvm_reg
和uvm_reg_block
的以及mcdf_rgm
寄存器模块。
// Dedicated register description [write-read reg] with uvm_reg type
class ctrl_reg extends uvm_reg;
`uvm_object_utils(ctrl_reg)
uvm_reg_field reserved;
rand uvm_reg_field pkt_len;
rand uvm_reg_field prio_level;
rand uvm_reg_field chnl_en;
covergroup value_cg;
option.per_instance = 1;
reserved: coverpoint reserved.value[25:0];
pkt_len: coverpoint pkt_len.value[2:0];
prio_level: coverpoint prio_level.value[1:0];
chnl_en: coverpoint chnl_en.value[0:0];
endgroup
function new(string name = "ctrl_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
reserved = uvm_reg_field::type_id::create("reserved");
pkt_len = uvm_reg_field::type_id::create("pkt_len");
prio_level = uvm_reg_field::type_id::create("prio_level");
chnl_en = uvm_reg_field::type_id::create("chnl_en");
reserved.configure(this, 26, 6, "RO", 0, 26'h0, 1, 0, 0);
pkt_len.configure(this, 3, 3, "RW", 0, 3'h0, 1, 1, 0);
prio_level.configure(this, 2, 1, "RW", 0, 2'h3, 1, 1, 0);
chnl_en.configure(this, 1, 0, "RW", 0, 1'h1, 1, 1, 0);
endfunction
function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction
function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass
// Dedicated register description [read-only reg] with uvm_reg type
class stat_reg extends uvm_reg;
`uvm_object_utils(stat_reg)
uvm_reg_field reserved;
rand uvm_reg_field fifo_avail;
covergroup value_cg;
option.per_instance = 1;
reserved: coverpoint reserved.value[23:0];
fifo_avail: coverpoint fifo_avail.value[7:0];
endgroup
function new(string name = "stat_reg");
super.new(name, 32, UVM_CVR_ALL);
void'(set_coverage(UVM_CVR_FIELD_VALS));
if(has_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg = new();
end
endfunction
virtual function void build();
reserved = uvm_reg_field::type_id::create("reserved");
fifo_avail = uvm_reg_field::type_id::create("fifo_avail");
reserved.configure(this, 24, 8, "RO", 0, 24'h0, 1, 0, 0);
fifo_avail.configure(this, 8, 0, "RO", 0, 8'h20, 1, 1, 0);
endfunction
function void sample(
uvm_reg_data_t data,
uvm_reg_data_t byte_en,
bit is_read,
uvm_reg_map map
);
super.sample(data, byte_en, is_read, map);
sample_values();
endfunction
function void sample_values();
super.sample_values();
if (get_coverage(UVM_CVR_FIELD_VALS)) begin
value_cg.sample();
end
endfunction
endclass
//MCDF top register block which includes child registers and the address map
class mcdf_rgm extends uvm_reg_block;
`uvm_object_utils(mcdf_rgm)
rand ctrl_reg chnl0_ctrl_reg;
rand ctrl_reg chnl1_ctrl_reg;
rand ctrl_reg chnl2_ctrl_reg;
rand stat_reg chnl0_stat_reg;
rand stat_reg chnl1_stat_reg;
rand stat_reg chnl2_stat_reg;
uvm_reg_map map;
function new(string name = "mcdf_rgm");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
chnl0_ctrl_reg = ctrl_reg::type_id::create("chnl0_ctrl_reg");
chnl0_ctrl_reg.configure(this);
chnl0_ctrl_reg.build();
chnl1_ctrl_reg = ctrl_reg::type_id::create("chnl1_ctrl_reg");
chnl1_ctrl_reg.configure(this);
chnl1_ctrl_reg.build();
chnl2_ctrl_reg = ctrl_reg::type_id::create("chnl2_ctrl_reg");
chnl2_ctrl_reg.configure(this);
chnl2_ctrl_reg.build();
chnl0_stat_reg = stat_reg::type_id::create("chnl0_stat_reg");
chnl0_stat_reg.configure(this);
chnl0_stat_reg.build();
chnl1_stat_reg = stat_reg::type_id::create("chnl1_stat_reg");
chnl1_stat_reg.configure(this);
chnl1_stat_reg.build();
chnl2_stat_reg = stat_reg::type_id::create("chnl2_stat_reg");
chnl2_stat_reg.configure(this);
chnl2_stat_reg.build();
// map name, offset, number of bytes, endianess
map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
map.add_reg(chnl0_ctrl_reg, 32'h00000000, "RW");
map.add_reg(chnl1_ctrl_reg, 32'h00000004, "RW");
map.add_reg(chnl2_ctrl_reg, 32'h00000008, "RW");
map.add_reg(chnl0_stat_reg, 32'h00000010, "RO");
map.add_reg(chnl1_stat_reg, 32'h00000014, "RO");
map.add_reg(chnl2_stat_reg, 32'h00000018, "RO");
// specify HDL path
chnl0_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV0_RW_REG), 0, 32);
chnl1_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV1_RW_REG), 0, 32);
chnl2_ctrl_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV2_RW_REG), 0, 32);
chnl0_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV0_R_REG ), 0, 32);
chnl1_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV1_R_REG ), 0, 32);
chnl2_stat_reg.add_hdl_path_slice($sformatf("mem[%0d]", `SLV2_R_REG ), 0, 32);
add_hdl_path("tb.dut.ctrl_regs_inst");
lock_model();
endfunction
endclass
实现reg2mcdf_adapter
类的方法reg2bus
以及bus2reg
。
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); //返回值类型是uvm_sequence_item,完成了子类句柄到父类句柄的转换
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
在mcdf_env
中分别声明register block
,adapter
和predictor
,并完成例化,同时在connect
阶段中完成句柄的连接。
// MCDF top environment
class mcdf_env extends uvm_env;
chnl_agent chnl_agts[3];
reg_agent reg_agt;
fmt_agent fmt_agt;
mcdf_checker chker;
mcdf_coverage cvrg;
mcdf_virtual_sequencer virt_sqr;
//declare the mcdf_rgm handle, reg2mcdf_adapter handle and the
//predictory handle
mcdf_rgm rgm;
reg2mcdf_adapter adapter;
uvm_reg_predictor #(reg_trans) predictor;
`uvm_component_utils(mcdf_env)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
this.chker = mcdf_checker::type_id::create("chker", this);
foreach(chnl_agts[i]) begin
this.chnl_agts[i] = chnl_agent::type_id::create($sformatf("chnl_agts[%0d]",i), this);
end
this.reg_agt = reg_agent::type_id::create("reg_agt", this);
this.fmt_agt = fmt_agent::type_id::create("fmt_agt", this);
this.cvrg = mcdf_coverage::type_id::create("cvrg", this);
virt_sqr = mcdf_virtual_sequencer::type_id::create("virt_sqr", this);
//instantiate those objects
// -mcdf_rgm object
// -reg2mcdf_adapter object
// -predictory object
//and finish necessary configuration
rgm = mcdf_rgm::type_id::create("rgm", this);
rgm.build();
adapter = reg2mcdf_adapter::type_id::create("adapter", this);
predictor = uvm_reg_predictor#(reg_trans)::type_id::create("predictor", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
chnl_agts[0].monitor.mon_bp_port.connect(chker.chnl0_bp_imp);
chnl_agts[1].monitor.mon_bp_port.connect(chker.chnl1_bp_imp);
chnl_agts[2].monitor.mon_bp_port.connect(chker.chnl2_bp_imp);
reg_agt.monitor.mon_bp_port.connect(chker.reg_bp_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
virt_sqr.reg_sqr = reg_agt.sequencer;
virt_sqr.fmt_sqr = fmt_agt.sequencer;
foreach(virt_sqr.chnl_sqrs[i]) virt_sqr.chnl_sqrs[i] = chnl_agts[i].sequencer;
// Link the register model with the adapter and the predictor
rgm.map.set_sequencer(reg_agt.sequencer, adapter);
reg_agt.monitor.mon_ana_port.connect(predictor.bus_in);
predictor.map = rgm.map;
predictor.adapter = adapter;
virt_sqr.rgm = rgm;
endfunction
二、寄存器模型的使用
在connect
阶段实现register block
句柄的传递。
// connect the virtual sequencer's rgm handle with rgm object
virt_sqr.rgm = rgm;
virtual task body();
`uvm_info(get_type_name(), "=====================STARTED=====================", UVM_LOW)
// connect rgm handle
rgm = p_sequencer.rgm; //virt_seq在执行body()时,通过p_sequencer拿到virt_sqr里面的rgm句柄
this.do_reg();
this.do_formatter();
this.do_data();
`uvm_info(get_type_name(), "=====================FINISHED=====================", UVM_LOW)
endtask
将mcdf_data_consistence_basic_virtual_sequence
原有的由总线sequence
实现的寄存器读写,改为由寄存器模型操作的寄存器读写方式。
task do_reg();
bit[31:0] wr_val, rd_val;
uvm_status_e status;
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
rgm.chnl0_ctrl_reg.write(status, wr_val);
rgm.chnl0_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
rgm.chnl1_ctrl_reg.write(status, wr_val);
rgm.chnl1_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
rgm.chnl2_ctrl_reg.write(status, wr_val);
rgm.chnl2_ctrl_reg.read(status, rd_val);
void'(this.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask
将mcdf_full_random_virtual_sequence
原有的由总线sequence
实现的寄存器读写,改为由寄存器模型预先设置寄存器值,再统一做总线寄存器更新的方式,并且由后门读取的方式取得寄存器值。
task do_reg();
bit[31:0] ch0_wr_val;
bit[31:0] ch1_wr_val;
bit[31:0] ch2_wr_val;
uvm_status_e status;
//reset the register block
rgm.reset();
//slv0 with len={4,8,16,32}, prio={[0:3]}, en={[0:1]}
ch0_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
ch1_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
ch2_wr_val = ($urandom_range(0,3)<<3)+($urandom_range(0,3)<<1)+$urandom_range(0,1);
//set all value of WR registers via uvm_reg::set()
rgm.chnl0_ctrl_reg.set(ch0_wr_val);
rgm.chnl1_ctrl_reg.set(ch1_wr_val);
rgm.chnl2_ctrl_reg.set(ch2_wr_val);
//update them via uvm_reg_block::update()
rgm.update(status);
//wait until the registers in DUT have been updated
#100ns;
//compare all of write value and read value
rgm.chnl0_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);
rgm.chnl1_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);
rgm.chnl2_ctrl_reg.mirror(status, UVM_CHECK, UVM_BACKDOOR);
// send IDLE command
`uvm_do_on(idle_reg_seq, p_sequencer.reg_sqr)
endtask
三、寄存器内建序列的应用
在mcdf_reg_builtin_virtual_sequence
类中,使用uvm_reg_hw_reset_seq
,uvm_reg_bit_bash_seq
和uvm_reg_access_seq
对MCDF寄存器模块展开全面测试。
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
仿真结果: