目录
*blocking_get端口的使用
get系列端口与put系列端口在某些方面完全相反。若要实现从A到B的通信,使用blocking_get系列端口的框图如下图所示:
在这种连接关系中数据流依然是从A到B,但A由动作发起者变成了动作接收者,而B由动作接收者变成了动作发起者。B_port的类型为uvm_blocking_get_port,A_export的类型为uvm_blocking_get_export,A_imp的类型为uvm_blocking_get_imp。与uvm_blocking_put_imp所在的component要实现一个put的函数/任务类似,uvm_blocking_get_imp所在的component要实现一个名字为get的函数/任务。A的代码为:
class A extends uvm_component;
`uvm_component_utils(A)
uvm_blocking_get_export#(my_transaction) A_export;
uvm_blocking_get_imp#(my_transaction) A_imp;
my_transaction tr_q[$];
...
endclass
function void A::build_phase(uvm_phasree phase);
super.build_phase(phase);
A_export = new("A_export", this);
A_imp = new("A_imp", this);
endfunction
function void A::connect_phase(uvm_phase phase);
super.connect_phase(phase);
A_export.connect(A_imp);
endfunction
task A::get(output my_transaction tr);
whilt(tr_q.size() == 0) #2;
tr = tr_q.pop_front();
endtask
task A::main_phase(uvm_phase phase);
my_transaction tr;
reoeat(10) begin
#10;
tr = new("tr");
tr_q.push_back(tr);
end
endtask
在A的get任务中,每隔2个时间单位检查tr_q中是否有数据,有就发送出去。当B在其main_phase调用get任务时会最终执行A的get任务。在A的connect_phase,需要把A_export和A_imp连接起来。
B的代码为:
class B extends uvm_component;
`uvm_component_utils(B)
uvm_blocking_get_port#(my_transaction) B_port;
...
endclass
function void B::build_phase(uvm_phase phase);
super.build_phase(phase);
B_port = new("B_port", this);
endfunction
task B::main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin
B_port.get(tr);
`uvm_info("B", "get a transaction", UVM_LOW)
tr.print();
end
endtask
env中的连接关系变为:
function void my_env::connnect_phase(uvm_phase phase);
super.connect_phase(phase);
B_inst.B_port.connect(A_inst.A_export);
endfunction
与blocking_put系列端口类似,blocking_get_port也可以直接连接到blocking_get_imp,同时blocking_get_port也可以连接到blocking_get_port,blocking_get_export也可连接到blocking_get_export。在这些连接关系中,需要谨记的是连接的终点必须是一个IMP。
*blocking_transport端口的使用
transport系列端口与put和get系列端口都不一样。在put和get系列端口中,所有通信都是单向的,而在transport系列端口中,通信变成了双向的。
在A中定义一个transport:
class A extends uvm_component;
`uvm_component_utils(A)
uvm_blocking_transport_port#(my_transaction, my_transaction) A_transport;
endclass
task A::main_phase(uvm_phase phase);
my_transaction tr;
my_transaction rsp;
repeat(10) begin
#10;
tr = new("tr");
assert(tr.randomize());
A_transport.transport(tr, rsp);
`uvm_info("A", "received rsp", UVM_MEDIUM)
rsp.print();
end
endtask
B中需要定义一个类型为uvm_blocking_transport_imp的IMP:
class B extends uvm_component;
`uvm_component_utils(B);
uvm_blocking_transport_imp#(my_transaction, my_transaction, B) B_imp;
...
endclass
task B::transport(my_transaction req, output my_transaction rsp);
`uvm_info("B", "receive a transaction", UVM_LOW)
req.print();
//do something according to req
#5;
rsp = new("rsp");
endtask
env中的连接关系为:
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
A_inst.A_transport.connect(B_inst.B_imp);
endfunction
在A中调用transport任务并把生成的transaction作为第一个参数。B中的transaport任务接收到这笔transaction, 根据这笔transaction做某些操作,并把操作结果作为transport的第二个参数发送出去。A根据接收到的rsp决定后面的行为。
上例是blocking_transport_port直接连接到blocking_transport_imp,前者还可连接blocking_transport_export, 这三者之间的连接关系与blocking_put系列端口类似。
nonblocking端口的使用
nonblocking端口的所有操作都是非阻塞的, 即必须用函数实现。以nonblocking_put端口为例介绍nonblocking端口的使用,图示的连接关系需要在A中定义一个nonblocking_put_port端口:
class A extends uvm_component;
`uvm_component_utils(A)
uvm_nonblocking_put_port#(my_transaction) A_port;
…
endclass
…
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
由于端口变为非阻塞,所以在送出transaction之前需调用can_put函数来确认是否能够执行put操作。can_put最终会调用B中的can_put:
class B extends uvm_component;
`uvm_component_utils(B)
uvm_nonblocking_put_imp#(my_transaction, B) B_imp;
my_transaction tr_q[$];
…
endclass
…
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
task B::main_phase(uvm_phase phase);
my_transaction tr;
while(1) begin
if(tr_q.size() > 0)
tr = tr_q.pop_front();
else
#25;
end
endtask
在A中使用can_put来判断是否可以发送,其实这里还可以不用can_put(不使用can_put的话,在B中依然需要定义一个名字为can_put的函数,这个函数里可以是一个空函数),而直接使用try_put:
task A::main_phase(uvm_phase phase);
my_transaction tr;
repeat(10) begin
tr = new("tr");
assert(tr.randomize());
while(!A_port.try_put(tr)) #10;
end
endtask
env中的连接关系为:
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
A_inst.A_port.connect(B_inst.B_imp);
endfunction
nonblocking_get系列端口和nonblocking_transport系列端口的使用与nonblocking_put类似。
UVM中的通信方式
UVM中的analysis端口
UVM中还有两种端口:analysis_port和analysis_export。这两者其实与put和get系列端口类似,都用于传递transaction。它们的区别是:
- 默认情况下,一个analysis_port(analysis_export)可连接多个IMP, 即analysis_port(analysis_export)与IMP之间的通信是一对多的通信,而put和get系列端口与相应IMP的通信是一对一的通信(除非在实例化时指定可连接的数量)。analysis_port(analysis_export)更像是一个广播。
- put与get系列端口有阻塞和非阻塞的区分。但analysis_port和analysis_export没有阻塞和非阻塞的概念。它本身就是广播,不必等待与其相连的其他端口响应。
- 一个analysis_port可以和多个IMP连接进行通信,但IMP的类型必须是uvm_analysis_imp,否则会报错。
- 对于put系列端口,有put、try_put、can_put等操作,对于get系列端口,有get、try_get和can_get等操作。对于analysis_port和analysis_export来说只有write操作。在analysis_imp所在的component必须定义write函数。
A的代码为:定义一个analysis_port,并在main_phase中每隔10个时间单位写入一个transaction。
class A extends uvm_component;
`uvm_component_utils(A)
uvm_analysis_port#(my_transaction) A_ap;
…
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
B的代码为:
class B extends uvm_component;
`uvm_component_utils(B)
uvm_analysis_imp#(my_transaction, B) B_imp;
…
endclass
…
function void B::write(my_transaction tr);
`uvm_info("B", "receive a transaction", UVM_LOW)
tr.print();
endfunction
B是B_imp所在的component,故要在B中定义名为write的函数。在B的main_phase中不需要做任何操作。C的代码与B完全相似,只要把相应的B替换为C即可。
env中的连接关系为:
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
A_inst.A_ap.connect(B_inst.B_imp);
A_inst.A_ap.connect(C_inst.C_imp);
endfunction
analysis_export和IMP也可进行类似的连接;与put系列端口的PORT和EXPORT直接相连会出错的情况一样,analysis_port如果和analysis_export直接相连也会出错。只有在analysis_export后面再连接一级uvm_analysis_imp才不会出错。
*一个component内有多个IMP
图中o_agt的monitor与scoreboard之间的通信使用analysis_port实现。在monitor中:
class monitor extends uvm_monitor;
uvm_analysis_port#(my_transaction) ap;
task main_phase(uvm_phase phase);
super.main_phase(phase);
my_transaction tr;
…
ap.write(tr);
…
endtask
endclass
在scoreboard中:
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp#(my_transaction, scoreboard) scb_imp;
task write(my_transaction tr);
//do something on tr
endtask
endclass
在env中可使用connect连接,由于monitor与scoreboard在UVM树中并不是平等的关系,中间还间隔了o_agt,所以这里有三种连接方式,第一种是直接在env中跨层次引用monitor中的ap:
function void my_env::connect_phase(uvm_phase phase);
o_agt.mon.ap.connect(scb.scb_imp);
…
endfunction
第二种是在agent中声明一个ap并实例化它,在connect_phase将其与monitor的ap相连,并可以在env中把agent的ap直接连接到scoreboard的imp:
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中声明一个ap但不实例化它,让其指向monitor中的ap。在env中可直接连接agent的ap到scoreboard的imp:
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
上述三种方式中第一种最简单,但其层次关系并不好,第二种稍显麻烦,第三种既具有明显的层次关系,同时其实现也较简单。
上面的monitor和scoreboard之间的通信是通过采用一个analysis_port和一个anslysis_imp相连的方式实现的。 对于analysis_imp来说,必须在其实例化的uvm_component中定义一个write的函数。在上例中,scoreboard只接收一路数据,但在现实情况中,scoreboard除了接收monitor的数据之外,还要接收reference model的数据。 相应的scoreboard就要再添加一个uvm_analysis_imp的IMP,如model_imp。此时问题就出现了,由于接收到的两路数据应该做不同的处理,所以这个新的IMP也要有一个write任务与其对应。但write只有一个,怎么办?
UVM考虑到了这种情况, 它定义了一个宏uvm_analysis_imp_decl来解决这个问题, 其使用方式为:
`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
通过宏uvm_analysis_imp_decl声明了两个后缀_monitor和_model。UVM会根据这两个后缀定义两个新的IMP类:uvm_analysis_imp_monitor和uvm_analysis_imp_model,并在my_scoreboard中分别实例化这两个类: monitor_imp和model_imp。当与monitor_imp和model_imp相连接的analysis_port执行write函数时,会自动调用write_monitor和write_model函数。所以,只要完成后缀的声明,并在write后面添加上相应后缀就可以正常工作了:
function void my_scoreboard::write_model(my_transaction tr);
expect_queue.push_back(tr);
endfunction
function void my_scoreboard::write_monitor(my_transaction tr);
my_transaction tmp_tran;
bit result;
if(expect_queue.size() > 0) begin
…
end
endfunction