UVM 史上最全TLM单向/双向/多向通信介绍
文章目录
一、TLM单向通信
1.概述
单向通信(unidirectional communiction)指的是从initiator到target之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。在UVM中,单一数据流向的TLM端口有很多类型:
uvm_blocking_put_PORT
uvm_nonblocking_put_PORT
uvm_put_PORT
uvm_blocking_get_PORT
uvm_nonblocking_get_PORT
uvm_get_PORT
uvm_blocking_peek_PORT
uvm_nonblocking_peek_PORT
uvm_peek_PORT
uvm_blocking_get_peek_PORT
uvm_nonblocking_get_peek_PORT
uvm_get_peek_PORT
2.类型
这里的PORT代表了三种端口名:port、export和imp。按照UVM端口名的命名规则,指出了通信的两个要素:
- 是不是阻塞的方式(即可以等待延时)
- 何种通信方法
3.方法
- 阻塞传输方式将blocking前缀作为函数名的一部分,而非阻塞方式则名为nonblocking。阻塞端口的方法类型为task,这保证了可以实现事件等待和延时;非阻塞端口的方式类型为function,这确保了方法调用可以立即返回。
- 我们才方法名也可以发现,例如uvm_blocking_put_PORT提供的方法task put()会在数据传送完后返回,uvm_non_blocking_put_PORT对应的两个函数try_put()和can_put()是立刻返回的。
- uvm_put_PORT则分别提供了blocking和nonblocking的方法,这为通讯方式提供了更多选择。blocking阻塞传输的方法包含:
1.put():initiator先生成数据Tt,同时将该数据传送至target。
2.get():initiator从target获取数据Tt,而target中的该数据Tt则应消耗。
3.peek():initiator从target获取数据Tt,而target中的数据Tt还应保留。 - 与上述三种任务对应的nonblocking非阻塞方法分别是:
1.try_put()
2.can_put()
3.try_get()
4.can_get()
5.try_peek()
6.can_peek() - 这六个非阻塞函数与对应阻塞任务的区别在于,它们必须立即返回,如果try_xxx函数可以发送或者获取数据,那么函数应该返回1,如果执行失败则应该返回0。
- 可以通过can_xxx函数先试探target是否可以接收数据,如果可以,再通过try_xxx函数发送,提高数据发送的成功率。
4.单向通信例子
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(int j=0;j<trans_num;j++)begin
forever begin
if(this.nbg_port.try_get(otr)==1)break;
else #1ns;
end
`uvm_info("TRYGET", $sformatf("get otrans id:'h%0x,data:'h%0x",otr.id,otr.data),UVM_LOW)
end
end
join
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 l;
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;
compl cl;
comp2 c2;
`uvm_component_utils(envl)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cl =comp1::type_id::create("cl",this);
c2 =comp2::type_id::create("c2",this);
endfunction:build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
cl.bp_port.connect(c2.bp_imp);
cl.nbg_port.connect(c2.nbg_imp);
endfunction:connect_phase
endclass
代码解析
首先comp1例化了两个port端口:
- uvm_blocking_put_port#(itrans)bp_port;
- uvm_nonblocking_get_port#(otrans)nbg_port;
comp2作为target则相应例化了两个对应的imp端口:
- uvm_blocking_put_imp#(itrans,comp2)bp_port;
- uvm_nonblocking_get_imp#(otrans,comp2)nbg_imp;
env1环境将comp1与comp2连接之前,需要在comp2中实现两个端口对应的方法:
- task put(itrans t)
- function bit try_get(output otrans t)
- function bit can_get();
接下来env1对两个组件的端口进行了连接,这使得comp1在run phase可以通过自身端口间接调用comp2中定义的端口方法。
因此在调用端口方法之前的几个步骤是必不可少的:
- 定义端口
- 实现对应方法
- 在上层将端口进行连接
二、TLM双向通信
1.概述
- 与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但是数据流向在端对端之间是双向的。
- 双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。
- UVM双向端口分为以下类型
uvm_blocking_transport_PORT
uvm_nonblocking_transport_PORT
uvm_transport_PORT
uvm_blocking_master_PORT
uvm_nonblocking_master_PORT
uvm_master_PORT
uvm_blocking_slave_PORT
uvm_nonblocking_slave_PORT
uvm_slave_PORT
2.分类
双向端口按照通信握手方式可以分为:
- transport双向通信方式
transport端口通过transport()方法,可以在同一方法调用过程中完成REQ和RSP的发出和返回。 - master和slave双向通信方式
master和slave的通信方式必须分别通过put、get和peek的调用,使用两个方法才可以完成一次握手通信。
master端口和slave端口的区别在于,当initiator作为master时,它会发起REQ送至target端(put()),而后再从target端获取RSP(get());当initiator使用slave端口时,它会先从target端获取REQ(get()),而后将RSP送至target端(put())。
对于master端口或者slave端口的实现方式,类似于之前介绍的单向通信方式,只是imp端口所在的组件需要实现的方法更多了。
3.双向通信例子
class comp1 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:'h%0x, 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, otrans,comp2) bt_imp;
`uvm_component_utils(comp2)
...
task transport(itrans req,output otrans rsp);
rsp=new("rsp",this);
rsp.id=req.id;
rsp.data=req.data<<8;
endtask
endclass
class env1 extends uvm_env;
双向端口处理类似于单向端口的是端口例化和连接,不同的只是要求实现对应的双向传输任务。
task transport(itrans req,output otrans rsp)
三、TLM多向通信
1.概述
- 多向通信(multi_directional communication)这个概念听起来容易让读者产生歧义,因为这种方式服务的仍然是两个组件之间的通信,而不是多个组件之间的通信,而不是多个组件之间的通信,毕竟多个组件的通信仍然可以由基础的两个组件的通信方式来构建。
- 多向通信指的是,如哦initiator与target之间的相同的TLM端口数目超过一时的处理解决办法。
- comp1有两个uvm_blocking_put_port,而comp2有两个uvm_blocking_put_imp端口,我们对于端口例化可以给不同名字,连接也可以通过不同名字来索引,但问题在于comp2中需要实现两个task put(itrans t),又因为不同端口之间要求在imp端口一侧实现专属方法,这就造成了命名冲突,即无法在comp2中定义两个同名的put任务。
2.端口宏声明
UVM通过端口宏声明方式来解决这一问题,它解决问题的核心在于让不同端口对应不同名的任务,这样便不会造成方法名的冲突。UVM为解决多向通信问题的宏按照端口名的命名方式分为:
3.实现步骤
当一个组件的两个端口通过相同方法(譬如task put())向另外一个组件传输数据时,就需要使用上面的宏,分别声明两个不同的imp类型,完整的实现步骤包括:
- 选择正确的imp宏来定义不同imp端口类型,而宏的参数SFX(后缀名)也会转换为相应的imp端口类型名。
在imp所例化的组件中,分别实现不同的put_SFX方法。
在port所例化的组件中,不需要对目标imp端口类型做区分,例如comp1中的bp_port1和bp_port2为相同的端口类型。
- 对于comp1调用put()方法,它只需要选择bp_port1或者bp_port2,而不需要更替put()方法名,即仍然按照put()来调用而不是put_p1()或者put_p2()。
- 在上层环境应该连接comp1和comp2的TLM端口。
用户只需要在例化多个imp端口的组件中实现不同名称的方法,使其与对应imp类型名保持一致。
而对于port端口一侧的组件,则不需要关心调用的方法名称,因为该方法名并不会发生改变。
- 所以通过这种方式可以防止通信方法名的冲突,从而解决多向通信的问题。
4.多向通信例子
`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_decl(_p2)
class comp1 extends uvm_component;
uvm_blocking_put_port#(itrans) bp_port1;
uvm_blocking_put_port#(itrans) bp_port2;
....
endclass
class comp2 extends uvm_component;
uvm_blocking_put_imp_p1#(itrans,comp2) bp_imp_p1;
uvm_blocking_put_imp_p2#(itrans,comp2) bp_imp_p2;
...
endclass
class env1 extends uvm__env;
comp1 c1;
comp2 c2;
`uvm_component_utils(env1)
function void build_phase(uvm_phase phase);
...
endfunction
function void connect_phase(uvm_phase phase);
c1.bp_port1.connect(c2.bp_imp_p1);
c1.bp_port2.connect(c2.bp_imp_p2);
endfunction:connect_phase
endclass
关注作者
- 自述
作者是一位中科大数字设计专业的研究生,水平有限,如有错误,请大家指正,想要与大家一同进步。 - 经历
曾获得国家奖学金,“高教社杯”数学建模国家二等奖等 - 陆续更新:
1.与UVM验证相关的system verilog后续内容;
2.与verilog数字设计相关的一些基础模块设计,例如FIFO,UART,I2C等的书写。
3.保研与竞赛经历等 - 微信公众号
欢迎大家关注公众号“数字IC小白的日常修炼”,期待与大家一同仗剑遨游数字IC世界。