UVM知识点总结-UVM中的通信机制

UVM中的通信

  1. TLM0
    1. 概述:TLM,事务级建模,transaction level是相对于DUT中各个模块之间的信号级别的通信来说的。  仅仅组件可以例化端口,transaction不能例化端口,因为transaction是继承与object.(1)put/get/peek;transport/master/slave(前三种是方法,还对应着try和can)(2)port/export/import  (3)blocking/nonblocking
      1. port之间的连接:(1)这三种端口按照控制流优先级排列,其中port优先级别最高,import最低(2)port可以连接自己和另外两个对象;export只可以连接自己和import,;export只能作为事务的传输者,不能作为事务的终点;import只能被连接,也就是他只能作为发生事务的终点(3)一个port可以连接到多个port上,但是一个port不能连接到多个import上,一个port能否连接到多个export上??一个export能否连接到多个inport上??(4)port之间的的连接同样支持层次化的嵌套
      2. 方法的调用:(1)peek和get的区别,对于FIFO来说,get任务被调用时FIFO内部的缓存中会少一个transaction,但是对于peek来说,调用时会把FIFO会把transaction复制一份发送出去,其内部缓存的transaction并不会减少
      3. 阻塞与非阻塞:(1)如果没有写blocking或者nonblocking,那么实际上对阻塞和非阻塞均支持
    2. 单向通信
      1. 创建传输数据的类型,定义两个事务类型
      2. (1)创建端口,两个传输的组件之间的端口要成对,阻塞与否、传递的数据类型要一致(2)uvm_blocking/nonblocking_put/get/peek(根据阻塞与否加try和can)_port/export/import #(xx,xx) 实例名;(3)注意(xx,xx)是根据是initiator还是target来决定的,对于initiator来说,括号内仅仅一个发送的对象即可,但是对于target来说括号内不仅仅要有发送的对象(位于第一个参数),还要有target所在类的类名(3)对于target来说,多传递了一个类名的根本原因要想完成数据的发送一方面需要initiator连接了target,另一方面initiator调用target中的方法,那么initiator如何调用target一侧的方法呢?实际上就是通过这里传递了target所在类的类名实现
      3. 顶层进行连接,连接的方向是从initiator(左侧)的port一端连接到了target的import(右侧)的一端,比如c1.bp_port.connect(c2.bp_imp),bp_port和bp_imp是实例名
    3. 双向通信
      1. transport:transport对应到创建端口的语句中与put/get/peek是相同的位置,调用这个transport表示在一次传输过程中既可以完成req的传输,也可以完成rsp的返回,initiaor都是A,target均是B,先Aput到B,再从Bget回A(
      2. master/slave:(1)两者的区别在于initiator作为master时,是先将req放到target中再从中拿回rsp;而initiator作为slave时,是先从target中获取req再将rsp放回target(2)master/slave必须通过put/get/peek调用,使用两次才能完成一次握手通信
      3. #(xx,xx,xx)变成2(initiator)或3(target)个参数,依次是发出去的,返回来,target所在类名(前两个是针对initiator的发和返)
    4. 多向通信(解决具有多个相同类型的端口但是调用的方法无法重名的问题)
      1. (1)通过宏定义两个相同的类型但不同名称的方法,`uvm_blocking/nonblocking_put/get/peek(根据阻塞与否加try和can)_port/export/import_decl (后缀名);具体的方法实际上是在target所在类中实现,因此initiator所在组件基本上不做改变,其创建端口的语句中方法部分也没有加上后缀,但是在target一侧,其创建端口的语句及具体定义的方法都需要加上后缀(2)对于initiator一侧,调用的端口类型即使相同也不需要区分
    5. 通信管道
      1. Analysis Port

(1)uvm_analysis_port,一端到多端,initiator到多个target,port到inport连接,允许没有连接任何一个target(也就是inport)(2)这个过程不同与端到端的连接,这种观察者模式(广播模式)既可以连接一个对象,也可以连接多个对象,同样不连接对象,而前面的端与端之间的连接必须要求两端的连接,本身是一种的过程,不存在阻塞的概念(3)这个过程不同于之前的put,他调用的是write函数,不耗时间,因此analysis port实际上也是不耗时的,也仅能调用write函数,在target所在类中需要定义write函数(4)analysis_port解决的是一个port与多个import连接,实际上export和import也可以像这样连接,也就是一个export与多个import连接,只需将analysis_port替换成analysis_export即可(5)analysis_port可以和analysis_export相连接,但是前提是analysis_export后面还有个analysis_import,直接连接后面不加analysis_import会报错

monitor与scoreboard连接中的一些问题:(1)顶层monitor与scoreboard之间的连接:一共有三种办法解决,分别是o_agt.mon.ap.connect(scb.scb_imp),此种方法层次化关系不太好;在agent中build_phase声明一个ap并且实例化,ap=new("ap",this),并且在agent类中的connect phase中将这个声明的ap与monitor的ap相连接,mon.ap.connect(this.ap),随后在顶层中直接将scoreboard的scb_imp与agent的ap相连接,o_agt.ap.connect(scb.scb_imp),这种方法可能看起来较为麻烦;第三种是在agent的connect_phase中声明一共ap但是不实例化他,让其指向monitor中的ap,ap=ap.monitor;上述方式的后两者的前提是在agent中都通过uvm_analysis_port定义了端口ap  (2)scoreboard不仅要和monitor连接,同时需要和reference modle连接,但是scoreboard的write函数仅仅有一个:类似与多向通信的操作,针对scoreboard通过宏定义后缀,比如`uvm_analysis_imp_decl(_monitor)和`uvm_analysis_imp_decl(_model),相应的定义write方法时也需要加上后缀

      1. FIFO

TLM FIFO,(1)uvm_tlm_fifo继承与uvm_component,uvm_tlm_fifo与uvm_tlm_analysis_fifo 均是FIFO,后者继承与前者,两者的区别在于后者有analysis_export的端口并且有一个write函数,很多情况下两者可以同时适用(2)FIFO调试的一些函数:is_empty函数用于判断当前的FIFO缓存是否为空,is_full判断是都为满,flush用于清空缓存中的所有数据,一般用于复位操作,原型是virtual function void flush();设置FIFO余量的函数,function new(string name,uvm_component parent =null,int size =1),fifo本质上是一个component,所以前两个参数和component一致,第三个参数表示FIFO余量,如果填0的化表示余量无穷大

Analysis TLM FIFO

继承于uvm_tlm_fifo,uvm_tlm_analysis_fifo  其作用在于一方面起到了uvm_tlm_fifo缓存的作用,另一方面添加了uvm_analysis_port中的port,inport端口及write函数

应用:通过Analysis TLM FIFO实现scoreboard主动接收数据(前面的Analysis Port只能实现monitor主动发送数据,scoreboard被动的接收数据),这里的uvm_tlm_analysis_fifo的本质是一块缓存加两个import。具体的操作方式是在agent和scoreboard之间增加一个uvm_tlm_analysis_fifo。在monitor与与uvm_tlm_analysis_fifo之间连接时,monitor与uvm_tlm_analysis_fifo的端口分别时port和import,而在scoreboard与uvm_tlm_analysis_fifo的连接中,两个端口分别时blocking_get_port和import(也就是uvm_tlm_analysis_fifo的两个端口均是import)

上述应用的具体例子是两个agent(或monitor)与scoreboard之间的连接,具体的方法体现在connect上(也就是定义好端口类型后实际上不需要在单独定义中间的FIFO,只需要在顶层连接时进行操作即可)。经过mdl后,scoreboard改变了原来的只能被动的从agent中monitor中接收数据,也可以主动的拿数据,也就是主动的从mdl_scb_fifo中blocking_get,然后mdl_scb_fifo间接的从mdl中拿数据。白皮书p125

      1. analysis port(往往代表着import)与FIFO之间的对比:(1)对于使用端口数组的情况下,采用FIFO 进行传递优于import,原因在于采用import进行连接,其例化的端口往往无法使用for循环语句,但是在FIFO中可以使用
      2. Request & Response
  • 具体的分类包括:(1)uvm_tlm_req_rsp_channel类,内部例化了两个信箱,分别是protect uvm_tlm_fifo #(req) m_request_fifo,protect uvm_tlm_fifo #(rsp) m_response_fifo,两个端口,分别调用两次方法;(2)master_port/slave_port ,前者在连接操作上的另一种形式,虽然是一个端口,但是任然需要调用两次方法;(3)uvm_tlm_transport_channel,在前面两者的基础上的另一种,是uvm_tlm_req_rsp_channel的子类,与master_port/slave_port两者区别在于uvm_tlm_transport_channel在initiator一侧通过transport改变了原有的req/rsp形式,因此initiator一侧只要调用一次transport就可以实现req和rsp的传输,而靠近 target一侧任然是一个端口调用两次方法的形式(slave端口),相较于uvm_tlm_req_rsp_channel新例化了transport端口,uvm_transport_imp #(req,rsp,this_type) transport_export.
  • 三种通信连接的具体形式:(1)两个端口,每个端口调用两次方法,initiator.put_port.connect(req_rsp_channel.put_request_export);target.get_peek_port.connect(req_rsp_channel.get_peek_request_export);target.put_port.connect(req_rsp_channel.put_response_export);initiator.get_peek_port.connect(req_rsp_channel.get_peek_response_export)  (2)initiator.master_port.connect(req_rsp_channel.master_export),target.slave_port.connect(req_rsp_channel.slave_export),上面这两种方式实际上都是uvm_tlm_req_rsp_channel,只是说有两种不同的连接过程(3)第三种方式仅仅在initiator端进行了改变,在target一侧可以使用第一种或者第二种方式连接,initiator.transport_port.connect(transport_channel.transport_export),
  • 总的来说,需要注意以下几点:(1)相较于双向管道来说,相当于不需要自定义函数复制(2)是面向双向通信的fifo,之前的一系列均是面向单向通信管道(3)master_port/slave_port与uvm_tlm_transport_channel的区别在于:对于后者initiator放入req后不会结束(第一步),随后target进行get到req(第二步),随后targetput入rsp(第三步),最后initiator进行get到rsp(第四步),在这里进行完第一步后transport并没有结束,而是必须等到transport拿回rsp(也就是第四步)结束后transport才会结束,也就是第一步和第四步必须是同步结束的,而对于第二部和第三步由于target一侧实际上没有采用transport进行传输,任然是slave,因此第二步和第三步其结束过程是独立的。而master_port/slave_port形式的端口由于initiator一侧也没有使用transport,因此他的四个过程(两个put操作两个get操作)均是独立结束的,执行完就结束
  1. TLM0(解决UVM和system c之间的连接问题)
    1. 接口实现:(1)端口类型:uvm_tlm_b/nb_***_initiator/target_stocke,b和nb分别代表阻塞和非阻塞,blocking和noblocking两种transport方式,.blocking的传输方式要求在一次传输过程中,完成request和response的传输;nonblocking的传输方式则将request和response的传输分为了两个独立的单向传输,而两次传输整体视为完成一次握手传输。(2)传输方法:参见讲义
    2. 传送数据(定义标准的数据包)
      1. 数据类型统一是uvm_tlm_generic_payload类,为了标准化,不建议在此类的基础上进行扩展,具体的标准化内容参见讲义,比如 : uvm_tlm_b_initiator_stocket  xx,注意这里的initiator没有指定具体的数据类型,这是因为数据类型已经固定,确定就是uvm_tlm_generic_payload类型,stocket也是标准化的,是各种port的组合
      2. 此外,在面临现有的数据类型无法通过标准化的数据进行表示时,有两种方法可以解决:(1)将其合并作为数据成员data数组的一部分,传到给target一侧时会自动解析(2)uvm_tlm_generic_payload::set_extension(uvm_tlm_extension_base xx)将这个数据类型set进去,uvm_tlm_extension_base是标准数据类型中针对标准无法覆盖时提供的扩展选项
    3. 时间标记,往往在systemc中自建时钟会导致运行效率的降低,uvm_tlm_time,uvm中新建的时间类,起一个时间标准的作用
  2. 通信原件的同步
    1. uvm_event
      1. 一些概念:(1)资源池uvm_object_string_pool #(T);T表示的是uvm_object,pool表示的是一个资源池,资源是按字符串索引的关联数组的形式存放的,而对于uvm_event_pool来说,实际上只是将其中的T变为uvm_event,uvm_event_pool是其的子类(2)对事件的创建可以调用uvm_event_pool,比如e1=uvm_event_pool::get_global("e1"),其原则是如果前面有uvm_event e1,则会创建e1;如果没有,那么会自动创建一个e1
      2. 基本的点:(1)wait_ptrigger相当于电平触发的意思(等价于SV中的trigger),而wait_trigger相当于SV中的@(必须在同一时刻时,该事件没有发送过,如果之前发生过,那么实际上等不到)(2)e1.trigger()括号中可以加内容也可以不加,加了比如wait_trigger(d)(d表示具体的数据),那么此时需要用wait_trigger_data语句等,对应到前面的数据传输,也就是等到前面的数据到了,如果前面没有d,那么用wait_trigger()也可以。另外,wait_trigger_data(xx)中的xx默认是object类型,如果后续需要访问子类的对象,需要使用类型转换函数$cast(3)uvm_event触发事件,wait_trigger/ptrigger等待事件,事件触发后,uvm_event需要通过reset()重制状态,随后才可以继续触发
      3. 相关函数:(1)回调函数,e1.add_calback(d),e1是事件,d是回调函数,其定义要在与uvm_event_callback的子类进行定义,通过这种办法将事件e1和回调函数d绑定在一起,此外在uvm_event_callback的子类中还可以定义wait_trigger调用前后的pre_trigger和post_trigger,pre_trigger需要有返回值,返回值为1时不执行后续的trigger和post_trigger,返回值为0继续执行后续任务(2)uvm_event可以通过get_num_waiters()获取等待他的进程数1
      4. object和component之间发生同步是无法通过端口的进行同步的,因为object无法例化端口,因此这两者可以通过event进行触发并且也可以发送一些数据;另外object与object之间也可以借助uvm_event完成一些数据的通讯,比如sequence与sequence之间进行同步,或者sequence与driver之间进行同步
    2. uvm_barrier:uvm_barrier b1;b1.wait_for();b1.set_threshold(xx)
    3. uvm_callback
      1. (1)基本步骤:预留回调函数入口,定义回调函数,在顶层例化回调函数及添加回调类的实例,(2)例子:定义回调函数 class cb1 extends uvm_callback; `uvm_object_untils(cb1)  这一步时对回调函数进行注册,uvm_callback属于uvm_object,后续在该类中定义具体的任务或函数,比如do_trans******将组件与回调函数关联,并将回调函数插入组件中,这两个步骤都需要在具体的组件类中(比如类comp1,该类继承与uvm_component),关联 `uvm_register_cb(comp1,cb1),插入组件`uvm_do_callbacks(comp1,cb1,do_trans(d))*******添加回调函数实例uvm_callbacks #(comp1)::add(c1,m_cb1),c1是comp1的实例,m_cb1是回调函数类cb1的实例 
      2. (3)回调函数间的继承需要注意的问题:在上面的基础上,假设有另一个类cb2继承与cb1,也就是有两个回调函数,但是假如在组件comp1中进行操作时,只对cb1进行了关联和插入,在添加回调函数实例的操作时对两个回调类均添加了实例,在这情况下cb2回调函数同样是可以添加进来的,原有在于cb2继承与cb1,也就是一旦发生继承关系时,就不需要在组件comp1中对子类的回调函数进行绑定和插入,只需要在在组件中对父类进行绑定和插入,那么在随后添加时就会自动执行子类的回调函数
      3. (4)子类父类回调函数同名时,按照上面的情况,如果cb1和cb2中均有一个同名的任务do_trans,在添加组件是,对父类的回调函数cb1不添加,而只添加子类的回调函数cb2,那么cb2会覆盖cb1的do_trans,因此通过这种顶层改变添加的回调函数的方式可以对类进行很好的修改
      4. 5)回调函数的退出:多个回调函数同时存在时,默认情况下时按照顺序进行,但是也可以通过`uvm_do_callbacks_exit_on的方式控制回调函数的退出,这个宏一共有四个参数,分别是组件类,回调函数类,回调函数中具体方法,值a,回调函数会一直执行,直到返回值a时退出
  • 0
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值