(4)UVM TLM通信之单向通信、双向通信和多向通信

UVM TLM通信之单向通信、双向通信和多向通信

单向通信

单向通信(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实战》、《芯片验证漫游指南》和某验证视频整理而成,仅作学习心得交流,如果涉及侵权烦请请告知,我将第一时间处理。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数字ic攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值