文章目录
简介
系统原型阶段和芯片验证阶段均使用了TLM通信方式。前者是为了更快地实现硬件原型之间的数据通信,后者是为了更快地实现验证组件之间的通信。
仿真速度是TLM对项目进度的最大贡献,同时TLM传输中的事务又可以保证足够大的信息量和准确性。
TLM并不是某种语言的产物,而是作为一种提高数据传输抽象级的标准存在的。
TLM是一种基于事务(Transaction)的通信方式,通常在高抽象级语言,例如SystemC、SV/UVM中作为模块间的通信方式。
TLM通信保证了 相邻组件之间的通信不再通过显式句柄引用,而是独立于组件的通信方式,为验证 组件的复用提供了很好地封闭性。
基本概念:
-
TLM通信需要两个通信的对象(分别称为initiator 和 target)。谁先发起通信请求,谁就属于initiator,而作为通信的响应方,称为target。按照Transaction的流向,可以将对象分为 producer 和 consumer。数据从哪里产生,它就属于producer,而数据流向了哪里,它就属于consumer。注意:transaction的流向不一定是从initiator流向target,也可能是target流向initiator。
initiator 与 target 的关系同 producer 与consumer的关系不是固定的 。
-
有了两个参与通信的对象后,用户需要将TLM通信方法在target一端中实现,以便于initiator将来作为发起方可以调用target的通信方法,实现数据传输。
-
最后需要将两个对象进行连接,需要在两个TLM对象中创建TLM端口,继而在更高层次中将这两个对象进行连接。
TLM通信步骤:
- 分辨出initiator 和 target ,producer 和 consumer;
- 在 target 中实现TLM通信方法;
- 在两个对象中创建TLM端口,initiator中创建port端口,target端创建imp端口;
- 在更高层次对两个对象的端口进行连接。
分类:
从数据流向来看,传输方向分为单向和双向
- 单向传输:由initiator发起request transaction
- 双向传输:由initiator发起request transaction,传送至target,继而target在消化了request transaction后,会发起responde transaction返回给initiator。
端口按照类型可以分为3种:
- port:经常作为initiator的发起端,initiator凭借port才可以访问target的TLM通信方法;
- export:作为initiator和target中间层次的端口;
- imp:只能作为target接受request的末端,它无法作为中间层次的端口,所以imp到的连接无法再次延伸。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7m65CAs-1623290831297)(C:\Users\xrt\AppData\Roaming\Typora\typora-user-images\1622704863388.png)]
1.端口的使用
就单向端口而言,声明port和export作为request发起方,参数只需要指定transaction类型参数,而声名imp作为request接收方,参数有2个,第一个指定指定transaction类型,第二个指定它所在的component类型。
就声明双向端口而言,指定参数需要考虑双向传输的因素,将传输类型transaction拆分为request transaction 和response transaction。
TLM端口连接的一般做法:
-
在initiator端例化port,在中间层例化export,在target端例化imp,在build_phase中创建;
-
多个port可以连接到同一个export或者imp;但是单个port或者export无法连接到多个imp上;
可以理解成多个initiator可以对同一个target发起request,但是同一个initiator无法连接多个target;
注:后文使用 Analysis Port或者Analysis TLM FIFO通信管道可以实现一端对多端。 -
port 可以连接port 、export 和 imp;export 可以连接 export 和imp 上;imp是数据传输的终点,无法扩展连接。
2.单向通信
-
单向通信是指由initiator 到target之间的数据流向是单一方向的,或者说initiator和target只能扮演producer和consumer中的一个角色。
-
声明port和export作为request发起方,参数只需要指定transaction类型参数,而声名imp作为request接收方,参数有2个,第一个指定指定transaction类型,第二个指定它所在的component类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ck0TC9FW-1623290831307)(C:\Users\xrt\AppData\Roaming\Typora\typora-user-images\1622706137470.png)]
- 阻塞端口的方法类型是task,这保证了可以实现事件等待和延时;非阻塞端口的方式为function类型,这确保调用可以立即返回。
blocking阻塞传输的方法:
- put():initiator先生成数据T t ,同时将数据传送至target
- get():initiator从target获取数据T t ,而target中的该数据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发送,提高数据发送的成功率。
3.双向通信
双向通信的initiator和target两端之间数据流向是双向的,即两端initiator和target同时扮演者producer和consumer,而initiator作为request发起方,在发起request后,还会等待response返回。
双向端口按照通信握手方式可以分为:
- transport 双向通信方式
- master和 slave 双向通信方式
transport 端口通过 transport() 方法,可以在同一方法调用过程中完成 REQ 和 REP的发出和返回。
master 和slave的通信方式必须分别通过put 、get 和peek()的调用,使用两个方法才完成一次握手信号
master端口和slave 端口的区别:当initiator 作为 master时,它会发送REQ至 target端,然后再从target端获取 RSP;当initiator使用slave端口时,它会先从target端获取REQ,然后将RSP送至target端。
4.多向通信
-
多向通信仍然是两个组件之间的的通信,而不是多个组件之间的通信。
-
多向通信是指,如果initiator与target之间的相同TLM端口数目超过一个的情况,在target端的解决方法。
-
UVM通过端口宏声明方式来解决这一问题,它解决的核心问题在于让不同端口对应不同的任务名,这样就不会造成方法名的冲突
注意:当在target中两个端口访问同一个资源时,要使用旗语控制资源访问。
//在端口后增加“-decl”
`uvm_blocking_put_imp_decl(SFX)//SFX表示后缀名称
`uvm_nonblocking_put_imp_decl(SFX)
...
-------------------------------------------------
`uvm_blocking_put_imp_decl(_p1)//定义了两个独一无二的imp端口类型
`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;
...
function void build_phase(uvm_phase phase);
bp_port1 = new("bp_port1",this);//例化端口
bp_port2 = new("bp_port2",this);
endfunction
task run_phase(uvm_phase phase);
itrans t1,t2;
fork
t1 = new("t1",this);
t2 = new("t2",this);
this.bp_port1.put(t1);//发送两个itrans
this.bp_port2.put(t2);
join
endtask
endclass
class comp2 extends uvm_component;
uvm_blocking_put_imp_p1#(itrans,comp2) bt_imp_p1;//根据宏定义了两个imp端口
uvm_blocking_put_imp_p2#(itrans,comp2) bt_imp_p2;
itrans t_q[$];
semaphore key;//注意共享资源要做互斥保护
...
function void build_phase(uvm_phase phase);
bt_imp_p1 = new("bt_imp_p1",this);//例化端口
bt_imp_p2 = new("bt_imp_p2",this);
endfunction
task put_p1(itrans t);//端口名增加了后缀,对应实现的put方法名也增加了后缀
key.get();//注意如果是非阻塞的通信,这里就要用try_get
t_q.push_back(t);
key.put();//相应的这里做try_put
endtask
task put_p2(itrans t);//实现两个不同名的put方法
key.get();
t_q.push_back(t);
key.put();
endtask
endclass
class env extends uvm_env;
comp1 c1;
comp2 c2;
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
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bp_port1.connect(c2.bt_imp_p1);
c1.bp_port2.connect(c2.bt_imp_p2);
endfunction
endclass
注意:
- initiator端只管发送,并不关心在target端实现对应方法会有方法名冲突的问题;
- target端为了防止同名方法名冲突,先定义宏`uvm_blocking_put_imp_decl(SFX) ,这里SFX表示后缀名称,以此可以声明带后缀的端口和方法名,如上面代码中的uvm_blocking_put_imp_p1#(itrans,comp2) bt_imp_p1和 put_p1(itrans t)
通信管道
TLM通信的实现方式都是端到端的,同时在target一端需要实现传输方法,如put() 和get()等
5.通信管道
TLM通信的实现方式都是端到端的,同时在target一端需要实现传输方法,如put() 和get()等;
monitor、coverage collector等组件在传输数据时,会存在一端到多端的传输,如何解决?
1. TLM_FIFO
在一般TLM传输过程中,无论initiator给target发起一个transaction,还是initiator从target获取一个transaction,transaction最终都会流向consumer中。在consumer没有分析transaction之前,我们希望将transaction先存储到本地的FIFO中供稍后使用,这就可以使用uvm_tlm_fifo这个组件。
-
uvm_tlm_fifo类是一个新组件,继承与uvm_component类,而且已经预先内置了多个端口以及实现了多个对应方法供用户使用。
-
uvm_tlm_fifo是组件,例化时需要传入两个参数,一个记录在工厂的参数名,一个parent;
-
uvm_tlm_fifo的功能类似于mailbox,不同的地方在于uvm_tlm_fifo提供了各种端口供用户使用。推荐initiator端例化put_port或者get_peek_port,来匹配uvm_tlm_fifo的端口类型。
2. Analysis Port
-
可以实现一端对多端的需求,即数据是从同一个源的TLM端口发出到达不同的组件。
-
如果数据源端发生变化需要通知跟它关联的多个组件时,可以利用软件设计模式之一 观察者模式(广播模式,observer pattern)来实现。
observer pattern的核心在于:
- 第一,这是从一个initiator端到多个target端的方式
- 第二,analysis port采用的是”push"模式,即从initiator端调用多个target端的write()函数实现数据传输。
连接方式:
- agent一侧例化了uvm_analysis_imp后还需要实现write()函数;
- 在顶层连接。顶层将initiator端的 uvm_analysis_port 与多个target端的 uvm_analysis_imp 进行连接;
- 当initiator端调用write()函数时,实际上是采用轮询的方式将所有连接的target端内置的write()函数进行了调用。
- 由于函数立即返回的特点,无论连接多少个target端,initiator端调用write()函数总是立即返回的,不同之前的单端口函数调用的是,即便没有target与之相连,调用write()函数也不会发生错误
3. Analysis TLM FIFO
-
uvm_tlm_analysis_fifo为用户提供了可以搭配uvm_analysis_port端口、uvm_analysis_imp端口和write()函数;
-
uvm_tlm_analysis_fifo类继承于uvm_tlm_fifo,这表明它本身具有面向单一TLM端口的数据缓存特性,而同时该类又有一个uvm_analysis_imp端口;
连接方式:
- 将initiator的analysis port连接到uvm_tlm_analysis_fifo 的 get_export 端口,这样数据可以从initiator发起,写到各个uvm_tlm_analysis_fifo的缓存中。
- 将多个target的get_port连接到tlm_analysis_fifo的get_export,注意保持端口类型的匹配,这样从target一侧需要调用get()方法就可以得到先前存储在tlm_analysis_fifo中的数据。
4. request & response 通信管道
UVM提供了两种简单的通信管道,他们作为数据缓存区域,既有TLM端口从外侧接收request 和 response,同时也有TLM端口供外侧获取request和response。
- uvm_tlm_req_rsp_channel
- uvm_tlm_transport_channel