uvm通信

TLM通信

在芯片开发流程中,有两个地方对项目的助推起到了关键作用:系统原型、芯片验证

  • 系统原型一般是通过硬件功能描述文档来模拟硬件行为,而行为要求不同于RTL模型。系统原型可以提供一个准确到硬件比特级别、按照地址段访问、不依赖于时钟周期的模型,该模型通常基于SystemC语言,而系统原型中各个模块通过TLM可以实现宽松时间范围内的数据包传输。
  • 芯片验证是在RTL模型初步建立后,通过验证语言和方法学例如sV/UVM来构建验证平台。该平台的特点是验证环境整体基于面向对象开发,组件之间的通信基于TLM,而在driver与硬件接口之间需要将TLM抽象事务降解到基于时钟的信号级别。
  • 系统原型阶段和芯片验证阶段均使用了TLM通信方式。前者是为了更快地实现硬件原型之间的数据通信,后者是为了更快地实现验证组件之间的数据通信。
  • 仿真速度(不依赖于时钟)是TLM对项目进度的最大贡献,同时TLM传输中的事务又可以保证足够大的信息量和准确性。
  • TLM并不是某一种语言的产物,而是作为一种提高数据传输抽象级的标准存在的。
  • 高抽象级的数据通信,可以用来表示宽松时间跨度内的硬件通信数据,而通过将低颗粒硬件周期内的数据打包为一个大数据,非常有利于整体环境的仿真速度。
  • TLM的运用越来越广泛,包括emulator同硬件仿真器的协同仿真框架中,也建议使用这种方式来降低混合环境之间的通信频率,以便提高整体的运算效率。
    uvm通信_第1张图片

        TLM是一种基于事务(transaction)的通信方式,通常在高抽象级语言例如SystemC或者SV/UVM中作为模块之间的通讯方式。
        TLM成功地将模块内的计算和模块之间的通信从时间跨度方面剥离开了。
        在抽象语言建模体系中,各个模块通过一系列并行的进程实现,并通过通信和计算来模拟出正确的行为。
        如果要提高系统模型的仿真性能,需要考虑两个方面

  • —个是建模自身的运算优化,另外一个是模型之间的通信优化。
  • 前者需要依靠开发者的经验还有性能分析工具来逐步优化模型。
  • 后者则可以通过将通信频率降低、内容体积增大的方式,来减少由不同进程之间同步带来的资源损耗。
  • TLM正是从通信优化角度提出的—种抽象通信方式。

        TLM是一种基于事务(transaction)的通信方式,通常在高抽象级语言例如SystemC或者SV/UVM中作为模块之间的通讯方式。

        TLM通信需要两个通信的对象,这两个对象分别称为initiator和target。区分它们的方法在于,谁先发起通信请求,谁就属于initiator,而谁作为发起通信的响应方,谁就属于target。
        在初学过程中读者们还应该注意,通信发起方并不代表了transaction的流向起点,即不一定数据是从initiator流向target,也可能是从target流向了initiator。
        因此按照transaction的流向,我们又可以将两个对象分为producer和consumer。区分它们的方法是,数据从哪里产生,它就属于producer,而数据流向了哪里,它就属于consumer。

        initiator与target的关系同producer与consumer的关系不是固定的。有了两个参与通信的对象之后,用户需要将TLM通信方法在target一端中实现,以便于initiator将来作为发起方可以调用target的通信方法,实现数据传输

        在target实现了必要的通信方法之后,最后一步我们需要将两个对象进行连接,这需要在两个对象中创建TLM端口,继而在更高层次中将这两个对象进行连接

uvm通信_第2张图片

比如monitor和scoreboard,monitor是发起端;driver和generator,driver是发起端,get到,消化后,put rsp回去。

分类

我们可以将TLM通信步骤分为:

  • 分辨出initiator和target,producer和consumer。
  • 在target中实现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的连接无法再次延伸。

如果将传输方向和端口类型加以组合,可帮助理解TLM端口的继承树。TLM端口一共可以分为六类:(既不是object也不是component,无法使用工厂create)

 uvm_UNDIR_port #(trans_t)
 uvm_UNDIR_export#(trans_t)
 uvm_UNDIR_imp # (trans_t, imp_parent_t)       单向
 uvm_BIDIR_port # (req_trans_t, rsp_trans_t)    双向
 uvm_BIDIR_export # (req_trans_t, rsp_trans_t)
 uvm_BIDIR_imp # (req_trans_t, rsp_trans_t, imp_parent_t)

