(13)UVM 史上最全TLM单向/双向/多向通信介绍

发布于:2023-01-04 ⋅ 阅读:(310) ⋅ 点赞:(0)

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端口名的命名规则,指出了通信的两个要素:

  1. 是不是阻塞的方式(即可以等待延时)
  2. 何种通信方法
    在这里插入图片描述

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世界。