UVM实战 卷I学习笔记7——UVM中的TLM通信(1)


TLM1.0

验证平台内部的通信

在这里插入图片描述

  • 如果要在两个uvm_component之间通信,如一个monitor向一个scoreboard传递一个数据(如图所示):最简单的方法就是使用全局变量,在monitor里对此全局变量进行赋值,在scoreboard里监测此全局变量值的改变。这种方法简单、直接,不过要避免使用全局变量,滥用全局变量只会造成灾难性的后果。
  • 稍微复杂的方法:在scoreboard中有一个设置为外部可直接访问(public)的变量在monitor中对此变量赋值,如下图所示。完成这个任务,要在monitor中有一个指向scoreboard的指针,否则虽然scoreboard把这个变量设置为非local类型的,但monitor依然无法改变。
    在这里插入图片描述
  • 这种方法的问题在于——整个scoreboard里面所有非local类型的变量都对monitor是可见的,假如monitor的开发人员不小心改变了scoreboard中的一些变量,那么后果将可能会是致命的。
  • 由config机制的特性可以想出第三种方法:即从uvm_object派生出一个参数类config_object,在此类中有monitor要传给scoreboard的变量。在base_test中实例化这个config_object,并将其指针通过config_db#(config_object)::set传递给scoreboard和monitor。当monitor要和scoreboard通信时,只要把config_object中相应变量的值改变即可。scoreboard监测变量值的改变之后做相应动作。
  • 这种方法比上面的两种方法都要好,但仍然显得有些笨拙。一是要引入专门的config_object类,二是一定要有base_test这个第三方的参与。大多数情况下这个第三方是不会惹麻烦的。但不能保证某一个从base_test派生而来的类会不会改变这个config_object类中某些变量的值。也就是依然存在一定的风险。
  • 上述问题只是最简单的情况,如果加入阻塞和非阻塞的概念则会更加复杂。当monitor向scoreboard传递数据时,scoreboard可能并不一定有时间立刻接收这些数据。此时对于monitor来说有两种处理方法,一种是等在那里直到scoreboard处理完事情后接收新的数据(阻塞操作),另一种是不等待,直接返回,至于后面是过一段时间继续发还是直接放弃不发了,则要看代码编写者的行为(非阻塞操作)。
  • 除了阻塞及非阻塞外,还存在的一个问题是如果scoreboard主动要求向monitor请求数据,这样的行为方式如何实现?
  • 这些问题使用SV中的一些机制,如Semaphore、Mailbox,再结合其他技术等都能实现,但其中的问题在于这种通信显得非常复杂,用户需浪费大量时间编写通信相关的代码。解决这些问题最好的办法就是在monitor和scoreboard之间专门建立一个通道,让信息只能在这个通道内流动,scoreboard也只能从这个通道中接收信息,就可保证scoreboard中的信息只能从monitor中来而不能从别的地方来;同时赋予这个通道阻塞或非阻塞等特性。UVM中的各种端口就可以实现这种功能。

TLM的定义

TLM是Transaction Level Modeling(事务级建模)的缩写,起源于SystemC的一种通信标准。所谓transaction level是相对DUT中各个模块之间信号线级别的通信来说的。简单来说,一个transaction就是把具有某一特定功能的一组信息封装在一起而成为的一个类。如my_transaction就是把一个MAC帧里的各个字段封装在了一起。

UVM中的TLM共有两个版本,分别是TLM1.0和TLM2.0,后者在前者的基础上做了扩展。使用TLM1.0足以搭建起一个功能强大的验证平台。

