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


tlm-socket

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