Why Learn UVM TLM? A UVM testbench is a set of components that never call each other directly: a monitor does not know which scoreboard listens to it, and a driver does not know which sequencer feeds it. Transaction Level Modeling is the plumbing that makes this possible, and every connection you write in a testbench uses it.

UVM TLM (Transaction Level Modeling) is the set of ports, exports and implementation ports that components use to pass transactions to each other. Instead of wiring signals, a component calls a method such as put(), get() or write() on a port, and the call is routed to whichever component implements that method. Neither side needs to know the other's type, so components can be swapped and reused freely.

8 min read | Intermediate Level

What You'll Learn

  • Explain what ports, exports and imps do and how they connect
  • Distinguish the put, get, peek, transport and analysis interfaces
  • Choose between blocking and nonblocking TLM methods
  • Apply the connection rules across levels of hierarchy

Transactions Instead of Signals

At the pin level, one bus write might involve an address, data, strobes and handshake signals over several clock cycles. At the transaction level, the same write is a single object with an address and a data field. Components above the driver and below the monitor work only with these objects, which makes them faster to simulate and much easier to read and debug.


class simple_packet extends uvm_object;
  `uvm_object_utils(simple_packet)

  rand bit [7:0] addr;
  rand bit [7:0] data;
       bit       rwb;        // 1 = read, 0 = write

  constraint c_addr { addr > 8'h2a; }
  constraint c_data { data inside {[8'h14:8'he9]}; }

  function new(string name = "simple_packet");
    super.new(name);
  endfunction
endclass

A simple_packet is the kind of object that travels through TLM connections. TLM does not care what is inside it; the port and the receiving method only need to agree on the type.

Ports, Exports and Imps

Every TLM connection is made from three kinds of connector, each shown with its usual symbol.

  • Port - placed in the component that initiates the call. The component calls a method on its port, for example m_put_port.put(pkt).
  • Imp (implementation port) - placed in the component that implements the method. The component defines the actual put() task, and the imp routes calls to it.
  • Export - placed in a parent component to forward calls down to an imp in one of its children, so the parent can expose a child's implementation without implementing anything itself.

The port always sits on the side that makes the call, whichever way the data moves. In a put() the data goes from initiator to target. In a get() the initiator still owns the port, but the data comes back from the target.

TLM Interface Types

Connectors are named after the interface they carry, as uvm_<interface>_port, uvm_<interface>_export and uvm_<interface>_imp.

InterfaceMethodsPurpose
putput(), try_put(), can_put()Send a transaction to the target
getget(), try_get(), can_get()Take a transaction from the target, consuming it
peekpeek(), try_peek(), can_peek()Look at the next transaction without consuming it
get_peekget and peek methods combinedBoth in one connector
transporttransport(), nb_transport()Send a request and receive a response in one call
analysiswrite()Broadcast to any number of subscribers

Each of put, get, peek, get_peek and transport comes in three flavors: blocking_ (only the task), nonblocking_ (only the try_/can_ functions) and the plain name, which offers both. For example, uvm_blocking_put_port, uvm_nonblocking_put_port and uvm_put_port.

Blocking vs Nonblocking

A blocking method is a task and may take simulation time. put() returns only once the target has accepted the transaction, and get() returns only once a transaction is available. A nonblocking method is a function that returns immediately with a status bit: try_put() returns 0 if the target cannot accept the transaction right now, and can_put() just asks without sending anything.

Note what a successful put() promises: the transaction has been delivered to the target, not necessarily processed. A target is free to store it and act on it later.

Quick Check

Q: A monitor must report every transaction it sees to a scoreboard and a coverage collector, without ever waiting for either. Which interface fits?

Show Answer

The analysis interface. write() is a function, so it never blocks, and one analysis port can be connected to any number of subscribers. Put and get connections link exactly one initiator to one target.

Connection Rules

Connections are made in connect_phase by calling connect() on the connector closer to the initiator, passing the one closer to the target.


// Initiator and target are siblings: port to imp
compA.m_put_port.connect(compB.m_put_imp);

// Child to parent on the initiator side: port to port
m_subcomp_A.m_put_port.connect(this.m_put_port);

// Parent to child on the target side: export to export, or export to imp
this.m_put_export.connect(m_subcomp_B.m_put_imp);
  • A port can connect to a port (going up the hierarchy), an export or an imp.
  • An export can connect only to another export or to an imp (going down the hierarchy).
  • An imp cannot be connected to anything. It is bound to its implementing component when it is constructed.
  • Both sides must use the same transaction type, and the provider must offer the interface the requester needs. A uvm_blocking_put_port can connect to a uvm_put_imp, because the imp offers blocking put among others, but not the other way round.

However many levels a connection crosses, UVM resolves the chain just before end_of_elaboration_phase. At run time every call goes straight to the implementation in a single hop. Worked examples of each chain are in UVM TLM Port to Export to Imp.

TLM in a Real Testbench

Most testbenches use three TLM patterns. A driver pulls items from its sequencer through a specialized pull port, seq_item_port. A monitor broadcasts captured transactions through an analysis port to scoreboards and coverage collectors. And a uvm_tlm_fifo buffers transactions between two components that run at different rates. Each of these is covered in its own article in this chapter.

UVM also includes TLM-2.0 sockets, based on the SystemC TLM-2.0 standard, for modeling memory-mapped buses with a generic payload. They are mainly used when connecting to SystemC models. See UVM TLM Sockets.

Common Mistakes

Mistake #1: Connecting in the wrong direction

Wrong: compB.m_put_imp.connect(compA.m_put_port);
Right: compA.m_put_port.connect(compB.m_put_imp);

Why? connect() is always called on the side nearer the initiator. An imp cannot be connected at all, so the reversed call is an error.

Mistake #2: Leaving a port unconnected

A put or get port expects exactly one connection. If connect() is never called, UVM reports an error when it resolves the connections at the end of elaboration, before the run starts. An analysis port is the exception: it may have zero subscribers.

Mistake #3: Mixing transaction types

A uvm_blocking_put_port #(packet) cannot connect to a uvm_blocking_put_imp #(other_packet, ...), even if the classes look similar. The type parameters must match exactly, or the connection fails to compile.

Summary

In this article, you learned:

  • TLM passes transaction objects between components through method calls instead of signals
  • A port initiates a call, an imp implements it, and an export forwards it to a child
  • put, get, peek and transport come in blocking, nonblocking and combined flavors, and analysis broadcasts with write()
  • Ports connect up the hierarchy or across, exports connect down, and imps are the end of every chain
  • Connections are resolved before end_of_elaboration_phase, so calls take a single hop at run time
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5