3. TLM FIFO:验证平台中的核心数据通道
TLM FIFO 是 UVM 验证平台中最基础、最常用的数据通信原语。它本质上是一个具有线程安全特性的先进先出缓冲区,用于在验证组件之间传递事务级数据对象。理解 tl_fifo 的原理与操作,是掌握 TLM 适配器设计与调试的前提。
3.1 tl_fifo 原理
tl_fifo 在 UVM 中对应 uvm_tlm_fifo 类,其核心设计遵循以下原则:
- 生产者-消费者模型:一个或多个 producer 通过
put写入数据,一个或多个 consumer 通过get读取数据。 - 阻塞与非阻塞操作:默认提供阻塞操作(等待直到成功),同时支持非阻塞的
try_put/try_get。 - 有界与无界模式:可配置 FIFO 深度,当深度为 0 时表示无界(动态增长),否则为有界 FIFO。
- 线程安全:内部使用 UVM 的
uvm_event和semaphore机制,确保多线程并发访问时数据一致。
关键概念:tl_fifo 不关心数据内容,只负责传递事务对象(transaction object)的句柄。这意味着传递的是引用而非数据副本,使用时需注意对象共享与修改问题。
3.2 tl_fifo 的 put/get 操作
put 和 get 是 tl_fifo 最核心的两个操作,它们分别对应数据的写入与读取。下表总结了不同操作模式的行为差异:
| 操作 | 阻塞模式 | 非阻塞模式 | 超时模式 |
|---|---|---|---|
| put | FIFO 满时挂起,直到有空位 | try_put:满时立即返回 0 | put(tr, timeout):超时后返回 0 |
| get | FIFO 空时挂起,直到有数据 | try_get:空时立即返回 0 | get(tr, timeout):超时后返回 0 |
| peek | 阻塞查看但不移除数据 | try_peek:空时立即返回 0 | peek(tr, timeout) |
实战建议:在验证平台中,
put 和 get 的阻塞特性天然适用于同步握手场景。例如,driver 从 sequencer 获取 sequence item 时使用 get,monitor 向 scoreboard 发送数据时使用 put。
以下代码展示了 tl_fifo 的基本 put/get 操作:
// 创建 FIFO 实例,深度为 8
uvm_tlm_fifo #(my_transaction) fifo;
fifo = new("fifo", this, 8);
// Producer 线程:put 操作
fork
begin
my_transaction tr;
for (int i = 0; i < 10; i++) begin
tr = my_transaction::type_id::create($sformatf("tr_%0d", i));
tr.data = i;
fifo.put(tr); // 阻塞写入
`uvm_info("PRODUCER", $sformatf("Put item %0d", i), UVM_MEDIUM)
end
end
// Consumer 线程:get 操作
begin
my_transaction tr;
for (int i = 0; i < 10; i++) begin
fifo.get(tr); // 阻塞读取
`uvm_info("CONSUMER", $sformatf("Got item %0d, data=%0d", i, tr.data), UVM_MEDIUM)
end
end
join
3.3 tl_fifo 的 used/available 状态
tl_fifo 提供了两个关键状态查询方法,用于监控 FIFO 的填充情况:
used():返回当前 FIFO 中已存储的事务数量。available():返回 FIFO 中剩余可存储的事务数量(仅对有界 FIFO 有效)。
这两个状态量满足关系:used() + available() == depth(有界 FIFO)。对于无界 FIFO,available() 返回 -1。
| 状态 | 含义 | 典型应用场景 |
|---|---|---|
used() == 0 |
FIFO 为空 | get 操作会阻塞,可用于检测数据流是否结束 |
used() == depth |
FIFO 为满 | put 操作会阻塞,可用于反压控制 |
used() > depth/2 |
FIFO 接近满 | 触发预警,降低 producer 速率 |
available() > 0 |
有可用空间 | 非阻塞 put 可成功执行 |
注意:在多线程环境中,
used() 和 available() 的返回值是瞬时快照。在查询状态和执行操作之间,其他线程可能已经改变了 FIFO 状态。因此,不应依赖状态查询来做精确的流程控制,而应使用阻塞操作本身的同步特性。
3.4 tl_fifo 在验证平台中的应用
tl_fifo 在 UVM 验证平台中扮演着数据通道的角色,以下是几个典型应用场景:
| 应用场景 | 连接组件 | 数据流向 | FIFO 深度建议 |
|---|---|---|---|
| Sequence → Driver | sequencer 与 driver | sequence item 下发 | 1(通常为 1,实现 handshake) |
| Monitor → Scoreboard | monitor 与 scoreboard | 采集数据上报 | 8~16(缓冲突发数据) |
| Driver → Coverage | driver 与 coverage collector | 驱动数据反馈 | 4~8(避免覆盖遗漏) |
| Reference Model → Scoreboard | reference model 与 scoreboard | 预期数据输出 | 16~32(匹配 DUT 延迟) |
设计模式:在 TLM 适配器设计中,tl_fifo 常用于桥接不同 TLM 接口。例如,将 blocking_put_port 转换为 nonblocking_get_port 时,中间插入一个 FIFO 可以解耦两端的时序要求。
以下代码展示了 tl_fifo 在验证平台中的典型连接方式:
// 在 env 中实例化 FIFO 并连接组件
class my_env extends uvm_env;
uvm_tlm_fifo #(my_transaction) mon2scb_fifo;
my_monitor mon;
my_scoreboard scb;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon2scb_fifo = new("mon2scb_fifo", this, 16);
mon = my_monitor::type_id::create("mon", this);
scb = my_scoreboard::type_id::create("scb", this);
endfunction
function void connect_phase(uvm_phase phase);
// monitor 的 put_port 连接到 FIFO 的 put_export
mon.mon_put_port.connect(mon2scb_fifo.put_export);
// scoreboard 的 get_port 连接到 FIFO 的 get_export
scb.scb_get_port.connect(mon2scb_fifo.get_export);
endfunction
endclass
调试技巧:在仿真过程中,可以通过
fifo.used() 实时监控 FIFO 深度。如果 used() 持续增长且不下降,说明 consumer 处理速度跟不上 producer,可能存在性能瓶颈或死锁风险。