目录
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 object和target 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通信的步骤可以分解为:
-
分辨出initiator和target,producer和consumer;
-
在target中定义并实现TLM通信方法;
-
在两个对象中创建TLM端口,在 initiator 中调用方法;
-
在更高的层次中将两个对象的端口进行连接;
从数据流向的方向来看,传输的方向可以分为单向(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或者imp;imp只能作为数据传送的终点,无法扩展连接;
以上面图中的例子为例:
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阻塞传输的方法分别包含:
-
put:initiator通过该方法可以自己生成数据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