C++ Device API v1

1 Introduction

Note: The C++ device API v1 is deprecated in favor of the C++ device API v2. See the migration guide chapter in the C++ device API v2 Programming Guide for how to migrate.
This document describes the Simics C++ Device API, which is a C++ layer built on top of the Simics C API. See the Model Builder User's Guide for more information about the C API. The C++ Device API is also used when integrating SystemC models into the Simics framework. See the SystemC Library Programming Guide for more information about Simics and SystemC models.

We recommend that you use DML for writing new simulation models, but it is often necessary to port existing simulations models from a different environment to Simics. If these are written in C++ or SystemC, you can use the C++ Device API to simplify the task. For SystemC there is further support described in the SystemC Library Programming Guide.

The Simics C++ Device API is a collection of C++ functions, data types, and templates that make it easier to connect a C++ simulation model to the Simics framework. The C++ Device API is implemented as a layer on top of the Simics C API and its source code can be found in [simics]/src/devices/c++-api. The detailed API documentation for the C++ API is available in the Simics C++ Device API Reference Manual.

While this application note aims to cover most aspects of porting C++ device models to the Simics APIs, it is assumed that you are familiar with the Simics device modeling concepts described in the Model Builder User's Guide.

2 The Simics C++ Device API

The Simics C++ Device API is exported in the C++ namespace simics. To use it, your C++ files need to #include <simics/c++/device-api.h>.

You will also need to set USE_CC_API := yes in your module's Makefile. This makes sure the module can find the necessary C++ API header files and that the C++ API support files are linked into the module.

See the provided source code for the module sample-device-c++ which shows how to use the API described in this document.

3 Preparations

Before you start to connect your C++ device to the Simics API, you need to consider which the logical components of your device model are, and how they are connected to each other and the rest of the simulation environment.

In Simics, device models are implemented as separate objects that are dynamically connected to each other using the Simics configuration system, and the objects communicate using explicitly requested interfaces. This is in contrast to C++, where the objects are often aggregated at compile time or by running compiled startup code and interfaces are resolved during compilation and linking. The Model Builder User's Guide has a more complete description of the Simics object system.

Just like in C++, the Simics objects are instances of classes, but the Simics object classes are dynamically defined by a Simics module by calling the Simics API to register callback functions to allocate and deallocate objects, and to add attributes to the classes.

3.1 Designing the Classes

The first step is to decide how the C++ model should appear within Simics. For a simple C++ model that models a small piece of hardware, it is probably nothing more than saying that the whole model should be a single Simics class.

But for a C++ model that consists of several components connected together, it is worth considering exposing it as several Simics classes. One reason for this is that it makes the Simics configuration more natural by creating a model that fits better with the Simics framework. And it allows for future separation of the model parts without major changes to the configurations.

Simply exposing the model as several classes will not automatically make the classes independent under the surface; they are still implemented as one conglomerate of C++ objects inside the implementation. To fully take advantage of the flexibility of the Simics configuration system, the device should be split it to discrete pieces that communicate only using Simics interfaces. By using well-known interfaces, the devices will become replaceable by newer implementations individually, and will allow experimentation in reconfiguration without having to recompile the model sources, or even having access to the source code.

This document focuses on creating Simics modules wrapping simple C++ models which define a single Simics class, but it easily extends to modules with several classes.

3.2 Defining Attributes

A Simics object attribute is used to accomplish primarily two things. The first is to specify configuration parameters when initially creating the model instance. This includes connections to other configuration objects, model parameters such as frequencies and buffer sizes, but anything is possible.

The other main purpose of attributes is to allow saving and restoring the model state to support checkpointing and reversibility. This means that the complete state of the model needs to be available to the Simics configuration system as attribute values in a well-defined way.

A third use of attributes is for inspection of the state of the model. This is usually covered by the same attributes used for checkpointing. The configuration of the model may also use the checkpoint attributes to set an initial state.

3.2.1 Configuration Attributes

The configurable aspects of the C++ model should be available as configuration attributes. In some cases this will mean small changes to the C++ implementation.

The most common configurable parameter is probably references to other Simics configuration objects, such as interrupt targets, memory spaces or DMA controllers. The model must never make any assumptions about what other objects appear in the system configuration. All rest-of-world information must be given in configuration parameters.

