TLM Basics & Handshakes

Transaction Level Modeling (TLM) allows components to communicate at a high abstraction level. Understanding the put, get, and analysis ports is key.

TLM 1.0: The Architectural Glue

What is TLM?

The Simple Version: TLM (Transaction Level Modeling) is how UVM components talk to each other. Instead of connecting wires, they call functions.

Think of it like: Phone calls vs. postal mail. TLM is like calling someone directly (fast, direct communication) instead of sending letters through the mail system.

In UVM, components don't connected with wires like hardware does. They talk to each other using Transaction Level Modeling (TLM). Think of it as sending messages: component A calls a function on component B to pass data (like "Here is a packet" or "Give me data"). This keeps things clean because component A doesn't need to know how component B works inside—it just sends the message.

New to these terms?
  • Port: The component requesting something (like "Put this apple in the box").
  • Imp (Implementation): The component that actually does the work (the box that receives the apple).
  • Blocking: Waiting for the action to finish before moving on (like waiting for a webpage to load).

The Three Pillars of TLM

  • Port: The "Requester". It defines the interface (API) but lacks implementation.
  • Export: The "Middleman". It forwards requests to an implementation.
  • Imp (Implementation): The "Worker". It contains the actual code for the method call.

The Connectivity Triangle

A successful TLM connection must follow a strict hierarchy. A Port must eventually be bound to an Imp.

Rigid Connection Rules:

  • Port to Port: Child to Parent (moving up the hierarchy).
  • Export to Export: Parent to Child (moving down the hierarchy).
  • Port to Export: Peer to Peer (at the same level).
  • Export to Imp: The final binding to the workforce.
Example: Connecting Driver to Sequencer

// Inside the Agent's connect_phase
virtual function void connect_phase(uvm_phase phase);
    // Port (Driver) connects to Export (Sequencer)
    driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
                            

Analysis Ports: Broadcasting to Multiple Subscribers

While Put/Get ports are 1-to-1, Analysis Ports allow a single component (like a Monitor) to broadcast a transaction to an unlimited number of subscribers (Scoreboard, Coverage Collector, Loggers).

The "Void" Rule

Analysis write() calls are always void functions. They cannot consume simulation time. This ensures that the monitor never waits for the scoreboard to finish its math—it just drops the packet and keeps sampling.

The Three Analysis Components

1. uvm_analysis_port (The Broadcaster)

This is what the Monitor uses to send transactions. It can connect to multiple subscribers.

Monitor with Analysis Port

// Inside the Monitor
class my_monitor extends uvm_monitor;
    uvm_analysis_port #(my_pkt) mon_ap;
    function new(string name, uvm_component parent);
        super.new(name, parent);
        mon_ap = new("mon_ap", this);
    endfunction
    task run_phase(uvm_phase phase);
        forever begin
            sample_bus(pkt);
            mon_ap.write(pkt); // Broadcast to all subscribers
        end
    endtask
endclass
                            

2. uvm_analysis_imp (The Subscriber)

This is what the Scoreboard or Coverage Collector uses to receive transactions. It implements the write() function.

Scoreboard with Analysis Imp

class my_scoreboard extends uvm_scoreboard;
    // Analysis implementation port
    uvm_analysis_imp #(my_pkt, my_scoreboard) mon_export;
    function new(string name, uvm_component parent);
        super.new(name, parent);
        mon_export = new("mon_export", this);
    endfunction
    // This function is called when the monitor writes
    function void write(my_pkt pkt);
        // Check the packet
        if (pkt.data != expected_data)
            `uvm_error("SCB", "Mismatch detected!")
    endfunction
endclass
                            

3. uvm_analysis_export (The Pass-Through)

This is used for hierarchical connections. It allows a parent component (like an Environment) to expose a child's analysis port to the outside world.

Environment with Analysis Export

class my_env extends uvm_env;
    my_agent agent;
    uvm_analysis_export #(my_pkt) mon_export;
    function new(string name, uvm_component parent);
        super.new(name, parent);
        mon_export = new("mon_export", this);
    endfunction
    function void connect_phase(uvm_phase phase);
        // Pass through: env's export connects to agent's monitor port
        mon_export.connect(agent.monitor.mon_ap);
    endfunction
endclass
                            

Multiple Subscribers Example

The real power of analysis ports is broadcasting to multiple components simultaneously.

One Monitor, Three Subscribers

class my_env extends uvm_env;
    my_agent agent;
    my_scoreboard sb;
    my_coverage_collector cov;
    my_logger logger;
    function void connect_phase(uvm_phase phase);
        // Connect monitor to scoreboard
        agent.monitor.mon_ap.connect(sb.mon_export);
        // Connect monitor to coverage collector
        agent.monitor.mon_ap.connect(cov.mon_export);
        // Connect monitor to logger
        agent.monitor.mon_ap.connect(logger.mon_export);
        // Now when the monitor calls mon_ap.write(pkt),
        // all three components receive the packet!
    endfunction
endclass
                            

Using uvm_tlm_analysis_fifo for Decoupling

Sometimes you want to decouple the monitor from the scoreboard. The uvm_tlm_analysis_fifo acts as a buffer between them.

Buffered Analysis Connection

class my_env extends uvm_env;
    my_agent agent;
    my_scoreboard sb;
    uvm_tlm_analysis_fifo #(my_pkt) fifo;
    function void build_phase(uvm_phase phase);
        agent = my_agent::type_id::create("agent", this);
        sb = my_scoreboard::type_id::create("sb", this);
        fifo = new("fifo", this);
    endfunction
    function void connect_phase(uvm_phase phase);
        // Monitor writes to FIFO
        agent.monitor.mon_ap.connect(fifo.analysis_export);
        // Scoreboard reads from FIFO (blocking get)
        // This happens in the scoreboard's run_phase:
        // fifo.get(pkt);
    endfunction
endclass
                            

When to Use Each

  • uvm_analysis_imp: Direct, immediate processing (e.g., protocol checking, coverage collection)
  • uvm_tlm_analysis_fifo: Buffered, decoupled processing (e.g., scoreboarding with complex comparisons)
  • uvm_analysis_export: Hierarchical pass-through (exposing child ports to parent level)

TLM FIFOs: The Buffer

When two components run in different threads (like a Monitor and a Scoreboard), you often need a uvm_tlm_analysis_fifo between them. This component stores the broadcast packets until the consumer is ready to get() them.

Architectural Decision

Use an Imp if you want immediate reaction to data (e.g., immediate protocol checking). Use a FIFO if you want to decouple the speeds of different components (e.g., Scoreboarding).