uvm通信_第3张图片

 端口的使用

uvm通信_第4张图片

        就单向端口而言,声明port和export作为request发起方,需要指定transaction类型参数,而声明imp作为request接收方,不但需要指定transaction类型,也需要指定它所在的component类型。
        就声明双向端口而言,指定参数需要考虑双向传输的因素,将传输类型transaction拆分为request transaction类型和response transaction类型。
从对应连接关系得出TLM端口连接的—般做法:

  • 在initiator端例化port,在中间层次例化export,在target端例化imp。
  • 多个port可以连接到同一个export或者imp;但是单个port或者export无法连接多个imp。这可以抽象为多个initiator可以对同一个target发起request,但是同一个initiator无法连接多个target。
  • port应为request起点,imp应为request终点,而中间可以穿越多个层次。基于单元组件的自闭性考虑,建议在穿越的中间层次声明export,继而通过层层连接实现数据通路。
  • port可以连接port、export或者imp; export可以连接export或者imp;imp只能作为数据传送的终点,无法扩展连接。连接的话,一般是initiator作为发起连接的对象,从左往右如上。

示例

从示例中可以得出关于建立TLM通信的常规步骤:

  • 定义TLM传输中的数据类型,上面分别定义了request类和response类。
  • 分别在各个层次的component中声明和创建TLM端口对象。
  • 通过connect()函数完成端口之间的连接。
  • 在imp端口类中要实现需要提供给initiator的可调用方法。例如,在comp2中由于有一个uvm_nonblocking put_imp #(request,comp2) nbp_imp,因此需要实现两个方法try_put()和can_put();而comp4中有一个uvm_blocking get_imp #(request, comp4)bg _imp,则需要实现对应的方法get()。
  • 需要注意的是,必须在imp端口类中实现对应方法,否则端口即使连接也无法实现数据传输。

单向通信

        单向通信(unidirectional communication)指的是从initiator到target之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。
在UVM中,单—数据流向的TLM端口有很多类型:(PORT分别表示为port,export,import)

  • uvm_blocking_put_PORT
  • uvm_nonblocking_put_PORT
  • uvm put_PORT
  • uvm blocking_get_PORT
  • uvm_nonblocking_get_PORT
  • uvm_get_PORT
  • uvm_blocking_peek_PORT
  • uvm_nonblocking_peek_PORT
  • uvm_peek_PORT
  • uvm_blocking_get_peek_PORT
  • uvm_nonblocking_get_peek_PORT
  • uvm_get_peek_PORT

按照UVM端口名的命名规则,它们指出了通信的两个要素:

  • 是否是阻塞(blocking)的方式(即可以等待延时)
  • 何种通信方法

uvm通信_第5张图片

 方法

        阻塞传输方式将blocking前缀作为函数名的一部分,而非阻塞方式则名为nonblocking。阻塞端口的方法类型为task,这保证了可以实现事件等待和延时;非阻塞端口的方式类型为function,这确保了方法调用可以立即返回
        我们从方法名也可以发现,例如uvm_blocking _put_PORT提供的方法task put()会在数据传送完后返回,uvm_nonblocking_put_PORT对应的两个函数try_put()和can_put()是立刻返回的。
        uvm_put_PORT则分别提供了blocking和nonblocking的方法,这为通讯方式提供了更多选择。blocking阻塞传输的方法包含:

  • Put(): initiator先生成数据T t,同时将该数据传送至target。
  • Get(): initiator从target获取数据Tt,而target中的该数据T t则应消耗。
  • Peek(): initiator从target获取数据Tt,而target中的该数据T t还应保留。

        与上述三种任务对应的nonblocking非阻塞方法分别是:. try_put()

  • can_put()
  • try_get()
  • can_get()
  • try_peek()
  • can_peek()

        这六个非阻塞函数与对应阻塞任务的区别在于,它们必须立即返回,如果try_xxx函数可以发送或者获取数据,那么函数应该返回1,如果执行失败则应返回0。或者通过can_xxx函数先试探target是否可以接收数据,如果可以,再通过try_xxx函数发送,提高数据发送的成功率。

