热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

UVM基础TLM通信机制(一)

目录基本概念TLM通信分类单向通信单向通信举例单向通信代码双向通信多向通信多向通信总结通信管道TLMFIFOAnalysisPortAnalysisTLMF

目录

基本概念

TLM通信分类

单向通信

单向通信举例

单向通信代码 

双向通信

多向通信

多向通信总结

通信管道

TLM FIFO

Analysis Port

Analysis TLM FIFO



        芯片验证是在RTL模型初步建立后,通过验证语言和方法学例如SV/UVM来构建验证平台。该平台的特点是验证环境整体基于面向对象开发,组件之间的通信基于TLM,而在driver与硬件接口之间需要将TLM抽象事务降解到基于时钟的信号级别。      


基本概念

        TLMTransaction Level Modeling(事务级建模)的缩写,它起源于SystemC的一种通信标准。所谓transaction level是相对DUT中各个模块之间信号线级别的通信来说的。简单来说,一个transaction就是把具有某一特定功能的一组信息封装在一起而称为的一个类。仿真速度是TLM对项目进度的最大贡献,同时TLM传输中的 transaction 又可以保证足够大的信息量和准确性。

        如果要提升系统模型的仿真性能,可以从两个方面出发:一个是建模本身的优化,另一个是模型之间的通信优化。通信优化可以通过降低通信频率,内容体积增大的方式来减少由于不同进程之间同步带来的资源损耗。TLM就是从通信优化角度提出的一种抽象通信方式。

        TLM通信需要两个通信的对象,这两个对象分别称为 initiator target 。区分它们的方法在于,谁先发起通信请求,谁就属于initiator;谁作为发起通信的响应方,谁就属于target ,但这个分类并不代表transaction一定是initiator发起的,transaction也可能是从target流向initiator。

        按照transaction 的流向,我们可以将两个对象分为 producer consumer 。数据从哪里产生,它就属于producer,数据流向了哪里,它就属于consumer。

        譬如transaction从发起端到接收端,是发起端向接收端调用get函数,此时发起端是producer ,接收端是 consumer;transaction也可以从接收端到发起端,是发起端向接收端调用put函数,此时发起端是consumer,接收端是producer。        


initiator 和 target 的关系同 producer 和 consumer的关系不是固定的


        有两个参与通信的对象之后,用户需要将TLM通信方法在 target 一端中实现,以便于 initiator 将来作为发起方可以调用 target 的通信方法,实现数据传输;在 target 中实现了必要的通信方法后,需要对两个对象进行连接,在两个对象中创建TLM端口,继而在更高层次中将这两个对象进行连接。


TLM通信分类

TLM通信步骤可以分为:

        ① 分辨出component是属于initiator还是target,是producer还是consumer。

        ② 在target中实现TLM通信方法。(此为UVM的规定模式)

        ③ 在两个对象中创建TLM端口。(TLM端口不需要预设,只需要实例化即可)

        ④ 在更高层次中将两个对象的端口进行连接。

从数据流向来看,传输方向可以分为单向(unidirection)和双向(bidirection)

        单向传输:由initiator发起request transaction。

        双向传输:由initiator发起request transaction,传送至target;而target在接受了request transaction后,会发起response transaction,继而返回给initiator。

端口按照类型可以划分为三种:

        ※ port:经常作为initiator的发起端,initiator凭借port才可以访问target的TLM通信方法。

        ※ export:作为initiator和target中间层次的端口。

        ※ imp:只能作为target接收request的末端,它无法作为中间层次的端口,所以imp的连接无法再次延伸。

        initiator需要有port,是起点,但是当initiator和target之间隔了很多层次的时候,那么中间这些层次的过渡就使用export,target上的就是imp,是终点。



        如果将传输方向(单、双向)端口类型(port、export、imp)加以组合,可以帮助理解TLM端口的继承树。TLM端口一共可以分为六类:

