UVM——TLM通信机制

  TLM(transaction level modeling)是一个基于事务(transaction)的通信方式,通常在高抽象级的语言中被引用作为模块之间的通讯方式。我们通常使用tlm 来作为不同component之间的数据传递和通信。
 

1,端口数据的流向(发起人和响应人)

在讨论数据流向前,我们先来讨论一个问题,对于两个端口之间的方向,有下面几个点需要注意:

1,发起人:数据的请求方,也就是initiator

2,响应人:数据的向英方,也就是target

initiator:永远有主动权,不论时请求数据还是主动发送数据,永远是发起的那一方;

target:永远时被动的,只能被动的接收数据,或者被通知需要发送数据;

也就是说initiator永远都是主动的一方,就是甲方爸爸,甲方让你发数,乙方就必须给,也就是说target就必须得给initiator发送数据,而甲方爸爸向你输出数据,乙方必须接受,也就是说initiator向target发送数据,target必须接收。

通过上面的说明,我们能够看到,数据方向发生了变化,initiator可以直接向target要数据(get操作),也可以直接向target发数据(put操作),唯一不变的是发起人和响应人。至于initiator是谁,需要根据具体应用去设计。

2,端口的连接

通信端口分为3类:port,export,imp

1,port:    通信请求方initiator的发起端,initiator凭借port端口才可以访问target。

2,export:作为initiator和target中间层次的端口(一个起到中间连接作用的port)

3,imp:    只能作为target接收请求的响应端,它无法作为中间层次的端口,imp为通信终点。

端口优先级:port > export > imp, 使用connect()建立连接关系时,只有优先级高的才能调用connect()做连接,即port可以连接port、export或者imp;export可以连接export或者imp;imp只能作为数据传送的重点,无法扩展连接。

实际的应用情况,我们下面再讲。

3,TML的通信方式,单向传输和双向传输

        按通信传输的方向可以分为单向(unidirection)和双向(bidirection)。需要说明的是,不论是单向传输还是双向传输都有阻塞和非阻塞之分。下面以阻塞为例来说明。

3.1,单向传输

1,由initiator发起request transaction ,get操作(甲方爸爸问你要,你必须给)

2,由initiator发起直接发送transaction,put操作(甲方爸爸要给你,你不能不要)

3.2,双向传输

由initiator发起request transaction,传送至target;而target在消化了request transaction后,也会发起response transaction,继而返回给initiator。数据的流向是双向的,这种传递方式类似于一个握手的过程,先发请求,再发数据,秩序请求是谁发起的,与单项传输类似,只不过这次遇见了一个好说话的甲方,不论干啥事情之前,先询问你,你准备好了,再发数据。

3.3,带缓存的传输方式

uvm_tlm_fifo类是一个新的组件,它继承于uvm_component,且预先内置了多个端口、有多个对应方法供用户使用;功能类似mailbox #(T),该邮箱没有尺寸限制,用来存储数据类型T,而uvm_tlm_fifo的多个端口对应的方法都是利用该邮箱实现了数据读写。由于tlm_fifo是用来作为缓冲使用的,最好的办法是设计成imp对接下游端口,如下图所示:

 为啥说最好设计成imp呢,因为对于上有来说,只需要向FIFO中丢数据即可,也就是这需要执行put操作,不论是不是阻塞的,因为你是FIFO所有有空间存储我无条件丢过去的数据,而对于下游get_ap来讲,我可以慢悠悠的从FIFO中拿数据,不用担心数据丢失的问题,因此从tlm_fifo这侧来看,你要我就给你数据,所以设计成imp的方式也是何理的。至于直接问FIFO要的方式,当然必须设计成imp了。

3.4,单向传输的实现 ---put()方法

put方法调用时要注意:

数据无条件的从component A 传递到 component B,不关心B除了接收外的事情。

如果为调用的是bloking相关的方法,必须放在task内,nonblocking 可以放在function中,实现的代码如下:

class A extends uvm_component;
   `uvm_component_utils(A)

   uvm_blocking_put_port#(my_transaction) A_port;
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   extern function void build_phase(uvm_phase phase);
   extern virtual  task main_phase(uvm_phase phase);
endclass

function void A::build_phase(uvm_phase phase);
   super.build_phase(phase);
   A_port = new("A_port", this);
endfunction

task A::main_phase(uvm_phase phase);
   my_transaction tr;
   repeat(10) begin
      #10;
      tr = new("tr");
      assert(tr.randomize());
      A_port.put(tr);
   end
endtask

class B extends uvm_component;
   `uvm_component_utils(B)

   uvm_blocking_put_imp#(my_transaction, B) B_imp;
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   extern function void build_phase(uvm_phase phase);
   extern function void connect_phase(uvm_phase phase);
   extern function void put(my_transaction tr);
   extern virtual  task main_phase(uvm_phase phase);
endclass

function void B::build_phase(uvm_phase phase);
   super.build_phase(phase);
   B_imp = new("B_imp", this);
endfunction

