Chapter 10: UVM-SystemC

TLM Communication in UVM

Understand how UVM components communicate transaction objects using Transaction Level Modeling (TLM) ports and exports.

A core philosophy of UVM is that verification components should be modular and highly reusable. If a driver accesses a monitor's variables directly, they become tightly coupled. To enforce loose coupling, UVM relies entirely on Transaction Level Modeling (TLM) for inter-component communication.

UVM-SystemC builds its TLM capabilities directly on top of the IEEE 1666 SystemC TLM standard. The IEEE 1800.2 UVM standard outlines three primary types of TLM interfaces used in verification: unidirectional point-to-point (put/get), bidirectional point-to-point (transport/req-rsp), and broadcast (analysis).

TLM-1.0 Blocking Ports

In UVM, components pass data using uvm_sequence_item or uvm_transaction objects. To send data from one component to another, we use ports and exports.

  • Port (uvm_blocking_put_port): The initiator of the communication. It requires an implementation of a method (like put()) to exist.
  • Export (uvm_blocking_put_export / uvm_port_base): The provider of the communication. It supplies the implementation.

In SystemC, the class supplying the export typically implements the interface methods (e.g. tlm::tlm_blocking_put_if) and binds the export to *this.

Analysis Ports (Broadcast Communication)

Blocking put and get ports are strictly one-to-one point-to-point connections. But what if a Monitor sees a transaction on the bus and needs to send it to a Scoreboard, a Coverage Collector, and a Protocol Checker all at once?

For this, UVM uses Analysis Ports. Analysis ports implement a broadcast (publish/subscribe) mechanism.

  • An Analysis Port can be bound to zero, one, or many Analysis Exports.
  • The transaction is passed by calling write().
  • write() is strictly a void non-blocking function. It must execute in zero time.

UVM Analysis Ports in SystemC

UVM-SystemC provides convenient wrappers around SystemC's tlm_analysis_port:

  • uvm_analysis_port<T>
  • uvm_analysis_export<T>
  • uvm_analysis_imp<T, IMP>

On the receiving end, you typically use a uvm_subscriber. The uvm_subscriber base class automatically provides an analysis_export and requires you to implement the write() function.

Sequencer-Driver Communication

The most specialized TLM connection in UVM is the communication between a uvm_sequencer and a uvm_driver.

UVM defines a dedicated request-response channel (uvm_tlm_req_rsp_channel) and specialized ports (uvm_seq_item_pull_port and uvm_seq_item_pull_export) for this. The driver acts as the active "puller", requesting items from the sequencer using methods like get_next_item() and item_done().

Complete TLM Example

Below is a complete, fully compilable sc_main example demonstrating a one-to-one point-to-point connection and a broadcast analysis connection.

#include <systemc>
#include <uvm>
 
// The data object exchanged between components
class my_transaction : public uvm::uvm_transaction {
public:
    int data;
    UVM_OBJECT_UTILS(my_transaction);
    my_transaction(const std::string& name = "my_transaction") : uvm::uvm_transaction(name), data(0) {}
};
 
// 1. Producer: Initiates a transaction via a blocking put port
class producer : public uvm::uvm_component {
public:
    UVM_COMPONENT_UTILS(producer);
 
    uvm::uvm_blocking_put_port<my_transaction> put_port;
 
    producer(uvm::uvm_component_name name) 
        : uvm::uvm_component(name), put_port("put_port") {}
 
    void run_phase(uvm::uvm_phase& phase) override {
        phase.raise_objection(this);
        my_transaction tx;
        tx.data = 42;
        
        UVM_INFO("PROD", "Sending transaction data: 42", uvm::UVM_LOW);
        
        // This is a blocking call; it will wait until the consumer finishes processing
        put_port.put(tx); 
        
        phase.drop_objection(this);
    }
};
 
// 2. Consumer/Monitor: Receives point-to-point and Broadcasts via Analysis Port
class consumer : public uvm::uvm_component, 
                 public tlm::tlm_blocking_put_if<my_transaction> {
public:
    UVM_COMPONENT_UTILS(consumer);
 
    // TLM-1 export for receiving point-to-point data
    sc_core::sc_export<tlm::tlm_blocking_put_if<my_transaction>> put_export;
    
    // Analysis port for broadcasting data
    uvm::uvm_analysis_port<my_transaction> ap;
 
    consumer(uvm::uvm_component_name name) 
        : uvm::uvm_component(name), put_export("put_export"), ap("ap") {
        
        // Bind the export to 'this' component
        put_export.bind(*this);
    }
 
    // Implementation of the blocking put method
    void put(const my_transaction& tx) override {
        UVM_INFO("CONS", "Received transaction, consuming time...", uvm::UVM_LOW);
        sc_core::wait(10, sc_core::SC_NS); // Consume simulation time
        
        UVM_INFO("CONS", "Broadcasting transaction to subscribers", uvm::UVM_LOW);
        // Non-blocking broadcast
        ap.write(tx);
    }
};
 
// 3. Subscriber (Scoreboard): Listens to Analysis Broadcasts
class scoreboard : public uvm::uvm_subscriber<my_transaction> {
public:
    UVM_COMPONENT_UTILS(scoreboard);
 
    scoreboard(uvm::uvm_component_name name) : uvm::uvm_subscriber<my_transaction>(name) {}
 
    // Implement the write() function mandated by uvm_subscriber
    void write(const my_transaction& t) override {
        UVM_INFO("SB", "Scoreboard intercepted broadcast transaction!", uvm::UVM_LOW);
        if (t.data == 42) {
            UVM_INFO("SB", "Data matches expected value.", uvm::UVM_LOW);
        }
    }
};
 
// 4. Environment: Connects them all
class my_env : public uvm::uvm_env {
public:
    producer* prod;
    consumer* cons;
    scoreboard* sb;
    
    UVM_COMPONENT_UTILS(my_env);
 
    my_env(uvm::uvm_component_name name) : uvm::uvm_env(name) {}
 
    void build_phase(uvm::uvm_phase& phase) override {
        uvm::uvm_env::build_phase(phase);
        prod = producer::type_id::create("prod", this);
        cons = consumer::type_id::create("cons", this);
        sb   = scoreboard::type_id::create("sb", this);
    }
    
    void connect_phase(uvm::uvm_phase& phase) override {
        // Point-to-point: Port binds to Export
        prod->put_port.connect(cons->put_export);
        
        // Broadcast: Analysis Port binds to Analysis Export (provided by uvm_subscriber)
        cons->ap.connect(sb->analysis_export);
    }
};
 
class my_test : public uvm::uvm_test {
public:
    my_env* env;
    UVM_COMPONENT_UTILS(my_test);
 
    my_test(uvm::uvm_component_name name) : uvm::uvm_test(name) {}
 
    void build_phase(uvm::uvm_phase& phase) override {
        uvm::uvm_test::build_phase(phase);
        env = my_env::type_id::create("env", this);
    }
};
 
int sc_main(int argc, char* argv[]) {
    uvm::run_test("my_test");
    return 0;
}

By heavily leveraging TLM, UVM components remain oblivious to what they are connected to, guaranteeing that your VIP (Verification Intellectual Property) can be reused seamlessly across different projects.

Comments and Corrections