If the C++ model is written with compile-time configuration using the preprocessor, consider rewriting it to be dynamically reconfigurable. This will make the model more versatile, and potentially much more useful to a broader audience. The overhead of runtime checking of these kinds of configuration parameters is negligible in the vast majority of devices, especially when run in the context of full-system simulation.

For example, a parameter to decide which hardware revision to be compatible with may very well be a run-time parameter. Another option is adding configuration parameters that go outside the scope of the known hardware, by allowing buffer sizes and similar parameters to be reconfigured for experimental purposes.

3.2.2 Model State

To support checkpointing, the model needs to be able to collect a full description of the current model state and it needs to be able to restore the model to the checkpointed state when the Simics object is created.

To support reversible execution, the requirements are stricter. The object needs to be able to restore from a checkpointed state at any time, even when there is a previous state that must be discarded.

An existing C++ model needs to be examined to find how its simulation state is defined. If the model is not written to handle checkpointing, it may need to be updated with a way to extract and restore the state.

The model state is made available to the Simics configuration system using a number of attributes. There should be one attribute for each piece of the model state. It is a good idea to design the set of attributes for the model in a way that allows some separation of the external, checkpointable representation of the state from the implementation details. Ideally, the checkpoint format should not need to be updated when the implementation is changed, including running on a different host platform, using different C++ classes, internal representation or other structural changes. As a minimum requirement the external representation must not depend on what compiler is used, or whether the model is built for a 32-bit or 64-bit environment. Preferably it should be fully portable between platforms regardless of what CPU architecture the simulation is running on. When changes to the checkpoint format is required, Simics provides ways to still be able to read old checkpoints with updated models.

For example, if the model models a device with 16 32-bit registers with different meaning, it is preferably represented as 16 integer-valued attributes, with names that match those used in the device programming manual.

An example of something to avoid doing is saving a copy of the memory representation of a C++ object. This is highly unportable and may break checkpoint compatibility just by recompiling the source with different compiler flags.

3.3 Interfaces

A Simics device model interacts with the rest of the simulation using interfaces and ports. Ports are named collections of one or more interfaces, and should be used when several interfaces together form one logical communication unit. There may be several ports implementing the same interface.

To be useful, the model should implement one or more interfaces. The most commonly implemented interface is called io_memory and is used to simulate memory transactions between processors, devices, and memory.

Finally, the model needs to define a number of attributes to support configuration, checkpointing and other Simics features.

For more information about how to design a model for Simics, see Model Builder User's Guide.

4 Simics Modules

The very first step of building a C++ simulation model for Simics is to create a Simics module. Usually, you put each Simics class in a model of its own. It is also possible to make several classes share one model, which may be necessary if the classes share code.

To create a C++ device model skeleton, you use project-setup:

Windows
> cd my-simics-project
project> bin\project-setup --c++-device my_cc_device

Linux
$ cd my-simics-project
project$ ./bin/project-setup --c++-device my_cc_device

See the Build Environment chapter in the Model Builder User's Guide for details on how to set up a project and creating Simics model skeletons.

As a Simics module is loaded into Simics, it is initialized by running its init_local() function, which must use C linkage; i.e., it must be extern "C".

This function is in particular responsible for registering all Simics classes with the Simics core, but you can add any other initialization code needed to that function.

5 Simics Classes

A Simics class has a name, a short description and a documentation string. Its functionality is implemented in a number of configuration attributes and the interfaces (ports).

For each instance of a Simics class defined using the C++ API, there will be a C++ object created to correspond to that Simics configuration object. This C++ object is an instance of a model-defined class that must inherit from the simics::SimicsObject class.

The C++ class must have a constructor taking a single parameter of type SimicsObjectRef which is passed on to the SimicsObject constructor.

#include <simics/c++/device-api.h>

class sample_instance : public simics::SimicsObject {
  public:
    explicit sample_instance(simics::SimicsObjectRef o)
        : simics::SimicsObject(o) {
        // initialize
    }
    // other members
};
 

This is the main object of the model instance, and everything goes through this. It should contain or reference anything that the model instance will need. Remember that there can be several instances of the model class, since the configuration allows the user to load multiple systems into the same simulation.

5.1 Defining the Simics Class

A class is added to Simics by creating an instance of the template class simics::ClassDef. This object is usually created as a local variable in the init_local function.

