UVM中的TLM通信
一、基本概念
1)、put操作:通信的发起者A把一个transaction发送给B。在这个过程中,A称为“发起者”,而B称为“目标”。A具有的端口(用方框表示) 称为PORT,而B的端口(用圆圈表示) 称为EXPORT。这个过程中,数据流是从A流向B的。
2)、get操作:在这个过程中,A依然是“发起者”,B依然是“目标”,A上的端口依然是PORT,而B上的端口依然是EXPORT。这个过程中,数据流是从B流向A的。
3)、transport操作:transport操作相当于一次put操作加一次get操作,这两次操作的“发起者”都是A,目标都是B。A上的端口依然是PORT,而B上的端口依然是EXPORT。在这个过程中,数据流先从A流向B,再从B流向A。
需要注意的是:在通信中port始终是发起者,EXPORT始终是被动接收的。
二、UVM中各种端口的互连
UVM中使用connect函数来建立连接关系。如A要和B通信( A是发起者) ,那么可以这么写:A.port.connect( B.export) ,但是不能写成B.export.connect( A.port) 。因为在通信的过程中,A是发起者,B是被动承担者。这种通信时的主次顺序也适用于连接时,只有发起者才能调用connect函数,而被动承担者则作为connect的参数。
但是直接用这种方法连接,编译器会报错不能通过。主要是PORT和EXPORT只是一个端口,不能存储transaction。要向连接成功需要加成IMP端口。
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;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
……//省略
function void B::build_phase(uvm_phase phase);
super.build_phase(phase);
B_export = new("B_export", this);
B_imp = new("B_imp", this);
endfunction
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);//必须实例化的put函数
`uvm_info("B", "receive a transaction", UVM_LOW)
tr.print();
endfunction
所以要实现连接通信,最核心是在B在实例化一个IMP,而且必须要在实现IMP的component中实现一个put函数/任务。在UVM中,只有IMP才能作为连接关系的终点。(其中对get操作必须实现一个get函数,对transport操作必须实现一个transport函数)
nonbllocking是非阻塞的,必须用函数来实现而不能用任务实现。由于端口变成非阻塞的,所以再送出transaction之前需要调用can_put函数来确认是否能够执行put操作。can_put最终会调用B中的can_put。
A中的main函数:
task A::main_phase(uvm_phase phase);
my_transaction tr;
repeat(10) begin
tr = new("tr");
assert(tr.randomize());
while(!A_port.can_put()) #10;
void'(A_port.try_put(tr));
end
endtask
B中的can_put和try_put
function bit B::can_put();
if(tr_q.size() > 0)
return 0;
else
return 1;
endfunction
function bit B::try_put(my_transaction tr);
`uvm_info("B", "receive a transaction", UVM_LOW)
if(tr_q.size() > 0)
return 0;
else begin
tr_q.push_back(tr);
return 1;
end
endfunction
三、UVM中的通信方式
1)analysis端口
在默认情况下,一个analysis_port可以连接多个IMP,可以理解analysis_port是一个广播。所以因此也可以理解analysis_port没有阻塞和非阻塞之分。
class A extends uvm_component;
`uvm_component_utils(A)
uvm_analysis_port#(my_transaction) A_ap;//定义一个analysis_port
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
extern function void build_phase(uvm_phase phase);
extern virtual task main_phase(uvm_phase phase);
endclass
……
task A::main_phase(uvm_phase phase);
my_transaction tr;
repeat(10) begin
#10;
tr = new("tr");
assert(tr.randomize());
A_ap.write(tr);
end
endtask
根据前面的IMP的要求,也是需要在实例化IMP的B中定义一个write函数。
2)使用FIFO通信
在通信汇中,单单使用PORT和EXPORT通信,始终有一方是处于主动地位,另一方处于被动地位。究竟有没有方法可以让双方都对数据主动接收呢?答案是使用FIFO。
FIFO的本质是一块缓存加两个IMP。
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);//i_agent和model连接
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);//model和scoreboard连接
end
o_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export); //o_agent和scoreboard连接
endfunction
实际上,虽然FIFO中的analysis_export有关键字export,但是其类型却是IMP。这点是FIFO设计隐藏了IMP,是对初学者的优化。