TLM通信中有如下几个常用的术语:
1)put操作如下图所示,通信的发起者A把一个transaction发送给B。在这个过程中A称为“发起者”,而B称为“目标”。A具有的端口称为PORT(方框),而B的端口称为EXPORT(圆圈)(这里感觉没有路桑红宝书形象)。这个过程中数据流是从A流向B的。
在这里插入图片描述
2)get操作如下图所示,A向B索取一个transaction。在这个过程中A依然是“发起者”,B依然是“目标”,A上的端口依然是PORT,而B上的端口依然是EXPORT。这个过程中数据流是从B流向A的。PORT和EXPORT体现的
是控制流而不是数据流。get操作中,数据是从EXPORT流向PORT的,但无论是get还是put操作,其发起者拥有的都是PORT端口而不是EXPORT。作为EXPORT来说只能被动地接收PORT的命令
在这里插入图片描述
3) transport操作如下图所示,transport操作相当于一次put操作加一次get操作,这两次操作的“发起者”都是A,目标都是B。A上的端口是PORT,而B上的端口依然是EXPORT。在这个过程中数据流先从A流向B,再从B流向A。相当于是A向B提交了一个请求(request),而B返回给A一个应答(response)。所以这种transport操作也常常被称做request-response操作。
在这里插入图片描述
put、get和transport操作都有阻塞和非阻塞之分

UVM中的PORT与EXPORT

UVM提供对TLM操作,在其中实现了PORT与EXPORT。不同操作有不同的PORT,UVM中常用的PORT有:(EXPORT和PORT类似,只是名字替换一下而已)

uvm_blocking_put_port#(T);
uvm_nonblocking_put_port#(T);
uvm_put_port#(T);
uvm_blocking_get_port#(T);
uvm_nonblocking_get_port#(T);
uvm_get_port#(T);
uvm_blocking_peek_port#(T);
uvm_nonblocking_peek_port#(T);
uvm_peek_port#(T);
uvm_blocking_get_peek_port#(T);
uvm_nonblocking_get_peek_port#(T);
uvm_get_peek_port#(T);
uvm_blocking_transport_port#(REQ, RSP);
uvm_nonblocking_transport_port#(REQ, RSP);
uvm_transport_port#(REQ, RSP);

前12个定义中的参数就是这个PORT中的数据流类型,最后3个定义中的参数表示transport操作中发起请求时传输的数据类型和返回的数据类型。这几种PORT对应TLM中的操作,同时以blocking和nonblocking关键字区分。对于名称中不含这两者的,则表示这个端口既可以用作是阻塞的,也可以用作是非阻塞的。

PORT和EXPORT体现的是一种控制流,在这种控制流中,PORT具有高优先级,而EXPORT具有低优先级。 只有高优先级的端口才能向低优先级的端口发起这三种操作。

UVM中各种端口的互连

PORT与EXPORT的连接

如图所示ABCD四个端口,要在A和B之间、C和D之间通信。为实现这个目标,必须要在A和B之间、C和D之间建立一种连接关系,否则A如何知道是和B通信而不是和C或者D通信呢?所以一定要在通信前建立连接关系。
在这里插入图片描述
UVM中使用connect函数建立连接关系。如A要和B通信(A是发起者):A.port.connect(B.export),但不能写成B.export.connect(A.port)。因为在通信的过程中A是发起者,B是被动承担者。这种通信时的主次顺序也适用于连接时,只有发起者才能调用connect函数,而被动承担者则作为connect的参数。

使用上述方式建立A.PORT和B.EXPORT之间的连接关系。A的代码为:

class A extends uvm_component;
	`uvm_component_utils(A)
	uvm_blocking_put_port#(my_transaction) A_port;
	…
endclass
function void A::build_phase(uvm_phase phase);
	super.build_phase(phase);
	A_port = new("A_port", this);
endfunction
task A::main_phase(uvm_phase phase);
endtask

其中A_port在实例化的时第一个参数是名字,第二个参数是一个uvm_component类型的父结点变量。事实
上,一个uvm_blocking_put_port的new函数的原型如下:function new(string name,uvm_component parent,int min_size = 1;int max_size = 1);

如果不看后两个参数,那么这个new函数其实是uvm_component的new函数。new函数中的min_size和max_size指的是必须连接到这个PORT的下级端口数量的最小值和最大值,即这个PORT应调用的connect函数的最小值和最大值。如果采用默认值,即min_size=max_size=1,则只能连接一个EXPORT

B的代码为:

class B extends uvm_component;
	`uvm_component_utils(B)
	uvm_blocking_put_export#(my_transaction) B_export;
	…
