UVM知识之TLM通信

文章详细介绍了UVM中的TransactionLevelModeling(TLM)通信机制,包括定义数据类型、组件端口的声明和连接、端口通信的流程,以及如何实现缓存功能。此外,还讨论了双向通信、多向通信以及使用uvm_event和uvm_barrier进行同步的方法。文章强调了TLM通信在组件复用、灵活性和仿真速度优化方面的优势,并提到了回调机制在扩展和复用组件功能上的应用。
摘要由CSDN通过智能技术生成

1. TLM通信的常规步骤:
     (1) 定义TLM通信的数据类型,一般包括request类和respond类
     (2) 在需要通信的各个层次的component组件声明并创建端口(端口要成对,除了端口类型外其他的要相同)
         声明imp端口时要多传输一个参数imp_parent_t,表示的是component的类型
           为了找到imp_parent_t句柄,顶层env将两个通信对象连接后,port端口所在组件的run_phase可以通过自身端口间接调用imp端口comp2中定义的端口方法
           创建端口使用new(string name, parent)
         注意:如果组件被另一个更高级的组件包括,则底层的组件端口连接到高一级的组件上,把这些端口看成该高层次组件的端口(和set_interface一样)
         问题1:如果monitor的端口连接到agent的端口,agent中需不需要调用put()以及需不需要实现对应的函数?
         问题2:多层次的组件连接如何保证端口是通的,比如monitor到checker,以及checker到refmod?
     (3) 在高层次组件的connect函数中对端口进行连接
     (4) 在imp端口中定义提供给initiator的函数
         task get()/function bit try_get()/function bit can_get()
         task put()/function bit try_put()/function bit can_put()
         task peek()/function bit try_peek()/function can_peek()
         如果是put(),那么initiator生成事务t并发送到target端
         如果是get(),那么initiator发送请求,向target端获取数据,target端该数据被消耗
         如果是peek(),那么initiator发送请求,向target端获取数据,target端该数据不被消耗
  2. 如何实现缓存功能
     (1) 在target端定义方法使用mailbox或者队列的方式来存储事务,
         比如monitor的put port向ref model的put imp发送数据时,target端实现的put任务可以把接收的事务put进mailbox里面
     (2) 使用自带缓存和端口以及target端任务实现的uvm_tlm_fifo/uvm_tlm_analysis_fifo
         实际上,将(1)包装起来就是一个uvm_tlm_fifo,内置的端口都是imp类型的,如果要实现一端对多端,则使用uvm_tlm_analysis_fifo
         注意,即使是uvm_tlm_analysis_fifo,实际上一端还是需要例化一个fifo的,重点是只有analysis_imp才能和analysis_port相连
         例如monitor要将数据转发给scb以及coverage,那么在monitor端定义一个analysis_port,然后通过write()将事务广播出去,
         scb以及coverage均例化一个uvm_tlm_analysis_fifo,一端与monitor的analysis_port相连,另一端与新定义的get_port相连,
         数据从monitor发送,存入fifo后,流向scb和coverage(uvm_tlm_analysis_fifo都是analysis_export)
  3. 双向通信
     (1) 主要分为两大类型,一种是transport,这种没有区分master个slave,在target端只需定义一个task transport(REQ req, output RSP rsp)
         另一种是区分master和slave的,需要在target端同时定义task get(output RSP rsp)/task peek(output RSP rsp)/task put(REQ req)
         注意:端口定义的方法是没有返回值的,put或者get是需要定义task的参数方向的。
     (2) transport是一次性发送REQ和接收RSP;master发送REQ然后等待接收slave端的RSP(initiator可以充当master或者slave)
     注意:不管区分master与slave与否,以上端口的数据传输方向均是双向的
  4. 多向通信(一端对多端)
     (1) 解决同个组件中具有相同类型的端口的方法重名问题,比如scb需要接收来自fmt、chnl、reg的monitor的数据,因此在scb端需要定义多个相同类型的imp端口来连接
         为了针对不同的组件实现不同的端口方法,通过UVM宏定义的方式来加以区分(只需要在target端加后缀区分,initiator不需要)
         `uvm_blocking_put_imp_decl(_reg);
         uvm_blocking_put_imp_reg 
         task put_reg(reg_trans t);
  5. TLM管道(component类)
     (3) 双向通信管道
     uvm_tlm_req_rsp_channel: 相当于一个支持双向通信的uvm_tlm_fifo(管道的一边使用两个单向端口)
     uvm_tlm_transport_channle: 相当于一个例化了双向通信端口的uvm_tlm_fifo
  6. UVM同步通信元件
     (1) 在SV中,可以使用event、mailbox以及semaphore来实现同步通信,但如果要实现组件之间的通信,需要进行句柄的传递才能获取相同的通信元件
         在UVM中,不需要进行句柄来进行跨出层次的传递,将uvm_event存放在唯一的全局资源池uvm_event_pool,然后不同的组件均可以通过字符来索引相同的event
         这样可以保证了组件环境的封闭性(定义的名字可以任意,不一定是路径索引,所以某个层次没有了也不影响)
         wait_trigger() : uvm_event的等待电平触发
         wait_ptrigger() : uvm_event的等待边沿触发
         trigger()
         @event
         ->event
     (2) evrnt的使用场景
         在uvm中,组件通信是采用TLM端口的,但是如果数据传输是偶然触发的,而且需要立即处理,那么就需要使用到uvm_event
         或者在component和object中通信(例如sequence和driver之间以及sequence和sequence之间,
         若没有同步,则sequence每次产生数据后就传输到driver,加入通信后可以等driver消化一个数据后再生成一个新的数据)
     (3) uvm_barrier和SV中的semaphore相似,和uvm_event类似,均有一个全局资源池使得不同组件可以使用字符串进行索引
         使用场景:若两个组件的run_phase需要同步运行,可以使用uvm_barrier来设置阈值控制什么时候“开阀”
     (4) UVM的callback机制
         采用了callback函数对基础的类进行拓展,以此来实现测试类的封装和复用
         具体的,基础的测试用例定义了一些虚任务,通过在这些虚拟任务里面设置外部的回调函数的钩子,就能拓展基础类的功能
         不同的回调函类拥有不同的回调函数的定义,因此传入不同的回调函类(类或者子类)可以实现不同的拓展功能,以应对不同的测试需求
         使用uvm_callback_iter()以及uvm_callbacks#(T, CB);来实现回调的顺序性和继承性
         使用步骤:
         1) 定义回调基类 class cb1 extends uvm_callback;以及在类中定义对应的回调函数,定义为虚拟的,以方便继承
         2) 在要实现回调的组件中注册和插入callback钩子
             注册:`uvm_resister_cb(comp1, cb1) // 将要实现回调的组件和调用的回调类(类型,不是实例)绑定在一起
             插入:`uvm_callbacks(comp1, cb1, do_trans(d)) // 在要调用回调函数的地方插入callback钩子,三个参数分别是组件、回调类、具体执行的回调方法
         3) 在顶层的组件中例化要调用回调的类以及回调类实例,然后使用uvm_callbacks #(comp1) :: add(c1, m_cb1);绑定具体的实例
             uvm_callbacks #(comp1) :: add(c1, m_cb1); 在build_phase()添加,
             若是在顶层env中包含comp1组件,则add()添加的是两个类别的实例;
             若顶层是继承于comp1,则是add()添加的是自己以及回调实例
             总的来说,add()添加的是想要插入的组件以及组件调用的callback实例
         4) 如何实现复用的?
             回调基类定义了需要执行的方法(例如在写testcase时就是挂载不同的sequence),方法类型全部为虚函数,因此可以使用继承的方式,定义不同的子类,
             来实现挂载不同的sequence,在顶层的test中,插入不同子类的回调钩子就能定义出不同的testcase。
         5) 优点:
             对于一个别人实现好的类,如果要对类里面的方法做修改的话,有继承、覆盖和使用回调函数两种方法。
             使用继承时,新定义的子类无法覆盖掉之前父类,除非手动把使用了父类的位置全部替换成子类,但有可能出现句柄类型不一致的问题;
             使用工厂的覆盖机制虽然能解决,除了工厂的覆盖机制外,还可以使用回调来对一些方法做延申,例如uvm域的自动化提供的do_compare() do_copy()等
      
  7. 相比SV中的mailbox,TLM端口通信具有什么优点?
     (1) TLM端口支持一端对多端
     (2) 在更高层次复用的时候,UVM如果不需要这个一对多的通信时可以切断,支持空发不报错。
     (3) 通过SV进行显式的句柄连接的时候,存在了跨层次的问题,如果某个层次不见了,需要在理解层次的情况下更改,
         如果是TLM,一方面,即使后面组件层次有修改,对于维护当前组件的人来说,并不需要在组件内部做修改,只需在顶层的connect_phase修改端口的连接,
         另一方面如果某个层次不见了,可以用port\export层层连接来定位连接,维护该层次的只需要把前后层次连接起来即可,其他层次的维护者不需要做修改
         (针对没有使用端口的情况)
     (3) 在monitor传入到coverage model时,并不需要做缓存,只是需要在monitor监控到相应的信号触发一个事件从而触发采样,
         TLM通信可以做到不做缓存直接通信。
     (4) 原本多个时钟周期传输的多个数据(要传输多个transaction),现在按数据包的概念把这些小的数据打包成大数据,
         相当于在一定时间内从传输多个transaction变成传输一个,增大内容体积,使得通信的频率降低了,提高整体环境的仿真速度。
  8. 注意要点
     (1) TLM通信的端口类型不继承于uvm_object,所以只能用new的方式传递,但是new的时候要带上所在的parent。
     (2) TLM通信是只能在组件里的使用,object内没有port的概念。
     (3) 端口是参数类,port端要声明传递参数,imp端不仅要声明传递参数,还要写当前存在的类的类名(表达一个对应的targe要实现的传递方法在哪个类里)
     (4) 实现的方法里面,如果做了句柄创建new的,传递了两个参数(string name,uvm_component)
     (5) 对比TLM_FIFO这种带存储的和mailbox存储的区别(如果是用Mailbox加端口的方法通信,在initiator一侧使用方法的时候,
         因为在自己的类里面没有mailbox句柄会报错),且如果你按照绝对的规范,每一层都有port、export做过渡,那么你总是维护当前层次下的连接就行了,再底层的连接是由那个模块的人负责,复用性变好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值