概述
事务级建模 TLM(transaction level modeling):
TLM通信是基于组件请求的发起和响应,组件之间涉及事务(transaction, trans)的传输,如sequencer与driver、monitor与scoreboard、monitor与coverage model。TLM仅仅适用于component之间的连接
TLM在各组件之间建立一个专用通道,让信息只在这个通道里流动,因此避免了全局变量,public成员变量和config机制通讯时带来的不便
TLM port
数据流方法 put/get/transport
TLM通信通信类型: put/get/transport
put操作: 组件 A 将一个 transaction 交给组件 B
get 操作(拿回来一个transaction):是组件 A 向组件 B 请求一个transaction
对于get系列端口,有get()、try_get() 和 can_get() 等操作
- get() 是一个阻塞调用,如果FIFO中没有transsaction,任务get()将等待
- try_get() 是一个非阻塞调用,没从FIFO中拿到transaction,也会立即返回,try_get()的返回值指示是否返回成功,事务立即可用,则将其写入输出参数并返回 1;否则,不修改输出参数并返回 0。
- can_get() 是否可以提供新事务,返回 1,否则返回 0。均不修改输出参数
transport 操作(先丢出去然后再拿回来一个transaction):组件 A 将一个 transaction 交给组件 B,然后向组件 B 请求一个 transaction
peek:initiator 向 target 索取 trans 的复制
参考链接 https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/
组件的通信端口阻塞性:
连接端口实现通信
-
发起通信请求的组件是initiator,响应请求的组件就是target
-
blocking(阻塞)与 nonblocking(非阻塞)
-
阻塞 (函数只有在得到结果之后才会返回):阻塞 PUT 表示 A 将该 transaction 交给 B后,等待 B 接收完成。
-
非阻塞 (函数没得到结果立刻返回):非阻塞 PUT 表示 A 将该 transaction 交给 B后,PUT不再等待 B 接收完成
控制流 port 、export 与 imp:
表示从initiator向target发起的请求的传输方向
- port:表示请求的发送方,initiator 中的端口常为 port
- export:表示请求的中转方,在 initiator 和 target 的中间层端口常为 export
- imp:即 import,表示请求的接收方,target中的端口常为 imp
如图,组件1和组件2有两对端口建立通信,第一对端口是put型、第二对端口是get型
根据 uvm_阻塞性_数据流_控制流 组合成 端口类:
例如端口类 uvm_blocking_put_port、uvm_nonblocking_get_imp 等等
- 端口类 不是object类,也不是component类,而是uvm_void,创建的时候要用new(),而不是type_id::create()
- 用new()创建对象可以放在func new()中,也可以放在build_phase中
- 用type_id::create()创建对象只能放在build_phase中
- 在创建组件的时候,一定要用type_id::create(),提供override的可能,还可以实现字符串的层次化
建立 TLM port 单向通信步骤
- 定义TLM传输的数据类型trans
- 确定TLM通信的initiator component 和 target component,并且initiator、target和中间层,在各自的build_phase中使用new函数,分别例化port端口、相同类型的imp端口 和 相同类型的export端口
- 在target实现该端口的数据流方法
- initiator和target的上层,在connect_phase使用connect方法建立连接
- initiator在run_phase调用相应的数据流方法,注意initiator调用的数据流方法是在target内实现的数据流方法
1.定义 transaction 事务类型
class trans1 extends uvm_sequence_item; //定义事务类型
rand int id;
rand int data;
...
endclass
class trans2 extends uvm_sequence_item;
int id;
int data;
...
endclass
2.确定TLM通信的 initiator component 和 target component,在各自build_phase中使用new函数,分别例化端口
注意端口是uvm_void, uvm_void的例化方式只能是new(string name, uvm_component parent);不能用factory(create)机制例化,会编译报错!!!
// 定义 initiator component
class comp1 extends uvm_component;
`uvm_component_utils(comp1) // component注册uvm
uvm_blocking_put_port#(trans1) bp_port; //initiator声明端口,类型为阻塞的put,用于发送trans1。 port端口传入1个参数(trans)
uvm_nonblocking_get_port#(trans2) ng_port; //initiator声明端口,类型为非阻塞的get,用于请求trans2
function void build_phase(uvm_phase phase); // 在build_phase中使用new函数,例化端口, uvm_void的例化方式只能是new
super.build_phase(phase);
bp_port = new("bp_port",this); //initiator例化端口,注意不继承于uvm_object或uvm_component,不可用factory机制例化!!!
ng_port = new("ng_port",this); //initiator例化端口,注意继承于uvm_object或uvm_component,不可用factory机制例化!!!
...
endfunction
task run_phase(uvm_phase phase); // 在run_phase调用相应的数据流方法:put/get/transport
trans1 t1;
trans2 t2;
fork
repeat(5) begin
t1 = trans1::type_id_create("t1"); // component用create例化,并随机化trans
assert(t1.randomize()); //用断言检查随机化,随机化成功,函数返回1;失败返回0
bp_port.put(t1); // 端口调用方法put
end
forever begin
if(ng_port.try_get(t2)) // 端口调用get的非阻塞方法 try_get,没拿到transaction,也会立即返回,
break;
end
join
endtask
endclass
initiator调用的数据流方法put/get/transport,是在target内实现的数据流方法
// 定义 target component
class comp2 extends uvm_component;
`uvm_component_utils(comp2) // component注册uvm
uvm_blocking_put_imp#(trans1,comp2) bp_imp; // target声明端口imp,imp端口传入两个参数(trans,comp)
uvm_nonblocking_get_imp#(trans2,comp2) ng_imp; // target声明端口imp
trans1 tq[$]; // target数据缓存 队列
function void build_phase(uvm_phase phase); // 在build_phase中使用new函数,例化端口
super.build_phase(phase);
bp_imp = new("bp_imp",this); //target例化端口
ng_imp = new("ng_imp",this); //target例化端口
...
endfunction
// initiator调用的数据流方法put/get/transport,是在target内实现的数据流方法
task put(trans1 t);
tq.push_back(t); // target被发送数据流, 数据缓存队列收下trans1
endtask
function bit try_get(output trans2 t); // target 被请求数据流, 对于target来说,是output
...
endfunction
function bit can_get(); // target 被询问是否可以请求数据流
return (tq.size() > 0); // 从initiator接收到的trans1放进了队列,队列里有数据,则可以返回
endfunction
endclass
3.在env的build_phase例化initiator component 和 target component,在connect_phase使用connect方法建立连接,连接 initiator component 和 target component
class env extends uvm_env;
`uvm_component_utils(env); // component注册uvm
comp1 c1; // 声明 initiator component 和 target component
comp2 c2;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
c1 = comp1::type_id::create("c1",this); // 例化compoent组件用create
c2 = comp2::type_id::create("c2",this);
...
endfunction
function void connect_phase(uvm_phase phase); // 在connect_phase使用connect方法连接initiator端口连接target端口
super.connect_phase(phase);
c1.bp_port.connect(c2.bp_imp); // initiator.port_put端口.连接(target.imp_put端口)
c1.ng_port.connect(c2.ng_imp); // initiator.port_get端口.连接(target.imp_get端口)
...
endfunction
endclass
单向通信补充情况:
- PORT to PORT
UVM支持带层次的连接关系;PORT与PORT之间的连接不止局限于两层,可以有无限多层
- EXPORT to EXPORT
UVM也支持EXPORT与EXPORT之间的连接;EXPORT与EXPORT之间的连接也不只限于两层,也可以有无限多层
TLM 多向通信(multi-directional communication)
多向通信指的是,如果initiator与target之间的相同TLM端口超过一个时的处理解决办法。
对于端口的例化可以给不同的名字,而连接也可以通过不同的名字来索引,但是问题在于comp2中需要实现两个task put(trans t)。因为不同的端口组之间要求在imp端口一侧实现专属的方法,这种要求造成了暂时的方法命名冲突,即无法在comp2中定义两个同名的put任务。
UVM通过延伸的端口宏声明方式来解决这一问题: 在全局通过宏注册后缀,例化加了后缀的新类型端口
// 定义 initiator
class comp1 extends uvm_component;
`uvm_component_utils(comp1)
uvm_blocking_put_port#(trans1) bp_port1;
uvm_blocking_put_port#(trans2) bp_port2;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
bp_port1 = new("bp_port1",this); // build_phase中new例化端口port
bp_port2 = new("bp_port2",this);
...
endfunction
task run_phase(uvm_phase phase);
trans1 t1;
repeat(5) begin
t1 = trans1::type_id_create("t1"); // run_phase中create例化组件initiator
assert(t1.randomize());
bp_port1.put(t1); // initiator端口调用方法put。注意了,此处依然是put!!不是put1、也不是put2!!!
bp_port2.put(t1);
end
endtask
endclass
注意initiator调用的数据流方法名称不会改变!!
对于comp1调用put方法而言,它只需要选择bp_port1或者bp_port2,而不需要更替put方法名,即仍然按照put()来调用而不是put_p1或者put_p2。
// 定义 target
`uvm_blocking_put_imp_decl(1); //宏注册后缀,之后就有了uvm_blocking_put_imp1#(T, IMP)类型,数据流方法也变成put1
`uvm_blocking_put_imp_decl(2); //注册了后缀2
class comp2 extends uvm_component;
`uvm_component_utils(comp2)
uvm_blocking_put_imp1#(trans1,comp2) bp_imp1; //看后缀
uvm_blocking_put_imp2#(trans1,comp2) bp_imp2;
trans1 tq[$];
function void build_phase(uvm_phase phase);
super.build_phase(phase);
bp_imp1 = new("bp_imp1",this);
bp_imp2 = new("bp_imp2",this);
...
endfunction
// 不同的端口组之间在imp端口一侧实现专属的方法,即不同名的put
task put1(trans1 t); // 看后缀
tq.push_back(t);
endtask
task put2(trans1 t);
tq.push_back(t);
endtask
endclass
通过这种方式,用户只需要在例化多个imp端口的组件中,实现不同名称的方法,与imp类型名保持一致。而对于port端口一侧的组件,则不需关心调用的方法名称,因为该名称并不会发生改变。所以,通过这种方式,可以解决多向通信的需要,而防止通信方法名的冲突。
一对一单向数据流 全部的通信端口
常见的PORT,EXPORT和IMP端口
//:PORT:UVM源代码
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);
EXPORT:UVM源代码
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);
IMP:UVM源代码
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);
TLM analysis port
端口
UVM中有两种特殊端口:analysis_port 和 analysis_export
- 默认情况下这两个端口可以连接多个IMP,也就是默认情况下是一对多通讯
- 而 put, get 默认情况下是一对一通信,所以这两个端口更像广播
- analysis_port 可以同时和多个 IMP 进行通信,但IMP的类型必须是 uvm_analysis_imp 类型,否则会报 error
无阻塞
- put, get 都有阻塞和非阻塞的区分,但 analysis_port 和 analysis_export 端口没有
数据流方法
只有write (广播)
- analysis_port 和 analysis_export 只有一种操作:write 。所以在 analysis_imp 所在的 component 里必须定义一个名字为 write(Tt) 的函数,注意是函数不耗时
- 如果一个 component 里需要定义两个 write 函数,则需要使用 uvm_analysis_imp_decl 宏来解决,类名后缀的注册`uvm_analysis_imp_decl(SFX)
FIFO通信
UVM提供的继承自uvm_component的类uvm_tlm_analysis_fifo类实例化后就是一种FIFO,它可以让通信的两个component都作为initiator,这来自于FIFO上的众多imp型端口。
FIFO的本质是一块缓存(使用mailbox实现)加上两个IMP(虽然关键字是export但实质还是IMP)
直接通信
使用FIFO通信
uvm_tlm_analysis_fifo #(T)
TLM FIFO中的get方法和peek方法,try_get有什么区别?
- get()操作将从TLM FIFO中返回一个事务(如果可用),并从FIFO中删除该事务。如果FIFO中没有可用的事务,它将阻塞并等待,直到FIFO至少有一个事务
- peek()操作将从TLM FIFO中返回一个事务(如果可用),而不会实际从FIFO中删除该项目。它也是一个阻塞调用,如果FIFO没有可用的条目,它将等待
- try_get()是一个非阻塞调用,即使FIFO中没有可用的事务,它也会立即返回。try_get()的返回值指示是否返回成功。
- FIFO上拥有transport之外的12种IMP,用于分别和相应的PORT和EXPORT连接
- 除此外还有put_ap和get_ap,当FIFO上的blocking_put_export或者put_export被连接到一个blocking_put_port或者put_port上时,FIFO内部定义的put任务被调用,这个put任务把传递过来的transaction放在FIFO的缓存里,同时,把这个transaction通过put_ap使用write函数发送出去
- Analysis FIFO含有uvm_analysis_port类对象put_ap和get_ap,说明Anylsis FIFO也可作为initiator向其他target做广播,支持一对多广播通信
- Analysis FIFO相连的initiator每次调用put或get数据流方法时,FIFO就会调用write广播,看源码
- 除了put_ap和get_ap,其他端口虽都以_export结尾,但实际上都是imp类型的
// `uvm_tlm_analysis_fifo #(T)源码
//...questasim64_2020.1verilog_srcuvm-1.2srctlm1uvm_tlm_fifos.svh
class uvm_tlm_fifo #(type T=int) extends uvm_tlm_fifo_base #(T);
...
virtual task put( input T t );
m.put( t );
put_ap.write( t ); //广播
endtask
virtual task get( output T t );
m_pending_blocked_gets++;
m.get( t );
m_pending_blocked_gets--;
get_ap.write( t ); //广播
endtask
...
endclass
class uvm_tlm_analysis_fifo #(type T = int) extends uvm_tlm_fifo #(T);
查看FIFO内部数据方法
//...questasim64_2020.1verilog_srcuvm-1.2srctlm1uvm_tlm_fifos.svh
class uvm_tlm_fifo #(type T=int) extends uvm_tlm_fifo_base #(T);
function new(string name, uvm_component parent = null, int size = 1); //创建函数,size可指定FIFO容量,0为无限大小
virtual function int used(); //返回FIFO中存储的trans的个数
virtual function bit is_empty(); //FIFO是否空
virtual function bit is_full(); //FIFO是否满
virtual function void flush(); //清空FIFO
endfunction
FIFO建立通信和调用
- FIFO还是直接使用IMP通信:在用FIFO通信的方法中,完全隐蔽了这个UVM中特有,而TLM中根本就没有的东西,用户可以完全不关心IMP。因此,对用户来讲,只需要知道analysis_port,blocking_get_port即可,这大大简化了初学者的工作量,尤其是在scoreboard中面临多个IMP时,这种优势更加明显。但是FIFO连接的方式增加了env中代码的复杂度,尤其当连接的端口数量众多时,这个缺点更加明显
将需要发送trans的component内创建put_port端口,需要接受trans的component内创建get_port端口,再在上层component创建一个Anylsis FIFO,将三者连接即可
如下例子,monitor将数据广播给scoreboard
1. 在 monitor 创建 uvm_analysis_port#(trans) ,analysis_port 用于 initiator 广播
// monitor组件
class monitor extends uvm_monitor;
`uvm_component_utils(monitor) // 注册component
virtual reg_intf vif;
uvm_analysis_port#(trans) a_port;
...
function void build_phase(uvm_phase phase); // build_phase中new例化端口port
super.build_phase(phase);
ap = new("ap",this); //
if(!uvm_config_db#(virtual reg_intf)::get(this,"","vif",vif))
uvm_report_error("INTF",$sformatf("%s gets interface failed",get_full_name()),UVM_NONE);
endfunction
task run_phase(uvm_phase phase);
forever begin
trans t;
t = trans::type_id::create("t"); // 在run_phase中create例化transaction
@(posedge vif.clk iff(vif.mon_ck.cmd != IDLE))
t.cmd = vif.mon_ck.cmd;
t.cmd_addr = vif.mon_ck.cmd_addr;
a_port.write(t);
end
endtask
endclass
2.在scoreboard 例化 uvm_blocking_get_port(trans), uvm_blocking_get_port 用于 target 主动请求数据
// scoreboard组件
class scoreboard extends uvm_scoreboard;
`uvm_component_utils(scoreboard)
uvm_blocking_get_port bg_port;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
bg_port = new("bg_port",this); // build_phase中new例化端口port
endfunction
task run_phase(uvm_phase phase);
forever begin
trans t; // 在run_phase中声明trans,并接收
bg_port.get(t);
...
end
endtask
endclass
3.在monitor和scb的上层env,例化uvm_tlm_analysis_fifo#(trans),建立FIFO通信
uvm_tlm_analysis_fifo#(trans) 端口也不属于component,在build_phase中new例化
// env
class env extends uvm_env;
`uvm_component_utils(env)
uvm_tlm_analysis_fifo#(trans) agt_scb_fifo;
agent agt;
scoreboard scb;
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt_scb_fifo = new("agt_scb_fifo",this); // build_phase中new例化FIFO端口
...
endfunction
function void connect_phase(uvm_phase phase); //在connect_phase与FIFO连接
agt.mon.a_port.connect(agt_scb_fifo.analysis_export); // agt的mon的ap连接fifo的analysis_export
scb.bg_port.connect(agt_scb_fifo.blocking_get_export); // scb的bp连接fifo的blocking_get_export
...
endfunction
endclass
TIPs:
analysis port和TLM port有什么区别?analysis FIFO和TLM FIFO的区别是什么?何时使用analysis FIFO 和 TLM FIFO?
- TLM port/FIFOs用于具有使用put/get方法建立的通信通道的两个组件之间的事务级通信。
- analysis port/FIFOs是另一种事务性通信通道,用于组件将事务广播给多个组件。
- TLM port/FIFO用于driver和sequencer之间的连接,而analysis port/FIFOs用于monitor广播事务,这些事务可由scoreboard或覆盖率收集组件接收。
最后
以上就是无情酸奶为你收集整理的UVM 事务级建模TLM 单向/多向通信 端口 FIFO通信事务级建模 TLM(transaction level modeling):TLM portTLM analysis portFIFO通信TIPs:的全部内容,希望文章能够帮你解决UVM 事务级建模TLM 单向/多向通信 端口 FIFO通信事务级建模 TLM(transaction level modeling):TLM portTLM analysis portFIFO通信TIPs:所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复