热门标签 | 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中已经写好的函数即可,降低了整个系统的维护成本。



推荐阅读
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
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社区 版权所有