(这些方法不是port的,port不实现任何方法,方法是在target里的)

uvm通信_第6张图片

component1:
uvm_blocking_put_port #(itrans) bp_port;   //注意参数化的类,参数为定义的transaction类型
uvm_nonblockINg_get_port #(otrans) ngb_port;
this.bp_port.put(itr);    //组件发送tr时,是不知道发送到哪里的(哪个组件),实现了组件之间更好的 
                          //隔离。put方法取决于接下来连接的组件是否实现put方法。
this.nbg_port.try_get(otr);

component2:
class comp2 extends uvm_component;
    umv_blocking_put_imp #(itrans,comp2) bp_imp;   //多传递一个类型,连接可以找到import端// //口,要想通过import找到组件里的方法,就将组件的句柄一同作为参数传入。 第一步通过connect找到import;第二步通过参数关系找到组件的put方法                                                                                   
    uvm_nonblocking_get_imp #(otrans,comp2) ngb_imp; 
    itrans itr_q[$];
    ……
    task put(itrans t);
        itr_q.push_back(t);   //target实际作为buffer,存储数据。从而使用put、get等方法。
    endtask
    ……
endclass
  1.  知道传输数据类型是什么
  2. 对应组件创建端口,端口成对出现
  3. target实现方法
  4. 在顶层环境将组建端口连接,左侧为initiator,右侧为target

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

  • .uvm_blocking_put_port #(itrans) bp_port;.
  • uvm_nonblocking_get_port #(otrans) nbg_port;

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

  • uvm_blocking_put_imp #(itrans, comp2) bp_imp;
  • uvm_nonblocking _get_imp #(otrans, comp2) nbg_imp;

env1环境将comp1与comp2连接之前,需要在comp2中实现两个端口对应的方法:

  • task put(itrans t)
  • function bit try_get (output otrans t)
  • function bit can_get();

双向通信

        与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但是数据流向在端对端之间是双向的。
        双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。
UVM双向端口分为以下类型:(transport既有req又有rsp。调用mater或slave时则只有一个,需要调用两次才行)

  • uvm_blocking_transport_PORT
  • uvm _nonblocking_transport_PORT
  • uvm_transport_PORT
  • uvm_blocking_master_PORT
  • uvm nonblocking_master_PORT
  • uvm master_PORT
  • uvm_biocking_slave_PORT
  • uvm_nonblocking_slave_PORT
  • uvm_slave_PORT

uvm通信_第7张图片

分类

         双向端口按照通信握手方式可以分为:

  •  transport双向通信方式
  • master和slave双向通信方式

        transport端口通过transport()方法,可以在同一方法调用过程中完成REQ和RSP的发出和返回。
        master和slave的通信方式必须分别通过put、get和peek的调用,使用两个方法才可以完成一次握手通信。
        master端口的slave端口的区别在于,当initiator作为master时,它会发起REQ送至target端,而后再从target端获取RSP;当initiator使用slave端口时,它会先从target端获取REQ,而后将RSP送至target端。
        对于master端口或者slave端口的实现方式,类似于之前介绍的单向通信方式,只是imp端口所在的组件需要实现的方法更多了。

uvm通信_第8张图片

comp1:
uvm_blocking_transport_port  #(itrans,otrans) bt_port;  //两个tr类型
this.bt_port.transport(itr,otr);  //两个tr句柄

comp2:
uvm_blocking_transport_imp #(itrans,otrans) bt_im;
task transport(itrans req,output rsp);
    ……

c1.bt_port.connect(c2.bt_imp)

多向通信

        多向通信(multi-directional communication)这个概念听起来容易让读者产生歧义,因为这种方式服务的仍然是两个组件之间的通信,而不是多个组件之间的通信,毕竟多个组件的通信仍然可以由基础的两个组件的通信方式来构建。
        多向通信指的是,如果initiator与target之间的相同TLM端口数目超过一个时的处理解决办法。
        compl有两个uvm_blocking put_port,而comp2有两个uvm_blocking_put_imp端口,我们对于端口例化可以给不同名字,连接也可以通过不同名字来索引,但问题在于comp2中需要实现两个task put(itrans t),又因为不同端口之间要求在imp端口一侧实现专属方法,这就造成了方法命名冲突,即无法在comp2中定义两个同名的put任务
