第四章. 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实战(卷Ⅰ)张强 编著 机械工业出版社