目录

  • 1.TLM通信
    • 1.1 概述
    • 1.2 基本概念
    • 1.3 分类
    • 1.4 端口的使用
  • 2. 单向通信
    • 2.1 概念
    • 2.2 方法
    • 2.3 示例
  • 3.双向通信
    • 3.1 概述
    • 3.2 分类
    • 3.3 transport
  • 4. 多向通信
    • 4.1 概述
    • 4.2 示例
  • 5.通信管道
    • 5.1 概述
    • 5.2 TLM FIFO
    • 5.3 Analysis Port
    • 5.4 analysis TLM FIFO
    • 5.5 request & response通信管道
  • 6.TLM2通信
    • 6.1概述
    • 6.2 接口实现
    • 6.3 传送数据
    • 6.4 时间标记
    • 6.5 示例
  • 7.同步通信元件
    • 7.1 概述
    • 7.2 uvm_event 应用
    • 7.3 uvm_barrier介绍
    • 7.3 uvm_callback介绍

1.TLM通信

1.1 概述

  • 在芯片开发流程中,有两个地方对项目的助推起到了关键作用:

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

  • TLM是一种基于事务(transaction)的通信方式,通常在高抽象级语言例如System C或者SV/UVM中作为模块之间的通讯方式。
  • TLM成功地将模块内的就算和模块之间的通信从时间跨度方面剥离开了。
  • 在抽象语言建模体系中,各个模块通过一系列并行的进程实现,并通过通信和计算来模拟出正确的行为。
  • 如果要提高系统模型的仿真性能,需要考虑两个方面
    • 一个是建模自身的运算优化,另外一个是模型之间的通信优化。
    • 前者需要依靠开发者的经验还有性能分析工具来逐步优化模型。
    • 后者则可以通过将通信频率降低、内容体积增大的方式,来减少由不同进程之间同步带来的资源损耗。
    • TLM正是从通信优化角度提出的一种抽象通信方式。

1.2 基本概念

  • 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端口,继而在更高层次中将这两个对象进行连接。

1.3 分类

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

    • 分辨出initiator和target,producer和consumer。
    • 在target中实现TLM通信方法。
    • 在两个对象中创建TLM端口
    • 在更高层次将两个对象的端口进行连接。
      从数据流向来看,传输方向可以分为单向(unidirection)和双向(bidirection)。
    • 单向传输:由initiator发起request transaction。
    • 双向传输:由initiator发起request transaction,传送至target;而target在消化了request transaction后,会发起request transaction,继而返回给initiator。
      端口的按照类型可以划分为三种:
    • port: 经常作为initiator的发起端,initiator凭借port才可以访问target的TLM通信方法。
    • export:作为initiator和target中间层次的端口
    • imp:只能作为target接收request的末端,它无法作为中间层的端口,所以imp的连接无法再次延伸。

如果将传输方向和端口类型加以组合,可帮助理解TLM端口的继承树。TLM端口一共可以分为六类:

  • uvm_UNDIR_port
  • uvm_UNDIR_export
  • uvm_UNDIR_imp
  • uvm_BIDIR_port
  • uvm_BIDIR_export
  • uvm_BIDIR_imp

1.4 端口的使用


示例

class request extends uvm_transaction;byte cmd;int addr;int req;
endclass
class response extends uvm_transaction;byte cmd;int addr;int rsp;int status;
endclass
class comp1 extends uvm_agent;uvm_blocking_get_port #(request) bg_port;`uvm_component_utils(comp1)
endclass
class comp2 extends uvm_agent;uvm_blocking_get_port #(request) bg_port;uvm_nonblocking_put_imp # (request,comp2) nbp_imp;`uvm_component_utils(comp2)...function bit try_put (request req);function bit can_put();
endclass
class comp3 extends uvm_agent;uvm_blocking_transport_port # (request, response) bt_port;`uvm_component_utils(comp3)...
endclass
class xomp4 extends uvm_agent;uvm_blocking_get_imp # (request, comp4) bg_imp;uvm_nonblocking_put_port # (request) nbp_port;`uvm_component_utils(comp4)...task get(output request req);
endclass
calss comp5 extends uvm_agent;uvm_blocking_transport_imp # (request, response, comp5) bt_imp;`uvm_component_utils(comp5)...task transport(request req, output response rsp);
endclass
class agent1 extends uvm_agent;uvm_blocking_get_port # (request) bg_port;uvm_nonblocking_put_export # (request) nbp_exp;uvm_blocking_transport_port # (request, response) bt_port;comp1 c1;comp2 c2;comp3 c3 ;`uvm_component_utils(agent1)...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);c3 = comp3::type_id::create("c3", this);endfunctionfunction void connect_phase(uvm_phase phase);super.connect_phase(phase);c1.bg_port.connect(this.bg_port);c2.bg_port.connect(this.pg_port);this.nbp_exp(c2.nbp_imp);c3.bt_port.connect(this.bt_port);endfunction
endclass
class env1 extends uvm_env;agent1 a1;comp4 c4;comp5 c5;`uvm_component_utils(env1)...function void build_phase(uvm_phase phase);super.build_phase(phase);a1 = agent1::type_id::create("a1",this);c4 = comp4::type_id::create("c4",this);c5 = comp5::type_id::create("c5",this);endfunction: build_phasefunction void connect_phase(uvm_phase phase);super.connect_phase(phase);a1.bg_port.connect(c4.bg_imp);c4.nbp_port.connect(a1.nbp_exp);a1.bt_port.connect(c5.bt_imp);endfunction: connect_phase
endclass
  • 从示例中可以得出关于建立TLM通信的常规步骤:

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

2. 单向通信

2.1 概念

  • 单向通信(unidirectional communication)指的是从initiator到target之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。
  • 在UVM中,单一数据流向的TLM端口有很多类型:
  • 在port代表了三种端口名:port、export和imp。
  • 按照UVM端口名的命名规则,它们指出了通信的两个要素:
    • 是否是阻塞的方式(即可以等待延时)
    • 何种通信方式

2.2 方法

  • 阻塞传输方式将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获取数据T t,而targrt中的该数据T t则应消耗。
    • Peek():initiator从target获取数据T t,而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函数发送,提高数据发送的成功率。

2.3 示例

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;uvm_noblocking_get_port # (otrans) nbg_port;`uvm_component_utils(comp1)...task run_phase(uvm_phase phase);itrans itr;otrans otr;int trans_num = 2;forkbeginfor(int i = 0; i<trans_num; i++)beginitr = new("itr",this);itr.id = i;itr.data = 'h10 + i;this.bp_port.put(itr);`uvm_info("PUT",$sformatf("put itrans id: 'h%0x, data: 'h%0x", itr.id, itr.data), UVM_LOW)endendjoinendtask
endclass
class comp2 extends uvm_component;uvm_blocking_put_imp #(itrans, comp2) bp_imp;uvm_nonblocking_get_imp # (otrans, comp) nbg_imp;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
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_port.connect(c2.nbg_imp);endfunction: connect_phase
endclass
输出结果:
uvm_test_top.env.c1 [PUT] put itrans id: 'h0,data: 'h10
uvm_test_top.env.c1 [PUT] put itrans id: 'h1,data: 'h11
uvm_test_top.env.c1 [TRYGET] get otrans id: 'h0,data: 'h1000
uvm_test_top.env.c1 [TRYGET] get otrans id: 'h1,data: 'h1100
  • 首先comp1例化了两个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();
  • 接下来env1对两个组件的端口进行了连接,这使得comp1在run phase可以通过自身端口间接调用comp2中定义的端口方法。
  • 因此在调用端口方法之前的几个步骤是必不可少的:
    • 定义端口
    • 实现对应方法
    • 在上层将端口进行连接

