什么是TLM
- 在芯片开发流程中,系统原型和芯片验证对项目的助推起到了关键作用。
- 系统原型一般是通过硬件功能描述文档来模拟硬件行为,而行为要求不同于RTL模型。系统原型可以提供一个准确到硬件比特级别、按照地址段访问、不依赖于时钟周期的模型,该模型通常基于SystemC语言;系统原型中各个模块通过TLM可以实现宽松时间范围内的数据包传输。
- 芯片验证是在RTL模型初步建立之后,通过验证语言和方法学来构建验证平台。该平台的特点是验证环境整体基于面向对象开发,组件之间的通信基于TLM,而在driver与硬件接口之间需要将TLM抽象事务降解到基于时钟的信号级别。
- 系统原型阶段和芯片验证阶段都使用了TLM通信方式,前者是为了更快地实现硬件原型之间的数据通信,后者是为了更快地实现验证组件之间的数据通信。
- 仿真速度是TLM对项目进度的最大贡献,同时TLM传输中的事务又可以保证足够大的信息量和准确性。
- TLM并不是某一种语言的产物,而是作为一种提高数据传输抽象级的标准存在。
- 高抽象级的数据通信,可以用来表示宽松时间跨度内的硬件通信数据,而通过将低颗粒硬件周期内的数据打包为一个大数据,非常有利于整体环境的仿真速度。
- TLM的运用也越来越广泛,包括emulator同硬件仿真器的协同仿真框架中,也建议使用这种方式来降低混合环境之间的通信频率,以便提高整体的运算效率。
- TLM是一种基于事务的通信方式,通常高抽象级语言例如SystemC或者SV/UVM中作为模块之间的通信方式。
- TLM成功将模块内的计算和模块之间的通信从时间跨度方面剥离开。
- 在抽象语言建模体系中,各模块通过一系列并行的进程实现,并通过通信和计算模拟出正确的行为。
- 如果要提高系统原型的仿真性能,需考虑建模自身的运算优化和模型间的通信优化:
- 前者需要依靠开发者的经验还有性能分析工具来逐步优化模型。
- 后者可通过将通信频率降低、内容体积增大的方式,减少由不同进程间同步带来的资源损耗。
- TLM是从通信优化角度提出的一种抽象通信方式。
基本概念
- 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端口,继而在更高层次中将这两个对象进行连接。
分类
可将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端口一共可以分为六类:
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,imp_parent_t)
端口的使用
- 就单向端口而言,声明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。
- port应为request起点,imp应为request终点,而中间可以穿越多个层次。基于单元组件的自闭性考虑,在穿越的中间层次声明export,继而通过层次连接实现数据通路。
- port可以连接port、export或imp,export可以连接export或imp,imp只能作为数据传送的终点,无法扩展连接。
class request extends uvm_transaction; //REQ
byte cmd;
int addr;
int req;
endclass
class response extends uvm_transaction; //RSP
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_noblocking_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 comp4 extends uvm_agent;
uvm_blocking_get_imp #(request, comp4) bg_imp;
uvm_noblocking_put_port #(request) nbp_port;
`uvm_component_utils(comp4)
...
task get(output request req);
endclass
class 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_noblocking_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);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
c1.bg_port.connect(this.bg_port); //connect左侧可以是initiator、export ,右侧是target
c2.bg_port.connect(this.bg_port);
this.nbp_exp.connect(c2.nbp_imp); //连接方向从agent的export到c2的import
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
function 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
endclass
从代码可以得出建立TLM通信的常规步骤:
- 定义TLM传输中的数据类型,上面分别定义了
request
类和response
类。 - 分别在各个层次的component中声明和创建TLM端口对象。
- 通过
connect()
函数完成端口之间的连接。 - 在imp端口类中要实现需要提供给initiator的可调用方法,例如comp2中由于有一个
uvm_noblocking_put_imp #(request, comp2) nbp_imp
,因此需要方法try_put()
和can_put()
,而comp4中有一个uvm_blocking_get_imp #(request, comp4) bg_imp
,需实现方法get()
。 - 需注意必须在imp端口类中实现对应方法,否则端口即使连接也无法实现数据传输。