SV -> SC -> SV loopback example with dual ports

Summary
SV -> SC -> SV loopback example with dual ports
Example of dual TLM target portsThis example is a slight modification of the single port SV -> SC -> SV exmaple and the handling of configuration extensions is identical so no need to repeat that here.
class producer TLM port containersThe two port container classes below instantiate TLM target blocking transport sockets (class uvm_tlm_b_target_socket).
class producer - SV initiator and targetThe class producer is a uvm_component that defines both initiator and target TLM-2 sockets since this is a loopback and the same producer plays the role of both initiator and eventual target.

Example of dual TLM target ports

This example is a slight modification of the single port SV -> SC -> SV exmaple and the handling of configuration extensions is identical so no need to repeat that here.

However, what this example does demonstrate is the case of having dual TLM initiator + target socket couplings for a single parent UVM component.  One is for WRITE operations and the other is for READ operations.  This might be desirable in the case where a modeler would want to have a dedicated ::b_transport() function for each type of operation, especially if time consuming WRITE and READ ops might overlap.

In SystemVerilog UVM, there is a dilema of how a TLM target model that is some derivation of uvm_component can define 2 TLM target sockets.  The reason is that these sockets make the assumption that their parent class is expected to define the required target ::b_transport() method implementation which is always assumed to have that fixed name.

In SystemC we can use convenience sockets for this where each convenience socket allows you to register the separately named transport callbacks for each target socket.

But, as mentioned UVM TLM does not allow this.  So what this example demonstrates is to have 2 uvm_component derived child members of the parent class producer and each of those child components define a single target UVM TLM blocking transport socket (class uvm_tlm_b_target_socket).  One of the containers handles WRITE operations and the other handles READ operations.

The diagram below illustrates the configuration showing two child uvm_component’s acting as TLM port containers inside the parent SV producer which is also a uvm_component.

class producer TLM port containers

The two port container classes below instantiate TLM target blocking transport sockets (class uvm_tlm_b_target_socket).

The first one, class producer_write_port_container handles WRITE operations exclusively.

The second one, class producer_read_port_container handles READ operations exclusively.

Both do so by calling the ::b_transport_write() and ::b_transport_read() methods of their parent class respectively.

typedef class producer;

class producer_write_port_container extends uvm_component; // {

    `uvm_component_utils(producer_write_port_container)

    // All ports default to TLM GP as transaction kind.
    uvm_tlm_b_target_socket #( producer_write_port_container ) in;

    producer parent = producer'(get_parent());

    function new(string name, uvm_component parent=null);
        super.new(name,parent);
        in = new("in", this);
    endfunction

    virtual task b_transport( uvm_tlm_gp t, uvm_tlm_time delay );
        parent.b_transport_write( t, delay );
    endtask
endclass // }

class producer_read_port_container extends uvm_component; // {

    `uvm_component_utils(producer_read_port_container)

    // All ports default to TLM GP as transaction kind.
    uvm_tlm_b_target_socket #( producer_read_port_container ) in;

    producer parent = producer'(get_parent());

    function new(string name, uvm_component parent=null);
        super.new(name,parent);
        in = new("in", this);
    endfunction

    virtual task b_transport( uvm_tlm_gp t, uvm_tlm_time delay );
        parent.b_transport_read( t, delay );
    endtask
endclass // }

class producer - SV initiator and target

The class producer is a uvm_component that defines both initiator and target TLM-2 sockets since this is a loopback and the same producer plays the role of both initiator and eventual target.

However in this example we want to have separate target ports for WRITE operations and READ operations.

Notice in this case however, that rather than directly instantiating two UVM TLM target sockets, rather, the parent class producer instantiates the containers for those sockets as described above.

class producer extends uvm_component; // {

    `uvm_component_utils(producer)

    // All ports default to TLM GP as transaction kind.
    uvm_tlm_b_initiator_socket  #()           out;        // "mainstream" port
    uvm_tlm_nb_initiator_socket #( producer ) out_config; // static config port
    uvm_tlm_nb_target_socket    #( producer ) in_config;
    producer_write_port_container in_write;
    producer_read_port_container in_read;