function void B::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
endfunction

function void B::put(my_transaction tr);
   `uvm_info("B", "receive a transaction", UVM_LOW) 
   tr.print();
endfunction

task B::main_phase(uvm_phase phase);
endtask

上述代码分别在component_A和component_B中声明并例化了两个端口实例:

     uvm_blocking_put_port #(transaction)         A_port;
     uvm_blocking_put_imp #(transaction,consumer)   B_imp;
最后在env中对两个组件之间的端口进行了连接,这使得producer(A)的main phase中可以通过自身的两个端口间接调用consumer(B)中的方法,如下代码所示:

class my_env extends uvm_env;

   A   A_inst;
   B   B_inst;
   
   
   function new(string name = "my_env", uvm_component parent);
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);

      A_inst = A::type_id::create("A_inst", this);
      B_inst = B::type_id::create("B_inst", this);

   endfunction

   extern virtual function void connect_phase(uvm_phase phase);
   
   `uvm_component_utils(my_env)
endclass

function void my_env::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   A_inst.A_port.connect(B_inst.B_imp);
endfunction

对于get()方法: 

与put()方法类似,get方法时通过component_B中将数据从component_A中,get出来,这种做法就是“强制性”的从A中拿走数据,与put的逻辑刚好相反。

总结:

        tlm_port 和 imp相连时,数据的传递方法有很多,具体可以参考张强的《UVM实战》这本书,对于是get方法还是put方法,要具体根据应用场景的需要来选择,但是这会影响put/get方法实现的位置,具体情况是,谁是“乙方”在谁的component中实现:

具体来讲就是:

当使用put操作,数据是从cmp_a到 cmp_b,那么需要在comp_b中实现put函数;

当使用get操作,数据是从cmp_a到 cmp_b,那么需要在comp_a中实现get函数;

对于输出传递的方法,最好每种都在源码上看一遍,有助于理解,我这里就不写了,每种方法都有不同的应用场景,这里只是把最常用的put和get方法罗列出来了。

3.5,双向传输的实现 transport()方法

对于双向transport的使用,本人使用的比较少,这块先不介绍了。

3.6,带fifo的数据传递

在这里插入图片描述

整个连接过程分为两段,comp_a 的port与 tml_fifo的 expoert 相连(其实是个imp),另一端是comp_b的export/port与tlm_fifo的export相连(其实是个imp)。对于FIFO的深度,可以通过参数设置,这里不做介绍了,具体请看源码。

4,一对多的传输方式

UVM提供的analysis port,它在组件中是以广播(broadcast)的形式向外发送数据的,而不管存在几个imp或者没有imp。分析端口的根据端口类型的不同分为:

uvm_analysis_port、 uvm_analysis_export、 uvm_analysis_imp

并且只有一个操作:write();

由于是广播操作,理论上来讲,应该是非阻塞的,不然会影响其他模块获取数据。

代码实现如下:

class A extends uvm_component;
   `uvm_component_utils(A)

   uvm_analysis_port#(my_transaction) A_ap;
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   extern function void build_phase(uvm_phase phase);
   extern virtual  task main_phase(uvm_phase phase);
endclass

function void A::build_phase(uvm_phase phase);
   super.build_phase(phase);
   A_ap = new("A_ap", this);
endfunction

task A::main_phase(uvm_phase phase);
   my_transaction tr;
   repeat(10) begin
      #10;
      tr = new("tr");
      assert(tr.randomize());
      A_ap.write(tr);
   end
endtas
class B extends uvm_component;
   `uvm_component_utils(B)

   uvm_analysis_imp#(my_transaction, B) B_imp;
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   extern function void build_phase(uvm_phase phase);
   extern function void connect_phase(uvm_phase phase);
   extern function void write(my_transaction tr);
   extern virtual  task main_phase(uvm_phase phase);
endclass

function void B::build_phase(uvm_phase phase);
   super.build_phase(phase);
   B_imp = new("B_imp", this);
endfunction

function void B::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
endfunction

function void B::write(my_transaction tr);
   `uvm_info("B", "receive a transaction", UVM_LOW) 
   tr.print();
endfunction

task B::main_phase(uvm_phase phase);
endtask
class C extends uvm_component;
   `uvm_component_utils(C)

   uvm_analysis_imp#(my_transaction, C) C_imp;
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   extern function void build_phase(uvm_phase phase);
   extern function void connect_phase(uvm_phase phase);
   extern function void write(my_transaction tr);
   extern virtual  task main_phase(uvm_phase phase);
endclass

function void C::build_phase(uvm_phase phase);
   super.build_phase(phase);
   C_imp = new("C_imp", this);
endfunction

function void C::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
endfunction