3.双向通信

3.1 概述

  • 与单向通信相同的是,双向通信(bidirectional communication)的两端也分为initiator和target,但是数据流向在端对端之间是双向的。
  • 双向通信中的两端同时扮演着producer和consumer的角色,而initiator作为request发起方,在发起request之后,还会等待response返回。
  • UVM双向端口分为以下类型:

3.2 分类

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

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

3.3 transport

class comp1 extends uvm_component;uvm_blocking_transport_port # (itrans, ctrans) bt_port;`uvm_component_utils(comp1)...task run_phase(uvm_phase phase);itrans itr;otrans otr;int trans_num = 2;for(int i = 0; i<trans_num; i++) beginitr = new("itr", this);itr.id = i;itr.data = 'h10 + i;this.bt_port.transport(itr,otr);`uvm_info("TRSPT",$sformatf("put itrans id: 'h%0x, data: 'h%0x | get otrans id: 'h%0x, data: 'h%0x", itr.id, itr.data, otr.id, otr.data), UVM_LOW)endendtask
endclass
class comp2 extends uvm_comonent;uvm_blocking_transport_imp # (itrans, otrans, comp2) bt_imp;`uvm_component_utils(comp2)...task transport(itrans req, output ortans rsp);rsp = new("rsp",this);rsp.id = req.id;rsp.data = req.data << 8;endtask
endclass
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.bt_port.connect(c2.bt_imp);endfunction: connect_phase
endclass
输出结果为:uvm_test_top.env.c1 [TRSPT] put itrans id: 'h0, data: 'h10 | get ortans id: 'h0, data: 'h1000uvm_test_top.env.c1 [TRSPT] put itrans id: 'h1, data: 'h11 | get otrans id: 'h1, data: 'h1100

从上面的例码可以看出,双向端口对端口例化和连接的处理类似于单向端口,不同的只是要求实现对应的双向传输任务:
task transport(itrans req, output ortans rsp)

4. 多向通信

4.1 概述

  • 多向通信(multi-directional communication)这个概念听起来容易让读者产生歧义,因为这种方式服务的仍然是两个组件之间的通信,而不是多个组件之间的通信,比较多个组件的通信仍然可以由基础的两个组件的通信方式来构建。
  • 多个通信指的是,如果initiator与target之间的相同TLM端口数目超过一个时的处理解决方法。
  • comp1有两个uvm_blocking_put_port,而comp2有两个uvm_blocking_put_imp端口,我们对于端口例化可以给不同名字,连接也可以通过不同名字来索引,但问题在于comp2中需要实现两个task put(itrans t),又因为不同端口之间要求在imp端口一侧实现专属方法,这就造成了方法命名冲突,即无法在comp2中定义两个同名的put任务。
  • UVM通过端口宏声明方式来解决这一问题,它解决问题的核心在于让不同端口对应不同名的任务,这样变不会造成方法名的冲突。UVM为解决多向通信问题的宏按照端口名的命名方式分为:

4.2 示例

