TLM 2.0 introduced socket which enables asynchronous bi-directional data transfer between the initiator and target component. A socket is derived from the same base class as ports and export - uvm_port_base
. Components that initiate transactions have initiator sockets and are called initiators, while components that receive transactions have target sockets and are called targets. Note that initiator sockets can be connected only to target sockets and target sockets only to initiator sockets.
TestBench

Let's take a look at the initiator component to see how a socket is declared and used. The timing annotation argument used in b_transport()
method allows the timing points to be offset from simulation times at which the task is called and returned.
class initiator extends uvm_component;
`uvm_component_utils (initiator)
// Declare a blocking transport socket (using initiator socket class)
uvm_tlm_b_initiator_socket #(simple_packet) initSocket;
uvm_tlm_time delay;
simple_packet pkt;
function new (string name = "initiator", uvm_component parent= null);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
// Create an instance of the socket
initSocket = new ("initSocket", this);
delay = new ();
endfunction
virtual task run_phase (uvm_phase phase);
// Let us generate 5 packets and send it via socket
repeat (5) begin
pkt = simple_packet::type_id::create ("pkt");
assert(pkt.randomize ());
`uvm_info ("INIT", "Packet sent to target", UVM_LOW)
pkt.print (uvm_default_line_printer);
// Use the socket to send data
initSocket.b_transport (pkt, delay);
end
endtask
endclass
Consider the target socket and you'll see that it is very similar to the ports and exports scheme we saw in previous sessions.
class target extends uvm_component;
`uvm_component_utils (target)
// Declare a blocking target socket
uvm_tlm_b_target_socket #(target, simple_packet) targetSocket;
function new (string name = "target", uvm_component parent = null);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
// Create an instance of the target socket
targetSocket = new ("targetSocket", this);
endfunction
// Provide the implementation method of b_transport in the target class
task b_transport (simple_packet pkt, uvm_tlm_time delay);
`uvm_info ("TGT", "Packet received from Initiator", UVM_MEDIUM)
pkt.print (uvm_default_line_printer);
endtask
endclass
The missing link is the connection between the two sockets, and the best place to do that is in the environment where both initiator and target components are instantiated.
class my_env extends uvm_env;
`uvm_component_utils (my_env)
initiator init;
target tgt;
function new (string name = "my_env", uvm_component parent = null);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
// Create an object of both components
init = initiator::type_id::create ("init", this);
tgt = target::type_id::create ("tgt", this);
endfunction
// Connect both sockets in the connect_phase
virtual function void connect_phase (uvm_phase phase);
init.initSocket.connect (tgt.targetSocket);
endfunction
endclass