1. uvm_UNDIR_port #(trans_t) //单向
2. uvm_UNDIR_export #(trans_t)
3. uvm_UNDIR_imp #(trans_t, imp_parent_t)
4. uvm_BIDIR_port #(req_trans_t, rsp_trans_t) //双向
5. uvm_BIDIR_export #(req_trans_t, rsp_trans_t)
6. uvm_BIDIR_imp #(req_trans_t, rsp_trans_t, imp_parent_t)

        要注意的是,端口是继承于uvm_void,端口既不是继承与object类型也不是继承与component类型,所以端口是不能使用type_id::create的。


单向通信

        单向通信(undirectional communication)指的是从initiatortarget之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。

         按照UVM端口名的命名规则,它们指出了通信的两大要素:① 是否为阻塞的方式(是否可以等待延时);② 采用何种通信方法(put、get、peek)。其中单一端口函数的PORT可以为port、export和imp。

        阻塞传输方式将blocking前缀作为函数名的一部分,而非阻塞方式则名为nonblocking。阻塞端口的方法类型为task,这保证可以实现事件等待和延时;非阻塞端口的方式类型为function,这保证方法调用可以立即返回。

        blocking阻塞传输的方法包含:Put():initiator 先生成数据T t,同时将该数据传送至target。Get():initiator从target获取数据T t,而target中的该数据T t则应消耗。Peek():initiator从target获取数据T t,而target中的该数据T t还应保留。

        nonblocking非阻塞方法分别是:try_put(); can_put(); try_get(); can_get(); try_peek(); can_peek()。非阻塞函数对应阻塞任务的区别在于它们必须立即返回值,执行成功返回1,执行失败返回0。


单向通信举例


单向通信代码 

class itrans extends uvm_transaction;int id;int data;...
endclass
class otrans extends uvm_transaction;int id;int data;...
endclass
class comp1 extends uvm_component;uvm_blocking_put_port #(itrans) bp_port; //blocking putuvm_nonblocking_get_port #(otrans) nbg_port; //nonblocking get`uvm_component_utils(comp1)...task run_phase(uvm_phase phase)itrans itr;otrans otr;int trans_num = 2;forkbeginfor(int i=0; iendclassclass comp2 extends uvm_component;uvm_blocking_put_imp #(itrans, comp2) bp_imp;uvm_nonblocking_get_imp #(otrans, comp2) nbg_imp;//对于import来讲,不仅要告知所传输的数据类型,还要告知所例化的comp名字。itrans itr_q[$];`uvm_component_utils(comp2)...task put(itrans t);itr_q.push_back(t);endtaskfunction bit try_get (output otrans t);itrans i;if(itr_q.size() != 0)begini = itr_q.pop_front();t = new("t", this);t.id = i.id;t.data = i.data <<8;return 1;endelse return 0;endfunctionfunction bit can_get();if(itr_q.size() != 0) return 1;else return 0;endfunction
endclass

        除了在component1和component2中定义任务和方法外,还要在env环境中对component1和component2做例化创建。以及接口的连接,这里要注意连接方向是initiator的port连接到target上面。