uvm通信_第9张图片

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

  • `uvm_blocking_put_imp_decl (SFX)
  • 'uvm_nonblocking_put_imp_decl (SFX)
  • 'uvm_put_imp_decl (SFX)
  • 'uvm_blocking_get_imp_decl (SFX)
  • 'uvm_nonblocking_get_imp_decl (SFX)
  • 'uvm_get_imp_decl (SFx)
  • 'uvm_blocking_peek_imp_decl(SFX)
  • 'uvm_nonblocking_peek_imp__decl (SFX)
  • 'uvm_peek_imp_decl (SFX)
  • 'uvm_blocking_get_peek_imp_decl (SFX)
  • 'uvm_nonblocking_get_peek_imp_decl (SFX)
  • 'uvm_get_peek_imp_decl (SFX)
  • 'uvm_blocking_transport_imp_decl(SFX)
  • 'uvm_nonblocking_transport_imp_decl(SFX)
  • 'uvm_transport_imp_decl (SFX)
  • 'uvm_blocking_master_imp_decl (SFX)
  • 'uvm_nonblocking_master_imp_decl (SFX)
  • 'uvm_peek_master_decl (SFX)
  • `uvm_blocking_slave_imp_decl (SFX)
  • `uvm_nonblocking_slave_imp_decl (SFX)
  • 'uvm_peek_slave_decl (SFX)

(decl:表示声明;SFX表示后缀名称)

不同名import,定义不同方法

`uvm_blocking_put_imp_decl(_p1)
`uvm_blocking_put_imp_decl(_p2)
comp1:
uvm_blocking_put_port #(itrans) bp_port1;
uvm_blocking_put_port #(itrans) bp_port2;
this.bp_port1.put(itr1);  //调用方法都是put,取决于connect。若调用put1,编译错误。
this.bp_port2.put(itr2);  //注意此时不知道组件连接到同一个组件还是两个组件

comp2
uvm_blocking_put_imp_p1 #(itrans,comp2) bt_imp_p1;  //参数化的类也加了后缀,帮助找到p1任务
uvm_blocking_put_imp_p2 #(itrans,comp2) bt_imp_p2; 
`uvm_component_utils(comp2)
itrans itr_q[$];
semaphore key;
task put_p1(itrans t);  //方法也加后缀,同端口一致
task put_p2(itrans t);
    key.get();
    itr_q.push_back(t);
    key.put;            //资源共享,用semaphore进行互斥保护。比如put_p1的时候,另外一个拿不到

c1.bp_port1.connect(c2.bt_imp_p1);
c1.np_port2.connect(c2.bt_imp_p2);

        当一个组件的两个端口通过相向方法(譬如task put())向另外一个组件传输数据时,就需要使用上述的宏,分别声明两个不同的imp类型,完整的实现步骤包括:

  • 选择正确的imp宏来定义不同的imp端口类型,而宏的参数SFX(后缀名)也会转化为相应的imp端口类型名。
  • 在imp所例化的组件中,分别实现不同的put_SFX方法。
  • 在port所例化的组件中,不需要对目标imp端口类型做区分,例如comp1中的bp_port1和bp_port2为相同的端口类型。
  • 对于compl调用put()方法,它只需要选择bp_port1或者bp_port2,而不需要更替put()方法名,即仍然按照put()来调用而不是put_p1()或者put_p2()。
  • 在上层环境应该连接comp1和comp2的TLM端口。

总结

  • 用户只需要在例化多个imp端口的组件中实现不同名称的方法,使其与对应imp类型名保持—致。
  • 而对于port端口一侧的组件,则不需关心调用的方法名称,因为该方法名并不会发生改变
  • 所以通过这种方式可以防止通信方法名的冲突,从而解决多向通信的问题。

通信管道

        TLM通信的实现方式,这些通信有一个共同的地方即都是端对端的,同时在target—端需要实现传输方法,例如put()或者get()。
        这种方式在实际使用也可能会给用户带来一些烦恼,如何可以不自己实现这些传输方法,同时可以享受到TLM的好处?(是否先可以先例化buffer,再实现方法,形成固定形式,不需要自己实现方法)
        对于monitor、coverage collector等组件在传输数据时,会存在—端到多端的传输,如何解决这一问题?
        几个TLM组件和端口可以帮助用户免除这些烦恼:

  • TLM FIFO 
  • analysis port
  • analysis TLM FIFO
  • request & response通信管道