`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; i < trans_num; i++) beginitr1 = new("itr1", this);itr1.id = i;itr1.data = 'h10 + i;this.bp_port1.put(itr1);endfor(int j = 0; j < trans_num; j++) beginitr2 = new("itr2", this);itr2.id = i;itr2.data = 'h10 + i;this.bp_port2.put(itr2);endjoinendtask
endclassclass 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("PUTP1", $sformatf("get otrans id: 'h%0x, data: 'h%0x", t.id, t.data), UVM_LOW)key.put();endtasktask put_p2(itrans t);key.get();itr_q.push_back(t);`uvm_info("PUTP2", $sformatf("get otrans id: 'h%0x, data: 'h%0x", t.id, t.data), UVM_LOW)key.put();endtask
endclassclass 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);endfunctionfunction void connect_phase(uvm_phase phase);super.connect_phase(phase);c1.bt_port1.connect(c2.bt_imp_p1);c1.bt_port2.connect(c2.bt_imp_p2);endfunction
endclass
仿真结果:
uvm_test_top.env.c2[PUTP1] put itrans id: 'h0, data: 'h10
uvm_test_top.env.c2[PUTP1] put itrans id: 'h1, data: 'h11
uvm_test_top.env.c2[PUTP2] put itrans id: 'h10, data: 'h20
uvm_test_top.env.c2[PUTP2] put itrans id: 'h11, data: 'h21
  • 当一个组件的两个端口通过相同方法(譬如task put())向另外一个组件传输数据时,就需要使用上述的宏,分别声明两个不同的imp类型,完整的实现步骤包括:

    • 选择正确的imp宏来定义不同的imp端口类型,而宏的参数SFX(后缀名)也会转化为响应的imp端口类型名。
    • 在imp所例化的组件中,分别实现不同的put_SFX方法。
    • 在port所例化的组件中,不需要对目标imp端口类型做区分,例如comp1中的bp_port1和bp_port2为相同的端口类型。
    • 对于comp1调用put()方法,它只需要选择bp_port1或者bp_port2,而不需要更替put()方法名,即仍然按照put()来调用而不是put_p1()或者put_p2()。
    • 在上层环境应该连接comp1和comp2的TLM端口。
    • 用户只需要在例化多个imp端口的组件中实现不同名称的方法,使其与对应imp类型名称保持一致。
    • 而对于port端口一侧的组件,则不需关心调用的方法名称,因为该方法并不会发生改变。
    • 所以通过这种方式可以防止通信方法名的冲突,从而解决多向通信的问题。

5.通信管道

5.1 概述

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

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

5.3 Analysis Port

  • 除了端对端的传输,在一些情况下还有多个组件会对同一个数据进行运算处理。
  • 如果这个数据是从同一个源的TLM端口发出到达不同组件,这就要求该种端口可以满足从一端到多段的需求。
  • 如果数据源端发生变化需要通知跟它关联的多个组件时,我们可以利用软件的设计模式之一观察模式(observer pattern)来实现。
  • observer pattern的核心在于:
    • 第一,这是从一个initiator端到多个target端的方式。
    • 第二,analysis port采取的是"push"模式,即从initiator端调用多个target端write()函数来实现数据传输。
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。
  • arget一侧例化了uvm_analysis_imp后还需要实现write()函数。
  • 在顶层可以将initiator端的uvm_analysis_port同多个target端的uvm_analysis_imp进行连接。
  • 在initiator端调用write()函数时,实际上它是通过循环的方式将所有连接的target端,initiator端调用write()函数进行了调用
  • 由于函数立即返回的特点,无论连接多少个target端,initiator端调用write()函数总是可以立即返回的。不同于之前单一端口函数调用的是,即使没有target与之相连,调用write()函数时也不会发生错误。

5.4 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;
  • 基于initiator到多个target的连接方式,yoghurt如果想轻松实现一端到多端的数据传输,可以插入多个uvm_tlm_analysis_fifo,我们这里给出连接方式:

    • 将initiator的analysis port连接到tlm_analysis_fifo的get_port端口,这样数据可以从iniator发起,写入到各个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);initiator.ap.connect(tlm_analysis_fifo2.analysis_export);
target2.get_port.connect(tlm_analysis_fifo2.get_export);initiator.ap.connect(tlm_analysis_fifo3.analysis_export);
target3.get_port.connect(tlm_analysis_fifo3.get_export);

5.5 request & response通信管道

  • 双向通信端口端口transport,即通过在target端实现trasnsport()方法可以在一侧传输中既发现request有可以接收respoense。
  • UVM提供了两种简便的通信管道,它们作为数据缓存区域,既有TLM端口从外侧接收resquest和response,同时也有TLM端口供外侧获取request和response。这两种TLM通信管道分别是:
uvm_tlm_req_rsp_channel
uvm_tlm_transport_channel
  • 对于uvm_tlm_req_rsp_channel而言,它提供的端口首先是单一方向的,为了让端口列表清爽一些,我们只列出该类例化的端口:

  • 有了这么多丰富的端口,用户可以在使用成对的端口进行数据的存储和访问。需要注意的是,uvm_tlm_req_rsp_channel内部优化了两个mailbox分别来存储request和response:
- protected uvm_tlm_fifo #(REQ) m_request_fifo;
- protected uvm_tlm_fifo #(RSP) m_reponse)_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.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;
  • 新添加的这个TLM FIFO组件类型是针对于一些无法流水化处理的request和response传输,例如iitiator一端要求每次发送完request,必须等到response接收到以后才可以发送到下一个request,这时transport()方法就可以满足这一需求。
  • 如果将上面的传输方式进行修改,需要变化的是initiator端都有req_rsp_channel的连接,应该修改为:
  • initiator.transport_port.connect(transport_channel.transport_export)
  • 至于transport_channel和target之间的连接,则可以仍然保留之前的单向传输连接方式。

6.TLM2通信

6.1概述

  • TLM是一种为来构建更高级抽象模型的传输方式。虽然SV语言本身没有原生的TLM传输方式,但是TLM在UVM很好地继承进来,并且在组件传输中得到了充分运用。
  • 之前介绍的UVM各个组件之间的通信是通过TLM1.0方式实现的,而伴随着SystemC模型的广泛应用,SystemC通信机制TLM2.0也引起了UVM标准委员会的兴趣。
  • TLM协议本身并不依赖于某一种语言,而是可以跨语言来实现其传输标准。
  • TLM2.0是SystemC模型之间的核心传输方式,它于2009年发布并随后成为IEEE标准IEEE 1666-2011。
  • 与TLM1.0相比,TLM2.0提供了更丰富刚强大的传输特性,它们主要包括:
    • 双向的阻塞或者非阻塞接口
    • 时间标记
    • 统一的数据包
  • 通过这些特性,TLM2.0使得接口之间的通信更趋于标准化,更容易为系统构建抽象模型。
  • 虽然TLM2.0一开始作为SystemC标准库的一部分(由C++实现),但是由于RTL与SystemC模型的混合仿真趋势,要求SV也能够有与之匹配的接口便于日后的相互嵌套。
  • 本届将重点介绍TLM2.0的特性、它在UVM中的实现方式以及通过一些例码使读者了解其使用方式。

