Creating Connections between Components in FPGA

In the context of REDHAWK, a component's implementation is not solely tied to a single process or thread running on a microprocessor. Its main functionality could be targeted for an FPGA. In the case of a single component running on an FPGA, the role of the component is to manage the bitfile load, provide command-and-control information to the embedded device, and perform data ingress and egress to and from embedded hardware. In the case where multiple components are targeted for the same FPGA, the role of the components is to manage their FPGA loads, provide command-and-control information to their respective processing elements, and manage data ingress and egress to the embedded hardware's endpoints. Connections, data ingress, and data egress between components on the FPGA can be managed using a custom transport layer for a REDHAWK BulkIO port. The following image displays multiple components targeted for the same FPGA.

Negotiable Transport Image

BulkIO allows for the addition of transports beyond those included by default. These transports are selected based on a priority, with CORBA's priority set to 99 and shared memory IPC set to 1; in the case of communications between components in the same process, a local transport is selected, which is defined as shared address space. In the following code examples, the first example demonstrates how a new transport is created and applied to links between components running in different processes. The second code example demonstrates how to subclass a BulkIO port and change the local transport to a custom one.

Note: The custom transport is designed to interact with other BulkIO ports. If out-of-band communications as well as traditional microprocessor-based communications are supported in component, then data needs to be transferred both out-of-band and using the stream API in the microprocessor code. This option is impractical, but is necessary if the BulkIO port is meant to support communications with other ports supporting the custom transport as well as traditional microprocessor-based software.

Adding Transports to BulkIO

In this example, a component called transport_out has a single output dataFloat port (called dataFloat_out) and another component called transport_in that has a single input dataFloat port (called dataFloat_in). This example demonstrates how to modify these components such that the data exchange occurs outside the scope of the built-in BulkIO mechanisms, as is required when the BulkIO connection is between software on a GPP and firmware on an FPGA.

The following pattern is implemented in both the out (uses) and in (provides) side: create a transport factory, which in turn is used to create a transport manager, which in turn creates the transport itself. The transport factory is instantiated globally, the transport manager is instantiated per port, and the transport is instantiated per connection. The BulkIO base classes exercise this pattern.

In this pattern, the transport factories register statically with a transport registry. This transport registry contains all the transports that are supported. At transport negotiation time, the transport names are matched in order of their priority, from the lowest number to the highest. In this example, the "custom" transport is given a priority of 0, making it the highest priority transport, guaranteeing that it will be selected if the transport is supported by both ends of the transaction.

Once a matching transport has been selected, an exchange of properties is performed. The output side implements getNegotiationProperties to provide transport information to the input side. The input side receives these properties in its createInputTransport function. The response properties from the input side are retrieved through the input side's getNegotiationProperties function. Once the output side receives the response from the input side, the response is passed to the port through the setNegotiationResult function.

In the following example, the properties included are examples only and do not need to be included in the actual implementation.

Several log statements are included in this code to demonstrate where in the connection process these functions are invoked. These are the locations where device-specific customizations are meant to occur.

Ideally, the transport definition would be developed in a library and linked into the component, like a soft package dependency. However, for the sake of simplicity, in this example, the transport definition is added as source to both components.

Component Edits

In each component, two files need to be modified: the component's user header and cpp files, transport_out.h/transport_out.cpp and transport_in.h/transport_in.cpp for transport_out and transport_in, respectively.