TLM_FIFO

        在一般TLM传输过程中,无论是initiator给target发起一个transaction,还是initiator从target获取一个transaction,transaction最终都会流向consumer中(initiator和target都可以是consumer) 。consumer在没有分析transaction时,我们希望将该对象先存储到本地FIFO中供稍后使用。用户需要分别在两个组件中例化端口,同时在target中实现相应的传输方法。多数情况下,需要实现的传输方法都是相似的,方法的主要内容即是为了实现一个数据缓存功能。
        TLM FIFO uvm_tlm_fifo类是一个新组件,它继承于uvm_component类,而且已经预先内置了多个端口以及实现了多个对应方法供用户使用
uvm通信_第10张图片

        uvm_tlm_fifo的功能类似于mailbox,不同的地方在于uvm_tlm_fifo提供了各种端口供用户使用。我们推荐在initiator端例化put_port或者get_peek_port,来匹配uvm_tlm_fifo的端口类型。            当然,如果用户例化了其它类型的端口,uvm_tlm_fifo还提供put、get以及peek对应的端口:

buffer只有一个,存放的数据类型是固定的。端口都是import类型,即使名称为export。

  • 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;
  • uvm_get _peek_imp #(T, this_type) blocking_peek_export;
  • uvm_get_peek_imp #(T, this_type) nonblocking_peek_export;
  • uvm_get_peek_imp #(T, this_type) peek_export;
  • uvm_get _peek_imp #(T, this_type) blocking_get_peek_export;
  • uvm_get _peek_imp #(T, this_type) nonblocking_get_peek_export;

analysis port

        除了端对端的传输,在一些情况下还有多个组件会对同一个数据进行运算处理
        如果这个数据是从同一个源的TLM端口发出到达不同组件,这就要求该种端口可以满足从—端到多端的需求。
        如果数据源端发生变化需要通知跟它关联的多个组件时,我们可以利用软件的设计模式之一观察者模式(observer pattern)(广播模式)来实现。(端到端必须有两个,这种广播涉及的听众可能没有,也可能多个)observer pattern的核心在于:

  • 第一,这是从一个initiator端到多个target端的方式。
  • 第二,analysis port采取的是"push"模式,即从initiator端调用多个target端的write()函数来实现数据传输。(函数立即返回,不耗时)

uvm通信_第11张图片

initiator.ap.connect(target1.aimp);
initiator.ap.connect(target2.aimp);
initiator.ap.connect(target3.aimp);

         一个典型的analysis port类型端口的连接方式,类似于其它TLM端口的是,按照传输方法和端口方向组合可以将analysis port分为uvm_analysis_port、uvm_analysis_export以及
uvm_analysis_imp。
        target一侧例化了uvm_analysis_imp后还需要实现write()函数。

        在顶层可以将initiator端的uvm_analysis_port同多个target端的uvm_analysis_imp进行连接。
        在initiator端调用write()函数时,实际上它是通过循环的方式将所有连接的target端内置的write()函数进行了调用
        由于函数立即返回的特点,无论连接多少个target端,initiator端调用write()函数总是可以立即返回的。不同于之前单一端口函数调用的是,即使没有target与之相连,调用write()函数时也不会发生错误。(循环时发现没有target相连就不调用write)

Analysis TLM FIFO

        由于analysis端口提出实现了一端到多端的TLM数据传输,而一个新的数据缓存组件类uvm_tlm_analysis_fifo为用户们提供了可以搭配uvm_analysis_port端口uvm_analysis_imp端口和write()函数。
        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;

(左侧一对多,右侧一对一,端到端)

uvm通信_第12张图片

        基于initiator到多个target的连接方式,用户如果想轻松实现一端到多端的数据传输,可以插入多个uvm_tlm_analysis_fifo,(相比maibox,支持各种形式端口)我们这里给出连接方式:

  • 将initiator的analysis port连接到tlm_analysis_fifo的get_export端口,这样数据可以从initiator发起,写入到各个tlm_analysis_fifo的缓存中。
  • 将多个target的get_port连接到tlm_analysis_fifo的get_export,注意保持端口类型的匹配,这样从target一侧只需要调用get()方法就可以得到先前存储在tlm_analysis_fifo中的数据。

