UVM实战笔记(四)

第四章. UVM中的TLM1.0通信
4.1 TLM1.0

4.1.1 验证平台内部的通信

  • 全局变量:简单,直接,但应尽量避免使用全局变量

  • public变量:组件A中的变量设置为public,在组件B中设置一个指向A中public变量的指针,需要对变量类型设置为public

  • config:引入config_object类,在base_test中进行实例化,然后使用config_db将config_object的指针分别传递给通讯组件A,B,这样就可以通过访问config_object进行通信。过程繁琐

4.1.2 TLM的定义

TLM是Transaction Level Modeling(事务级建模)的缩写,起源于SystemC的一种通信标准。TLM就是在通信组件A,B之间专门建立一个通道,让信息只能在通道内流动。这样几乎就可以保证A中的信息只能从B中来。同时可以赋予这个通道阻塞或者非阻塞等特性。所谓transaction level是相对于DUT中各模块之间信号线级别的通信来说的。UVM中的TLM共有两个版本,分别是TLM1.0和TLM2.0。

TLM通讯中有一下几个常用的术语:

  • put:通信发起者A将一个transaction发送给B。A为动作的发起者,B称为目标。A中的端口称为PORT,B中的端口称为EXPORT,这个过程中数据是从A流向B的。

  • get:通讯发起者A向B索要一个transaction。A依然为动作发起者,B称为目标。A中的端口称为PORT,B中的端口称为EXPORT,这个过程中数据是从B流向A的。

  • transport:transport相当于一次put+get。两次操作的发起者都是A,目标都是B。A中的端口依然是PORT,B中的端口依然是EXPORT。数据流先从A流向B,再从B流向A。在现实世界中,相当于A向B提交了一个请求,B返回给A一个应答,所以transport也叫做request-response。

put/get/transport都有阻塞/非阻塞之分。
在这里插入图片描述
在这里插入图片描述

4.1.3 UVM中的PORT

UVM中常用的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);

按照端口阻塞/非阻塞分为:

  • uvm_blocking_***_port #(T):阻塞端口

  • uvm_noblocking_***_port #(T):非阻塞端口

  • uvm_***_port #(T):既可以用作阻塞,也可以用作非阻塞

按照端口的操作类型分为:

  • put:执行put操作

  • get:执行get操作

  • peek:执行peek操作,与get的区别在于查看数据后将数据放回

  • get_peek:可以执行get和peek操作,相当于get+peek端口

  • transport:执行transport操作

UVM把一个端口固定为只能执行某种操作,阻塞/非阻塞,操作类型之间组合。因此在定义端口时应该考虑清楚其都将会用到什么操作,然后根据需要用到的操作定义相应的端口类型。

4.1.4 UVM中的EXPORT

UVM中常用的EXPORT:

uvm_blocking_put_export#(T);
uvm_nonblocking_put_export#(T);
uvm_put_export#(T);
uvm_blocking_get_export#(T);
uvm_nonblocking_get_export#(T);
uvm_get_export#(T);
uvm_blocking_peek_export#(T);
uvm_nonblocking_peek_export#(T);
uvm_peek_export#(T);
uvm_blocking_get_peek_export#(T);
uvm_nonblocking_get_peek_export#(T);
uvm_get_peek_export#(T);
uvm_blocking_transport_export#(REQ, RSP);
uvm_nonblocking_transport_export#(REQ, RSP);
uvm_transport_export#(REQ, RSP);

类似PORT,按端口是否阻塞分为:

  • uvm_blocking_***_export #(T):阻塞端口

  • uvm_noblocking_***_export #(T):非阻塞端口

  • uvm_***_export #(T):既可以用作阻塞,也可以用作非阻塞

类似PORT,按照端口操作类型分为:

  • put:执行put操作

  • get:执行get操作

  • peek:执行peek操作,与get的区别在于查看数据后将数据放回

  • get_peek:可以执行get和peek操作,相当于get+peek端口

  • transport:执行transport操作

EXPORT端口的类型与PORT的端口一一对应。PORT和EXPORT的分类体现的是一种控制流,即PORT具有高的优先级,而EXPORT具有低的优先级。只有高优先级的端口才能向低优先级的端口发起操作

4.1.5 UVM中的IMP

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

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);