6.2 接口实现

  • TLM2.0的传输是双向的,一位这在一次完整传输中有request和response类型。
  • 这与TLM1.0transport端口传输方式是类似的。
  • TLM2.0支持blocking和nonblocking两种transport方式:
  • TLM2.0支持blocking和nonblocking两种transport方式:
    • blocking的传输方式要求在一次传输过程中,完成request和response的传输。
    • nonblocking的传输方式则将request和response的传输分为了两个独立的单向传输,而两次传输整体视为完成一次握手传输。
  • 两种传输方式对应的UVM方法如下:
- task b_transport(T t, uvm_tlm_time delay);
- function uvm_tlm_sync_e nb_transport_fw(T t,ref P p,input uvm_tlm_time delay);
- function uvm_tlm_sync_e nb_transport_bw(T t,ref P p,input uvm_tlm_time delay);
  • 这里T代表着统一的传输数据类uvm_tlm_generic_payload,而P代表着在nonblocking传输方式中用来做状态同步的类型。
  • 在定义TLM2.0的过程中,仍然有initiator和target的概念,也有Port、export以及imp端口类型。
  • 对于port类型,它是用来发起请求并调用target一端的传输方法,export用来传导这一要求,最后由imp端口所有组件来实现数据传输方法。
  • 为了区别于TLM1.0对于端口类型的称谓,UVM将TLM2.0端口类型称之为socket。它们是由port、export和imp组合而成的。
  • 一个socket首先是双向传输的,例如TLM1.0的双向传输端口transport可以用来做单次完成的双向传输,master和slave端口来完成多次的单向传输。
  • socket则按照blocking和nonblocking的传输方式,组合initiator或者target的发起端的区别,可以分为下面这些端口类型:
  • socket类型都继承于uvm_port_base,具有同TLM1.0端口一样的基础函数,而在这些socket内部,它们是通过例化port、export以及imp最终实现数据的双向传输的。
  • TLM2.0的port、export和imp类型不同于TLM1.0。
    • 首先这些相关的端口类型是最新引入的类,例如uvm_tlm_b_transport_port、uvm_tlm_b_transport_export和uvm_tlm_b_transport_imp。
    • 这里没有改变的概念是不同端口类型之间的连接关系,改变的知识新的端口类型名所匹配的方法不再是put()、get()、peek()而是变为了b_transport()、nb_transport_fw()和nb_transport_bw()。
  • 这些socket通过内置这些端口,最终可以实现数据的双向传输。

6.3 传送数据

  • TLM2.0标准指定的背景就是为了解决总线级别的抽象问题,所以它的统一数据格式也是按照总线数据的内容来定义的。
- bit[63:0] m_address: 数据的读写地址。
- uvm_tlm_command_e m_command:数据的读写命令。
- byte unsigned data[]:写入的数据或者读出的数据,由byte unsigned的类型构成动态数组,这是按照总线传输的最小粒度进行划分,便于target一侧进行数据整合。
- int unsigned length:data数组的长度,该数值应该与data数组的实际容量保持一致。
- uvm_tlm_response_status_e m_response_status:由target返回的状态值,表示数据传输是否完成和有效
- byte unsigned m_byte_enable[]:用来标记写入数据的有效性,标记那个byte应该写入。
- int unsigned m_byte_enable_length:该数值应该等于m_byte_enable数组的容量值。
- m_stream_width:用来表示连续传输时的数据传输长度。
- uvm_tlm_extedsion_base m_extensions[uvm_extension_base]:如果一些数据不在上面的部分,那么可以在这个数据延伸域中添加。
  • 从各个数据域的介绍来看,对于一般总线传输而言,这里包含的数据信息已经足够,那么如果该传输还包括其他数据的内容,该怎么办呢?
  • 一种办法是,将其合并作为数据成员data数组中的一部分。
  • 另外一种办法是创建新的uvm_tlm_extension类,将额外的数据成员装入到该数据延伸对象中,通过uvm_tlm_generic_payload::set_extension(uvm_tlm_extension_base ext)来添加一部分数据。
  • 对于一个数据类而言,拷贝、比较和打印等功能是必不可少的,而uvm_tlm_extension类也提供了do_copy()、do_compare()和do_print()等回调函数来满足这一要求。

6.4 时间标记

  • 不同的时间标记间隔是SystemC可以构建不同时间精确模型的重要手段。
  • 尽管SystemC原则上也可以通过自建时钟源利用时钟时间来驱动内部逻辑,但为了提高模型的运行效率,将数据传输的处理的时间通过标记时间来反映,可以很大程度上避免时钟依赖。
  • 在TLM2传输中,由于可以标定延迟时间,这使得target端可以模拟延迟,并且在准确的延迟时刻做出响应。
  • 为了便于标记延迟时间,例如实数范围的延迟1.1ns(SystemVerilog继承于Verilog的时间精度方式,只能使用整数的延迟方式),UVM新建了一个时间类uvm_tlm_time。
  • 这个时间类的便捷支持在于用户可以随时设置它的时间单位(默认为1ps),并且进行时间的增减操作。
  • 同时这个类的存在,也是为来解决在不同模块或数据包之间出现的不同时间单位和精度单位的问题。
  • 有了这么灵活的时间类,target一侧要进行时间等待这些操作就容易得多了,也不会出现时间单位或精度单位错误的问题。

6.5 示例

  • 我们利用TLM2的socket来建立一个轻量级的例子,从这个例子体会一下与TLM1.0相比,TLM2.0传输的特点有哪些。
