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


UVM中的通信方式

*使用FIFO通信

上节实现monitor和scoreboard之间的通信时先声明了两个后缀,然后再写相应的函数,这种方法看起来有些麻烦且有些难理解,那有没有简单的方法呢?另外两者的通信是monitor占据主动地位,而scoreboard只能被动地接收,那有没有方法也让scoreboard实现主动的接收呢?答案是肯定的,那就是利用FIFO来实现monitor和scoreboard的通信。

如图b,在agent和scoreboard之间添加一个uvm_analysis_fifo。FIFO的本质是一块缓存加两个IMP。在monitor与FIFO的连接关系中,monitor中依然是analysis_port,FIFO中是uvm_analysis_imp,数据流和控制流的方向相同。在scoreboard与FIFO的连接关系中,scoreboard中使用blocking_get_port端口:

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

而FIFO中使用的是一个get端口的IMP。在这种连接关系中,控制流是从scoreboard到FIFO,数据流是从FIFO到scoreboard
在这里插入图片描述
在env里面以如下方式连接:

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;
	uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
	uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
	…
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);
	mdl.ap.connect(mdl_scb_fifo.analysis_export);
	scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
	o_agt.ap.connect(agt_scb_fifo.analysis_export);
	scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction

如图b所示,FIFO中有两个IMP,但是在上面的连接关系中,FIFO中却是EXPORT,这是为什么呢?实际上,FIFO中的analysis_export和blocking_get_export虽然名字中有export,但其类型却是IMP。UVM为了掩饰IMP的存在,在它们的命名中加入了export。如analysis_export的原型如下:uvm_analysis_imp #(T, uvm_tlm_analysis_fifo #(T)) analysis_export;

使用FIFO连接之后,第一个好处是不必在scoreboard中再写一个名为write的函数scoreboard可按照自己的节奏工作,而不必跟着monitor的节奏。第二个好处是FIFO的存在隐藏了IMP。第三个好处是可以轻易解决当reference model和monitor同时连接到scoreboard应如何处理的问题。事实上,FIFO的存在自然而然地解决了它,这根本就不是一个问题了。

FIFO上的端口及调试

上节介绍了uvm_tlm_analysis_fifo并介绍了它的两个端口:blocking_get_export和analysis_export。事实上,FIFO上的端口并不局限于上述两个,一个FIFO中有众多的端口,如图所示。
在这里插入图片描述
上图所有以圆圈表示的EXPORT本质上都是IMP。包含12种IMP,用于分别和相应的PORT及EXPORT连接。前文已经介绍put和get系列端口,这里简要说明peek系列端口:peek端口与get相似,其数据流、控制流都相似,唯一区别在于当get任务被调用时FIFO内部缓存中会少一个transaction, 而peek被调用时,FIFO会把transaction复制一份发送出去, 其内部缓存中的transaction数量并不会减少。

除了这12个IMP外,图中还有put_ap和get_ap。当FIFO上的blocking_put_export或put_export被连接到一个blocking_put_port或put_port上时,FIFO内部被定义的put任务被调用,把传递过来的transaction放在FIFO内部的缓存里,同时,把这个transaction通过put_ap使用write函数发送出去。FIFO的put任务定义如下:

virtual task put( input T t );
	m.put( t ); //m即是FIFO内部的缓存,使用SV中的mailbox来实现
	put_ap.write( t );
endtask

与put_ap相似,当FIFO的get任务被调用时,同样会有一个transaction从get_ap上发出:

virtual task get(output T t);
	m_pending_blocked_gets++;
	m.get(t);
	m_pending_blocked_gets--;
	get_ap.write(t);
endtask

一个blocking_get_port连接到了FIFO上,当它调用get任务获取transaction时就会调用FIFO的get任务。除此之外,FIFO的get_export、get_peek_export和blocking_get_peek_export被相应的PORT或者EXPORT连接时,也能调用FIFO的get任务。

FIFO的类型有两种:uvm_tlm_analysis_fifo和uvm_tlm_fifo。两者的差别在于前者有一个analysis_export端口并有一个write函数,而后者没有。

UVM提供用于FIFO调试的函数:used函数用于查询FIFO缓存中有多少transactionflush函数,用于清空FIFO缓存中的所有数据,它一般用于复位等操作。is_empty函数用于判断当前FIFO缓存是否为空is_full函数用于判断当前FIFO缓存是否已经满了。作为一个缓存来说,其能存储的transaction是有限的。那么这个最大值是在哪里定义的呢?FIFO的new函数原型如下:function new(string name, uvm_component parent = null, int size = 1);

