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缓存中有多少transaction;flush函数,用于清空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,都能实现同样的目标,两者各有其优势与劣势。在实际应用中可根据自己的习惯来选择合适的连接方式。