Modify transport_in.h and transport_out.h to include the following class declarations:

     #include <ossie/debug.h>
     #include <ossie/ProvidesPort.h>
     #include <ossie/UsesPort.h>
     #include <BulkioTransport.h>


    // class implementing the custom input transport
    class CustomInputTransport : public bulkio::InputTransport<BULKIO::dataFloat>
    {
    public:

        CustomInputTransport(bulkio::InPort<BULKIO::dataFloat>* port,
                             const std::string& transportId,
                             const redhawk::PropertyMap &usesTransportProps )
          : bulkio::InputTransport<BULKIO::dataFloat>(port, transportId) {

          // save off source end point description
          _usesTransportProps = usesTransportProps;
        };

        virtual ~CustomInputTransport() {
           RH_NL_INFO("custom.transport", "CustomInputTransport::~CustomInputTransport" );
        }

        std::string transportType() const {
             return "custom";
        };

        redhawk::PropertyMap    getNegotiationProperties();

        /**
          Control interface used by port when a connectPort occurs to start the transport after the negotiation has completed.
         */
        void startTransport() {
           RH_NL_INFO("custom.transport", "CustomInputTransport::startTransport");
        };

        /**
          Control interface used by port when a disconnectPort occurs to stop the transport.
         */
        void stopTransport() {
           RH_NL_INFO("custom.transport", "CustomInputTransport::stopTransport");
        };

        protected:
          //  provides transport layer statistics to port
          redhawk::PropertyMap  _getExtendedStatistics() {
              return redhawk::PropertyMap();
          }

        private:
           redhawk::PropertyMap     _usesTransportProps;
    };

    // class implementing the custom output transport
    class CustomOutputTransport : public bulkio::OutputTransport<BULKIO::dataFloat>
    {
    public:

        CustomOutputTransport(bulkio::OutPort<BULKIO::dataFloat>* parent,
                              BULKIO::dataFloat_ptr port,
                              const std::string& connectionId,
                              const redhawk::PropertyMap &props )
          : bulkio::OutputTransport<BULKIO::dataFloat>(parent, port) {
            _connectionId = connectionId;
            _inProps = props;
        };

        virtual ~CustomOutputTransport() {
           RH_NL_INFO("custom.transport", "CustomOutputTransport::~CustomOutputTransport" );
        };

        std::string transportType() const {
            return "custom";
        };

        virtual CF::Properties transportInfo() const {
            redhawk::PropertyMap props;
            props["transport_side_information"] = "outbound";
            props["another_number"] = static_cast<short>(100);
            return props;
        };

        void _pushSRI(const BULKIO::StreamSRI& sri) {};
        void _pushPacket(const BufferType& data, const BULKIO::PrecisionUTCTime& T, bool EOS, const std::string& streamID) {};

        void disconnect() {
            RH_NL_INFO("custom.transport", "CustomOutputTransport::disconnect" );
            // perform disconnection
        };

        redhawk::PropertyMap  getNegotiationProperties();

    protected:
        std::string _connectionId;
        redhawk::PropertyMap  _inProps;
    };

    // manager class that creates input transport layer for a negotiable port
    class CustomInputManager : public bulkio::InputManager<BULKIO::dataFloat>
    {
    public:

        CustomInputManager(bulkio::InPort<BULKIO::dataFloat>* port)
          : bulkio::InputManager<BULKIO::dataFloat>(port) {
        };

        virtual ~CustomInputManager() {
        };

        CustomInputTransport* createInputTransport(const std::string& transportId, const redhawk::PropertyMap& properties) {
            RH_NL_INFO("custom.transport", "CustomInputManager::createInputTransport" );
            for ( redhawk::PropertyMap::const_iterator it = properties.begin(); it != properties.end(); it++) {
                 RH_NL_INFO("custom.transport", "CustomInputManager, key (from uses): "<<it->id );
            }
            return new CustomInputTransport(this->_port, transportId, properties);
        };

        std::string transportType(){
            return "custom";
        };

        redhawk::PropertyMap getNegotiationProperties(redhawk::ProvidesTransport* providesTransport);

    };

    // manager class that creates output transport layer for a negotiable port
    class CustomOutputManager : public bulkio::OutputManager<BULKIO::dataFloat>
    {
    public:

        CustomOutputManager(bulkio::OutPort<BULKIO::dataFloat>* port)
          : bulkio::OutputManager<BULKIO::dataFloat>(port) {};

        virtual ~CustomOutputManager() {};

        std::string transportType() {
                return "custom";
        }

        virtual CF::Properties transportProperties();
        virtual CustomOutputTransport* createOutputTransport(PtrType object, const std::string& connectionId, const redhawk::PropertyMap& properties);
        virtual redhawk::PropertyMap getNegotiationProperties(redhawk::UsesTransport* transport);
        virtual void setNegotiationResult(redhawk::UsesTransport* transport, const redhawk::PropertyMap& properties);
    };

    // factory class that registers input/ouptut managers with a negotiation port
    class CustomTransportFactory : public bulkio::BulkioTransportFactory<BULKIO::dataFloat>
    {
    public:

        std::string transportType() {
            return "custom";
        };

        int defaultPriority() {
            return 0;
        };

        virtual ~CustomTransportFactory() {};

        CustomOutputManager* createOutputManager(OutPortType* port);
        CustomInputManager* createInputManager(InPortType* port);
    };

