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)的句柄。这意味着传递的是引用而非数据副本,使用时需注意对象共享与修改问题。
tl_fifo 生产者-消费者模型 Producer (put 操作) tl_fifo 缓冲区 T1 T2 T3 T4 T5 used = 5 available = 3 Consumer (get 操作) put(T) get(T) 状态查询

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 状态。因此,不应依赖状态查询来做精确的流程控制,而应使用阻塞操作本身的同步特性。
tl_fifo 状态转换与操作流程 FIFO 空 used=0, get阻塞 FIFO 正常 0 < used < depth FIFO 满 used=depth, put阻塞 put() put() get() get() used() / available() 提供瞬时状态快照

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,可能存在性能瓶颈或死锁风险。