class env1 extends uvm_env;comp1 c1;comp2 c2;`uvm_component_utils(env1)...function void build_phase(uvm_phase phase);super.build_phase(phase);c1 = comp1::type_id::create("c1", this);c2 = comp2::type_id::create("c2", this);endfunction: build_phasefunction void connect_phase(uvm_phase phase);super.connect_phase(phase);c1.bp_port.connect(c2.bp_imp);c1.nbg_prt.connect(c2.nbg_imp);endfunction: connect_phase
endclass

首先comp1例化了两个port端口:


  uvm_blocking_put_port #(itrans) bp_port;             //blocking put
  uvm_nonblocking_get_port #(otrans) nbg_port;    //nonblocking get


 comp2作为target例化了两个对应的imp端口:


  uvm_blocking_put_imp #(itrans, comp2) bp_imp;
  uvm_nonblocking_get_imp #(otrans, comp2) nbg_imp;


连接的方法是使用connect,其中connect的左侧是initiator,connect的右侧是target。

 总结起来还是三步骤: ① 定义端口;② 实现对应方法;③ 在上层将端口进行连接。




双向通信

        与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但数据流向在端对端之间是双向的。绝大多数的环境都是采用单向通信,双向通信应用的比较少。

        双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。

UVM双向端口不再采用get、put和peek,而是采用新的方式(transport、master和slave):

        作为master的一端,在方法声明中,既有put()也有get()。 

        双向端口按照通信握手方式可以分为:① transport双向通信方式;② master和slave双向通信方式。transport端口通过transport()方法,可以在同一方法调用过程中完成REQ和RSP的发出和返回。master和slave的通信方式必须通过put、get和peek的调用,使用两个方法才可以完成一次握手通信。




多向通信

        多向通信(multi-directional communication)不是多个组件多个方向的通信,而是指initiator与target之间的相同TLM端口(同名,又相同类型)数目大于1时的处理办法。

产生问题的原因:

        comp1有两个uvm_blocking_put_port,而comp2有两个uvm_blocking_put_imp端口,我们对于端口例化可以给不同的名字,连接也可以通过不同名字进行索引,但问题在于comp2中需要实现两个task put(itrans t),又因为不同端口之间要求在imp端口一侧实现专属方法,这就造成了方法命名冲突,即无法在comp2中定义两个同名的put任务。

解决方法:

        UVM通过端口宏声明方式来解决这一问题,它解决问题的核心在于让不同端口对应不同名的任务,这样便不会造成方法名的冲突。

`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_decl(_p2)//宏定义了两个独一无二的class comp1 extends uvm_component;uvm_blocking_put_port #(itrans) bp_port1;uvm_blocking_put_port #(itrans) bp_port2;`uvm_component_utils(comp1)...task run_phase(uvm_phase phase);itrans itr1, itr2;int trans_num = 2;forkfor(int i=0; iendclassclass comp2 extends uvm_component;uvm_blocking_put_imp_p1 #(itrans, comp2) bt_imp_p1;uvm_blocking_put_imp_p2 #(itrans, comp2) bt_imp_p2;itrans itr_q[$];semaphore key;`uvm_component_utils(comp2)...task put_p1(itrans t);key.get();itr_q.push_back(t);`uvm_info("PUT_P1", $sformatf("put itrans id: &#39;h%0x ,data: &#39;h%0x", t.id, t.data), UVM_LOW)key.put();endtask
endclass

这里涉及到很多知识点,在最开始的两行宏定义,就是解决多向通信问题的办法:


`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_decl(_p2)


        使用了如上的宏定义后,那么原来的uvm_blocking_put_imp包括里面的函数,都应该定义成加后缀“_p1”和后缀"_p2"的形式,所以在从comp2中定义target以及put函数后面都加了对应的后缀。而对于port端口而言,它只需要把itrans发出去就行了,而不必管itrans被发送到哪里,所以不需要对initiator组件内部的port进行宏定义

        不仅如此,为了防止互斥访问,还使用了旗语semaphore。通过key.get()和key_put()的方式来实现互斥访问,解决访问共同资源的数据冲突问题。

        另外再提一点细节,因为我们调用的TLM端口时blocking类型的,所以可以有延时,因此我们使用的put函数是一个task类型,如果我们定义的TLM端口时non-blocking类型的,那么就要定义function try_get和can_put等,同样的后面也要加上对应的宏定义后缀。不仅如此,使用旗语semaphore也不能使用key.get,而是使用key.try_get(),如果get不到key要立即返回0。

根据三步骤,定义完端口和方法,最后一步就是在顶层进行连接,给出外部env1代码:

class env1 extends uvm_env;comp1 c1;comp2 c2;`uvm_component_utils(env1)...function void build_phase (uvm_phase phase);super.build_phase(phase);c1 = comp1::type_id::create("c1", this);c2 = comp2::type_id::create("c2", this);endfunction: build_phasefunction void connect_phase(uvm_phase phase);super.connect_phase(phase);c1.bp_port1.connect(c2.bt_imp_p1);c1.bp_port2.connect(c2.bt_imp_p2);endfunction: connect_phase
endclass

        如果有多个相同类型的import,那么必须通过宏定义的方式,定义不同名称的import, 从而解决任务名冲突的问题。

        对于组件1来讲不需要对port进行区分,并且对于从comp1中调用的函数还是put函数,而不是put_p1或者put_p2,这就是宏定义方法的好处。对于initiator中的port来说,他不需要管连接的import是哪个,所定义的put函数被宏定义成什么名称,只要我们在import中进行了宏定义后,连接到port时,它会自动调用与put相对应的put_p1或者put_p2函数。


