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 (likeput()) 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 avoidnon-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