endclass
function void B::build_phase(uvm_phase phase);
	super.build_phase(phase);
	B_export = new("B_export", this);
endfunction
task B::main_phase(uvm_phase phase);
endtask

在env中建立两者之间的连接:

class my_env extends uvm_env;
	A A_inst;
	B B_inst;
	…
	virtual function void build_phase(uvm_phase phase);
		…
		A_inst = A::type_id::create("A_inst", this);
		B_inst = B::type_id::create("B_inst", this);
	endfunction
	…
endclass
function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	A_inst.A_port.connect(B_inst.B_export);
endfunction

运行上述代码,可以看到仿真器给出如下的错误提示:

# UVM_ERROR @ 0: uvm_test_top.env.B_inst.B_export [Connection Error] connection count of 0 does not mee
# UVM_ERROR @ 0: uvm_test_top.env.A_inst.A_port [Connection Error] connection count of 0 does not meet
# UVM_FATAL @ 0: reporter [BUILDERR] stopping due to build errors

connect函数的使用没有什么问题,A_port与B_export的连接也没有问题,那么问题出在什么地方?

上述的put操作A通过其端口A_port把一个transaction传送给B,PORT恰如一道门,EXPORT也如此,那么它们也就只是一个通行的作用,它不可能把一笔transaction存储下来,除了转发操作之外不作其他操作。因此,这笔transaction一定要由B_export后续的某个组件进行处理。UVM中完成这种后续处理的也是一种端口:IMP。

*UVM中的IMP

IMP才是UVM中的精髓,承担了UVM中TLM的绝大部分实现代码。UVM中的IMP如下所示:

uvm_blocking_put_imp#(T, IMP);
uvm_nonblocking_put_imp#(T, IMP);
uvm_put_imp#(T, IMP);
uvm_blocking_get_imp#(T, IMP);
uvm_nonblocking_get_imp#(T, IMP);
uvm_get_imp#(T, IMP);
uvm_blocking_peek_imp#(T, IMP);
uvm_nonblocking_peek_imp#(T, IMP);
uvm_peek_imp#(T, IMP);
uvm_blocking_get_peek_imp#(T, IMP);
uvm_nonblocking_get_peek_imp#(T, IMP);
uvm_get_peek_imp#(T, IMP);
uvm_blocking_transport_imp#(REQ, RSP, IMP);
uvm_nonblocking_transport_imp#(REQ, RSP, IMP);
uvm_transport_imp#(REQ, RSP, IMP);

IMP定义中的blocking、nonblocking、put、get、peek、get_peek、transport等关键字的意思并不是它们发起相应类型的操作,只意味着它们可以和相应类型的PORT或EXPORT进行通信,且通信时作为被动承担者。按照控制流的优先级排序,三种端口顺序为:PORT、EXPORT、IMP。IMP的优先级最低,一个PORT可以连接到一个IMP并发起三种操作,反之不行。

前六个IMP定义中的第一个参数T是IMP传输的数据类型。第二个参数IMP,UVM解释为实现这个接口的一个component

以blocking_put端口为例,图中A_port连接到B_export、B_export连接到B_imp。写下A.A_port.put(transaction)时,B.B_imp会通知B有transaction过来了,这个过程是如何进行的呢?可简单理解成A.A_port.put(transaction)会调用B.B_export的put,B.B_export的put(transaction)又会调用B.B_imp的put(transaction),而B_imp.put最终又会调用B的相关任务,如B.put(transaction)。所以关于A_port的操作最终会落到B.put这个任务上,该任务是属于B的任务,与A及A的PORT无关,也与B的EXPORT和IMP无关。也就是这些put操作最终要由B这个component来实现,即要由一个component来实现接口的操作。所以每个IMP要和一个component相对应
在这里插入图片描述
有了IMP之后,PORT与EXPORT之间的连接就可以实现了。A的代码为:

