1、何为事务级、何为事务级通信(TLM)?有什么用?
事务级:一个transaction就是把具有某一特定功能的一组信息封装在一起而形成的一个类。
事务级通信:在TLM中,使用事务对不同模块之间的通信进行建模;即使DUT的实际接口由信号级别的活动表示,但大多数验证任务(如生成激励,功能检查,收集覆盖率数据等)都可以在事务级别上更好地完成,只要使它们独立于实际信号级别的细节即可 。
作用:
2、何为单向、多向?
单向:从initiator到target的数据流是单向的,或者说initiator和target智能搬移producer和consumer中的一个角色。
多向:多向通信仍是两个组件之间的通信,指如果initiator与target之间的相同TLM端口数目超过1个的情况。比如下图组件1和组件2分别有两个uvm_blocking_put_port和uvm_blocking_put_imp端口。虽然我们可以给端口例化不同的名字,连接也可以通过不同名字来索引,但是端口对应的task名都是put,会产生冲突。
通过端口宏定义就可以解决上述问题,即让不同的端口对应不同命的任务来解决方法名冲突问题。
下面代码为示例:
//解决target一端方法名冲突的几个要点
//端口宏声明
`uvm_blocking_put_imp_deck(_p1)
`uvm_blocking_put_imp_deck(_p2)
//端口声明
uvm_blocking_put_imp_p1 #(type, comp2) bp_imp_p1;
uvm_blocking_put_imp_p2 #(type, comp2) bp_imp_p2;
//端口例化
function new(string name, uvm_component parent);
super.new(name, parent);
bt_imp_p1 = new("bt_imp_p1", this);
bt_imp_p2 = new("bt_imp_p2", this);
……
endfunction
//不同名put任务的定义
task put_p1(itrans t);
……
key.put();
endtask
task put_p2(itrans t);
……
key.put();
endtask
注意:只有端口类型为imp(target)的才需要通过端口宏声明来解决方法名冲突的问题,而port不需要关注对方名字,直接用put就行。这是因为initiator一端不用去关心谁将会和它连接,有几个端口,只要正常地声明、例化端口即可,方法就用put\get\peek等就行,不用加后缀。而这些需要修改的部分只在target端进行即可。
假设我们在initiator这边的端口也将方法名设置为put_p1等有后缀的,那么一来会报错,二来下次你连接的target只要一个端口,那么你到时又需要修改。这就是耦合度高了,不利于维护。
1、声明port和export端口,只需要指定transaction参数类型。而声明imp端口,则需要同时指定transaction参数类型以及所在的component类型。因为端口只是作为通信的管道,最终的操作还是要落实到component上,所以需要指定所在的component类型;
2、在initiator端例化port,在中间层次例化export,在target端例化imp;
之前的monitor到checker的通信,以及checker与refmod的通信都是通过mailbox以及在上层进行句柄传递实现的。接下来我们将采用TLM端口进行通信。
如图1所示,我们将在monitor、checker和refmod上添加若干TLM的通信端口,完成下列要求:
1、将monitor中的用来与checker中的mailbox通信的mon_mb句柄替换为对应的uvm_blocking_put_port类型。
以chnl_monitor为例。
// mailbox #(mon_data_t) mon_mb;
uvm_blocking_put_port #(mon_data_t) mon_bp_port;
2、在checker中声明与monitor通信的import端口类型,以及reference model通信的import端口类型。由于checker与多个monitor以及reference model通信(数据从多个组件流入checker),是典型的多方向通信类型,因此需要使用多端口通信的宏声明方法。在使用了宏声明端口类型之后,再在checker中声明其句柄,并且完成例化。
//宏声明
`uvm_blocking_put_imp_decl(_chnl0)
`uvm_blocking_put_imp_decl(_chnl1)
`uvm_blocking_put_imp_decl(_chnl2)
`uvm_blocking_put_imp_decl(_fmt)
`uvm_blocking_put_imp_decl(_reg)
`uvm_blocking_get_peek_imp_decl(_chnl0)
`uvm_blocking_get_peek_imp_decl(_chnl1)
`uvm_blocking_get_peek_imp_decl(_chnl2)
`uvm_blocking_get_imp_decl(_reg)
//句柄声明
uvm_blocking_put_imp_chnl0 #(mon_data_t, this) chnl0_bp_imp;
uvm_blocking_put_imp_chnl1 #(mon_data_t, this) chnl1_bp_imp;
uvm_blocking_put_imp_chnl2 #(mon_data_t, this) chnl2_bp_imp;
uvm_blocking_put_imp_fmt #(fmt_trans, this) fmt_bp_imp;
uvm_blocking_put_imp_reg #(reg_trans, this) reg_bp_imp;
uvm_blocking_get_peek_imp_chnl0 #(mon_data_t, this) chnl0_bgpk_imp;
uvm_blocking_get_peek_imp_chnl1 #(mon_data_t, this) chnl1_bgpk_imp;
uvm_blocking_get_peek_imp_chnl2 #(mon_data_t, this) chnl2_bgpk_imp;
uvm_blocking_get_imp_reg #(reg_trans, this) reg_bg_imp;
//例化
chnl0_bp_imp = new("chnl0_bp_imp", this);
chnl1_bp_imp = new("chnl1_bp_imp", this);
chnl2_bp_imp = new("chnl2_bp_imp", this);
fmt_bp_imp = new("fmt_bp_imp", this);
reg_bp_imp = new("reg_bp_imp", this);
chnl0_bgpk_imp = new("chnl0_bgpk_imp", this);
chnl1_bgpk_imp = new("chnl1_bgpk_imp", this);
chnl2_bgpk_imp = new("chnl2_bgpk_imp", this);
reg_bg_imp = new("reg_bg_imp", this);
3、根据声明的import端口类型,实现其对应的方法。
//put
task put_chnl0(mon_data_t t);
chnl_mbs[0].put(t);
endtask
task put_chnl1(mon_data_t t);
chnl_mbs[1].put(t);
endtask
task put_chnl2(mon_data_t t);
chnl_mbs[2].put(t);
endtask
task put_fmt(fmt_trans t);
fmt_mb.put(t);
endtask
task put_reg(reg_trans t);
reg_mb.put(t);
endtask
//peek、get
task peek_chnl0(output mon_data_t t);
chnl_mbs[0].put(t);
endtask
task peek_chnl1(output mon_data_t t);
chnl_mbs[1].put(t);
endtask
task peek_chnl2(output mon_data_t t);
chnl_mbs[2].put(t);
endtask
task get_chnl0(output mon_data_t t);
chnl_mbs[0].get(t);
endtask
task get_chnl1(output mon_data_t t);
chnl_mbs[1].get(t);
endtask
task get_chnl2(output mon_data_t t);
chnl_mbs[2].get(t);
endtask
task get_reg(output reg_trans t);
reg_mb.get(t);
endtask
4、根据图1,继续在mcdf_refmod中声明用来与mcdf_checker中的import连接的端口,并且完成例化。同时注意其端口类型的区别。
//声明端口
// mailbox #(reg_trans) reg_mb;
// mailbox #(mon_data_t) in_mbs[3];
uvm_blocking_get_port #(reg_trans) reg_bg_port;
uvm_blocking_get_peek_port #(mon_data_t) in_bgpk_ports[3];
//例化
reg_bg_port = new("reg_bg_port", this);
foreach(in_bgpk_ports[i]) in_bgpk_ports[i] = new(sformatf("in_bgpk_ports[%0d]",i),this);
5、在mcdf_env的connect_phase()阶段,完成monitor的TLM port 与mcdf_checker TLM import的连接
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_bg_imp);
fmt_agt.monitor.mon_bp_port.connect(chker.fmt_bp_imp);
注意:只有initiator才能调用connect函数,target则作为connect的参数。**此处initiator是monitor,它们要将数据put到checker里,所以是monitor来调用connect函数,而checker作为参数。
6、在mcdf_checker的connect_phase阶段,完成mcdf_refmod的TLM port与mcdf_checker的TLM import的连接。
// foreach(this.refmod.in_mbs[i]) begin
// this.refmod.in_mbs[i] = this.chnl_mbs[i];
// this.exp_mbs[i] = this.refmod.out_mbs[i];
// end
this.refmod.in_bgpk_ports[0].connect(this.chnl0_bgpk_imp);
this.refmod.in_bgpk_ports[1].connect(this.chnl1_bgpk_imp);
this.refmod.in_bgpk_ports[2].connect(this.chnl2_bgpk_imp);
this.refmod.reg_bg_port.connect(this.reg_bp_imp);
同样,此处的initiator是refmod,他们要将数据get过来,所以是refmod来调用connect函数。
完成上述试验后感觉到工作量增加了不少,比如需要自己去实现get、put、peek方法。有没有既可以使用TLM端口,又不用自己去实现的方法呢?有,那就是通信管道!!!
TLM通信管道的作用?
可以不再target一端实现传输方法,也可以享受到TLM的好处
几个TLM组件和端口可以帮助用户免除这些烦恼:
FIFO的本质是一块缓存加两个IMP。
引入原因:
uvm_tlm_fifo组件内部实现
常用端口
作用:
如果数据源端发生变化需要通知和它关联的多个组件,就可以用到analysis端口,可以满足一端到多端的通信
分类:
uvm_analysis_port、uvm_analysis_export、uvm_analysis_imp
说明:
analysis_port/analysis_export和port/export端口的区别
1、可连接imp数量
2、方法区别
如果想轻松实现一端到多端的数据传输,可以如下插入多个uvm_tlm_analysis_fifo
1、将原本在mcdf_refmod中的out_mb替换为uvm_tlm_fifo类型,并且完成例化,以及对应的变量名替换;
//TODO-2.1 replace the out_mbs[3] with uvm_tlm_fifo type
// mailbox #(fmt_trans) out_mbs[3];
uvm_tlm_fifo #(fmt_trans) out_tlm_fifos[3];
//TODO-2.1 instantiate the TLM fifos
// foreach(this.out_mbs[i]) this.out_mbs[i] = new();
foreach(out_tlm_fifos[i]) out_tlm_fifos[i] = new($sformatf("out_tlm_fifos[%0d]",i),this);
//TODO-2.1 replace the out_mbs[3] with uvm_tlm_fifo type
// this.out_mbs[id].put(ot);
this.out_tlm_fifos[id].put(ot);
2、将原本在mcdf_checker中的exp_mbs[3]的邮箱句柄数组,替换为uvm_blocking_get_port类型句柄数组,并做相应的例化以及变量名替换;
//TODO-2.2 replace exp_mbs[3] with TLM uvm_blocking_get_port type
// mailbox #(fmt_trans) exp_mbs[3];
uvm_blocking_get_port #(fmt_trans) exp_bg_ports[3];
//TODO-2.2 instantiate the TLM blocking_get ports
foreach(exp_bg_ports[i]) exp_bg_ports[i] = new($sformatf("exp_bg_ports[%0d]",i),this);
//TODO-2.2 replace the exp_mbs with the TLM ports
// this.exp_mbs[mont.ch_id].get(expt);
this.exp_bg_ports[mont.ch_id].get(expt);
3、在mcdf_checker中,完成在mcdf_checker中的TLM port端口到mcdf_refmod中的uvm_tlm_fifo自带的blocking_get_export端口的连接。
//TODO-2.3 connect the TLM blocking_get ports to the blocking_get
//exports of the reference model
foreach(exp_bg_ports[i]) begin
exp_bg_ports[i].connect(refmod.out_tlm_fifos[i].blocking_get_export);
end
此处的blocking_get_export我们不需要去声明以及例化,uvm_tlm_fifo中预先内置了。
1、callback机制必要性?
2、为什么要专门定义一个uvm_callback类?
以前用户自定义了回调函数不就够了,为啥还要专门定义一个uvm_callback类?
为了使函数回调拥有顺序和继承性(是通过两个相关类uvm_callback_iter和uvm_callback#(T,CB)来实现的)。
uvm_callbacks #(comp1)::add(c1,m_cb1);
uvm_callbacks #(comp1)::add(c1,m_cb2);
1、定义callback类
class cb1 extends uvm_callback;
`uvm_object_utils(cb1)
function new(string name = "cb1");
super.new(name);
endfunction
virtual function void do_trans(edata d);//此处do_trans指具体的回调函数
d.data = 200;
`uvm_info("CB", $sformatf("cb1 executed with data %0d", d.data), UVM_LOW)
endfunction
endclass
2、绑定及插入callback
绑定及插入cb(在要预留callback函数/任务接口的类中调用uvm_register_cb进行绑定,并在调用callback函数/任务接口的函数/任务中, 使用uvm_do_callbacks宏来插入cb
`uvm_register_cb(comp1,cb1)
注意,comp1和cb1都是类名,而不是实例名。
`uvm_do_callbacks(comp1,cb1,do_trans(d))
这里uvm_do_callbacks的三个参数意义如下:
3、添加callback
`uvm_callbacks #(comp1)::add(c1,m_cb1)
注意,c1和m_cb1都是实例名。
接下来,我们将练习umv_callback的定义、连接和使用方式。由此,我们可以将原由的mcdf_data_consistence_basic_test和mcdf_full_random_test的类实现方式(即类继承方式)修改为回调函数的实现方式,完成类的复用除了可以使用继承,还可以使用回调函数。
1、请在给定的uvm_callback代码中,预先定义需要的几个虚方法。
class cb_mcdf_base extends uvm_callback;
`uvm_object_utils(cb_mcdf_base)
mcdf_base_test test;
function new (string name = "cb_mcdf_base");
super.new(name);
endfunction
virtual task cb_do_reg();
// User to define the content
endtask
virtual task cb_do_formatter();
// User to define the content
endtask
virtual task cb_do_data();
// User to define the content
endtask
endclass
回调类中需要包含之后要用到的回调函数。
2、请使用callback对应的宏,完成目标uvm_test类型与目标uvm_callback类型的关联;
`uvm_register_cb(mcdf_base_test,cb_mcdf_base)
3、请继续在目标uvm_test类型中指定的方法中,完成uvm_callback的方法回调指定;
virtual task do_reg();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_reg())
endtask
// do external formatter down stream slave configuration
virtual task do_formatter();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_formatter())
endtask
// do data transition from 3 channel slaves
virtual task do_data();
//TODO-3.3 Use callback macro to link the callback method
`uvm_do_callbacks(mcdf_base_test, cb_mcdf_base, cb_do_data())
endtask
此处要调用回调函数的类为mcdf_base_test,包含回调函数的callback类为cb_mcdf_base,要调用的回调函数分别为cb_do_reg()、cb_do_formatter()、cb_do_data()。
4、请分别完成uvm_callback和对应test类的定义;
a、cb_mcdf_data_consistence_basic 和 cb_mcdf_data_consistence_basic_test
此处cb_mcdf_data_consistence_basic 加上 cb_mcdf_data_consistence_basic_test,其作用就相当于mcdf_data_consistence_basic_test。
class cb_mcdf_data_consistence_basic extends cb_mcdf_base;
`uvm_object_utils(cb_mcdf_data_consistence_basic)
function new (string name = "cb_mcdf_data_consistence_basic");
super.new(name);
endfunction
task cb_do_reg();
//user to adapt contents from mcdf_data_consistence_basic_test
bit[31:0] wr_val, rd_val;
super.cb_do_reg();
// slv0 with len=8, prio=0, en=1
wr_val = (1<<3)+(0<<1)+1;
test.write_reg(`SLV0_RW_ADDR, wr_val);
test.read_reg(`SLV0_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV0_WR_REG"));
// slv1 with len=16, prio=1, en=1
wr_val = (2<<3)+(1<<1)+1;
test.write_reg(`SLV1_RW_ADDR, wr_val);
test.read_reg(`SLV1_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV1_WR_REG"));
// slv2 with len=32, prio=2, en=1
wr_val = (3<<3)+(2<<1)+1;
test.write_reg(`SLV2_RW_ADDR, wr_val);
test.read_reg(`SLV2_RW_ADDR, rd_val);
void'(test.diff_value(wr_val, rd_val, "SLV2_WR_REG"));
// send IDLE command
test.idle_reg();
endtask
task cb_do_formatter();
//user to adapt contents from mcdf_data_consistence_basic_test
super.cb_do_formatter();
void'(test.fmt_gen.randomize() with {fifo == LONG_FIFO; bandwidth == HIGH_WIDTH;});
test.fmt_gen.start();
endtask
task cb_do_data();
//user to adapt contents from mcdf_data_consistence_basic_test
super.cb_do_data();
void'(test.chnl_gens[0].randomize() with {ntrans==100; ch_id==0; data_nidles==0; pkt_nidles==1; data_size==8; });
void'(test.chnl_gens[1].randomize() with {ntrans==100; ch_id==1; data_nidles==1; pkt_nidles==4; data_size==16;});
void'(test.chnl_gens[2].randomize() with {ntrans==100; ch_id==2; data_nidles==2; pkt_nidles==8; data_size==32;});
fork
test.chnl_gens[0].start();
test.chnl_gens[1].start();
test.chnl_gens[2].start();
join
#10us; // wait until all data haven been transfered through MCDF
endtask
endclass: cb_mcdf_data_consistence_basic
//TODO-3.4 define cb_mcdf_data_consistence_basic_test
class cb_mcdf_data_consistence_basic_test extends mcdf_base_test;
// declare uvm_callback member
`uvm_component_utils(cb_mcdf_data_consistence_basic_test)
function new(string name = "cb_mcdf_data_consistence_basic_test", uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
// instantiate uvm_callback and add it
cb = cb_mcdf_data_consistence_basic::type_id::create("cb"); //创建回调函数
uvm_callbacks#(mcdf_base_test)::add(this,cb);//添加回调函数。注意mcdf_base_test是父类
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// connect test handle to uvm_callback member
cb.test = this;
endfunction
endclass: cb_mcdf_data_consistence_basic_test
可以发现,cb_mcdf_data_consistence_basic_test 中并没有具体实现do_reg()、do_formatter()、do_data(),具体实现的内容放在了cb_mcdf_data_consistence_basic;
我们要做的是,采用下列语句将要调用回调函数的组件类以及包含回调函数的回调类进行add,
uvm_callbacks#(mcdf_base_test)::add(this,cb);
那么运行test时,就会调用cb_mcdf_data_consistence_basic这个回调类里的回调函数。
b、cb_mcdf_full_random 和 cb_mcdf_full_random_test
类似,此处略。
我们也可以回顾实验1对uvm_root类的应用,学习更多关于uvm_root类的方法,请按照以下实验要求实现代码:
1、2实验要求代码如下:
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
uvm_root::get().set_report_verbosity_level_hier(UVM_HIGH);
uvm_root::get().set_report_max_quit_count(1);
uvm_root::get().set_timeout(10ms);
endfunction
原先仿真的退出逻辑如下:do_watchdog的作用是,超过某个时间后如果仿真数据还没发送完,也直接结束。也就是,在fork… join_any块中,如果在规定时间内,数据正常发送完,那就正常退出。如果规定时间到了还没发送完,那么do_watchdog()完成,也会直接跳出当前的fork… join_any块,从而调用drop_objection结束仿真。
fork
this.do_data();
this.do_watchdog();//此处用join_any,也就是达到指定时间数据还没发送完,就drop_objection
join_any