class comp1 extends uvm_component;uvm_tlm_b_initiator_socket b_ini_skt;·uvm_component_utils(comp1)...task run_phase(uvm_phase phase);byte unsigned data[] = {1,2,3,4,5,6,7,8};uvm_tlm_generic_payload p1 = new("p1");uvm_tlm_time delay = new("delay");pl.set_address('h0000F000);p1.set_data_length(8);p1.set_data(data);p1.set_byte_enable_length(8);p1.set_write();delay.incr(0.3ns,1ps);`uvm_info("INITRSP",$sformatf("initiated a trans at %0d ps", $realtime()),UVM_LOW)b_ini_skt.b_transport(p1,delay);endtask
endclass
class comp2 extends uvm_component;uvm_tlm_b_target_socket #(comp2) b_tgt_skt;`uvm_component_utils(comp2)...task b_transport(uvm_tlm_generic_payload p1, uvm_tlm_time delay);`uvm_info("TGTTRSP",$sformatf("received a trans at %0d ps",$realtime()),UVM_LOW)p1.print();#(delay.get_realtime(1ps));p.set_response_status(UVM_TLM_OK_RESPONSE);`uvm_info("TGTTRSP",$sformatf("completed a trans at %0d ps",$realtime()),UVM_LOW)p1.print();endtask
endclass
class env1 extends 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_phase
function void connect_phase(uvm_phase phase);super.connect_phase(phase);c1.b.ini_skt.connect(c2.b_tgt_skt);
endfunction:connect_phasee
endclass
  • 从这个例子可以看出,有了标准的传输数据包和准确的延迟时间,方便了模块之间的复用和建立更高层级的模型。
  • 尽管目前UVM组件组件的传输仍建立在TLM1.0的基础上,也不排除日后要求支持TLM2.0的传输方式。
  • 至少目前我们已经可以看到,一些成熟的商业VIP接口同时支持TLM1,0的sequence item的传输方式和TLM2.0的socket的接口方式。
  • 从使用角度来看,TLM2.0在UVM的实现,是为了可以同SystemC的TLM2.0接口无缝衔接,关于SystemC模型在UVCM中的嵌入和使用方式。

7.同步通信元件

7.1 概述

  • SV用来做线程同步的几种元件,它们分别是semaphore、event和mailbox。
  • 在UVM中,需要同步线程不再只局限于在同一个对象中,还需要解决不同组件之间的线程同步问题。
  • 一旦线程同步要求发生在不用组件,这就要求组件之间可以通过 某种方法来实现同步。
  • 考虑到UVM组件的封闭性原则,我们并不推荐通过层次索引的形式在组件中来索引公共的event或者semaphore,UVM为了解决封闭性的问题,定义了如下的类来满足组件之间的同步要求:
    • uvm_event,uvm_event_pool和uvm_event_callback
    • uvm_barrier,uvm_barrier_pool
  • 这两组分别用于服务两个组件之间的同步和多个组件之间的同步。
  • 回调函数作为一种实现基类复用的手段,在UVM中也被进一步封装为一个类uvm_callback
  • uvm_callback不但具备普通回调函数可以在函数执行前后调用的特点,还增加了丰富的特性来完成层次化的调用,uvm_callback类作为我们对函数调用的同步手段了解。

7.2 uvm_event 应用

  • 与event相比,uvm_event类有下面几个重要特性:

    • event被->触发之后,触发使用@等待该事件的对象;uvm_event通过trigger()来触发,触发使用wait_trigger()等待该事件的对象。要再次等待事件触发,event只需要再次用->触发,而uvm_event需要先通过reset()方法重置初始状态,再使用trigger()来触发。
    • event无法携带更多的信息,而uvm_event可以通过trigger(T data = null)的可选参数,将伴随触发的数据对象都写入到该触发事件中,而等待该事件的对象可以通过方法 wait_trigger_data(output data)来获取事件触发时写入数据对象。
    • event触发时无法直接触发回调函数,而uvm_event可以通过add_callback(uvm_event_callback cb,bit append = 1)函数来添加回调函数。
    • event无法直接获取等待它的进程数目,而uvm_event可以通过get_num_waiters()来获取等待它的进程数目。
  • 不同组件可以共享同一个uvm_event,这不需要通过扩层次传递uvm_event对象句柄来实现共享,因为这不符合环境封闭的原则,该共享方式是通过uvm_event_pool这一全局资源池来实现的。
  • 这个资源池类是uvm_object_string_pool#(T)的子类,它可以生成和获取通过字符串来索引uvm_event对象。
  • 通过全局资源池(唯一的),环境中的任何组件都可以从资源池获取共享的对象句柄,这就避免了组件之间的相互依赖。
  • 我们结合uvm_event、uvm_event_pool和uvm_event_callback来讲解一个典型用例。
class comp1 extends uvm_compoent;uvm_event e1;`uvm_component_utills(comp1)...function void build_phase(uvm_phase phase);super.build_phase(phase);e1 = uvm_event_pool::get_global("e1");endfunctiontack run_phase(uvm_phase phase);edata d = new();ecb cb = new();d.data = 100;#10ns;e1.add_callback(cb);e1_trigger(d);`uvm_info("ETRIG",$sformatf("trigger sync event at %t ps",$time),UVM_LOW)endtask
endclass
class comp2 extends uvm_component;uvm_event e1;`uvm_copmonent_utils(comp2)...function void build_phase(uvm_phase phase);super.build_phase(phase);e1 = uvm_event_pool:get_gobal("e1");endfunctiontask run_phase(uvm_phase phase);uvm_object tmp;edata d;`uvm_info("ESYNC",$sfomatf("wait sync event at %t ps",$time),UVM_LOW) e1.wait_trigger_data(tmp));void'{$cast(d,tmp)};`uvm_info("ESYNC",$sformatf("get data %0d after sync %t ps",d.data,$time),UVM_LOW)endtask
endclass
class env1 extends uvm_env;comp1 c1;comp2 c2;`uvm_cmponent_utils(env1)...
endclass
输出结果:
UVM_INFO @ 0:reporter[RNSTS] Running test test1...
UVM_FIFO @ 0:uvm_test_top.env.c2 [ESYNC] wait sync event at 0 ps
UVM_INFO @ 10000:reporter [epretrig] before trigger event e1
UVM_INFO @ 10000:reporter [EPOSTRIG] after trigger event e1
UVM_INFO @ 10000:uvm_test_top.env.c1 [ETRIG] trigger sync event at 10000ps
UVM_INFO @ 10000:uvm_test_top.env.c2 [ESYNC] get data 100 after sync at 10000 ps
  • 组价c1和c2之间完成了从c1到c2的同步,而且在同步过程中通过uvm_event e1传递了数据edata,并且调用了回调函数类ecb的pre_trigger()和post_trigger()方法。

  • 关于示例,由几个需要注意:

    • 无论由多少个组件,只要它们寻求同一个名称的uvm_event,就可以共享该uvm_event对象。例如上面c1和c2通过uvm_event_pool::get_global(“e1”)来获取同一个名称的uvm_event对象,即便该对象不存在,uvm_event_pool资源池也会在第一次调用get_global()函数时创建这样一个对象以供使用。
    • 如果要传递数据,用户可以定义扩展与uvm_object的数据字了,并通过uvm_event::trigger(T data = null)来传递数据对象。而在等待uvm_event一侧的组件,则需要通过uvm_event::wait_trigger_data(output T data)来获取该对象。
    • 用户也可以扩展uvm_event_callback类,定义uvm_event被trigger前后调用方法pre_trigger()和post_trigger()。pre_trigger()需要有返回值,如果返回值为1,则表示uvm_event不会被trigger,也不会再执行post_trigger()方法;如果返回值为0,则会需要trigger该事件对象。
    • 如果用户无法确定在等待事件之前,uvm_event是否已经被trigger,那么用户还可以通过 方法wait_ptrigger()和wait_ptrigger_data()来完成等待。这样即便在调用事件等待方法之前该事件已经被处罚,等待方法仍然不会被阻塞并且可以继续执行结束。
  • uvm_event总结

    • 组件之间的常规数据流向是通过TLM通信方法实现的,比如sequencer与dirver之间,或者moniter与scoreboard之间。
    • 这些时候,数据传输是偶然处罚的,并且需要立即响应,这个时候uvm_event就是得力的助手了。
    • uvm_evnet同时也解决了一个重要的问题,那就是在一些uvm_object和uvm_component对象之间如果要发生同步,但是无法通过TLM完成数据传输,因为TLM传输必须是组件(component)和组件之间进行的。
    • 如果要在sequencer与sequence之间进行同步,或者sequence与driver之间进行同步,就可以借助uvm_evnet来实现。

7.3 uvm_barrier介绍

  • 在SV模块中,多个线程的同步除了可以通过semaphore和mailbox来进行,也可以通过fork-join的结构空来语句块来控制整体的运行节奏。
  • 然而对于uvm环境中的对个独立组件,SV的这些方法都受到了作用于的局限。
  • UVM提供了一个新的类uvm_barrier来对多个组件进行同步协调,同时为了解决组件独立运行的封闭性需要,也定义了新的类uvm_barrier_pool来全局管理这些uvm_barrier对象。
  • uvm_barrier_pool同之前的uvm_event_pool一样,也是基于通用的参数类uvm_object_string_pool来定义的。
typedef uvm_object_string_pool #(uvm_barrier) uvm_barrier_pool;
typedef uvm_object_string_pool #(uvm_event#(uvm_object)) uvm_event_pool;
  • uvm_barrier可以设置一定的等待阈值threshould,当有不少于该阈值的进程在等待该对象时,才会触发改时间,同时激活所有正在等待的进程,使其可以继续进行。
class comp1 extends uvm_component;uvm_barrier b1;`uvm_component_utils(comp1)...function void build_phase(uvm_phase phase);super.build_phase(phase);b1 = uvm_barrier_pool::get_global("b1");endfunctiontask run_phase(uvm_phase phase);#10ns;`uvm_info("BSYNC",$sformatf("c1 wait for b1 at %0t ps",$time),UVM_LOW) b1.wait_for();`uvm_info("BSYNC",$sformatf("c1 is activated at %0 ps", $time),UVM_LOW)endtask
endclass
class comp2 extends uvm_component;uvm_barrier b1;`uvm_component_utils(comp2)...
function void build_phase(uvm_phase phase)super.build_phase(phase);b1 = uvm_barrier_pool::get_global("b1");
endfunction
task run_phase(uvm_phase phase);#20ns;`uvm_info("BSYNC",$formatf("c2 wait for b1 at %0t ps",$time),UVM_LOW)b1.wait_for();`uvm_info("BSYNC",$sformatf("c2 is activated at %0t ps",$time),UVM_LOW)
endtask
endclass
class env1 extends uvm_env;comp1 c1;comp2 c2;uvm_barrier b1;`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);b1 = uvm_barrier_pool::get_global("b1");endfunction:build_phasetask run_phase(uvm_phase phase);b1.set_threshould(3);`uvm_info("BSYNC",$sformatf("env set b1 threshould %d at %0t ps",b1.get_thrshould(),$time),UVM_LOW)#50ns;b1_set_threashould(2);`uvm_info("BSYNC",$sformatf("ecv set b1 threashould %d at %0 ps",b1.get_thrshould(),$time),UVM_LOW)endtask
endclass
输出结果为:
UVM_INFO @ 0:reporter [RNTST] Running test test1...
UVM_INFO @ 0:uvm_test_top.env [BSUNC] env set b1 threshould 3 at 0 ps
UVM_INFO @ 10000:uvm_test_top.env.c1 [BSYNC] c1 wait for b1 at 10000 ps
UVM_INFO @ 20000:uvm_test_top.env.c2 [BSYNC] c2 wait for b1 at 20000 ps
UVM_INFO @ 50000:uvm_test_top.env [BSYNC] env set b1 threashould 2 at 50000 ps
UVM_IFNO @ 50000:uvm_test_top.env.c1 [BSYNC] c1 is activted at 50000 ps
UVM_INFO @ 50000:uvm_test_top.env.c2 [BSYNC] c2 is activted at 50000 ps
  • 从这个例子来看,c1和c2的run_phase任务之间需要同步,而同步它们的元件则是来自于顶层的一个uvm_barrier b1。
  • 由于c1、c2和env1都共享该对象,这使得c1和c2可以通过wait_for()来等待激活,而env1可以设置阈值来调控什么时间来“开阀”。
  • 从仿真结果可以看到,在一开始的时候,阈值设置为3,但由于等待该barrier的进程只有2个,无法达到阈值条件,使得两个进程都无法激活。
  • 在env1将b1的阈值设置为2时,等待该barrier的两个进程都被激活。
  • 因此通过uvm_barrier::set_threshould()和uvm_barrier::wait_for()这样的方式,可以实现多个组件之间的同步,同时可以保持各个组件之间独立性。