initiator.ap.connect(tlm_analysis_fifo1.analysis_export);

target1.get_port.connect(tlm_analysis_fifo1.get_export)  (target是针对initiator而言)

initiator.ap.connect(t1m_analysis_fifo2.analysis_export) ;

target2.get _port.connect(tlm_analysis_fifo2.get_export); //注意这里是从target开始

initiator.ap.connect(tlm_analysis_fifo3.analysis_export);

target3.get_port.connect(tlm_analysis_fifo3.get_export) ;

initiator和target不用实现方法,可以直接调用put和get,这些方法在analysis_fifo里都有。(这也是为什么taget调用get方法,连接时在前面吗?import变为port)

Request&Response通信管道

        双向通信端口transport,即通过在target端实现transport()方法可以在一次传输中既发送request又可以接收response。
        UVM提供了两种简便的通信管道,它们作为数据缓存区域,既有TLM端口从外侧接收request和response,同时也有TLM端口供外侧获取request和response。这两种TLM通信管道分别是:

  • uvm_tlm_req_rsp_channel
  • uvm_tlm_transport_channel

对于uvm_tlm_req_rsp_channel而言,它提供的端口首先是单一方向的,为了让端口列表清爽一些,我们只列出该类例化的端口:

  • uvm_put_export #(REQ) put_request_export;
  • uvm_put_export #(RSP) put_response_export;
  • uvm_get _peek_export #(RSP) get_peek_response_export;
  • uvm_get_peek_export #(REQ) get _peek_request_export;
  • uvm_analysis_port#(REQ) request_ap ;
  • uvm_analysis_port#(RSP) response_ap;
  • uvm_master_imp #(REQ,RSP,this_type,uvm_tlm_fifo #(REQ),uvm_tlm _fifo #(RSP) )master_export;
  • uvm_slave_imp#(REQ,RSP,this_type,uvm_tlm_fifo #(REQ),uvm_tlm_fifo #(RSP) )slave_export;

         有了这么多丰富的端口,用户可以在使用成对的端口进行数据的存储和访问。需要注意的是,uvm_tlm_req_rsp_channel内部例化了两个mailbox分别用来存储request和response:

  • protected uvm_tlm_fifo #(REQ) m_request_fifo;
  • protected uvm_tlm_fifo #(RSP)m_response_fifo;

        例如initiator端可以连接channel的put_request_export,target连接channel的get_peek_request_export,同时target连接channel的put_response_export,initiator连接channel的get_peek_response_export端口。

        通过这种对应的方式,使得initiator与target可以利用uvm_tlm_req_rsp_channel进行request与response的数据交换。

  • 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);

         也可以利用另外—种连接方式:(相比前面借助两个端口,发送两次(put、get)。这里只要一个端口,还是调用两次)

  • initiator.master_port.connect(req_rsp_channel.master_export);
  • target.slave_port.connect(req_rsp_channel.slave_export) ;

        通过所述的这些方式,我们可以实现initiator与target之间自由的request和response传输,而这两种连接方式仍然需要分别调用两次方法才可以完成request和response的传输。

        在uvm_tlm_req_rsp_channel的基础上,UVM又添加了具备transport端口的管道组件uvm_tlm_transport_channel类。它继承于uvm_tlm_req_rsp_channel,并且新例化了transport端口:
uvm_transport_imp #(REQ,RSP,this_type) transport_export;

 左侧变为transport_port,右侧target依然是slave_port

        新添加的这个TLM FIFO组件类型是针对于一些无法流水化处理的request和response传输,例如initiator—端要求每次发送完request,必须等到response接收到以后才可以发送下一个request,这时transport()方法就可以满足这一需求。(前面只要单侧get、put就结束?)
        如果将上面的传输方式进行修改,需要变化的是initiator端到req_rsp_channel的连接,应该修改为︰
initiator.transport_port.connect(transport_channel.transport_export)

        至于transport_channel和target之间的连接,则可以仍然保留之前的单向传输连接方式。

你可能感兴趣的:(uvm,uvm)