extern "C" void
init_local()
{
    simics::ClassDef<sample_instance>(
        // Simics class name
        "sample_device_auto_v1",
        // short description
        "sample C++ device",
        // class documentation
        "This is a sample Simics device written in C++.");
} 

The constructor parameters for ClassDef are the class name, the long name (or short description), and the class documentation. See the documentation of SIM_register_class for more information about these.

The template argument is the class that should be instantiated to represent the Simics object. It must inherit publically from simics::SimicsObject as noted above.

The registration of the Simics class is done in the destructor for the ClassDef class, so it is automatically performed when the object is no longer used. In the example above, the temporary object is immediately destroyed, and the class is registered. If the object is bound to a local variable, the registration will be performed at the end of init_local.

Sometimes it is necessary to know the Simics class that is being registered, and for that purpose the register_with_simics method can be called explicitly instead of letting the destructor do it. This method returns the conf_class_t pointer for the newly registered Simics class. If this pointer is null, it means that the registration has failed and the init_local function should exit without doing any more work. The error will afterwards be reported properly.

extern "C" void
init_local()
{
    simics::ClassDef<sample_instance> def(
        "sample_device_explicit_v1",
        "sample C++ device",
        "This is a sample Simics device written in C++.");

    // register prematurely (not generally recommended)
    conf_class_t *cls = def.register_with_simics();
    if (!cls)
        return;

    // use cls to do any other registrations necessary
} 

6 Attributes

The C++ API provides a number of ways to define the attributes, depending on which method of accessing the internal state is convenient. Generally, the attribute definition needs a way to extract the state from the C++ object, and then a way to convert this state to a Simics attribute value. These two parts are handled separately, and the ways to extract the state is described first.

The attribute is defined by creating an object of type Attribute and adding it to the ClassDef instance using the << operator. The Attribute type is never instantiated directly. Instead, one of its subclasses is used, depending on how the state is accessed. They all share a few common features, and are created like this:

    def << simics::SomeAttribute<sample_instance, mapper>(
        "value", "A value.", ...)

The mapper is covered later. The first string is the attribute name, and must be unique for the class and stable between revisions of the model. The second string is the documentation. The rest of the arguments depend on which kind of attribute is defined.

6.1 Mappers

A mapper translates between C++ values and the data representation used by Simics attributes. Simics attribute values are stored in values of the type attr_value_t, and it can contain numbers (both integer and floating-point), booleans, strings, object references, binary data. It can also be a list of attribute values, or a dictionary mapping attribute values to other attribute values.

A mapper is a class that looks like this:

class mapper {
  public:
    typedef T valuetype;
    static const std::string type_string() { return "type"; }
    static valuetype from_attr(const attr_value_t &val);
    static attr_value_t to_attr(const valuetype &val);
};

The type T is the C++ type that it can map to attribute values. An attribute definition that uses this mapper class will have functions or variable references using this type.

Type type string used by the type_string function is a Simics type string as defined by the API function SIM_register_attribute. For instance, for integer values it is "i", and for strings it is "s".

The include file simics/c++/base/state_mapper.h contains some predefined mapper classes.

    state_mapper<char>
    state_mapper<signed char>
    state_mapper<unsigned char>
    state_mapper<int>
    state_mapper<unsigned>
    state_mapper<long>
    state_mapper<unsigned long>
    state_mapper<long long>
    state_mapper<unsigned long long>
    state_mapper<bool>
    state_mapper<double>
    conf_obj_state_mapper<allow_null>
    string_state_mapper
    attr_value_state_mapper

Some of them are of the form state_mapper<T>, which is mostly for convenience, but it also means that it can be used with a T that is a synonym for one of the types in the defined, so state_mapper<uint32> is also possible.

A model may define its own mappers, and they can be arbitrarily complicated, for instance mapping bit-level internal state to symbolic tables in the external representation.

For brevity, the examples below assume the following typedef:

typedef simics::state_mapper<int> int_mapper;

6.2 SimpleAttribute

The simics::SimpleAttribute class is used when the state is represented directly as a public member variable in the C++ class used in the ClassDef declaration. The third parameter in the constructor is a member pointer to the state variable.

class sample_instance : public simics::SimicsObject {
  public:
    explicit sample_instance(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), value(0) { }

    int value;
};
                        //...
extern "C" void
init_local()
{
    simics::ClassDef<sample_instance>("sample_simple_attrib_v1",
                                      "simple attribute", "...")
        << simics::SimpleAttribute<sample_instance, int_mapper>(
            "value", "A value.", &sample_instance::value);
} 