7.3 uvm_callback介绍

  • 除了UVM提供新的类方便组件之间的同步之外,另外一种同步方式回调函数(callback)也方便了类的封装复用。
  • 通常情况下得到了一个封闭的包,其中的类如果有些成员方法需要修改,或者需要扩展新的方法时,应该怎么做呢?如果这个包是外来的,那么维护方法不建议去修改这个类本身。
  • 如果我们通过类的继承满足这一要求,又无法在该包环境中用心的子类替换原来的父类,那么UVM的覆盖机制(override)可以帮忙。
  • 除了覆盖机制,还有callback也可以为用户提供自定义的处理方法,这就使得如果用户不需要添加新的方法,而是想延展之前的方法,就无需通过继承类的方式而只需要通过在后期定义callback方法来实现。
  • uvm_object本身提供了一些callback方法供用户们定义:
    • copy()/do_copy()
    • print()/do_print()
    • compare()/do_compare()
    • pack()/do_pack()
    • unpack()/do_unpack
    • record()/do_record()
  • 默认情况下,这些回调函数do_xxx是定义为空的。
  • 如果用户执行了uvm_object()函数,那么该函数执行末尾会自动执行uvm_object::do_copy()。
  • do_copy()是copy()的回调函数,uvm_object()会在copy的执行尾端构筑(hook)callback函数即do_copy()。
  • 如果用户自定义了这些回调函数,就可以在对函数执行结束后再执行扩展后的回调方法。
  • 通过这个新添加的类,使得函数回调有了顺序和继承性。关于顺序和继承性的实现,UVM是通过两个相关类uvm_callback_iter和uvm_callbacks #(T,CB)来实现的。