多向通信总结

① 用户只需要在例化多个imp端口的组件中实现不同名称的方法,使其对应imp类型名保持一致。

② 而对于port端口一侧的组件,则不需要关心调用的方法名称,因为该方法名并不会发生改变。

③ 所以通过这种方式可以防止通信方法名的冲突,从而解决多向通信的问题。 




通信管道

        经过上面的介绍,我们需要在target端定义get()和put()函数,每次定义target都要进行函数的声明,是否有方法不自己实现这些数据传输方法,同时可以使用TLM?以及数据传输过程中,如果出现一端到多端的情况,怎么处理?

        几个TLM组件和端口可以帮助我们解决上面的问题:


※ TLM FIFO
※ analysis port
※ analysis TLM FIFO
※ request & response 



TLM FIFO

        在一般TLM传输过程中,无论是initiator给target发起一个transaction,还是initiator从target获取一个transaction,transaction最终都会流向consumer中。consumer在没有分析transaction时,我们希望先将对象存储到本地FIFO中供稍后使用。

        UVM库中内置了一个uvm_tlm_fifo类,这个类是一个组件,它继承与uvm_component类,而且已经预先内置了多个端口以及实现了多个对应方法供用户使用。

        对于uvm_tlm_fifo来讲,存放的数据类型是固定的,因为作为一个fifo里面存放的数据肯定类型是一致的。uvm_tlm_fifo还提供put、get以及peek对应的端口。  

uvm_put_imp #(T, this_type) blocking_put_export;
uvm_put_imp #(T, this_type) nonblocking_put_export;
uvm_get_peek_imp #(T, this_type) blocking_get_export;
uvm_get_peek_imp #(T, this_type) nonblocking_get_export;
uvm_get_peek_imp #(T, this_type) get_export;
...

        上面端口都是imp端口,虽然后面有get_export,但不是export端口,里面是有定义方法的。虽然看起来端口名是export,但真正的类型是imp。


Analysis Port

        除了端对端的传输,在一些情况下还有多个组件会对同一个数据进行运算处理,如果这个数据是从同一个源的TLM端口发出到达不同组件,这就要求该种端口可以满足从一端到多端的需求。

        如果数据源端发生变化需要通知跟它关联的多个组件时,我们可以利用软件的设计模式之一观察者模式(observer pattern)来实现。

        observer pattern的核心在于:① 这是从一个initiator端到多个target端的方式。② analysis port采取的是“push”模式,从initiator端调用多个target端的write()函数来实现数据传输。

        按照传输方法和端口方向组合,可以将 analysis port 分为:uvm_analysis_port 、uvm_analysis_export 以及uvm_analysis_imp。target 一侧例化了 uvm_analysis_imp 后还需要实现write()函数。从initiator端调用write()函数时,它是通过轮询的方式将所有连接的target端内置的write()函数进行了调用。也因为是函数,所以无论一个initiator连接了多少target,initiator端调用的write()函数都是可以立即返回的。并且,特殊的是,采用analysis port,即使initiator没有和任何target相连都不会报错,这是和前面端对端的(port和imp成对出现)port有区别的地方


Analysis TLM FIFO

         uvm_tlm_analysis_fifo类继承于uvm_tlm_fifo,它具有单一TLM端口的数据特性,同时该类又有一个uvm_analysis_imp端口analysis_export并且实现了write()函数。


uvm_analysis_imp #(T, uvm_tlm_analysis_fifo #(T)) analysis_export;


        整个数据流是initiator把数据push到fifo中,target需要的时候把数据从fifo中get出来,注意target定义的都是port端口fifo的端口都是imp,这样的好处是uvm_tlm_analysis_fifo既可以实现一端到多端的目的端(已经内置各种数据处理函数)又可以实现数据缓存。并且由于initiator和target的端口类型都是port类型,所以不需要内置函数,只需要调用uvm_tlm_analysis_fifo中已经写好的函数即可,降低了整个系统的维护成本。



推荐阅读
author-avatar
AK47GXF
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有