class A extends uvm_component;
	`uvm_component_utils(A)
	uvm_blocking_put_port#(my_transaction) A_port;
	…
endclass
…
task A::main_phase(uvm_phase phase);
	my_transaction tr;
	repeat(10) begin
		#10;
		tr = new("tr");
		assert(tr.randomize());
		A_port.put(tr);
	end
endtask

B的代码为:(关键是要实现put函数,如果不实现会报错)

class B extends uvm_component;
	`uvm_component_utils(B)
	uvm_blocking_put_export#(my_transaction) B_export;
	uvm_blocking_put_imp#(my_transaction, B) B_imp;
...
endclass
function void B::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	B_export.connect(B_imp);
endfunction
function void B::put(my_transaction tr);
	`uvm_info("B","receive a transaction", UVM_LOW)
	tr.print();
endfunction

此处env的代码和上面my_env的代码一致;运行上述代码可以见到B正确地收到A发出的transaction。在上述连接关系中,IMP是作为连接的终点。在UVM中只有IMP才能作为连接关系的终点。如果是PORT或者EXPORT作为终点,则会报错。

*PORT与IMP的连接

UVM三种端口按控制流优先级排列:PORT优先级最高,IMP最低。因此PORT可调用connect函数并把IMP作为函数调用时的参数。假如有三个component:A、B和env,其中env是A和B的父结点,现在要把A中的PORT和B中的IMP连接起来实现通信,如下图所示:
在这里插入图片描述
A的定义与上面A的代码相同,B的定义如下:

class B extends uvm_component;
	`uvm_component_utils(B)
	uvm_blocking_put_imp#(my_transaction, B) B_imp;//对应A的blocking_put_port