class edata extends uvm_object;int data;`uvm_object_utils(edata)...
endclass
class cb1 extends uvm_callback;`uvm_object_utils(cb1)...virtula function void do_trans(edata d);d.data = 200;`uvm_info("CB",$sformatf("cb1 executed with data %0d",d.data),UVM_LOW)endfunction
endclass
class cb2 extends cb1;`uvm_object_utils(cb2)...function void do_trans(edata d);d.data = 300;`uvm_info("CB",$sformatf("cb2 executed with data %0d",d.data),UVM_LOW)endfunction
endclass
class comp1 extends uvm_component;`uvm_component_utils(comp1)`uvm_register_cb(comp1,cb1)...task run_phase(uvm_phase phase);edata d = new();d.data = 100;`uvm_info("RUN",$sformatf("proceeding data %0d",d.data),UVM_LOW)`uvm_do_callbacks(comp1,cb1,do_trans(d))endtask
endclass
class env1 extends uvm_env;comp1 c1;cb1 m_cb1;cb2 m_cb2;`uvm_component_utils(env1)function new(string name,uvm_component parent);super.new(name,parnet);m_cb1 = new("m_cb1");m_cb2 = new("m_cb2");endfunctionfunction void build_phase(uvm_phase phase);super.build_phase(phase);c1 = comp::type_id::crate("c1",this);uvm_callbacks #(comp1)::add(c1,m_cb1);uvm_calllbacks #(comp1)::add(c1,m_cb2);endfunction:build_phase
endclass
输出结果:
UVM_INFO @ 0:reporter [RNTST] Running test test1...
UVM_INFO @ 0:uvm_test_top.env.c1[RUN] proceeding data 100
UVM_INFO @ 0:reporter [CB] cb1 executed with data 200
UVM_INFO @ 0:reporter [CB] cb2 executed woth data 300

