TLM通信
在芯片开发流程中,有两个地方对项目的助推起到了关键作用:
系统原型&芯片验证
系统原型一般通过硬件功能描述文档模拟硬件行为, 行为要求不同于RTL模型。 系统原型提供一个准确到硬件比特级别、 按照地址段访问、 不依赖于时钟周期的模型, 该模型通常基于SystemC语言,而系统原型中各模块通过TLM可以实现宽松时间范围内的数据包传输。
芯片验证是在RTL模型初步建立后, 通过验证语言和方法学如SV/UVM来构建验证平台。该平台的特点是验证环境整体基于面向对象开发,组件之间的通信基于TLM, 而在driver与硬件接口之间需将TLM抽象事务降解到基于时钟的信号级别。
系统原型阶段和芯片验证阶段均使用了TLM通信方式。前者是为了更快地实现硬件原型之间的数据通信,后者是为了更快地实现验证组件之间的数据通信。
仿真速度是TLM对项目进度的最大贡献,同时TLM传输中的事务又可以保证足够大的信息量和准确性。
TLM并不是某一种语言产物,而是作为一种提高数据传输抽象级的标准存在。
高抽象级的数据通信,可以用来表示宽松时间跨度内的硬件通信数据,而通过降低颗粒硬件周期内的数据打包为一个大数据,非常有利于整体环境的仿真速度。
TLM的运用越来越广泛,包括emulator同硬件仿真器的协同仿真框架中,也建议使用这种方式来降低混合环境之间的通信频率,以便提高整体的运算效率。
TLM 是一种基于事务 (transaction) 的通信方式, 通常在高抽象级语言如 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 通信步骤分为:
1 分辨出 initiator和target, producer和consumer。
2 在 target 中实现 TLM 通信方法。
3 在两个对象中创建 TLM 端口。
4 在更高层次中将两个对象的端口进行连接。
从数据流向来看, 传输方向可以分为单向(unidirection)和双向 (bidirection):
• 单向传输:由initiator发起request transactio;
• 双向传输:由initiator发起request transaction, 传送至target; 而target在消化 了request transaction后,会发起response transaction, 继而返回给initiator。
端口的按照类型可以划分为三种:
• port: 经常作为initiator的发起端, initiator凭借port才可以访问target的TLM通信方法。
• export: 作为initiator和target中间层次的端口。
• imp: 只能作为target接收request的未端, 它无法作为中间层次的端口, 所以imp的连接无法再次延伸。
如果将传输方向和端口类型加以组合,综合下来, TLM 端口一共分为 6 类:
• uvm_UNDIR_port #(trans_t) uvm_单双向_端口类型 # (类型参数)
• 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)
就单向端口而言,声明 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 只能作为数据传送的终点, 无法扩展连接。
class request extends uvm_transaction;//声明了一个request类继承与transaction
byte cmd;
int addr;
int req; //定义传递的数据
endclass
class response extends uvm_transaction;//声明了一个response类继承与transaction
byte cmd;
int addr;
int rsp;
int status;
endclass
class compl extends uvm_agent;//声明了一个comp1组件继承与uvm_agent
uvm_blocking_get_port #(request) bg_port; //定义端口
`uvm_component_utils(compl) //注册
... //缺少例化
endclass
class comp2 extends uvm_agent;//声明了一个comp2组件继承与uvm_agent
uvm_blocking_get_port #(request) bg_port; //定义端口
uvm_nonblocking_put_imp #(request,comp2) nbp_imp; //comp2作为接收方,还需要指定它所在的 component 类型
`uvm_component_utils(comp2)//注册
function bit try_put (request req); //在target端定义方法,注意try_put,非阻塞function中使用
function bit can_put();
endclass
class comp3 extends uvm_agent;//声明了一个comp3组件继承与uvm_agent
uvm_blocking_transport_port #(request, response) bt_port;//双向端口的定义
`uvm_component_utils(comp3)//注册
...//缺少例化
endclass
class comp4 extends uvm_agent;//声明了一个comp4组件继承与uvm_agent
uvm_blocking_get_imp #(request, comp4) bg_imp; //comp4作为接收方,还需要指定它所在的 component 类型
uvm_nonblocking_put_port #(request) nbp_port; //定义端口
`uvm_component_utils(comp4) //注册
... //缺少例化
task get (output request req); //定义任务,get()为阻塞型,用task
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;//声明了一个agent组件继承与uvm_agent
uvm_blocking_get_port #(request) bg_port;
uvm_nonblocking_put_export #(request) nbp_exp;
uvm_blocking_transport_port #(request, response) bt_port;
compl cl;
comp2 c2;
comp3 c3;
'uvm_component_utils(agentl)
...
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cl = compl::type_id::create("cl", this);
c2 = comp2::type_id::create("c2", this);
c3 = comp3::type_id::create("c3", this);//组件例化在build.phase
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
cl.bg_port.connect(this.bg_port);
c2.bg_port.connect(this.bg_port);
this.nbp_exp.connect(c2.nbp_imp);
c3.bt_port.connect(this.bt_port); //端口连接
endfunction
endclass
class envl extends uvm_env;
agentl al;
comp4 c4;
comps c5;
`uvm_component_utils(envl)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
al = agentl::type_id::create("al", this);
c4 = comp4::type_id::create ("c4", this);
c5 = comp5::type_id::create ("c5", this); //组件例化在build.phase
endfunction:build_phase
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
al.bg_port.connect(c4.bg_imp);
c4.nbp_port. connect(al.nbp_exp);
al.bt_port.connect(c5.bt_imp); //端口连接
endfunction: connect_phase
endclass
从示例中可以得出关于建立TLM通信的常规步骤:
• 定义TLM传输中的数据类型,上面分别定义了request类和response 类。
• 分别在各个层次的component中声明和创建TLM端口对象。
• 通过connect()函数完成端口之间的连接。
• 在imp端口类中要实现需要提供给initiator的可调用方法。例如,在 comp2中由于有一个uvm_nonblocking_put_imp #(request, comp2) nbp_imp, 因此需要实现两个方法try_pu( )和can_put();而comp4中有一个uvm blocking_get_imp #(request, comp4) bg_imp, 则需要实现对应的方法get()。
• 需要注意的是,必须在imp端口类中实现对应方法,否则端口即使连接也无法实现数据传输。