function void C::write(my_transaction tr);
   `uvm_info("C", "receive a transaction", UVM_LOW) 
   tr.print();
endfunction

task C::main_phase(uvm_phase phase);
endtask
class my_env extends uvm_env;

   A   A_inst;
   B   B_inst;
   C   C_inst;
   
   
   function new(string name = "my_env", uvm_component parent);
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);

      A_inst = A::type_id::create("A_inst", this);
      B_inst = B::type_id::create("B_inst", this);
      C_inst = C::type_id::create("C_inst", this);

   endfunction

   extern virtual function void connect_phase(uvm_phase phase);
   
   `uvm_component_utils(my_env)
endclass

function void my_env::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   A_inst.A_ap.connect(B_inst.B_imp);
   A_inst.A_ap.connect(C_inst.C_imp);
endfunction

上面这3段代码就实现了comp_a 向 comp_b 和 comp_c同时发送数据,所以write函数必然要在comp_b和comp_c上实现,这块没有多少隐藏在源码中的东西,按照这种规则实现就好,下一节我们在讨论tlm_port源码中的内容。

5,tlm port源码解析

这里讨论的源码是在 macros文件夹内叫做:uvm_tlm_defines.svh的源码,我们在这里举两个例子:

`define uvm_blocking_put_imp_decl(SFX)

// MACRO: `uvm_blocking_put_imp_decl
//
//| `uvm_blocking_put_imp_decl(SFX)
//
// Define the class uvm_blocking_put_impSFX for providing blocking put
// implementations.  ~SFX~ is the suffix for the new class type.

`define uvm_blocking_put_imp_decl(SFX) \
class uvm_blocking_put_imp``SFX #(type T=int, type IMP=int) \
  extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
  `UVM_IMP_COMMON(`UVM_TLM_BLOCKING_PUT_MASK,`"uvm_blocking_put_imp``SFX`",IMP) \
  `UVM_BLOCKING_PUT_IMP_SFX(SFX, m_imp, T, t) \
endclass

class uvm_blocking_put_imp (uvm_tlm_defines.svh)

class uvm_blocking_put_imp #(type T=int, type IMP=int)
  extends uvm_port_base #(uvm_tlm_if_base #(T,T));
  `UVM_IMP_COMMON(`UVM_TLM_BLOCKING_PUT_MASK,"uvm_blocking_put_imp",IMP)
  `UVM_BLOCKING_PUT_IMP (m_imp, T, t)
endclass

我们看这两段代码其实都是在表达同样的内容,那为什么uvm还要再定义一套macros去表达类似的内容?咋一看确实没有这个必要,但是仔细去看的话,第一段代码其实是定义了一个后缀为SFX的uvm_blocking_put_imp,它内部定义的`UVM_BLOCKING_PUT_IMP_SFX这个宏去重新封装了一边put函数,代码如下:

`define UVM_BLOCKING_PUT_IMP_SFX(SFX, imp, TYPE, arg) \
  task put( input TYPE arg); imp.put``SFX( arg); endtask

有没有想过,为什么要这么做呢?我可以直接使用第二段代码直接定义一个imp,为什么还要多此一举,通过宏定义的方式创建一个带SFX的imp ,并且从功能上说,两者本质上是一样的?

其实原因也很好说明,就拿put函数来讲,只要是同一个类型的tlm_port,不管是针对哪个imp,所有的put函数都是一样的,因此以scoreboard来讲,如果有多个imp,我怎么区分到底是哪个tlm_port上的put函数呢,这一下子把用户就搞晕了,也罢验证环境搞死了,所以uvm就采用了使用`define uvm_blocking_put_imp_decl(SFX) 这个宏的方式来区分不同的tlm_port,以及不同的put函数,只要SFX后缀不同,那么tlm_port实例化的class不同,调用的put函数也就不同了。这就是这个宏牛逼的地方!

`uvm_analysis_imp_decl(SFX)

// MACRO: `uvm_analysis_imp_decl
//
//| `uvm_analysis_imp_decl(SFX)
//
// Define the class uvm_analysis_impSFX for providing an analysis
// implementation. ~SFX~ is the suffix for the new class type. The analysis 
// implemenation is the write function. The `uvm_analysis_imp_decl allows 
// for a scoreboard (or other analysis component) to support input from many 
// places. For example:
//
//| `uvm_analysis_imp_decl(_ingress)
//| `uvm_analysis_imp_decl(_egress)
//|



`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
  extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
  `UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
  function void write( input T t); \
    m_imp.write``SFX( t); \
  endfunction \
  \
endclass

有了上面的例子,对于`uvm_analysis_imp_decl(SFX)来说,同样的也是为了区分不同的write函数,而定义的这个uvm_analysis_imp_decl宏,具体的用法我们这块不在展开了,可以学习张强的《UVM实战》来学习这部分的内容,并且后面我会专门再出一篇文章讲讲如何使用`uvm_analysis_imp_decl这个宏,对于tlm源码的解析,这里只是起到一个抛砖引玉的作用,还是那句话,请认真阅读源码,并结合实际情况去理解我们使用到的uvm源码,不要看别人这么写,我们就想都不想就这么用,通过学习源码的代码编写,不仅能看到前人的付出和智慧,也能加强我们的编码能力以及对验证环境的理解。

  • 11
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值