This example uses &sample_instance::value to tell the attribute definition where the state variable is stored, given an instance of sample_instance.

For most models, the state variables are not as easily accessible, so one of the following attribute definition classes needs to be used instead.

6.3 GetSetAttribute

The simics::GetSetAttribute class is used when the top-level object has methods to get and set the state variables that correspond to the attribute. The third and fourth parameters in the constructor are pointers to the member functions to get and set the state.

#include <simics/c++/device-api.h>
#include <simics/c++/base/state_mapper.h>

class sample_get_set_attrib : public simics::SimicsObject {
  public:
    explicit sample_get_set_attrib(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), value(0) { }

    int get_value() const;
    simics::SetResult set_value(const int &v);

  private:
    int value;
};

// ...

int
sample_get_set_attrib::get_value() const
{
    return value;
}

simics::SetResult sample_get_set_attrib::set_value(const int &v)
{
    if (v < 256) {
        value = v;
        return simics::SetResult::no_error();
    } else {
        return simics::SetResult::error("Too large value");
    }
}

extern "C" void
init_local()
{
    simics::ClassDef<sample_get_set_attrib>(
            "sample_get_set_attrib_v1",
            "sample get/set attribute device",
            "N/A")
        << simics::GetSetAttribute<sample_get_set_attrib,
        simics::state_mapper<int> >(
            "value", "A value.",
            &sample_get_set_attrib::get_value,
            &sample_get_set_attrib::set_value);
}

By wrapping the value with functions, it is also possible to add extra checks.

The getter function simply returns the value. The return type must match the type defined by the mapper. The setter function takes a reference to a value, and can accept or reject it by returning either simics::SetResult::no_error() or simics::SetResult::error(msg), where msg is a short message describing why it was rejected.

If the getter is 0, it means that the attribute is write-only. If the setter is 0, it means that it is read-only. If either is 0, the attribute cannot be checkpointed and must be marked as such by giving the attribute flag Sim_Attr_Pseudo.

The GetSetAttribute template class also has optional third and fourth template parameters. If a data type is given as the third parameter, then the constructor, and the setter and getter functions will take an extra parameter which is a pointer to that data type. For example, this example uses a single pair of setter/getter functions for several flag attributes.

class sample_get_set_flag : public simics::SimicsObject {
  public:
    explicit sample_get_set_flag(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), flags(32) { }

    bool get_flag(int *idx) const;
    simics::SetResult set_flag(const bool &v, int *idx);

  private:
    std::vector<bool> flags;
};

bool
sample_get_set_flag::get_flag(int *idx) const
{
    return flags.at(*idx);
}

simics::SetResult
sample_get_set_flag::set_flag(const bool &v, int *idx)
{
    flags.at(*idx) = v;
    return simics::SetResult::no_error();
}

extern "C" void
init_local()
{
    simics::ClassDef<sample_get_set_flag> def(
            "sample_get_set_flag",
            "sample get/set flag device",
            "N/A");

    def << simics::GetSetAttribute<sample_get_set_flag,
        simics::state_mapper<bool>,int>(
            "flag0", "The first flag.",
            &sample_get_set_flag::get_flag,
            &sample_get_set_flag::set_flag,
            new int(0))
        << simics::GetSetAttribute<sample_get_set_flag,
        simics::state_mapper<bool>,int>(
            "flag1", "The second flag.",
            &sample_get_set_flag::get_flag,
            &sample_get_set_flag::set_flag,
            new int(1));
}

The optional fourth template parameter is a function that takes the data pointer and destroys it when it is no longer needed. The default is to delete it.

6.4 LocateAttribute

The simics::LocateAttribute class is used when there is a state variable that contains the state to export through the attribute, but it isn't as readily available as when SimpleAttribute is used. Instead of a member pointer to the variable, a function is given that returns a reference to the state variable.

This is for instance useful when the state variable is in another object that is referenced from the main object.

struct model {
    int value;
};

class sample_locate_attrib : public simics::SimicsObject {
  public:
    explicit sample_locate_attrib(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), m() { }

    model m;
};

int &locate_value(sample_locate_attrib &obj) {
    return obj.m.value;
}