FIFO本质上是一个component,所以其前两个参数是uvm_component的new函数中的两个参数。第三个参数是size,用于设定FIFO缓存的上限默认情况下为1。若要把缓存设置为无限大小,将传入的size参数设置为0即可。通过size函数可以返回这个上限值

用FIFO还是用IMP

对于这个问题有不同的答案。在用FIFO通信的方法中,完全隐藏了IMP这个UVM中特有、而TLM中根本就没有的东西。用户可以完全不关心IMP。对于用户来说只需要知道analysis_port、blocking_get_port即可。这简化了初学者的工作量,尤其是在scoreboard面临多个IMP,且需要为IMP声明一个后缀时,这种优势更加明显。

FIFO连接的方式增加了env中代码的复杂度,满满的看上去似乎都是与FIFO相关的代码。尤其是当要连接的端口数量众多时,这个缺点更加明显。

不过对于使用端口数组的情况,FIFO要优于IMP。假如参考模型中有16个类似端口要和scoreboard中相应的端口相互通信,如此多数量的端口,在参考模型中可以使用端口数组来实现:

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(int i = 0; i <16; i++)
		ap[i] = new($sformatf("ap_%0d", i), this);
endfunction

如果连接关系使用IMP加后缀的方式,那么在scoreboard中的代码如下:(高下立判)

`uvm_analysis_imp_decl(_model0)
...
`uvm_analysis_imp_decl(_modelf)
`uvm_analysis_imp_decl(_monitor)
class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_analysis_imp_monitor#(my_transaction, my_scoreboard) monitor_imp;
	uvm_analysis_imp_model0#(my_transaction, my_scoreboard) model0_imp;
	…
	uvm_analysis_imp_modelf#(my_transaction, my_scoreboard) modelf_imp;
	`uvm_component_utils(my_scoreboard)
	extern function new(string name, uvm_component parent = null);
	extern virtual function void build_phase(uvm_phase phase);
	extern virtual task main_phase(uvm_phase phase);
	extern function void write_monitor(my_transaction tr);
	extern function void write_model0(my_transaction tr);extern function void write_modelf(my_transaction tr);
endclass
…
function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	monitor_imp = new("monitor_imp", this);
	model0_imp = new("model0_imp", this);
	…
	modelf_imp = new("modelf_imp", this);
endfunction
function void my_scoreboard::write_model0(my_transaction tr);
	expect_queue.push_back(tr);
endfunction
…
function void my_scoreboard::write_modelf(my_transaction tr);
	expect_queue.push_back(tr);
endfunction
function void my_scoreboard::write_monitor(my_transaction tr);
	…
endfunction

并且在env中,需要:

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);
	o_agt.ap.connect(scb.monitor_imp);
	mdl.ap[0].connect(scb.model0_imp);
	…
	mdl.ap[15].connect(scb.modelf_imp);
endfunction

如上列出的代码中使用了很多省略号,但即使这样也能感受到代码严重的的程度。这一切都是因为ap与imp直接相连而不能使用for循环引起的。

假如使用FIFO连接, 那么在scoreboard中可以:

class my_scoreboard extends uvm_scoreboard;
	my_transaction expect_queue[$];
	uvm_blocking_get_port #(my_transaction) exp_port[16];
	uvm_blocking_get_port #(my_transaction) act_port;
	…
endclass
…
function void my_scoreboard::build_phase(uvm_phase phase);
	super.build_phase(phase);
	for(int i = 0; i < 16; i++)
		exp_port[i] = new($sformatf("exp_port_%0d", i), this);
		act_port = new("act_port", this);
endfunction
task my_scoreboard::main_phase(uvm_phase phase);for(int i = 0; i < 16; i++)
	fork
		automatic int k = i;
	while (1) begin
		exp_port[k].get(get_expect);
		expect_queue.push_back(get_expect);
	end
	join_none
	while (1) begin
	act_port.get(get_actual);
	…
	end
endtask

在env中也可以使用for循环:

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(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(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

无论使用FIFO还是使用IMP,都能实现同样的目标,两者各有其优势与劣势。在实际应用中可根据自己的习惯来选择合适的连接方式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值