UVM TLM 1.0:验证组件间通信的基石
1. 概述
在UVM验证方法学中,TLM(Transaction Level Modeling 事务级建模)是组件间通信的核心机制。TLM 1.0是起源于SystemC的一种通信方式,为验证工程师提供了一套标准化的接口,用于组件之间的数据传输和同步。本文将探讨TLM 1.0的核心概念、使用方法和实际应用。
什么是TLM?
TLM是一种基于事务的通信机制,与信号级的通信相比,它具有以下优势:
- 抽象层次更高:不关心具体的信号时序
- 仿真速度更快:减少不必要的事件触发
- 可重用性更好:组件接口标准化
- 调试更简单:事务级别的调试信息
2. TLM 1.0 核心概念
2.1 常用的操作方式
a) put操作
通信的发起者A把一个Transaction发送给B,在这个过程中,A成为"发起者",B成为"接收者"。A具有的端口称为PORT,B的端口称为EXPORT。此时数据流时从A到B。 
b) get操作
A向B请求一个Transaction。在这个过程中,A依然是"发起者",B依然是"接收者",A上的端口依然是PORT,B上的端口依然是EXPORT。在这个过程中,数据流时从B流向A的。PORT和EXPORT体现的是控制流而不是数据流。 
c) transport操作
Transport操作相当于一次PUT操作加一次GET操作,这两次操作A都是"发起者",B都是"接受者"。A上的端口依然是PORT,B的端口依然是EXPORT。在这个过程中,数据流先从A流向B,在从B流向A。相当于A向B提交了一个请求,而B返回给A一个应答。 
2.2 端口类型
TLM 1.0定义了三种主要的端口类型:
a) 端口(Port)
- 发起通信的组件
- 调用TLM方法
b) 导出(Export)
- 中间连接组件
- 传递TLM方法调用
c) 实现(Implementation)
- 最终处理通信的组件
- 实现TLM方法的具体功能
3. 通信接口类型
3.1 常用的port
systemverilog
// 阻塞接口
uvm_blocking_put_port #(T)
uvm_blocking_get_port #(T)
uvm_blocking_peek_port #(T)
uvm_blocking_get_peek_port #(T)
uvm_blocking_transport_port #(REQ,RSP)
// 非阻塞接口
uvm_nonblocking_put_port #(T)
uvm_nonblocking_get_port #(T)
uvm_nonblocking_peek_port #(T)
uvm_nonblocking_get_peek_port #(T)
uvm_nonblocking_transport_port #(REQ,RSP)
// 组合接口
uvm_put_port #(T)
uvm_get_port #(T)
uvm_peek_port #(T)
uvm_get_peek_port #(T)
uvm_transport_port #(REQ,RSP)
其中参数T为要传输的事务类型,参数REQ为请求事务类型,参数RSP为响应事务类型。 阻塞端口操作发起后,会等待直到操作完成(像打电话,不挂断就一直等着对方回应) 非阻塞端口操作发起后,会立即返回(像发短信,发完就继续干别的,不等回复)
3.2 常用的export
与port类似,export有如下接口:
systemverilog
// 阻塞接口
uvm_blocking_put_export #(T)
uvm_blocking_get_export #(T)
uvm_blocking_peek_export #(T)
uvm_blocking_get_peek_export #(T)
uvm_blocking_transport_export #(REQ,RSP)
// 非阻塞接口
uvm_nonblocking_put_export #(T)
uvm_nonblocking_get_export #(T)
uvm_nonblocking_peek_export #(T)
uvm_nonblocking_get_peek_export #(T)
uvm_nonblocking_transport_export #(REQ,RSP)
// 组合接口
uvm_put_export #(T)
uvm_get_export #(T)
uvm_peek_export #(T)
uvm_get_peek_export #(T)
uvm_transport_export #(REQ,RSP)
3.3 常用的imp
imp有如下接口:
systemverilog
// 阻塞接口
uvm_blocking_put_imp #(T,IMP)
uvm_blocking_get_imp #(T,IMP)
uvm_blocking_peek_imp #(T,IMP)
uvm_blocking_get_peek_imp #(T,IMP)
uvm_blocking_transport_imp #(REQ,RSP,IMP)
// 非阻塞接口
uvm_nonblocking_put_imp #(T,IMP)
uvm_nonblocking_get_imp #(T,IMP)
uvm_nonblocking_peek_imp #(T,IMP)
uvm_nonblocking_get_peek_imp #(T,IMP)
uvm_nonblocking_transport_imp #(REQ,RSP,IMP)
// 组合接口
uvm_put_imp #(T,IMP)
uvm_get_imp #(T,IMP)
uvm_peek_imp #(T,IMP)
uvm_get_peek_imp #(T,IMP)
uvm_transport_imp #(REQ,RSP,IMP)
其中参数T、REQ、RSP与port、export定义想通,参数IMP为实现接口的Component。 3种接口的优先级:port->export->imp,高优先级可以连接到低优先级,但是低优先级不能连接到高优先级;同等级的也可以嵌套连接,例如port->port,export也可省略,但是目的端一定为imp。
实际传输时,在port中调用对应的方法,在imp中实现相应的方法,不同端口对应方法如下:
| 端口类型 | 方法 |
|---|---|
| 阻塞端口 | |
| uvm_blocking_put_port | put(t) |
| uvm_blocking_get_port | get(t) |
| uvm_blocking_peek_port | peek(t) |
| uvm_blocking_get_peek_port | get(t),peek(t) |
| uvm_blocking_transport_port | transport(req,rsp) |
| 非阻塞端口 | |
| uvm_nonblocking_put_port | try_put(t),can_put(t) |
| uvm_nonblocking_get_port | try_get(t),can_get(t) |
| uvm_nonblocking_peek_port | try_peek(t),can_peek(t) |
| uvm_nonblocking_get_peek_port | try_get(t),can_get(t),try_peek(t),can_peek(t) |
| uvm_nonblocking_transport_port | nb_transport(req,rsp) |
peek操作只查看数据,不消耗数据,后续可以继续操作,get_peek结合了get和peek的特性 try_*方法,尝试执行操作,成功返回1,失败返回0,can_*方法,查询是否可操作,可操作返回1,否则返回0
4. TLM1.0使用步骤
4.1 端口声明
声明方式如下:
systemverilog
//port声明
class A extends uvm_component;
`uvm_component_utils(A)
uvm_blocking_put_port#(my_transaction) a_port;
function new (string name = "A", uvm_component parent = null);
super.new(name, parent);
endfunction
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
endclass
//export和imp声明
class B extends uvm_component;
`uvm_component_utils(B)
C C_inst;
uvm_blocking_put_export#(my_transaction) b_export;
function new (string name = "B", uvm_component parent = null);
super.new(name, parent);
endfunction
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
endclass
class C extends uvm_component;
`uvm_component_utils(C)
uvm_blocking_put_export#(my_transaction) c_export;
uvm_blocking_put_export#(my_transaction,C) c_imp;
function new (string name = "C", uvm_component parent = null);
super.new(name, parent);
endfunction
extern function void build_phase(uvm_phase phase);
extern task main_phase(uvm_phase phase);
endclass
4.2 创建实例
创建实例方式如下:
systemverilog
//port实例
function void A::build_phase(uvm_phase phase);
super.build_phase(phase);
a_port = new("a_port",this);
endfunction
//export实例
function void B::build_phase(uvm_phase phase);
super.build_phase(phase);
b_export = new("b_export",this);
C_inst = B::type_id::create("C_inst",this);
endfunction
//export和imp实例
function void C::build_phase(uvm_phase phase);
super.build_phase(phase);
c_export = new("c_export",this);
c_imp = new("c_imp",this);
endfunction
4.3 实现方法
如果实现的连接关系a_port->b_export->c_export->c_imp,则在类C中实现方法:
systemverilog
function void C::put(my_transaction tr);
`uvm_info("C","put a transaction",UVM_LOW)
tr.print();
endfunction
4.4 连接端口
如果连接关系如下图,需要再my_env中连接port->export,在Class B 中连接b_export->c_export。 
systemverilog
function void my_env::connect_phase(uvm_phase phase)
super.connect_phase(phase);
A_inst.a_port.connect(B_inst.b_export);
endfunction
function void B:connect_phase(uvm_phase phase);
super.connect_phase(phase);
this.b_export.connect(C_inst.c_export)
endfunction
5. TLM1.0常见的应用场景
5.1 analysis端口介绍
Analysis端口是UVM TLM通信机制中的一种特殊类型。与传统的阻塞或非阻塞端口不同,analysis端口具有独特的广播特性,允许单个生产者向多个消费者同时发送数据,而无需知道消费者的具体身份和数量。
- 单向广播:一个analysis端口可以连接多个接收端
- 非阻塞:写入操作立即返回,不等待处理
- 无返回值:不返回任何状态或数据
- 常用于:监测数据分发、覆盖率收集、记分板更新
- 接收方通过实现 write() 方法接收数据
5.2 monitor与reference model/scoreboard的传输
在UVM验证环境中,Monitor负责监视DUT的接口信号并将其转换为事务级数据,然后通过TLM端口将这些数据传输给其他验证组件。这种传输机制是验证环境数据流的核心环节。
Monitor到Reference Model的数据流 Monitor捕获DUT接口上的事务后,通过analysis端口将事务广播给Reference Model。Reference Model接收到这些激励事务后,会根据设计规范计算出预期的响应结果。这个过程模拟了理想情况下DUT应该具有的行为,为后续的验证比较提供基准。
systemverilog
class my_monitor extends uvm_monitor;
uvm_analysis_port #(my_transaction) mon_analysis_port;
virtual task run_phase(uvm_phase phase);
forever begin
my_transaction tx;
// 捕获DUT事务
tx = capture_transaction();
// 广播到所有连接的组件
mon_analysis_port.write(tx);
end
endtask
endclass
class reference_model extends uvm_component;
uvm_analysis_imp #(my_transaction, reference_model) analysis_imp;
// 必须实现write方法
virtual function void write(my_transaction tx);
// 根据输入事务计算预期输出
my_transaction expected = predict_output(tx);
// 将预期结果发送到scoreboard
result_analysis_port.write(expected);
endfunction
endclass
Monitor到Scoreboard的数据流 Monitor同时也会将捕获的实际事务发送给Scoreboard。Scoreboard作为验证环境的核心检查组件,负责比较实际结果与预期结果。通常,Scoreboard会实现两个analysis imp端口:一个用于接收Monitor发送的实际事务,另一个用于接收Reference Model发送的预期事务。通过对比这两组数据,Scoreboard可以自动判断DUT的功能是否正确。
systemverilog
class scoreboard extends uvm_component;
uvm_analysis_imp #(my_transaction, scoreboard) actual_imp;
uvm_analysis_imp #(my_transaction, scoreboard) expected_imp;
// 存储实际和预期事务的队列
my_transaction actual_queue[$];
my_transaction expected_queue[$];
virtual function void write(my_transaction tx);
// 根据端口区分实际和预期数据
if (this.actual_imp != null)
actual_queue.push_back(tx);
else
expected_queue.push_back(tx);
// 进行比较检查
check_match();
endfunction
endclass
scoreboard需要接收两路数据,一路来自monitor,一路来自reference model,对于一个imp而言,必须在其实例化的uvm_component中定义一个write函数,现在scoreboard接收两路数据,但是write函数只有一个,通过使用uvm_analysis_imp_decl来解决这个问题
systemverilog
`uvm_analysis_imp_decl(_monitor)
`uvm_analysis_imp_decl(_model)
class my_scoreboard extends uvm_scoreboard;
my_transaction expect_queue[$];
uvm_analysis_imp_monitor#(my_transaction, my_scoreboard) monitor_imp;
uvm_analysis_imp_model#(my_transaction,my_scoreboard) model_imp;
extern function void write_monitor(my_transaction tr);
extern function void write_model(my_transaction tr);
extern virtual task main_phase(uvm_phase phase);
endclass
数据同步机制 由于实际事务和预期事务可能在不同的时间到达Scoreboard,通常需要设计巧妙的同步机制。常见的做法包括使用事务ID进行匹配、基于时间戳的对比,或者使用TLM FIFO来缓冲数据以确保比较的准确性。
5.3 sequencer与driver的传输
Sequencer和Driver之间的TLM通信是UVM验证平台中sequence机制的核心,这种通信模式实现了测试用例与DUT驱动逻辑的分离,大大提高了测试的可重用性和灵活性。 sequencer与Driver的传输需具有以下特性:
- 阻塞获取:Driver等待Sequencer提供序列项
- 完成确认:Driver必须告知Sequencer事务已完成
- 响应机制:可选地返回响应数据
- 序列控制:支持序列的暂停、停止等控制
无法使用简单的get()或put()实现,因此设计了专用的通信端口seq_item_port,其包含的方法:get_next_item(),item_done(),put_response()。 sequencer与Driver的通过seq_item_port传输,可通过以下步骤实现
- 声明,定义Driver组件时已经从父类继承,可以直接使用
systemverilog
// 在 uvm_driver 中自动拥有:
// uvm_seq_item_pull_port #(REQ, RSP) seq_item_port
class my_driver extends uvm_driver #(my_transaction);
// 不需要手动声明 seq_item_port
// 它已经从 uvm_driver 基类继承
endclass
- 连接
systemverilog
class my_agent extends uvm_agent;
my_driver driver;
my_sequencer sequencer;
function void connect_phase(uvm_phase phase);
// 连接 driver 的 seq_item_port 到 sequencer 的 seq_item_export
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
endclass
- 实现方法
systemverilog
class my_driver extends uvm_driver #(my_transaction);
virtual task run_phase(uvm_phase phase);
forever begin
// 实现协议方法调用
seq_item_port.get_next_item(req);
// ... 驱动逻辑 ...
seq_item_port.item_done();
end
endtask
endclass
5.4 tlm_fifo的使用场景
TLM FIFO是UVM提供的一种特殊组件,它在TLM端口之间充当缓冲区,主要用于解决验证组件间速度不匹配、实现数据暂存和提供额外的同步点。
systemverilog
class my_env extends uvm_env;
my_monitor monitor;
scoreboard sb;
uvm_tlm_fifo #(my_transaction) tlm_fifo;
virtual function void build_phase(uvm_phase phase);
monitor = my_monitor::type_id::create("monitor", this);
sb = scoreboard::type_id::create("sb", this);
tlm_fifo = new("tlm_fifo", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
// Monitor快速写入FIFO
monitor.mon_analysis_port.connect(tlm_fifo.analysis_export);
// Scoreboard按自己的速度从FIFO读取
tlm_fifo.blocking_get_port.connect(sb.get_export);
endfunction
endclass
- 作用:解决生产者和消费者速率不匹配的问题
- 特点:提供数据缓冲功能,自动处理读写同步,支持多种访问方式
- 应用场景:Monitor 采集数据快于 Scoreboard 处理速度时,不同时钟域组件间的数据传递,需要数据排队处理的场景
6 总结
TLM 1.0作为UVM验证环境中的通信骨干,提供了灵活而强大的组件间交互机制。通过合理使用不同类型的TLM接口,可以构建出高效、可重用的验证组件。掌握TLM 1.0不仅有助于理解UVM的通信机制,也是构建复杂验证环境的基础技能。 在实际项目中,建议根据具体的通信需求选择合适的TLM接口,并遵循统一的连接规范,这样才能构建出稳定可靠的验证环境。