Modify transport_in.cpp and transport_out.cpp to define these functions:


    redhawk::PropertyMap CustomInputTransport::getNegotiationProperties() {
        RH_NL_INFO("custom.transport", "CustomInputTransport::getNegotiationProperties");
        redhawk::PropertyMap props;
        props["data::requestSize"] = static_cast<CORBA::Long>(1000);
        props["data::address"] = "0.0.0.0";
        props["data::port"] =  static_cast<CORBA::Long>(0);
        props["data::protocol"] = "udp";
        return props;
    }

    redhawk::PropertyMap CustomInputManager::getNegotiationProperties(redhawk::ProvidesTransport* providesTransport)
    {
        CustomInputTransport* _transport = dynamic_cast<CustomInputTransport*>(providesTransport);
        if (!_transport) {
            throw redhawk::FatalTransportError("Invalid provides transport instance");
        }

        // return data end point connection information
        redhawk::PropertyMap properties;
        properties =  _transport->getNegotiationProperties();
        return properties;
    }

    CustomInputManager* CustomTransportFactory::createInputManager(bulkio::InPort<BULKIO::dataFloat>* port) {
        return new CustomInputManager(port);
    }


    redhawk::PropertyMap CustomOutputTransport::getNegotiationProperties() {
        RH_NL_INFO("custom.transport", "CustomOutputTransport::getNegotiationProperties");
        redhawk::PropertyMap props;
        props["data_protocol"] = "hello";
        return props;
    }

    CF::Properties CustomOutputManager::transportProperties() {
        RH_NL_INFO("custom.transport", "CustomOutputManager::transportProperties");
        redhawk::PropertyMap props;
        return props;
    }

    CustomOutputTransport* CustomOutputManager::createOutputTransport(PtrType object,
                                                                      const std::string& connectionId,
                                                                      const redhawk::PropertyMap& inputTransportProps)
    {
        return new CustomOutputTransport(this->_port, object, connectionId, inputTransportProps );
    }

    redhawk::PropertyMap CustomOutputManager::getNegotiationProperties(redhawk::UsesTransport* transport) {
        CustomOutputTransport* _transport = dynamic_cast<CustomOutputTransport*>(transport);
        if (!_transport) {
            throw redhawk::FatalTransportError("Invalid transport object provided.");
        }

        redhawk::PropertyMap properties;
        properties =  _transport->getNegotiationProperties();
        return properties;
    }

    void CustomOutputManager::setNegotiationResult(redhawk::UsesTransport* transport, const redhawk::PropertyMap& properties) {

         if (!transport) {
             throw redhawk::FatalTransportError("Invalid transport object provided.");
         }

        RH_NL_INFO("custom.transport", "CustomOutputManager::setNegotiationResult");
        for ( redhawk::PropertyMap::const_iterator it = properties.begin(); it != properties.end(); it++) {
              RH_NL_INFO("custom.transport",  "CustomOutputManager, key (from provides): "<<it->id );
        }
    }

    CustomOutputManager* CustomTransportFactory::createOutputManager(OutPortType* port)
    {
        return new CustomOutputManager(port);
    };

Modify transport_in.cpp and transport_out.cpp to register the transport:

    static int initializeModule() {
        static CustomTransportFactory factory;
        redhawk::TransportRegistry::RegisterTransport(&factory);
        return 0;
    }

    static int initialized = initializeModule();

Testing the New Transport

To test the previous code examples, compile and install both components (transport_out and transport_in).

The following Python session demonstrates how to run the components, connect them, and verify the state of the connections. (Note that shared is set to False, forcing the components to run in different process spaces.)

    >>> from ossie.utils import sb
    >>> src=sb.launch('transport_out', shared=False)
    >>> snk=sb.launch('transport_in')
    >>> src.connect(snk)
    CustomOutputTransport::getNegotiationProperties
    CustomInputManager::createInputTransport
    CustomInputManager, key (from uses): data_protocol
    CustomInputTransport::startTransport
    CustomInputTransport::getNegotiationProperties
    CustomOutputManager::setNegotiationResult
    CustomOutputManager, key (from provides): data::requestSize
    CustomOutputManager, key (from provides): data::address
    CustomOutputManager, key (from provides): data::port
    CustomOutputManager, key (from provides): data::protocol
    >>> src.ports[0]._get_connectionStatus()
    [ossie.cf.ExtendedCF.ConnectionStatus(connectionId='DCE_66bd31e4-3cab-452b-8c21-6c3a2bc165eb', port=<bulkio.bulkioInterfaces.BULKIO.internal._objref_dataFloatExt object at 0x7f55d294f990>, alive=True, transportType='custom', transportInfo=[ossie.cf.CF.DataType(id='transport_side_information', value=CORBA.Any(CORBA.TC_string, 'outbound')), ossie.cf.CF.DataType(id='another_number', value=CORBA.Any(CORBA.TC_short, 100))])]
    >>> src.disconnect(snk)
    CustomOutputTransport::disconnect
    CustomInputTransport::~CustomInputTransport
    CustomOutputTransport::~CustomOutputTransport