uvm_callback总结

  • uvm_callback类使得狗子属性变得更加容易控制和继承,有需要注意的地方:

    • uvm_callback可以通过继承的方式来满足用户更多的定制,例如上面的cb2继承于cb1。
    • 为了保证调用uvm_callback的组件类型T与uvm_callback类型CB保持匹配,建议用户在T中声明与CB的匹配,该声明可以通过宏`uvm_register_cb(T,CB)来实现。在用户养成的注册习惯之后,如果以后调用的T与CB不匹配,那么在检查完匹配注册表之后系统会打印warning信息,提示用户回调函数的潜在问题。
    • uvm_callback建立了回调函数执行的层次性,养成在实现方面,不再是在T的方法中直接呼叫某一个回调方法,而是通过宏`uvm_do_calllbacks(T,CB,METHOD)来实现。该宏最直观的作用在于会训话执行已经与该对象结对的uvm_callbacks类的方法。此外宏 uvm_do_callbacks_exit_on(T,CB,METHOD,VAL)可以进一步控制执行回调函数的层次,简单来讲,回调函数会保持执行直到返回值与给入的VAL值相同才会返回,这一点使得回调方法在执行的顺序上面有更多的可控性。
    • 有了`uvm_do_callbacks宏还不够,需要注意的是,在执行回调方法时,依赖的是已经例化的uvm_callback对象。所以最后一步需要例化uvm_callback对象,上面的例子中分别例化了cb1和cb2,通过“结对子”的方式,通过uvm_callbacks #(T,CB)类的静态方法add()来添加成对的uvm_object对象和uvm_callback对象。

芯片漫游指南(3)-- UVM通信相关推荐

  1. 芯片漫游指南(1)-- UVM世界观

    目录 1 uvm中的学习内容 2 类库地图 3 工厂机制 3.1 工厂的意义 3.2 工厂提供的便利 3.3 覆盖方法 4 核心基类 4.1 uvm_object 4.2 域的自动化 4.3 拷贝(c ...

  2. 芯片验证漫游指南_IC验证入门资料

    这学期马上就要结束了,SV课昨天也考试了,大家大部分考的都还不错,感谢大家的支持.所以后面关于SV可能就不会更新那么多了,也有可能会从头开始,出一个关于SV从入门到自我放弃的系列.再看吧~~这主要取决 ...

  3. 《深入理解计算机系统》漫游指南

    导读:如果你也读CS专业,如果你也不知道怎么回答"学计算机不就是学修电脑"这个CS系的宿命之问,推荐一定读一读<深入理解计算机系统>. <深入理解计算机系统> ...

  4. [星系漫游指南]分享一个查看近期火星天气的小程序

    首先证件照: Github 地址:https://github.com/dongsuo/marsWeather 线上小程序: 1. 介绍 本指南目前可以查看近日火星的天气,包括气温.风速.风向.气压等 ...

  5. 「云端 JavaScript 漫游指南」

    7 月 30 日在 w3ctech 的 JavaScript 活动广州场 讲了一节「云端 JavaScript 漫游指南」,实质上就是 Node.js 入门讲座.希望通过这一节讲座,让原本熟悉 Jav ...

  6. 爬虫漫游指南:浏览器指纹

    爬虫漫游指南 浏览器指纹 最近深入阅读了瑞数的源码,发现瑞数收集了很多浏览器的特征信息,这部分源码对了解浏览器具有很高的学习价值,为此整理了这些特征信息,并尽可能还原了可读可用的代码来获取这些信息.本 ...

  7. 爬虫漫游指南:HTTP/2 网站爬取

    爬虫漫游指南 HTTP/2 网站爬取 最近写爬虫的时候遇到了一个用HTTP 2.0协议的网站,requests那套老经验在它身上不好用了,得专门针对HTTP 2.0进行开发. 因为与HTTP 1.x的 ...

  8. 蓝牙BLE芯片PHY6222之I2C主从通信

    蓝牙BLE芯片PHY6222之I2C主从通信 开发环境 I2C主机 I2C从机 注意事项 开发环境 1.PHY6222开发板 2.SDK版本以及路径:SDK\release_bbb_sdk-PHY62 ...

  9. 爬虫漫游指南:瑞数的反调试陷阱

    爬虫漫游指南 瑞数的反调试陷阱 遇上有反爬的网站,第一反应肯定是要先打开开发者工具调试一波,于是,反爬工程师们就在此处设下了第一道防线.初级一点的,例如监听F12,禁用鼠标右键,作为防线的一部分,这些 ...

最新文章

  1. SQLServer的数据类型
  2. Javascript基础系列之(三)数据类型 (数值 Number)
  3. 计算机安装操作系统的目的是什么,安装计算机操作系统.doc
  4. 使用Redis bitmaps进行快速、简单、实时统计
  5. nginx搭建基于http协议的视频点播服务器
  6. UNIX TCP回射服务器/客户端之使用epoll模型的服务器
  7. Java多线程编程核心技术-多线程基础使用
  8. dockerHub登录失败
  9. (原创) cocos2d-x 3.0+ lua 学习和工作(4) : 公共函数(8): 生成只读table
  10. Failed to run the WC DB work queue associated with 错误的解决
  11. 小D课堂 - 零基础入门SpringBoot2.X到实战_第9节 SpringBoot2.x整合Redis实战_40、Redis工具类封装讲解和实战...
  12. 二维码----百度百科
  13. 冒险岛历代版本科普(干货)-琳琳冒险岛
  14. 工业产品表面缺陷检测方法——综述
  15. html超链接自动下划线,html超链接下划线应该加入吗?
  16. 《JavaSE-第七章》之抽象的类-实例的对象-合理的封装
  17. win server服务器 关闭危险端口 135,137,138,139,445的方法
  18. 11月28号工作计划
  19. 混淆电路——混淆电路原理
  20. java前端桌面设计javafx布局思路

热门文章

  1. 使用python量化交易接口有哪些分析指标和策略?
  2. 科达陆吉良: 行业“管多控少”,交通AI化是大势所趋
  3. 应急响应之windows日志排查
  4. 如何解决SolidWorks2018及以上版本中打开STEP不能编辑的问题
  5. CACC 协同式自适应巡航模型 搭建四辆车在carsim和simulink进行协同式自适应巡航 其中间距策略考虑领航车速的影响,各个车辆采用分层式控制,分层式控制器主要分为下层控制
  6. AJ 组件库之通用数据字典 DataDict
  7. JRE安装出错,显示1603的错误。
  8. 全球及中国非接触式磁致伸缩位置传感器行业研究及十四五规划分析报告
  9. 华为p9总是显示切换服务器中,原来华为手机是有双系统模式的,保护个人隐私,一部手机相互切换!...
  10. Vulnhub_Beelzebub