类似PORT,按是否阻塞分为:

  • uvm_blocking_***_imp #(T, IMP):阻塞端口
  • uvm_noblocking_***_imp #(T, IMP):非阻塞端口
  • uvm_***_imp #(T, IMP):既可以用作阻塞,也可以用作非阻塞

按照端口的操作类型:

  • put:执行put操作
  • get:执行get操作
  • peek:执行peek操作,与get的区别在于查看数据后将数据放回
  • get_peek:可以执行get和peek操作,相当于get+peek端口
  • transport:执行transport操作

15种IMP与PORT和EXPORT分别一一对应。

注:以上构造函数中T参数为端口连接中传输的数据类型,IMP参数为uvm_component类型(详见4.2.1)。

4.2 UVM中各种端口的互联

4.2.1 PORT与EXPORT的连接

UVM中使用connect函数建立连接关系。通信的发起者A调用connect函数对目标B进行连接。只有通信的发起者A才能调用connect函数,而通信的目标对象B只能作为connect的参数。

A.port.connect(B.export);

EXPORT不能作为连接的终点,如果不对此连接做处理,则会报错。只有IMP可以作为连接的终点,通常是将EXPORT作为连接的中间点,如下是PORT连接到EXPORT,EXPORT再连接到IMP。

在这里插入图片描述

按照控制流的优先级顺序:PROT>EXPORT>IMP。高优先级的可以作为通信的发起者,低优先级的作为通信的承受者。其中IMP的优先级最低,一个PORT可以连接到一个IMP,反之不行。

当在A中执行A.A_port.put(transaction)时,会调用B的put操作,然后B调用B.B_export.put(transaction),此时又会调用B.B_imp.put(transaction)。所以A_port的操作最终会落到B.put这个任务上,这个任务是属于组件B的一个任务,与A无关,与A的PORT无关,也与B的EXPORT和IMP无关。也就是说,这些put操作最终还是要由B这个组件来实现,即要由一个component来实现接口的操作,作为相应操作的执行者,所以每一个IMP要和一个component相对应

4.2.2 PORT与IMP连接
在这里插入图片描述

A中的put操作最终会由组件B调用其put函数/任务来实现,所以组件B中的关键是定义一个put函数/任务。又根据接口的阻塞/非阻塞类型需要实现相应的操作:

  • 非阻塞(noblocking)

    • try_***
    • can_***
  • 阻塞(blocking)

    • 只需定义相应的操作(put,get,peek,get_peek)即可
  • 阻塞和非阻塞

    • try_***
    • can_***
    • 相应的阻塞操作put,get,peek,get_peekt)

注:如果端口类型为transport,定义函数有变化,分别为transport(阻塞),nb_transport(非阻塞)。

上述端口中对于bolcking系列端口,可以定义相应的任务或函数对于noblocking系列端口,只能定义函数connect函数一定要在connect_phase调用。

4.2.3 EXPORT与IMP的连接

与PORT和IMP连接相似,EXPORT也可以与IMP连接。这里需要突出的是EXPORT是作为传输的起点与IMP连接。

4.2.4 PORT与PORT连接

以上的连接中,都是不同类型的端口类型连接(PROT与IMP,PORT与EXPORT,EXPORT与IMP),且不存在层次的关系。UVM中,支持相同端口的连接,支持带层次的连接关系(相同的端口连接是为了突出层次关系)
在这里插入图片描述

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

4.2.5 EXPORT与EXPORT连接
在这里插入图片描述

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

4.3 UVM中的通信方式

4.3.1 analysis端口

analysis_port和analysis_export,与PORT/EXPORT端口类似,都用于传递transaction,区别在于:

  • 默认情况下,一个analysis_port/analysis_export可以连接多个IMP,即一对多通信。而PORT和EXPORT端口与相应IMP通信是一对一analysisg_port更像是一个广播
  • analysis_port/analysis_export没有阻塞非阻塞的概念

在这里插入图片描述

analysis_port连接的终点的IMP类型必须为uvm_analysis_imp,否则会报错。analysis_port/analysis_export只有一个操作write。在analysis_imp所在的component,必须定义一个名字为write的函数

4.3.2 端口的跨层次连接

