UVM TLM

目录

 

TLM通信概述

单向、双向及多向通信

通信管道应用 

TLM Sockets

 


TLM通信概述

TLM(transaction level modeling) 用于建立各组件和系统之间的抽象传输模型。在这个模型中,数据是以传输类(transaction,这个类中包含了各种随机变量)的形式流通于各个组件之间,各个组件之间的接口称为TLM端口

UVM提供了一系列的TLM端口用来各个组件之间通信,这样就提高了各个组件的可重用性和灵活性。

如下面的transaction类simple_packet:

class simple_packet extends uvm_object;
  `uvm_object_utils (simple_packet)
 
  rand bit [7:0] addr;
  rand bit [7:0] data;
     bit     rwb;
 
  constraint c_addr { addr > 8'h2a; };
  constraint c_data { data inside {[8'h14:8'he9]};
 
endclass

它可以通过TLM端口流通于各个组件之间,如下面的A和B:

发送请求(request)的端口为port(发起者 intiator),中间接收请求的端口为export,最后实现的端口为imp(实现者 target).

在实现的过程中,TLM通信需要两个通信的对象,这两个对象分别称之为initiator objecttarget object。区分它们的方法在于,谁首先发起通信的要求,谁就属于initiator,而谁作为发起通信的响应方,谁就属于target,上面A是initiator,B是target。在初学过程中,还应该注意,通信发起方并不代表了transaction的流向起点,即不一定数据是从initiator流向target,也可能是从target流向了initiator。因此,按照transaction的流向,我们又可以将两个对象分为producer和consumer。区分它们的方法是,数据从哪里产生,它就属于producer,而数据流向了哪里,它就属于consumer 

从下面的这张图可以看出,initiator与target的关系同producer与consumer的关系,不是固定的。而有了两个参与通信的对象之后,用户需要将TLM的通信方法在target一端中实现,以便于initiator将来作为发起方可以调用target内的通信方法,实现数据传输。在target实现了必要的通信方法之后,最后一步需要将两个对象进行连接。这需要首先在两个对象内创建端口,继而在更高的层次中将这两个对象进行连接.

所以,抽象来看TLM通信的步骤可以分解为

  1. 分辨出initiator和target,producer和consumer;

  2. target中定义并实现TLM通信方法;

  3. 在两个对象中创建TLM端口,在 initiator 中调用方法;

  4. 在更高的层次中将两个对象的端口进行连接;

从数据流向的方向来看,传输的方向可以分为单向(unidirection)和双向(bidirection)。

  • 单向传输:由initiator发起request transaction。

  • 双向传输:由initiator发起request transaction,传送至target;而target在消化了request transaction后,也会发起response transaction,继而返回给initiator

 端口的按照类型可以划分为三种

  • port:经常作为initiator的发起端,也凭借port,initiator才可以访问target中实现的TLM通信方法;

  • export作为initiator和target中间层次的端口;

  • imp:只能是作为target接收request的末端,它无法作为中间层次的端口,所以imp的连接无法再次延伸

来看看下面的例子中,关于TLM端口的类型、层次和对应的连接,可以从对应的连接关系中初步得出TLM端口连接的一般做法:

  • 在initiator端中例化port,在中间层次例化export,而在target端例化imp;

  • 多个port可以连接到同一个export或者imp;但是单个port或者export无法连接多个imp,这可以抽象为多个initiator可以对同一个target发起request,但是同一个initiator无法连接多个target

  • port应为request起点,imp应为request终点,而中间可以穿越多个层次。基于单元组件的自闭性考虑,建议在这些穿越的中间层次中声明export,继而通过一级一级的连接实现数据最终的通路;

  • port可以连接port、export或者imp;export可以连接export或者impimp只能作为数据传送的终点,无法扩展连接

 以上面图中的例子为例:

class request extends uvm_transaction;
......
endclass

class response extends uvm_transaction;
....
endclass
 
class comp1 extends uvm_component;
 `uvm_component_utils(comp1)
  uvm_blocking_put_port #(request) put_port;
  ....
  function new(string name="comp1",uvm_component parent);
   super.new(name,parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    put_port=new("put_port",this);//创建端口对象
  endfunction
  ....
  virtual task run_phase(uvm_phase phase);
     super.run_phase(phase);
     request req;
     req=new("req");
     put_port.put(req);
  ....
  endtask
endclass

class comp2 extends uvm_component;
 `uvm_component_utils(comp2)
  uvm_blocking_put_port #(request) put_port;
  uvm_nonblocking_put_imp #(request,comp2) imp_port;//imp端口多一个组件参数
  ...
  function new(string name="comp2",uvm_component parent);
    super.new(name,parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   put_port=new("put_port",this);
   imp_port=new("imp_port",this);
  endfunction
  ...
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    request req;
    req=new("req");
    put_port.put(req);
.....
  endtask

   function bit can_put();
   ....
   endfunction

   function bit try_put(input request req);
     ....
   endfunction

endclass

class comp4 extends uvm_component;
 `uvm_component_utils(comp4)
  uvm_blocking_put_imp #(request,comp4) put_imp;//imp端口多一个组件参数
  uvm_nonblocking_put_port #(request) put_port;
  ...
  function new(string name="comp4",uvm_component parent);
    super.new(name,parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   put_imp=new("put_imp",this);
   put_port=new("put_port",this);
  endfunction
  ...
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    request req;
    req=new("req");
    while(!put_port.can_put()) #10;//等待can_put
    put_port.try_put(req);
.....
  endtask

   task put(input request req);
   ....
   endfunction

endclass

class comp3 extends uvm_component;
 `uvm_component_utils(comp3)
 uvm_blocking_transport_port #(request,response) t_port;

 function new(string name="comp3",uvm_component parent);
  super.new(name,parent);
 endfunction

 virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  //request req=new("req");传输的对象一般在run_phase中创建
  //response rsp=new("rsp");
  t_port=new("t_port",this);
 endfunction

 virtual task run_phase(uvm_phase phase);
   super.run_phase(phase);
   request req=new("req");
   response rsp;
   t_port.transport(req,rsp);
  ....
 endtask
endclass

class comp5 extends uvm_component;
 `uvm_component_utils(comp5)
 uvm_blocking_transport_imp #(request,response,comp5) t_imp;

 function new(string name="comp5",uvm_component parent);
  super.new(name,parent);
 endfunction

 virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  //request req=new("req");传输的对象一般在run_phase中创建
  //response rsp=new("rsp");
  t_imp=new("t_imp",this);
 endfunction

 virtual task run_phase(uvm_phase phase);
   super.run_phase(phase);
  ....
 endtask

 task transport(input request req,output response rsp);
   response rsp=new("rsp");
   .....
 endtask
endclass

class agent1 extends uvm_agent;
 `uvm_component_utils(agent1)
 uvm_blocking_put_port #(request) put_port;
 uvm_nonblocking_put_export #(request) put_export;
 uvm_blocking_transport_port #(request,response) t_port;

 comp1 c1;
 comp2 c2;
 comp3 c1;

 function new(string name="agent1",uvm_component parent);
  super.new(name,parent);
 endfunction

 virtual function void build_phase(uvm_phase phase);
   c1=comp1::type_id::create("c1",this);
   c2=comp2::type_id::create("c2",this);
   c3=comp3::type_id::create("c3",this);
   put_port=new("put_port",this);
   put_export=new("put_export",this);
   t_port=new("t_port",this);
 endfunction

 virtual function void connect_phase(uvm_phase);
   c1.put_port.conncet(this.put_port);
   c2.put_port.conncet(this.put_port);
   this.put_export.connect(c2.put_imp);
   c3.t_port.conncet(this.t_port);
 endfunction
endclass

class env1 extends uvm_env;
  `uvm_component_utils(env1)
   agent1 a1;
   comp4 c4;
   comp5 c5;

 function new(string name="env1",uvm_component parent);
   super.new(name,parent);
 endfunction

 virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   a1=agent1::type_id::create("a1",this);
   c4=comp4::type_id::create("c4",this);
   c5=comp5::type_id::create("c5",this);
 endfunction

 virtual function void connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   a1.put_port.connect(c4.put_imp);
   c4.put_port.connect(a1.put_export);
   a1.t_port.connect(c5.t_imp);
 endfunction
endclass


单向、双向及多向通信

单向通信(unidirectional communication)

单向通信指的是从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

 

这里的PORT代表了三种端口名:port、export和imp。这么一计算的话,那么对于单一方向的传输端口一共有36种。看起来这么多的端口类型似乎对读者的记忆不太友好,实际上记忆这么多的端口名是有技巧的。按照每一个端口名的命名规则,它们也指出了通信的两个要素:

  • 是否是阻塞的方式(即可以等待延时

  • 何种通信方法,包括put、get、peek、get_peek等方法,这些都需要在target中实现

 如果将这两种组合做一个表格的话,那么这些端口对应的通信方式就很清楚了,读者们也可以根据需要选择端口类型:

 

 

首先,阻塞的传输方式通过blocking的前缀来作为函数名的一部分,而非阻塞的方式则名为nonblocking。阻塞的方法类型为task,这保证了可以实现等待事件和延时非阻塞的方法类型为function,这确保了方法调用可以立刻返回。同时从方法名也可以发现,例如uvm_blocking_put_PORT提供的方法task put()可以在数据传送完毕之后返回,而uvm_nonblocking_put_PORT的两个函数try_put()和can_put()从名字来看,也是立刻返回的方式。而uvm_put_PORT则分别提供了blocking和nonblocking的方法,这为通讯方式提供了更多的选择.

其次, 再来看看数据blocking阻塞传输的方法分别包含:

  • putinitiator通过该方法可以自己生成数据T t,同时将该数据传送至target;

  • get:initiator通过该方法可以从target获取数据T t,而target中的该数据则应消耗;

  • peek:initiator通过该方法可以从target获取数据T t,而target中的该数据还应该保留

与上述三种task对应的nonblocking非阻塞的方法分别是:

  • try_put//返回值默认为1或0

  • can_put//回调方法,在调用try_xxx()方法时自动调用

  • try_get

  • can_get

  • try_peek

  • can_peek

这六个函数与其对应的task的区别在于,它们必须立即返回,如果try_xxx函数可以发送或者获取数据,那么函数还应该返回1,如果执行失败则应该返回0。或者通过can_xxx函数先试探target是否可以接收数据,通过返回值,再通过try_xxx函数发送,提高数据发送的成功率 

接下来来看下面例子,来看看如何通过上述的TLM类型端口来实现数据的传输.这个例子中,comp1为initiator,comp2为target。comp1会将数据写入(put)到comp2中,带comp2处理完之后,comp1会再从comp2中获取(get)处理过的数据

 

class itrans extends uvm_transaction;
...
endclass

class otrans extends uvm_transaction;
...
endclass

class comp1 extends uvm_component;
 `uvm_component_utils(comp1)
 uvm_blocking_put_port #(itrans) put_port;
 uvm_nonblocking_get_port #(otrans) get_port;

 function new(string name="comp1",uvm_component parent);
   super.new(name,parent);
 endfunction
 
 virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   put_port=new("put_port",this);//将TLM视作组件,所以要声明父节点
   get_port=new("get_port",this);
 endfunction

 virtual task run_phase(uvm_phase phase);
   itrans itr;
   otrans otr;
   put_port.put(itr);//调用put方法
   get_port.try_get(otr);//调用try_get方法
   .....
 endtask
.....
endclass

class comp2 extends uvm_component;
  `uvm_component_utils(comp2)
   uvm_blocking_put_imp #(itans,comp2) put_imp;//定义imp端口时会多一个组件的参数
   uvm_nonblocking_get_imp #(otrans,comp2) get_imp;

   itrans itr[$];//定义动态数组

function new(string name="comp2",uvm_component parent);
   super.new(name,parent);
 endfunction

virtual function void build_phase(uvm_phase phase);
   put_imp=new("put_imp",this);//将TLM视作组件,所以要声明父节点
   get_imp=new("get_imp",this);
 endfunction

task put(input itrans t);//定义put方法
  itr.push_back(t);
endtask

function bit can_get();
 if(itr.size() != 0)
   return 1;
 else
   return 0;
endfunction

function bit try_get(output otrans t);
   t=new("t",this);
   ....
  return 1;
endfunction
endclass

class env1 extends uvm_env;
 `uvm_component_utils(env1)
 comp1 c1;
 comp2 c2;

function new(string name="env1",uvm_component parent);
  super.new(name,parent);
endfunction

virtual 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

virtual function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  c1.put_port.connect(c2.put_imp);
  c1.get_port.connect(c2.get_imp);
endfunction
endclass

  首先在comp1中例化了两个TLM端口 :

uvm_blocking_put_port #(itrans) put_port;//在单向通信中,port和export端口的参数只有一个,就是item类

uvm_nonblocking_get_port #(otrans) get_port;

而comp2作为target,则相应地例化了两个对应的imp类型端口

   uvm_blocking_put_imp #(itrans, comp2) put_imp;

   uvm_nonblocking_get_imp #(otrans, comp2) put_imp;//注意在单向通信中,对于imp端口,参数有两个,第一个是传递的item类,第二个是实现该端口的component

在env1环境对comp1与comp2进行连接之前,需要在comp2中实现两个端口对应的方法

  • task put(itrans t)//阻塞端口需要定义为任务task

  • function bit try_get (output otrans t)

  • function bit can_get()//也就是说非阻塞端口要定义两种函数function

也由于其后的env1中对两个组件之间的端口进行了连接,这使得comp1的run phase中可以通过自身的两个端口间接调用comp2中的方法。在这里读者需要注意的是,在调用方法之前的几个步骤是必不可少的:

  • 定义端口

  • 实现对应方法

  • 在上层将端口进行连接

双向通信(bidirectional communication) 

与单向通信相同的是,双向通信的两端也分为initiator和target,但是数据的流向在端对端之间是双向的。读者也可以认为双向通信中的两端同时扮演者producer和consumer的角色,而initiator作为request的发起方,在发起了request之后,还会等待response的返回。对于目前的双向端口类型,一共分为了下面这么多种:

  • 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

 这里的PORT代表了三种端口名:port、export和imp;按照阻塞方式和通信方法,又可以将上面的这些端口种类进行划分:

 

可以将上面的端口按照通信方法分为两类:

  • transport 双向通信方式

  • master和slave 双向通信方式

 transport端口的通信通过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端口所在的组件需要实现的方法更多了。我们接下来主要为读者们介绍transport双向通信方式,因为它很明显区分于之前的单向通信方式(即调用的方法传送数据是单向的),来看下面例子:

class comp1 extends uvm_component;
 `uvm_component_utils(comp1)
  uvm_blocking_transport_port #(itrans,otrans) t_port;
  
 function new(string name="comp1",uvm_component parent);
  super.new(name,parent);
 endfunction
 
 virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   t_port=new("t_port",this);
 endfunction
 
 virtual task run_phase(uvm_phase phase);
  super.run_phase(phase);
  itrans itrs;
  otrans otrs;

  itrs=new("itrs");
  t_port.transport(itrs,otrs);
  ....
 endtask
endclass

class comp2 extends uvm_component;
 `uvm_component_utils(comp2)
  uvm_blocking_transport_imp #(itrans,otrans,comp2) t_imp;//注意imp端口多一个组件参数
  
 function new(string name="comp2",uvm_component parent);
   super.new(name,parent);
 endfunction

 virtual function void build_phase(uvm_phase phase);
   super.build_phase(phase);
   t_imp=new("t_imp",this);
 endfunction

 task transport(input itrans req,output otrans rsp);
   rsp=new("rsp");
   .....
 endtask
endclass

class env1 extends uvm_env;
 `uvm_component_utils(env1)
 comp1 c1;
 comp2 c2;
  
 function new(string name="env1",uvm_component parent);
  super.new(name,parent);
 endfunction

 virtual 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

 virtual function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  c1.t_port.connect(c2.t_imp);
 endfunction
endclass

 首先在comp1和comp2中分别例化了transport端口:

uvm_blocking_transport_port #(itrans,otrans)  t_port;//双向通信,port和export端口的参数有两个,分别表示REQ和RSP

uvm_blocking_transport_imp#(itrans,otrans,comp2)  t_imp;//双向通信,imp端口的参数有两个,分别表示REQ和RSP,以及实现该端口的component

然后在env1环境对comp1与comp2进行连接之前,需要在comp2中实现两个端口对应的方法 task transport()

最后在env1中对两个组件之间的端口进行了连接,这使得comp1的run_phase中可以通过自身的端口间接调用comp2中的方法;

从上面的例码可以看出,类似于单向端口连接的是端口的例化和连接,不同的只是要求实现对应的双向传输任务

  task transport(input itrans req, output otrans rsp)

多向通信

这里的多向通信指的是。如果intiator和target之间的相同端口的数目超过一个时的处理办法,如下图:

comp1存在两个uvm_blocking_put_port端口,而comp2存在两个uvm_blocking_put_imp端口,同时还要定义两个put()方法;我们可以在端口例化时创建不同的端口名,但是问题在于comp2中要定义两个task put(),不同的端口之间要求在imp端口一侧实现专属方法,这就造成了方法命名冲突,即无法在comp2中定义两个同名put方法。

UVM通过端口宏声明来解决这一问题,解决该问题的核心在于让不同的端口对应不同命的任务,这样不会造成方法名的冲突。简单来说就是加上声明imp端口的宏,如下: 

·uvm_blocking_put_imp_dec1(SFX)
`uvm_nonblocking_put_imp_dec1(SFX)
`uvm_put_imp_dec1(SFX)

//方法类型可以替换为其他,SFX为imp端口和方法的后缀名

以上面图中的例子为例:

`uvm_blocking_put_imp_dec1(_p1)
`uvm_blocking_put_imp_dec1(_p2)//_p1和_p2为后缀

class comp1 extends uvm_component;
 `uvm_component_utils(comp1)
  uvm_blocking_put_port #(request) put_port1;
  uvm_blocking_put_port #(request) put_port2;

function new(string name="comp1",uvm_component parent);
  super.new(name,parent);
endfunction

virtual function void build_phase(uvm_phase phase);
 super.build_phase(phase);
 put_port1=new("put_port1",this);
 put_port2=new("put_port2",this);
endfunction
virtual function void run_phase(uvm_phase phase);
 request req1,req2;
 fork
     begin
	    req1=new("req1");
		put_port1.put_p1(req1);
        //....		
	 end
	 begin
	    req1=new("req1");
		put_port1.put_p2(req1);
        //....
	 end
 join
endfunction 
endclass

class comp2 extends uvm_component;
 `uvm_component_utils(comp2)
  uvm_blocking_put_imp_p1 #(request,comp2) put_imp_p1;
  uvm_blocking_put_imp_p2 #(request,comp2) put_imp_p2;
  
  function new(string name="comp2",uvm_component parent);
   super.new(name,parent);
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	put_imp_p1=new("put_imp_p1",this);
	put_imp_p2=new("put_imp_p2",this);
  endfunction
  
  task put_p1(input request req);
     //...
  endtask
  task put_p2(input request req);
    //...
  endtask
endclass

class env1 extends uvm_env;
 `uvm_component_utils(env1)
  comp1 c1;
  comp2 c2;
  
  function new(string name="env1",uvm_component parent);
   super.new(name,parent);
  endfunction
  
  virtual function void connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   c1.put_port1.connect(c2.put_imp_p1);
   c2.put_port2.connect(c2.put_imp_p2);
  endfunction
endclass

 下面来看两个put和get的实例:

TLM Put 

下面我们来看两个组件是如何通过port和imp进行通信的。

如果我们的组件是通过继承uvm_component而来的,那么端口就已经被包括在这个组件中了。端口像其他的类一样,都需要声明和创建。声明是在组件中进行的,创建是在组件的build_phase()中使用new()来进行的。如下:

class componentA extends uvm_component;
   `uvm_component_utils (componentA)
 
   // We are creating a blocking TLM put port which will accept a "simple_packet" type of data
   uvm_blocking_put_port #(simple_packet) put_port;//声明端口
   simple_packet  pkt;
 
   function new (string name = "componentA", uvm_component parent= null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Remember that TLM put_port is a class object and it will have to be 
      // created with new ()
      put_port = new ("put_port", this);//创建端口
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      // Let us generate 5 packets and send it via the TLM put port
      repeat (5) begin
         pkt = simple_packet::type_id::create ("pkt");
         assert(pkt.randomize ()); 
         `uvm_info ("COMPA", "Packet sent to CompB", UVM_LOW)
         pkt.print (uvm_default_line_printer);
 
         // Call the TLM put() method of put_port class and pass packet as argument
         put_port.put (pkt);//调用put()发送数据,put需要在imp中描述
      end
   endtask
endclass

 可以看到,发送数据只需要用"端口名.put(数据包名)"的格式就可以了;在componentB中,我们需要描述put()方法,并且用uvm_blocking_put_imp定义一个imp端口。如下:

class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   // Mention type of transaction, and type of class that implements the put ()
   uvm_blocking_put_imp #(simple_packet, componentB) put_export;

   function new (string name = "componentB", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      put_export = new ("put_export", this);
   endfunction
 
   task put (simple_packet pkt);//定义put()方法,阻塞端口要定义为任务
      // Here, we have received the packet from componentA 
      `uvm_info ("COMPB", "Packet received from CompA", UVM_LOW)
      pkt.print ();
   endtask
endclass

然后我们需要在environment的connect_phase中连接conponentA和componentB,如下: 

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   componentA compA;
   componentB compB;
 
   function new (string name = "my_env", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      compA = componentA::type_id::create ("compA", this);
      compB = componentB::type_id::create ("compB", this);
   endfunction
 
   // Connection between componentA and componentB is done here
   virtual function void connect_phase (uvm_phase phase);
      compA.put_port.connect (compB.put_export);//发起者调用connect,响应者作为connect的参数  
   endfunction
endclass

TLM Get 

如果componentB向componentA要求数据时,我们可以用get()方法。此时B中的端口为port,A中的端口为imp.

首先需要在组件B中用uvm_blocking_get_port定义一个port,并在build_phase中创建端口对象:

class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   // Create a get_port to request for data from componentA
   uvm_blocking_get_port #(simple_packet) get_port;
 
   function new (string name = "componentB", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      get_port = new ("put_export", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      simple_packet pkt;
      repeat (5) begin
         get_port.get (pkt);//调用get()方法
         `uvm_info ("COMPB", "ComponentA just gave me the packet", UVM_LOW)
         pkt.print ();
      end
   endtask
endclass

在componentA中,我们需要描述get()方法,并且用uvm_blocking_get_imp定义一个imp。如下:

class componentA extends uvm_component;
   `uvm_component_utils (componentA)
 
   // Create an export to send data to componentB
   uvm_blocking_get_imp #(simple_packet, componentA) get_export;
   simple_packet  pkt;
 
   function new (string name = "componentA", uvm_component parent= null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Remember that put_port is a class object and it will have to be 
      // created with new ()
      get_export = new ("put_port", this);
   endfunction
 
   // This task will output a new packet 
   virtual task get (output simple_packet pkt);//定义get()方法,阻塞端口要定义为任务
      // Create a new packet
      pkt = new();
      assert (pkt.randomize());
      `uvm_info ("COMPA", "ComponentB has requested for a packet, give the following packet to componentB", UVM_LOW)
      pkt.print (uvm_default_line_printer);
   endtask
endclass

 请注意,get()方法的参数是对于组件A是输出.

在上层组件的connect_phase中将两个端口连接,如下:

virtual function void connect_phase (uvm_phase phase);
   compB.get_port.connect (compA.get_export);  
endfunction

通信管道应用 

TLM Fifo [uvm_tlm_fifo]

前面通信有一个共同的地方即都是端对端的方式,同时在target一端需要实现传输方法,例如put()或者get()。这种方式在实际使用过程中也不免会给用户带来一些烦恼:

  • 如何可以不自己实现这些传输方法,同时可以享受到TLM的好处

  • 对于monitor、coverage collector等组件在传输数据时,会存在一端到多端的传输,如何解决这一问题

本节我们将继续深入通信的方式,下面介绍的几个组件和TLM端口能够帮助用户免除上述的烦恼。首先来看看TLM FIFO的功能和用法:

uvm_tlm_fifo类是一个新的组件,它继承于uvm_component,且预先内置了多个端口、实现了多个对应方法供用户使用;从组件内部来看,它内置了一个mailbox #(T),该信箱没有尺寸限制,用来存储数据类型T,而uvm_tlm_fifo的多个端口对应的方法都是利用该信箱实现了数据读写。创建一个uvm_tlm_fifo的例子如下:

uvm_tlm_fifo #(T) tlm_fifo;//fifo中存储的类为T
tlm_fifo=new("tlm_fifo",this,2);//2为fifo的深度

new方法的定义如下:
function new(
string name,
uvm_component parent = null,
int size = 1
)

一个uvm_tlm_fifo会包含多个TLM端口,我们列举出几个常用端口供读者选择:

  • put_export:用户通过该端口调用put()、try_put()、can_put()方法;
  • get_peek_export:用户通过该端口调用get()、try_get()、can_get()、peek()、try_peek()、can_peek()方法; 

 推荐在initiator端例化put_port或者get_peek_port,来匹配uvm_tlm_fifo的端口类型。当然,如果用户例化了其它类型的端口,uvm_tlm_fifo还提供了各种各样的put、get以及peek对应的端口:

 uvm_put_imp      #(T, this_type) blocking_put_export;

 uvm_put_imp      #(T, this_type) nonblocking_put_export;

 uvm_get_peek_imp #(T, this_type) blocking_get_export;

 uvm_get_peek_imp #(T, this_type) nonblocking_get_export;

 uvm_get_peek_imp #(T, this_type) get_export;

 uvm_get_peek_imp #(T, this_type) blocking_peek_export;

 uvm_get_peek_imp #(T, this_type) nonblocking_peek_export;

 uvm_get_peek_imp #(T, this_type) peek_export;

 uvm_get_peek_imp #(T, this_type) blocking_get_peek_export;

 uvm_get_peek_imp #(T, this_type) nonblocking_get_peek_export;

可以看出,uvm_tlm_fifo所带的端口类型都是imp!!! 

在前面的例子中,我们通过port和imp实现了将数据从A发送到B,也可以在A和B之间加入一个FIFO进行缓存,如下图:

 

上面的例子如下:

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   componentA compA;
   componentB compB;
 
   // Create the UVM TLM Fifo that can accept simple_packet
   uvm_tlm_fifo #(simple_packet)    tlm_fifo;//定义TLM FIFO
 
   function new (string name = "my_env", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      compA = componentA::type_id::create ("compA", this);
      compB = componentB::type_id::create ("compB", this);
 
      // Create a FIFO with depth 2
      tlm_fifo = new ("tlm_fifo", this, 2);
   endfunction
 
   // Connect the ports to the export of FIFO.//连接A和B到FIFO
   virtual function void connect_phase (uvm_phase phase);
      compA.put_port.connect (tlm_fifo.put_export);
      compB.get_port.connect (tlm_fifo.get_export);
   endfunction
 
   // Display a message when the FIFO is full
   virtual task run_phase (uvm_phase phase);
      forever begin
         #10 if (tlm_fifo.is_full ())//uvm_tlm_fifo常用的方法见UVM手册 
               `uvm_info ("UVM_TLM_FIFO", "Fifo is now FULL !", UVM_MEDIUM)
      end
   endtask
endclass

TLM Hierarchy

我们来考虑下图中的情况:

 componentA中包含两个subcomp:subComp1和subComp2,两者之间通过TLM fifo连接;三部分的定义与前面相同,除了subComp2需要额外定义一个port以连接到componentA.这需要用到uvm_put_port,如下:

class subComponent2 extends uvm_component;
   `uvm_component_utils (subComponent2)
 
   // Mention type of transaction, and type of class that implements the put ()
   uvm_blocking_get_port #(simple_packet) get_port;
   uvm_put_port #(simple_packet)          put_portA;
 
   function new (string name = "subComponent2", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      get_port = new ("get_port", this);
      put_portA = new ("put_portA", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      simple_packet pkt;
      phase.raise_objection (this);
      repeat (5) begin
         #10;
         get_port.get (pkt);
         `uvm_info ("SCOMP2", "subComponent1 just gave me the packet", UVM_LOW)
         pkt.print ();
 
         // Remember to send this packet through the port, else ComponentB will not get data
         `uvm_info ("SCOMP2", "Send this to subComponent3", UVM_LOW)
         put_portA.put (pkt);
      end
      phase.drop_objection (this);
   endtask
endclass

在compnentA的connect_phase()中将 subComponent2的put_portA连接到compnentA的put_portA,如下:

class componentA extends uvm_component;
   `uvm_component_utils (componentA)
 
   subComponent1 subComp1;
   subComponent2 subComp2;
   uvm_tlm_fifo #(simple_packet)    tlm_fifo;
   uvm_put_port #(simple_packet)  put_portA;
 
   function new (string name = "componentA", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      subComp1 = subComponent1::type_id::create ("subComp1", this);
      subComp2 = subComponent2::type_id::create ("subComp2", this);
 
      // Create a FIFO with depth 2
      tlm_fifo = new ("tlm_fifo", this, 2);
 
      // Create a port to connect with subcomponent2
      put_portA = new ("put_portA", this);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      subComp1.put_port.connect (tlm_fifo.put_export);
      subComp2.get_port.connect (tlm_fifo.get_export);
 
      // Connect put_portA of componentA with subComponent2
      subComp2.put_portA.connect (this.put_portA);//componentA连接subComponent2
   endfunction
endclass

在componentB中,我们需要定义一个export连接到FIFO的export,这需要用到uvm_put_export,如下:

class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   subComponent3                    subComp3;
   uvm_tlm_fifo #(simple_packet)    tlm_fifo;
   uvm_put_export #(simple_packet)  put_export;
 
   function new (string name = "componentB", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      subComp3 = subComponent3::type_id::create ("subComp3", this);
 
      // Create a FIFO with depth 2
      tlm_fifo = new ("tlm_fifo", this, 2);
 
      // Create the export to connect with subComponent
      put_export = new ("put_export", this);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      // Connect from componentB export to FIFO export
      put_export.connect (tlm_fifo.put_export);//componentB连接到fifo
 
      // Connect from FIFO export to subComponent3 port 
      subComp3.get_port.connect (tlm_fifo.get_export);
   endfunction
endclass

在environment中将A的port与B的export连接:

virtual function void connect_phase (uvm_phase phase);
   compA.put_portA.connect (compB.put_export);
 endfunction

TLM Analysis Port 

前面我们讨论了通过z在imp端定义方法来进行组件间的通信,这要求在imp一端定义这两个方法,也就是说必须要有imp端。UVM提供了一种分析端口(analysis port),它在组件中是以广播(broadcast)的形式向外发送数据的,而不管存在几个imp或者没有imp.

分析端口的根据端口类型的不同分为uvm_analysis_port、uvm_analysis_export、uvm_analysis_imp,如下例:

class componentB extends uvm_component;
  `uvm_component_utils (componentB)
 
  // Create an analysis port by the name "ap" that can broadcast packets of type "simple_packet"
  uvm_analysis_port #(simple_packet) ap;//定义分析port端口
 
  function new (string name = "componentB", uvm_component parent = null);
    super.new (name, parent);
  endfunction
 
  virtual function void build_phase (uvm_phase phase);
    super.build_phase (phase);
    ap = new ("analysis_port", this);//创建端口对象
  endfunction
 
  virtual task run_phase (uvm_phase phase);
    super.run_phase (phase);
 
    for (int i = 0; i < 5; i++) begin
        simple_packet pkt = simple_packet::type_id::create ("pkt");
        pkt.randomize();
           // Now pass it to other components via the analysis port write() method
          ap.write (pkt);//调用write()方法,将数据发送出去
    end
  endtask
endclass

在分析端口的订阅端(subscriber) ,我们需要定义write()方法;订阅端通过继承uvm_subscriber得到:

virtual class uvm_subscriber #(type T=int) extends uvm_component;
  typedef uvm_subscriber #(T) this_type;
 
  uvm_analysis_imp #(T, this_type) analysis_export;//定义分析imp端口
 
  pure virtual function void write (T t);
endclass

这里与前面不同的是,即使没有target与intiator相连,intiator调用write()方法也不会报错! 可以看到在uvm_subscriber中已经定义了分析端口analysis_export,所以在其子类中直接使用即可,如下:

class sub #(type T = simple_packet) extends uvm_subscriber #(T);
   `uvm_component_utils (sub)
 
   function new (string name = "sub", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
   endfunction
 
   // Note that the class object name has to be "t" - anything else will result
   // in compilation error
   virtual function void write (input T t);
      `uvm_info (get_full_name(), "Sub got transaction", UVM_MEDIUM)
   endfunction
endclass

 在environment中通过分析端口将B与sub1、sub2、sub3连接,如下:

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   componentA  compA;
   componentB  compB;
   sub         sub1; 
   sub         sub2; 
   sub         sub3; 
 
   function new (string name = "my_env", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      compA = componentA::type_id::create ("compA", this);
      compB = componentB::type_id::create ("compB", this);
      sub1 = sub::type_id::create ("sub1", this);
      sub2 = sub::type_id::create ("sub2", this);
      sub3 = sub::type_id::create ("sub3", this);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      compA.put_port.connect (compB.put_export);  
 
      // Connect Analysis Ports
      compB.ap.connect (sub1.analysis_export);
      compB.ap.connect (sub2.analysis_export);
      compB.ap.connect (sub3.analysis_export);
   endfunction
endclass

Analysis TLM FIFO

 由于上面的分析端口实现了一端到多端的TLM数据传输,一个新的数据缓存组件类uvm_tlm_analysis_fifo为用户提供了可以搭配uvm_analysis_port端口、uvm_analysis_imp端口、以及write()函数。uvm_tlm_analysis_fifo继承于uvm_tlm_fifo,这表明它本身具有面向单一TLM端口的数据缓存特性,也就是说它具有uvm_tlm_fifo的所有端口,同时该类又有一个uvm_analysis_imp端口 analysis_export并且实现了write()函数,从而用这个端口连接uvm_analysis_port端口

例子如下:

 

class intiator extends uvm_component;
 `uvm_component_utils(intiator)
  uvm_analysis_port #(T) ap;//创建分析端口句柄
  function new(string name="intiator",uvm_component parent);
    super.new(name,parent);
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	ap=new("ap",this);
  endfunction  
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
	T t=new("t");
	ap.write(t);
	//....
  endtask
endclass

class target extends uvm_component;
 `uvm_component_utils(target)
  uvm_get_port #(T) get_port;
  
  function new(string name="target",uvm_component parent);
    super.new(name,parent);
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	get_port=new("get_port",this);
  endfunction 
  
  function write(input T t);
    //....
  endfunction
endclass

class env1 extends uvm_env;
 intiator in1;
 target t1,t2,t3;
 uvm_tlm_analysis_fifo #(T) fifo1,fifo2,fifo3;
 `uvm_component_utils(env1)
 
 function new(string name="env1",uvm_component parent);
    super.new(name,parent);
  endfunction
  
 virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
	in1=intiator::type_id::create("in1",this);
	t1=target::type_id::create("t1",this);
	t2=target::type_id::create("t2",this);
	t3=target::type_id::create("t3",this);
	fifo1=new("fifo1",this);//uvm_tlm_analysis_fifo的深度不限
	fifo2=new("fifo2",this);
	fifo3=new("fifo3",this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   in1.ap.connect(fifo1.analysis_export);//分析端口连接到fifo提供write方法的端口
   in1.ap.connect(fifo2.analysis_export);
   in1.ap.connect(fifo3.analysis_export);
   t1.get_port.connect(fifo1.get_export);
   t2.get_port.connect(fifo2.get_export);
   t3.get_port.connect(fifo3.get_export);
  endfunction 
endclass

TLM Sockets

TLM2.0引入了socket的概念用来实现在发起端(initiator)目的端(target)异步双向数据传输.目的端的socket只能连接发起端的socket,反之也一样。

来看下面的例子,定义发起端socket:

class initiator extends uvm_component;
   `uvm_component_utils (initiator)
 
   // Declare a blocking transport socket (using initiator socket class)
   uvm_tlm_b_initiator_socket #(simple_packet) initSocket;//定义发起端socket
   uvm_tlm_time   delay;//定义发送延时
   simple_packet  pkt;
 
   function new (string name = "initiator", uvm_component parent= null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
 
      // Create an instance of the socket
      initSocket = new ("initSocket", this);
      delay = new ();
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      // Let us generate 5 packets and send it via socket
      repeat (5) begin
         pkt = simple_packet::type_id::create ("pkt");
         assert(pkt.randomize ()); 
         `uvm_info ("INIT", "Packet sent to target", UVM_LOW)
         pkt.print (uvm_default_line_printer);
 
         // Use the socket to send data
         initSocket.b_transport (pkt, delay);//调用b_transport()方法,该方法允许时钟延时
      end
   endtask
endclass

在目的端除了要定义目的端socket外,还需要定义b_transport()方法,如下:

class target extends uvm_component;
   `uvm_component_utils (target)
 
   // Declare a blocking target socket
   uvm_tlm_b_target_socket #(target, simple_packet) targetSocket;
 
   function new (string name = "target", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
 
      // Create an instance of the target socket
      targetSocket = new ("targetSocket", this);
   endfunction
 
  // Provide the implementation method of b_transport in the target class
   task b_transport (simple_packet pkt, uvm_tlm_time delay);
      `uvm_info ("TGT", "Packet received from Initiator", UVM_MEDIUM)
      pkt.print (uvm_default_line_printer);
   endtask
endclass

在environment中将发起端和目的端连接:

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   initiator   init;
   target      tgt;
 
   function new (string name = "my_env", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      init = initiator::type_id::create ("init", this);
      tgt = target::type_id::create ("tgt", this);
   endfunction
 
   // Connect both sockets in the connect_phase
   virtual function void connect_phase (uvm_phase phase);
      init.initSocket.connect (tgt.targetSocket);  
   endfunction
endclass

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值