【前端验证】通关寄存器与ral_model —— apb agent组件自测环境与波形确认
终于在经历了众多流程后,终于到了激动人心的一刻——在uvm验证平台中连接ral_model!在之前研究验证平台时,uvm的ral_model例化就一直觉得特别的难懂尤其里面复杂的配置和mapping关系(显然,我现在也不太懂),因此这次在大神的帮助下完成了ral_model的例化连接和读写操作,让我觉得简直是神清气爽!
uvm验证方法学中,ral_model与RTL之间的连接关系如下图:
关于apb_agent在上一篇博客中已经说明过了,重点说明下adapter和predictor模块的作用。adapter核心的功能就两个:reg2bus和bus2reg,作用就是在uvm_reg_bus_op和apb_transaction(或者用户定义的其他总线协议transaction)之间互相转换。uvm_reg_bus_op是一种ral_model能够识别的类,apb_transaction是apb_agent能够识别的类,因此adapter将ral_model和apb_agent(主要是apb_sqr和apb_driver)成功连接。关于adapter部分的具体行为可以参考很多文章比如 UVM寄存器模型:reg adapter实现和集成 里面讲解的非常详细了。
predictor主要用于捕捉总线的行为,并修改ral_model的值。比如总线上“莫名”的出现了一笔写寄存器请求,那么monitor就会捕捉到这笔操作,传递给predictor再经由adapter刷新ral_model中对应寄存器的值。但是,对于我这种从vmm开始接触验证平台的人是非常不熟悉这个操作的,因为对寄存器发起了什么操作实际上验证环境是知道的,根本不需要检测总线一样可以完成ral_model的更新,因此我的实验中没有例化predictor(保留了代码但是注释了)而是通过ral_model.default_map.set_auto_predict(1)来保证ral_model自动进行更新而不是检测总线更新。换句话说,我实际搭建的测试平台是这样的:
测试平台的整体环境是基于之前 【验证小bai】一个简单可make run的uvm demo环境 修改的,文末我会把整个工程网盘连接附上。
注意,一定要把provides_responses = 0!!!!!
`ifndef __APB_ADAPTER_SV__
`define __APB_ADAPTER_SV__
class apb_adapter extends uvm_reg_adapter;
`uvm_object_utils(apb_adapter)
extern function new(string name = "apb_adapter");
extern virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
extern virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
endclass
function apb_adapter::new(string name = "apb_adapter");
super.new(name);
provides_responses = 0;
endfunction: new
function uvm_sequence_item apb_adapter::reg2bus(const ref uvm_reg_bus_op rw);
apb_transaction apb;
`uvm_warning("apb_adapter", "call reg2bus!")
apb = apb_transaction::type_id::create("apb_trans");
apb.kind = (rw.kind == UVM_READ) ? apb_transaction::READ : apb_transaction::WRITE;
apb.addr = rw.addr;
apb.data = rw.data;
`uvm_warning("apb_adapter", "call reg2bus over!")
return apb;
endfunction : reg2bus
function void apb_adapter::bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
apb_transaction apb;
if(!$cast(apb, bus_item)) begin
`uvm_fatal("NO APB TYPE","Provided bus_item is not of the correct type!")
return;
end
`uvm_warning("apb_adapter", "call bus2reg!")
rw.kind = (apb.kind == apb_transaction::READ) ? UVM_READ : UVM_WRITE;
rw.addr = apb.addr;
rw.data = apb.data;
rw.status = UVM_IS_OK;
`uvm_warning("apb_adapter", $sformatf("bus2reg get addr = 'h%0h", rw.addr));
endfunction : bus2reg
`endif
这次我把ral_model和相关组件都例化在env中,但是出于sys集成考虑,更加合适的例化处实际是base_case。下面的代码只摘录与ral_model相关的操作。
`ifndef MY_ENV_SV
`define MY_ENV_SV
class my_env extends uvm_env;
apb_agent apb_mst;
ral_sys_Gjm_sys ral_model;
apb_adapter adapter;
//uvm_reg_predictor #(apb_transaction) predictor;
endclass: my_env
function void my_env::build_phase(uvm_phase phase);
apb_mst = apb_agent::type_id::create("apb_mst", this);
apb_mst.is_active = UVM_ACTIVE;
if(!uvm_config_db #(ral_sys_Gjm_sys)::get(this, "", "ral_model", ral_model)) begin
ral_model = ral_sys_Gjm_sys::type_id::create("ral_model", this);
ral_model.build();
ral_model.lock_model();
ral_model.reset();
end
adapter = apb_adapter::type_id::create("adapter", this);
//predictor = uvm_reg_predictor#(apb_transaction)::type_id::create("predictor", this);
endfunction: build_phase
function void my_env::connect_phase(uvm_phase phase);
ral_model.default_map.set_sequencer(apb_mst.sqr, adapter);
ral_model.default_map.set_auto_predict(1);
//apb_mst.mon.ap.connect(predictor.bus_in);
//predictor.map = ral_model.get_default_map();
//predictor.adapter = adapter;
endfunction
`endif
apb_driver是要做出一些修改的。第一点,apb_driver的行为必须在run_phase中(这点本身已经符合),因为在仿真的全流程都有可能进行寄存器访问;第二点,run_phase中不能有objection相关操作,换句话说仿真是否完成与寄存器的总线行为是不需要有直接联系的,要通过主业务的main_phase(或者run_phase)去控制仿真结束;第三点,apb_driver的run_phase中不要使用try_next_item不到就break,因为ral_model的访问是随时可能出现的,也不是连续的。修改后的apb_driver代码如下:
`ifndef __APB_DRIVER_SV__
`define __APB_DRIVER_SV__
class apb_driver extends uvm_driver #(apb_transaction);
virtual apb_interface vif;
`ifdef UTILS_TEST
uvm_analysis_port #(apb_transaction) ap;
`endif
`uvm_component_utils(apb_driver)
extern function new(string name = "apb_driver", uvm_component parent=null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task run_phase(uvm_phase phase);
extern virtual task drive_pkt(ref apb_transaction pkt);
extern virtual task drive_write(ref apb_transaction pkt);
extern virtual task drive_read(ref apb_transaction pkt);
extern virtual task drive_idle();
endclass: apb_driver
function apb_driver::new(string name = "apb_driver", uvm_component parent=null);
super.new(name, parent);
endfunction: new
function void apb_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info("apb_driver", "build_phase is called", UVM_HIGH);
if(!uvm_config_db #(virtual apb_interface)::get(this, "", "vif", vif))begin
`uvm_fatal("apb_driver", "virtual interface get fatal");
end
`ifdef UTILS_TEST
ap = new("ap", this);
`endif
endfunction: build_phase
task apb_driver::run_phase(uvm_phase phase);
super.run_phase(phase);
//phase.raise_objection(this);
`uvm_info("apb_driver", "run_phase is called", UVM_HIGH);
this.drive_idle();
while(!this.vif.presetn) @(posedge vif.pclk);
@this.vif.master_cb;
while(1)begin
apb_transaction req;
seq_item_port.try_next_item(req);
//seq_item_port.get_next_item(req);
if(req == null) begin
this.drive_idle();
continue;
// break;
end
else begin
`uvm_warning("apb_driver", "need drive a pkt start");
`ifdef UTILS_TEST
ap.write(req);
`endif
this.drive_pkt(req);
//`uvm_warning("apb_driver", "drive a pkt over");
seq_item_port.item_done();
end
end
`uvm_warning("apb_driver", "out from while 1");
this.drive_idle();
//#1000;
//phase.drop_objection(this);
endtask: run_phase
task apb_driver::drive_pkt(ref apb_transaction pkt);
//@this.vif.master_cb;
case (pkt.kind)
apb_transaction::WRITE : drive_write(pkt);
apb_transaction::READ : drive_read(pkt);
endcase
repeat(pkt.cycle_post) this.drive_idle();
endtask: drive_pkt
task apb_driver::drive_write(ref apb_transaction pkt);
this.vif.master_cb.psel <= '1;
this.vif.master_cb.penable <= '0;
this.vif.master_cb.pwrite <= '1;
this.vif.master_cb.pwdata <= pkt.data;
this.vif.master_cb.paddr <= pkt.addr;
while(1)begin
if(this.vif.master_cb.pready == 1'b1) break;
@this.vif.master_cb;
end
@this.vif.master_cb;
this.vif.master_cb.penable <= '1;
this.drive_idle();
endtask: drive_write
task apb_driver::drive_read(ref apb_transaction pkt);
this.vif.master_cb.psel <= '1;
this.vif.master_cb.penable <= '0;
this.vif.master_cb.pwrite <= '0;
this.vif.master_cb.paddr <= pkt.addr;
while(1)begin
if(this.vif.master_cb.pready == 1'b1) break;
@this.vif.master_cb;
end
@this.vif.master_cb;
this.vif.master_cb.penable <= '1;
pkt.data = this.vif.master_cb.prdata;
this.drive_idle();
endtask: drive_read
task apb_driver::drive_idle();
@this.vif.master_cb;
this.vif.master_cb.psel <= 'd0;
this.vif.master_cb.penable <= 'd0;
this.vif.master_cb.pwrite <= 'd0;
this.vif.master_cb.paddr <= 'dx;
this.vif.master_cb.pwdata <= 'dx;
this.vif.master_cb.pstrb <= 'dx;
endtask: drive_idle
`endif
在这个测试中,我没有真正的例化寄存器的rtl,只是例化了总线最后我们通过观察总线波形来确认读写行为是否符合预期。
apb_interface u_apb_if(clk, rst_n);
initial begin
uvm_config_db#(virtual apb_interface)::set(null, "uvm_test_top.env.apb_mst", "vif", u_apb_if);
end
initial begin
u_apb_if.pready = 1'b1;
u_apb_if.prdata = '1;
u_apb_if.pslverr = '0;
#1000ns;
force harness.u_mul.m_valid = 1;
#10ns;
release harness.u_mul.m_valid;
end
`ifndef SANITY_CASE_SV
`define SANITY_CASE_SV
class sanity_case_seq extends my_sequence;
extern function new(string name = "sanity_case_seq");
extern virtual task body();
`uvm_object_utils(sanity_case_seq)
endclass: sanity_case_seq
function sanity_case_seq::new(string name = "sanity_case_seq");
super.new(name);
endfunction: new
task sanity_case_seq::body();
repeat(10) begin
`uvm_do_with(my_tr, {my_tr.par_err == 0;})
end
#100;
endtask: body
class sanity_case extends base_test;
ral_block_Gjm_sys_global_cfg ral_model;
uvm_status_e status;
extern function new(string name = "base_test", uvm_component parent=null);
extern virtual function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
`uvm_component_utils(sanity_case)
endclass: sanity_case
function sanity_case::new(string name = "base_test", uvm_component parent=null);
super.new(name, parent);
endfunction: new
function void sanity_case::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(uvm_object_wrapper)::set(
this,
"env.i_agt0.sqr.main_phase",
"default_sequence",
sanity_case_seq::type_id::get()
);
uvm_config_db #(uvm_object_wrapper)::set(
this,
"env.i_agt1.sqr.main_phase",
"default_sequence",
sanity_case_seq::type_id::get()
);
endfunction: build_phase
task sanity_case::main_phase(uvm_phase phase);
bit [31:0]data;
super.main_phase(phase);
phase.raise_objection(this);
//this.env.ral_model.global_cfg.status_cfg.set(32'h87654321);
//$display("ral_model.get = 'h%0h", this.env.ral_model.global_cfg.status_cfg.get());
this.env.ral_model.global_cfg.status_cfg.write(status, 32'h34343434);
$display("ral_model.get = 'h%0h", this.env.ral_model.global_cfg.status_cfg.get());
`uvm_warning("main_phase", "update over!!!!!");
#10;
this.env.ral_model.global_cfg.status_cfg.write(status, 32'h33445566);
$display("ral_model.get = 'h%0h", this.env.ral_model.global_cfg.status_cfg.get());
`uvm_warning("main_phase", "update over!!!!!");
#10;
this.env.ral_model.global_cfg.status_cfg.read(status, data);
$display("ral_model.get = 'h%0h", this.env.ral_model.global_cfg.status_cfg.get());
`uvm_warning("main_phase", "update over!!!!!");
phase.drop_objection(this);
endtask: main_phase
`endif
在sanity_case中我们针对同一个寄存器发起三次读写访问:写-写-读,然后get值看下行为是否合理。
执行仿真后,在apb总线上看到了三次访问行为:
同时可以在log上看到如下信息:
ral_model.get = 'h34343434
...
ral_model.get = 'h33445566
...
ral_model.get = 'hffffffff
因为总线上rdata一直为全1,所以都回来的值必然是'hffffffff
uvm_ral_model_test: 基于uvm的ral_model读写测试仿真环境