Overloading BulkIO Ports

Adding transports enables the developer to customize the transport mechanism beyond that provided by the REDHAWK baseline. One of the limitations of the transport mechanism addition is that if the two components are located in the same process space, the transport selection mechanism defaults to shared address space, which is optimal for threads located on the same process. However, there are instances in which this approach is not optimal. For example, some embedded hardware requires a single point of entry from the microprocessor, so all processing threads that require access to the embedded hardware through the driver must be placed in the same process space. In this case, it may be desirable for the embedded hardware resources to connect to each other directly even though the controlling software resides in two separate threads in the microprocessor. In such instances, it is necessary to overload the provided ports and change the behavior of the default transport mechanism for components that share address space.

For this example, assume that the components with the updated transport shown in the previous example are modified as described in the example. The only additional change is to overload the required ports to select the custom transport for shared address space components; in the case of transport definition, endpoints that share the same address space are considered local.

This example requires that the \*\_base files be modified on each of these components, so component re-generation for attributes like new properties is not possible while safeguarding these changes. Furthermore, the IDE has been updated to hide several of the CORBA base classes, so the _remove_ref member is shown as an error. To hide this error: In the Project Explorer view, right-click the project and select Properties->C/C++ General->Paths and Symbols->GNU C++, and add HAVE_OMNIORB4 as a symbol (no value necessary).

Because the output port selects the transport to be used, the only port that must be overloaded is the output port, so only transport_out needs to be modified.

Modifications to transport_out_base.h

Modifications to transport_out_base.h include a port declaration inherited from the BulkIO OutFloatPort base case. This new class redefines the behavior of the _createLocalTransport method to disable the local transport when both endpoints reside in the same process space.

Modify transport_out_base.h by adding the new port declaration:

    class CustomOutPort : public bulkio::OutFloatPort {
    public:
            virtual ~CustomOutPort() {};
            CustomOutPort(std::string port_name)
              : bulkio::OutFloatPort(port_name) {};

            virtual redhawk::UsesTransport* _createLocalTransport(PortBase* port, CORBA::Object_ptr object, const std::string& connectionId);
    };

Modifications to transport_out_base.cpp

In transport_out_base.cpp, the new port behavior needs to be defined and instantiated in place of the BulkIO base class port.

Add the following function definition in transport_out_base.cpp:

    redhawk::UsesTransport* CustomOutPort::_createLocalTransport(PortBase* port, CORBA::Object_ptr object, const std::string& connectionId) {
        // disable the local transport and force a negotiation
        return 0;
    }

Change the following class instantiation in transport_out_base.cpp from:

    dataFloat_out = new bulkio::OutFloatPort("dataFloat_out");

to:

    dataFloat_out = new CustomOutPort("dataFloat_out");

Testing the New Port

As in the previous example, compile and install transport_out, the only component modified for this example.

The following Python session demonstrates how to run the components, connect them, and verify the state of the connections. (Note that shared is not set to False; therefore, both components run in the same process space.)

    >>> from ossie.utils import sb
    >>> src=sb.launch('transport_out')
    >>> snk=sb.launch('transport_in')
    >>> src.connect(snk)
    CustomOutputTransport::getNegotiationProperties
    CustomInputManager::createInputTransport
    CustomInputManager, key (from uses): data_protocol
    CustomInputTransport::startTransport
    CustomInputTransport::getNegotiationProperties
    CustomOutputManager::setNegotiationResult
    CustomOutputManager, key (from provides): data::requestSize
    CustomOutputManager, key (from provides): data::address
    CustomOutputManager, key (from provides): data::port
    CustomOutputManager, key (from provides): data::protocol
    >>> src.ports[0]._get_connectionStatus()
    [ossie.cf.ExtendedCF.ConnectionStatus(connectionId='DCE_66bd31e4-3cab-452b-8c21-6c3a2bc165eb', port=<bulkio.bulkioInterfaces.BULKIO.internal._objref_dataFloatExt object at 0x7f55d294f990>, alive=True, transportType='custom', transportInfo=[ossie.cf.CF.DataType(id='transport_side_information', value=CORBA.Any(CORBA.TC_string, 'outbound')), ossie.cf.CF.DataType(id='another_number', value=CORBA.Any(CORBA.TC_short, 100))])]
    >>> src.disconnect(snk)
    CustomOutputTransport::disconnect
    CustomInputTransport::~CustomInputTransport
    CustomOutputTransport::~CustomOutputTransport