extern "C" void
init_local()
{
    simics::ClassDef<sample_locate_attrib>(
            "sample_locate_attrib",
            "sample locate attribute device",
            "N/A")
        << simics::LocateAttribute<sample_locate_attrib, int_mapper>(
            "value", "A value.", locate_value);
}

In this example, the locate_value chases the reference m to find the variable. It could call instance methods or do anything to achieve the same results. The important thing is that it needs to return a reference to an object that can be read from and written to.

6.5 ProxyAttribute

The simics::ProxyAttribute class is the most versatile, and uses getter and setter functions. These functions are not member functions, but instead get the main object as a function parameter. Other than that, it works in a very similar way to GetSetAttribute.

class sample_proxy_attrib : public simics::SimicsObject {
  public:
    explicit sample_proxy_attrib(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), value(0) { }

    void set_hidden_value(int v) { value = v; }
    const int &get_hidden_value() const { return value; }

  private:
    int value;
};

// ...

int get_value(const sample_proxy_attrib &obj) {
    return -obj.get_hidden_value();
}

simics::SetResult set_value(sample_proxy_attrib &obj, const int &v) {
    obj.set_hidden_value(-v);
    return simics::SetResult::no_error();
}

extern "C" void
init_local()
{
    simics::ClassDef<sample_proxy_attrib>(
            "sample_proxy_attrib_v1",
            "sample proxy attributes device",
            "N/A")
        << simics::ProxyAttribute<sample_proxy_attrib, int_mapper>(
            "value", "A value.", get_value, set_value);
}

By wrapping the value with functions, it is also possible to add extra checks and transformations. In the example above, the external representation is the negation of the value stored internally.

The getter function simply returns the value. The return type must match the type defined by the mapper. The setter function takes a reference to a value, and can accept or reject it by returning either simics::SetResult::no_error() or simics::SetResult::error(msg), where msg is a short message describing why it was rejected.

If the getter is 0, it means that the attribute is write-only. If the setter is 0, it means that it is read-only. If either is 0, the attribute cannot be checkpointed and must be marked as such by giving the attribute flag Sim_Attr_Pseudo.

The ProxyAttribute template class has optional third template parameters that adds a data pointer to the constructor and passes the data pointer to the getter and setter functions, and an optional fourth template parameter that gives a delete function. See the description of GetSetAttribute for more information.

6.6 Attribute Flags

All attribute classes have constructors where an extra parameter of type attr_attr_t has been added. This can be used to indicate that an attribute is required (Sim_Attr_Required) or that it should not be part of checkpoints (Sim_Attr_Pseudo).

6.7 Creating Custom Mappers

As mentioned before, a mapper translates between C++ values and the data representation used by Simics attributes. Predefined mappers exists for simple types, like integers and floating point values. For more complex types it is possible to create custom mappers. For example, it may be desirable to map a list of integers to a vector of integers. All that is required to perform this mapping is to create a new mapper type and to implement the various functions as shown in the example below:

#include <simics/c++/device-api.h>
#include <vector>

class vect_mapper {
  public:
    typedef std::vector<int> valuetype;
    static const std::string type_string() { return "[i*]"; }
    static valuetype from_attr(const attr_value_t &val);
    static attr_value_t to_attr(const valuetype &val);
};

vect_mapper::valuetype
vect_mapper::from_attr(const attr_value_t &val)
{
    vect_mapper::valuetype out_val;
    out_val.reserve(SIM_attr_list_size(val));
    for (unsigned n = 0; n < SIM_attr_list_size(val); ++n)
        out_val.push_back(SIM_attr_integer(SIM_attr_list_item(val, n)));

    return out_val;
}

attr_value_t
vect_mapper::to_attr(const vect_mapper::valuetype &val)
{
    attr_value_t out_val = SIM_alloc_attr_list(val.size());
    for (unsigned n = 0; n < val.size(); ++n)
        SIM_attr_list_set_item(&out_val, n, SIM_make_attr_int64(val[n]));

    return out_val;
}

class sample_instance : public simics::SimicsObject {
  public:
    explicit sample_instance(simics::SimicsObjectRef o)
        : simics::SimicsObject(o), values() { }

    std::vector<int> values;
};
                        //...
extern "C" void
init_local()
{
    simics::ClassDef<sample_instance>("sample_state_mapper",
                                      "sample state mapper", "...")
        << simics::SimpleAttribute<sample_instance, vect_mapper>(
            "value", "A value.", &sample_instance::values);
} 

