单向通信
单向通信(unidirectional communication)指的是从initiator到target之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。
在PORT代表了三种端口名:port、export和imp。
按照UVM端口名的命名规则,它们指出了通信的两个要素:
- 是否是阻塞的方式(即可以等待延时)
- 何种通信方法
阻塞传输方式将blocking前缀作为函数名的一部分,而非阻塞方式则名为nonblocking。阻塞端口的方法类型为task,这保证了可以实现事件等待和延时;非阻塞端口的方式类型为function,这确保了方法调用可以立即返回。
从方法名也可以发现,例如uvm_blocking_put_PORT提供的方法task put()会在数据传送完后返回,uvm_nonblocking_put_PORT对应的两个函数try_put()和can_put()是立刻返回的。
uvm_put_PORT则分别提供了blocking和nonblocking的方法,这为通讯方式提供了更多选择。blocking阻塞传输的方法包含:
- Put():initiator先生成数据Tt,同时将该数据传送至target。
- Get():initiator从target获取数据Tt,而target中的该数据Tt则应消耗。
- Peek():initiator从target获取数据Tt,而target中的该数据Tt还应保留。
与上述三种任务对应的nonblocking非阻塞方法分别是: - try_put)
- can_put()
- try_get()
- can_get()
- try_peek)
- can_peek()
这六个非阻塞函数与对应阻塞任务的区别在于,它们必须立即返回
,如果try_xxx函数可以发送或者获取数据,那么函数应该返回1,如果执行失败则应返回0。
或者通过can_xxx函数先试探target是否可以接收数据,如果可以,再通过try_xxx函数发送,提高数据发送的成功率。
代码如下
class itrans extends uvm _transaction;
int id;
int data;
...
endclass
class otrans extends uvm transaction;
int id;
int data;
...
endclass
class compl extends uvm_component;
uvm_blocking_put_port #(itrans)bp_port;
uvm_nonblocking_get_port #(otrans)nbg_port;
uvm_component_utils(comp1)
...
task run_phase(uvm_phase phase);
itrans itr;
otrans otr;
int trans_num=2;
fork
begin
for(int i=0;i<trans_num;i++) begin
itr=new("itr", this);
itr.id=i;
itr.data='h10+i;
this.bp_port.put(itr);
'uvm info("PUT",$sformatf("put itrans_id:'h%0x, data:'h$0x", itr.id,itr.data),UVM_LOW)
end
end
begin
for(intj=0;j<trans_num;j++) begin
forever begin
if(this.nbg_port.try_get(otr)==1) break;
else #1ns;
end
'uvm info("TRYGET", Ssformatf("get otrans id:'h80x, data: h%0x",otr.id,otr.data),UVM_LOW)
end
end
join
endtask
endclass
class comp2 extends uvm component;
uvm_blocking_put_imp#(itrans,comp2) bp_imp;
uvm_nonblocking_get_imp#(otrans,comp2)nbg_imp;
itrans itr_q[$];
`uvm_component_utils(comp2)
...
task put(itrans t);
itr_q.push_back(t);
endtask
function bit try_get (output otrans t);
itrans i;
if(itr_q.size()!=0) begin
i=itr_q.pop front();
t=new("t", this);
t.id=i.id;
t.data=i.data<<8;
return 1;
end
else return 0;
endfunction
function bit can_get();
if(itr_q.size()!=0) return 1;
else return 0;
endfunction
endclass
class envl extends uvm_env;
comp1 c1;
comp2 c2;
`uvm_component_utils(env1)
...
function void build_phase(uvm_phase phase);
super.build phase(phase);
c1=compl::type_id::create("c1", this);
c2=comp2::type_id::create("c2", this);
endfunction:build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bp_port.connect(c2.bp_imp);
c1.nbg_port.connect(c2.nbg_imp);
endfunction: connect_phase
endclass
双向通信
与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但是数据流向在端对端之间是双向的。
双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。
双向端口按照通信握手方式可以分为:
- transport双向通信方式
- master和slave双向通信方式
transpot端口通过transport()方法,可以在同一方法调用过程中完成REQ和RSP的发出和返回。
master和slave的通信方式必须分别通过put、get和peek的调用,使用两个方法才可以完成一次握手通信。
master端口和slave端口的区别在于,当initiator作为master时,它会发起REQ送至target端,而后再从target端获取RSP;当initiator使用slave端口时,它会先从target端获取REQ,而后将RSP送至target端。
对于master端口或者slave端口的实现方式,类似于之前介绍的单向通信方式,只是imp端口所在的组件需要实现的方法更多了。
class compl extends uvm_component;
uvm_blocking_transport_port #(itrans, otrans) bt_port;
`uvm_component_utils(comp1)
...
task run_phase(uvm phase phase);
itrans itr;
otrans otr;
int trans num=2;
for(int i=0;i<trans_num;i++) begin
itr=new("itr", this);
itr.id=i;
itr.data='h10+i;
this.bt_port.transport(itr,otr);
uvm_info("TRSPT", $sformatf("put itrans id:'h80x, data:'h%0x
| get_otrans id:'h%0x, data:'h%0x",
itr.id, itr.data, otr.id, otr.data), UVM LOW)
end
endtask
endclass
class comp2 extends uvm_component;
uvm_blocking_transport_imp #(itrans,otraps,comp2) bt_imp;
`uvm_component_utils(comp2)
...
task tranport(itrans req,output otrans rsp);
rsp=new("rsp", this);
rsp.id=req.id;
rsp.data=req.data<<8;
endtask
endclass
class envl extends uvm env;
compl c1;
comp2 c2;
`uvm_component_utils(env1)
...
function void build_phase(uvm phase phase);
super.build_phase(phase);
c1=compl::type_id::create("c1",this);
c2=comp2::type_id::create("c2",this);
endfunction: build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bt_port.connect(c2.bt_imp);
endfunction: connect_phase
endclass
1、事先定义initiator和target;2、例化端口;3、在target一侧定义import所对应的方法;4、顶层环境做连接;
多向通信
多向通信(multi-directional communication)服务的仍然是两个组件之间的通信,而不是多个组件之间的通信,毕竟多个组件的通信仍然可以由基础的两个组件的通信方式来构建。
多向通信指的是,如果initiator与target之间的相同TLM端口数目超过一个时的处理解决办法。
comp1有两个uvm_blocking_put_port,而comp2有两个uvm_blocking_put_imp端口,我们对于端口例化可以给不同名字,连接也可以通过不同名字来索引,但问题在于comp2中需要实现两个task put(itrans t),又因为不同端口之间要求在imp端口一侧实现专属方法,这就造成了方法命名冲突,即无法在comp2中定义两个同名的put任务。
UVM通过端口宏声明方式来解决这一问题,它解决问题的核心在于让不同端口对应不同名的任务,这样便不会造成方法名的冲突。
`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_decl(_p2)
class compl extends uvm_component;
uvm_blocking_put_port #(itrans) bp_port1;
uvm_blocking_put_port #(itrans) bp_port2;
`uvm_component_uti1s(comp1)
...
task run_phase(uvm_phase phase);
itrans itrl, itr2;
int trans_num=2;
fork
for(int i=0;i<trans_num;i++) begin
itrl=new("itrl", this);
itrl.id=i;
itrl.data='h10+i;
this.bp_port1.put(itrl);
end
for(int i=0;i<trans_num;i++) begin
itr2=new("itr2", this);
itr2.id='h10+i;
itr2.data='h20+i;
this.bp_port2.put(itr2);
end
join
endtask
endclass
class comp2 extends uvm_component;
uvm_blocking_put_imp_pl #(itrans, comp2) bt_imp_pl;
uvm_blocking_put_imp_p2 #(itrans, comp2) bt_imp_p2;
itrans itr_q[$];
semaphore key;
uvm_component_uti1s(comp2)
...
task put_p1(itrans t);
key.get();
itr_q.push back(t);
uvm_info("PUTP1", $sformatf("put itrans id:' h%0x, data:'h%0x",t.id,t.data),UVM_LOW)
key.put();
endtask
task put_p2(itrans t);
key.get();
itr_q.push back(t);
uvm_info("PUTP2",$sformatf("put itrans id:'h%0x, data:'h%0x", t.id,t.data),UVM_LOW)
key.put();
endtask
endclass
class env1 extends uvm_env;
comp1 c1;
comp2 c2;
`uvm_component_uti1s(env1)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
c1=comp1::type_id::create("c1",this);
c2=comp2::type_id::create("c2",this);
endfunction: build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bp_port1.connect(c2.bt_imp_p1);
c1.bp_port2.connect(c2.bt_imp_p2);
endfunction: connect_phase
endclass
其中仿真结果如下所示:
uvm_test_top.env.c2[PUTP1] put itrans id:'h0, data:'h10
uvm_test_top.env.c2[PUTP1] put itrans id:'h1, data:'h11
uvm_test_top.env.c2[PUTP2] put itrans id:'h10, data:'h20
uvm_test_top.env.c2[PUTP2] put itrans id:'h11, data:'h21
上述代码通过宏uvm_blocking_put_imp_decl声明了两个后缀_p1和_p2。UVM会根据这两个后缀定义两个新的IMP类:
uvm_blocking_put_imp_p1和uvm_blocking_put_imp_p2,并实例化这两个类:当相连接执行函数时,会自动调用,所以,只要完成后缀的声明,并在方法后面添加上相应的后缀就可以正常工作了
这篇笔记参考《UVM实战》、《芯片验证漫游指南》和某验证视频整理而成,仅作学习心得交流,如果涉及侵权烦请请告知,我将第一时间处理。