…
endclass
…
function void B::put(my_transaction tr);
	`uvm_info("B", "receive a transaction", UVM_LOW)
	tr.print();
endfunction

B中的关键是定义put,A_port的put操作最终要落到B的put上。所以在B中要定义名字为put的任务/函数。这里有如下的规律:

  • 当A_port的类型是nonblocking_put,B_imp的类型是nonblocking_put时,就要在B中定义名为try_put和can_put的函数。
  • 当A_port的类型是put,B_imp的类型是put时,就要在B中定义3个接口:put任务/函数,try_put函数和can_put函数
  • 当A_port和B_imp的类型是blocking_get时,就要在B中定义名为get的任务/函数。
  • 当A_port和B_imp的类型都是nonblocking_get时,就要在B中定义名为try_get和can_get的函数。
  • 当A_port和B_imp的类型是get时,就要在B中定义3个接口:get任务/函数,try_get函数和can_get函数
  • 当A_port和B_imp的类型是blocking_peek时,就要在B中定义名为peek的任务/函数
  • 当A_port和B_imp的类型是nonblocking_peek时,就要在B中定义名字为try_peek和can_peek的函数
  • 当A_port和B_imp的类型是peek时,就要在B中定义3个接口:peek任务/函数,try_peek函数和can_peek函数
  • 当A_port和B_imp的类型是blocking_get_peek时,就要在B中定义名为get和peek的任务/函数
  • 当A_port和B_imp的类型是nonblocking_get_peek时,就要在B中定义名字为try_get和can_get、try_peek和can_peek的函数
  • 当A_port和B_imp的类型是get_peek时,就要在B中定义6个接口:get任务/函数,try_get函
    数,can_get函数,peek任务/函数,try_peek函数和can_peek函数
  • 当A_port和B_imp的类型是blocking_transport时,就要在B中定义名为transport的任务/函数
  • 当A_port和B_imp的类型是nonblocking_transport时,就要在B中定义名为nb_transport的函数
  • 当A_port和B_imp的类型是transport时,就要在B中定义两个接口:transport任务/函数和nb_transport函数

在这些规律中,对于所有blocking系列的端口来说,可以定义相应的任务或函数,如对于blocking_put端口来说,可以定义名为put的任务或函数。这是因为A会调用B中名为put的接口,而不管这个接口的类型。由于A中的put是个任务,所以B中的put可以是任务或函数。但对于nonblocking系列端口来说,只能定义函数

当B中完成B_imp和put的定义后,在env的connect_phase就需要把A_port和B_imp连接在一起了:

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	A_inst.A_port.connect(B_inst.B_imp);
endfunction

connect函数一定要在connect_phase调用。连接完成后,当在A中通过put向A_port写入一个transaction时,B的put马上会被调用并执行其中的代码。A的定义与上面A的代码相同,A向A_port写入了10个transaction,因此B的put会被调用10次。

*EXPORT与IMP的连接

PORT可以与IMP相连接,同样的EXPORT也可以与IMP相连接,其连接方法与PORT和IMP的连接完全一样。要实现A中的EXPORT与B中的IMP连接,A和的代码没有变化。my_env中的连接关系为:

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	A_inst.A_export.connect(B_inst.B_imp);
endfunction

与上节中的例子对比可以发现,除了A_port变成B_export之外,其他没有任何改变。在B中也必须定义一个名字为put的任务。上节中罗列的规律对于EXPORT依然适用。

*PORT与PORT的连接

前面的连接中都是不同类型的端口之间连接(PORT与IMP、PORT与EXPORT、EXPORT与IMP),且不存在层次的关系。在UVM中支持带层次的连接关系,如下图所示:
在这里插入图片描述
在上图中A与C中是PORT、B中是IMP。UVM支持C的PORT连接到A的PORT并最终连接到B的IMP。C和A的代码为:(B和env的代码和上节一致)

class C extends uvm_component;
	`uvm_component_utils(C)
	uvm_blocking_put_port#(my_transaction) C_port;
…
endclass
…
task C::main_phase(uvm_phase phase);
	my_transaction tr;
	repeat(10) begin
		#10;
		tr = new("tr");
		assert(tr.randomize());
		C_port.put(tr);
end
endtask

class A extends uvm_component;
	`uvm_component_utils(A)
	C C_inst; //需在A里面实例化C
	uvm_blocking_put_port#(my_transaction) A_port;
…
endclass
function void A::build_phase(uvm_phase phase);
	super.build_phase(phase);
	A_port = new("A_port", this);
	C_inst = C::type_id::create("C_inst", this);
endfunction
function void A::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	C_inst.C_port.connect(this.A_port);
endfunction
task A::main_phase(uvm_phase phase);
endtask

PORT与PORT之间的连接不只局限于两层,可以有无限多层。

*EXPORT与EXPORT的连接

UVM同样支持EXPORT与EXPORT之间的连接,如下图所示:A中是PORT,B与C中是EXPORT,B中还有一个IMP。C的EXPORT连接到B的EXPORT,并最终连接到B的IMP。
在这里插入图片描述
C的代码:

class C extends uvm_component;
	`uvm_component_utils(C)
	B B_inst;
	uvm_blocking_put_export#(my_transaction) C_export;
	…
endclass
function void C::build_phase(uvm_phase phase);
	super.build_phase(phase);
	C_export = new("C_export", this);
	B_inst = B::type_id::create("B_inst", this);
endfunction
function void C::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	this.C_export.connect(B_inst.B_export);
endfunction
task C::main_phase(uvm_phase phase);
endtask

env中的连接关系为:

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	A_inst.A_port.connect(C_inst.C_export);
endfunction

同样,EXPORT与EXPORT之间的连接也不只局限于两层,也可以有无限多层

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值