7 Ports and Interfaces

To implement an interface, either as part of a port or not, there are four steps needed:

Interface declarations
Declare C++ methods that implement the interface and declare an interface.
Interface definition
Define a C++ preprocessor macro to tie the interface above to an object.
Interface method implementation
Implement the declared C++ methods for the interface.
Interface registration
Register the interfaces witht the ClassDef object.

7.1 Interface declarations

Your instance class needs to define C++ methods that implement the different methods in the interface, as well as an interface declaration.

For example:

class sample_interface : public simics::SimicsObject {
  public:
    explicit sample_interface(simics::SimicsObjectRef o)
        : simics::SimicsObject(o) { }

    // interface method declarations
    int map(addr_space_t memory_or_io, map_info_t map_info);
    exception_type_t operation(generic_transaction_t *mem_op,
                               map_info_t map_info);
    // interface declaration
    typedef simics::io_memory_interface<
        sample_interface,
        &sample_interface::map,
        &sample_interface::operation> io_memory_iface;
};

The interface method declarations are straight-forward. They should have the same signature as the interface method has in the Simics Reference Manual (or as shown with the Simics command api-help), except that the first C argument of type conf_object_t * is left out, as it corresponds to the C++ class instance.

The interface declaration consists of a typedef of a template class (simics::io_memory_interface in the example). The name of the class is the name of the interface, suffixed with _interface. The template arguments are the instance class followed by the interface methods in the order they appear in the interface definition. The typedef name can be anything and will be used later to refer to this declaration.

7.2 Interface definition

The interface definition is a C++ preprocessor macro call that ties the interface declaration above to an object that is used to register the interface with the model's ClassDef:

DEFINE_IO_MEMORY_INTERFACE(dev_io_memory, sample_interface::io_memory_iface);

This creates a dev_io_memory object tied to the interface defined by the io_memory_iface typedef in the instance class.

The macro corresponding to the name interface is called DEFINE_NAME_INTERFACE.

7.3 Interface method implementation

The interface methods are normal C++ methods:

exception_type_t sample_interface::operation(
    generic_transaction_t *mop,
    map_info_t info)
{
    // implement behavior here
    return Sim_PE_No_Exception;
}

7.4 Interface registration

Interfaces are registered with the ClassDef object using the << operator, just like attributes:

extern "C" void
init_local()
{
    simics::ClassDef<sample_interface>("sample_interface_v1",
                                       "sample interface", "N/A")
        << dev_io_memory;
}

Here, dev_io_memory is the object defined using the DEFINE_IO_MEMORY_INTERFACE macro above.

7.5 User defined interface

The C++ bindings for interfaces are only available for standard Simics interfaces. If there is a need to support additional interfaces the C-API has to be used. Refer to the Model Builder User's Guide and the API Reference Manual for more information about defining and calling user defined interfaces. The following example shows how an "example" interface can be defined and redirected to a method in the SimicsObject object.

class sample_user_interface : public simics::SimicsObject {
  public:
    explicit sample_user_interface(simics::SimicsObjectRef o)
        : simics::SimicsObject(o) { }

    // Interface method declarations
    void iface_callback() {
        // TODO: implement
    }
};

extern "C" {
// Interface definition
SIM_INTERFACE(example) {
    void (*iface_fun)(conf_object_t*);
};

// Interface callback to convert call to C++ object method
static void
iface_callback(conf_object_t *o)
{
    dynamic_cast<sample_user_interface&>(
        simics::SimicsObjectRef(o).as_simics_object()).iface_callback();
}

void
init_local()
{
    simics::ClassDef<sample_user_interface> dev(
            "sample_user_interface_v1",
            "sample user interface",
            "N/A");

    conf_class_t *cls = dev.register_with_simics();
    static example_interface_t example;
    example.iface_fun = iface_callback;
    SIM_register_interface(cls, "example", &example);
}
}

7.6 Port registration

Ports are registered by first creating an instance of the simics::Port class, which in turn is registered with the ClassDef object using the << operator. The Port constructor takes one argument, the port's name. You also add interfaces to the port object using the << operator:

    simics::Port bus_a_port("BUS_A");
    bus_a_port << bus_a_io_memory;

    simics::ClassDef<sample_interface>("sample_port_interface",
                                       "sample interface with port",
                                       "N/A")
        << bus_a_port;

Here, bus_a_io_memory is an interface definition object.