如果要连接monitor和scoreboard端口,因monitor比scoreboard低一个层次(参考UVM平台树状关系图),那么应如何在跨层次结构中连接端口?有如下三种:

  • 第一种方法直接在env中跨层次引用monitor中的端口,连接scoreboard。此种连接方式最简单,但是其层次关系并不好
function void my_env::connect_phase(uvm_phase phase);
o_agt.mon.ap.connect(scb.scb_imp);
endfunction
  • 第二种方法在monitor和agent分别建立一个端口,连接monitor与agent的端口,再在env中连接agent与scoreboard。此方式需连接两次,过程较麻烦
class my_agent extends uvm_agent ;
	uvm_analysis_port #(my_transaction) ap;
	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		ap = new("ap", this);
	endfunction
function void my_agent::connect_phase(uvm_phase phase);
	mon.ap.connect(this.ap);
endfunction
endclass
    
function void my_env::connect_phase(uvm_phase phase);
	o_agt.ap.connect(scb.scb_imp);
endfunction
  • 第三种:在agent声明一个端口的句柄,但不实例化此端口,将agent中的句柄指向monitor的端口实例。在env中直接连接agent的端口到scoreboard的端口。此方式能体现出明显的层次关系,实现也较简单,推荐此方式
class my_agent extends uvm_agent;
	uvm_analysis_port #(my_transaction) ap;
function void my_agent::connect_phase(uvm_phase phase);
	ap = mon.ap;
endfunction
endclass

function void my_env::connect_phase(uvm_phase phase);
	o_agt.ap.connect(scb.scb_imp);
endfunction

4.3.3 一个component内有多个IMP

如Scb中要接收来自monitor和refm的数据,每个analysis_port都要建立一个write函数,那么如何处理同一component内有多个IMP的情况?UVM中定义了一个宏uvm_analysis_imp_decl来解决这个问题。需要做如下两步:

  • 在C(包含多个IMP的组件)中通过uvm_analysis_imp_decl宏声明带有不同的后缀(_A,_B)的IMP进行区分(在IMP端口类型后面添加后缀)
  • 在C中实现带有不同后缀的write函数(write_A,write_B)
//my_scoreboard.sv
	`uvm_analysis_imp_decl(_monitor)
	`uvm_analysis_imp_decl(_model)
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_analysis_imp_monitor #(my_transaction, my_scoreboard) monitor_imp;
	uvm_analysis_imp_model #(my_transaction, my_scoreboard) model_imp;
	
	extern function void write_monitor(my_transaction tr);
	extern function void write_model(my_transaction tr);
	extern virtual task main_phase(uvm_phase phase);
endclass

经过此两步骤,当A中的analysis_port要进行数据传输时,执行C中的write_A函数。当B中的analysis_port要进行数据传输时,执行C中的write_B函数。通过对IMP端口类型和write函数添加后缀名来区分数据来源端口,此问题得到解决

4.3.4 FIFO通信

跨层次端口连接也可以利用FIFO进行通信。FIFO本质上是一块缓存加上两个IMP。在agent和scb之间添加一个uvm_analysis_fifo,然后将uvm_analysis_fifo分别连接到monitor和scb。在monitor与FIFO的连接中,monitor的类型是analysis_port,FIFO上的端口类型是uvm_analysis_imp。在scb与FIFO的连接中,scb是blocking_get_port类型。

//my_scoreboard.sv
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port;
	uvm_blocking_get_port #(my_transaction) act_port;
endclass

task my_scoreboard::main_phase(uvm_phase phase);
	fork
		while(1)
        begin
			exp_port.get(get_expect);
			expect_queue.push_back(get_expect);
		end
		while(1)
        begin
			act_port.get(get_actual);
		end
	join
endtask
//my_env.sv
class my_env extends uvm_env;
	my_agent i_agt;
	my_agent o_agt;
	my_model mdl;
	my_scoreboard scb;

	uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;	//mon to scb
	uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;	//mon to refm
	uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;	//refm to scb
endclass

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	//connect input-monitor to refmodel using uvm_analysis_fifo
	i_agt.ap.connect(agt_mdl_fifo.analysis_export);		//analysis_export is IMP type
	mdl.port.connect(agt_mdl_fifo.blocking_get_export);
	//connect refmodel to scb using uvm_analysis_fifo
	mdl.ap.connect(mdl_scb_fifo.analysis_export);
	scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
	//conncet output-monitor to scb using uvm_analysis_fifo
	o_agt.ap.connect(agt_scb_fifo.analysis_export);
	scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction

相比于monitor直接与scb连接进行通信,使用FIFO进行通信中,monitor的控制流和数据流没有发生变化变化,而scb从被动收数据变为主动接收数据(get操作)。

在这里插入图片描述

使用FIFO进行连接可以:

  • 省略了write函数的编写(FIFO内部已实现)。scoreboard可以通过get函数主动控制数据的接收。
  • FIFO隐藏了IMP
  • 具有多个端口,解决了同一个component作为连接终点连接多个component的问题。

4.3.5 FIFO上的端口

在这里插入图片描述

  • put类型:put_export,blocking_put_export,noblocking_put_export
  • get类型:get_export,blocking_get_export,nonblocking_get_export
  • peek类型:peek_export,blocking_peek_export,nonblocking_peek_export
  • get_peek类型:get_peek_export,blocking_get_peek_export,nonblocking_get_peek_export
  • analysis类型:put_ap,get_ap

注:上述端口中名称都为export结尾,但是实际端口类型均为IMP

注:上述相同端口类型的端口共享一块缓存。假如put_export和blocking_put_export同时连接到控制端时,当控制端发起put操作,那么put_export和blocking_put_export会各自调用一次fifo的put操作,fifo缓存数据量为2。get端相同。

peek端口与get相似,其数据流,控制流都相似,唯一的区别在于get任务被调用时,FIFO内部缓存中会少一个transaction,而peek被调用时,FIFO会把transaction复制一份发送出去,其内部缓存中的transaction数量并不会减少

FIFO有两种类型,一种是上述的uvm_analysis_fifo,另一种是uvm_tlm_fifo。区别在于后者没有analysis_export端口,也没有一个write函数。除此之外,两者均相同。

4.3.6 FIFO调试

  • used:查询FIFO缓存中有多少transaction
  • is_empty:判断当前FIFO缓存是否为空
  • if_full:判断当前FIFO缓存是否已满
  • flush:用于清空FIFO缓存中的所有数据,一般用于复位操作。

4.3.7 使用FIFO还是IMP

FIFO

  • 隐藏IMP,初学者友好
  • 增加env中代码的复杂度(端口连接由一次变为两次
  • 适用于端口数组的情况(多个端口是如使用uvm_analysis_imp_decl宏代码量太多,使用FIFO可以借助for循环简化声明和连接
//my_model.sv
class my_model extends uvm_component;
	uvm_blocking_get_port #(my_transaction) port;
	uvm_analysis_port #(my_transaction) ap[16];
endclass

function void my_model::build_phase(uvm_phase phase);
	super.build_phase(phase);
	port = new("port", this);
	//支持for循环,并且在实例化名称时使用格式化字符串函数$sformatf
	for(int i = 0; i < 16; i++)	ap[i] = new($sformatf("ap_%0d", i), this);
endfunction
//my_env.sv
class my_env extends uvm_env;
	uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
	uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
	uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo[16];

virtual function void build_phase(uvm_phase phase);
	agt_scb_fifo = new("agt_scb_fifo", this);
	agt_mdl_fifo = new("agt_mdl_fifo", this);
	//for循环配合格式化字符串$sformatf声明fifo数组
	for(int i = 0; i < 16; i++)
	mdl_scb_fifo[i] = new($sformatf("mdl_scb_fifo_%0d", i), this);
endfunction
endclass

function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	i_agt.ap.connect(agt_mdl_fifo.analysis_export);
	mdl.port.connect(agt_mdl_fifo.blocking_get_export);
	//for循环进行fifo数组连接
	for(int i = 0; i < 16; i++)
    begin
		mdl.ap[i].connect(mdl_scb_fifo[i].analysis_export);
		scb.exp_port[i].connect(mdl_scb_fifo[i].blocking_get_export);
	end
	o_agt.ap.connect(agt_scb_fifo.analysis_export);
	scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction

参考文献:

UVM实战(卷Ⅰ)张强 编著 机械工业出版社

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

搬砖小张

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

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

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

打赏作者

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

抵扣说明:

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

余额充值