10 Example Models 12 Using Python in a Simics module
Model Builder User's Guide  /  II Device Modeling  / 

11 Defining New Interface Types

The Simics API defines a number of useful interface types, but sometimes they are not enough, for example if you are using a bus type that is not supported by the predefined interface types.

In order for a new interface type to be used from Python, Python bindings for the interface must be compiled into a Simics module, using the Makefile variable IFACE_FILES; this is further documented in section 3.5.3. The easiest way to create a new interface type is to use the interface template module provided by Simics:

This creates a module directory in [project]/modules/my-bus-interface (note the added -interface suffix), containing the following files:

Now, you will have to enter the DML and C definitions of your interface into the generated .dml and .h files.

11.1 Example

Assume that we, for example, want to create an interface that supports a new, simple bus type. It is a very simple bus using 32-bit addresses and 64-bit data, and it only allows read and write operations.

Create the my-bus-interface module as described above:

    project-setup --interface my-bus

Edit my-bus-interface.dml to contain the following code:

    dml 1.4;

    header %{
    #include "my-bus-interface.h"
    %}

    extern typedef struct {
        uint64 (*read)(conf_object_t *obj, uint32 address);
        void (*write)(conf_object_t *obj, uint32 address, uint64 value);
    } my_bus_interface_t;

Edit my-bus-interface.h to match the DML file:

    #ifndef MY_BUS_INTERFACE_H
    #define MY_BUS_INTERFACE_H

    #include <simics/device-api.h>

    SIM_INTERFACE(my_bus) {
            uint64 (*read)(conf_object_t *obj, uint32 address);
            void (*write)(conf_object_t *obj, uint32 address,
                          uint64 value);
    };
    #define MY_BUS_INTERFACE "my_bus"

    #endif /* ! MY_BUS_INTERFACE_H */

Then, compile the my-bus-interface module, which will provide Simics with the necessary Python support for using the my_bus interface:

 project> make my-bus-interface

You can now use your new interface from DML, Python, and C. For example, to use the new interface from a DML device, create a new module called my_bus_device using project-setup --device my_bus_device. Then, edit modules/my_bus_device/my_bus_device.dml to contain the following:

    dml 1.4;

    device my_bus_device;
    param desc = "example bus device";

    import "utility.dml";

    import "my-bus-interface.dml";

    implement my_bus {
        method read(uint32 address) -> (uint64) {
            log info, 1: "read from 0x%x", address;
            return 4711;
        }
        method write(uint32 address, uint64 value) {
            log info, 1:"write 0x%x to 0x%x", value, address;
        }
    }

To compile such a DML device, you need to tell it where to find the definition of the interface. Do that by adding the following to its Makefile:

    EXTRA_MODULE_VPATH += my-bus-interface

From Python, you can now use the my_bus interface:

simics> @SIM_create_object("my_bus_device", "mydev")
<the my_bus_device 'mydev'>
simics> @conf.mydev.iface.my_bus.write(0x1020, 0x4321)
[mydev info] write 0x4321 to 0x1020

Note that when you did this, the my-bus-interface module was automatically loaded, providing the Python support for the new interface.

If you want to create new instances of the interface, you will need to use the SIM_get_python_interface_type to look up the Python data type corresponding to the interface. This function will, if necessary, load the Simics module that provides the Python support for your new interface.

The return value from SIM_get_python_interface_type is a regular Python data type, which can be instantiated by calling it as a function. Using the example as above, this is how you could define its interface from Python instead:

simics> def read_fn(obj, address):
........    print('read from address 0x%x' % address)
........    return 1234
........
simics> def write_fn(obj, address, value):
........    print('write 0x%x to address 0x%x' % (value, address))
........
simics> @myiface_type = SIM_get_python_interface_type('my_bus')
simics> @myiface = myiface_type(read = read_fn, write = write_fn)
simics> @SIM_register_interface('mydev', 'my_bus', myiface)
simics> @conf.mydev.iface.my_bus.read(0x4321)
read from address 0x4321
1234

To use the newly defined interface type from C, you just need to include my-bus-interface.h. Set EXTRA_MODULE_VPATH in the C module's makefile the same way as described above, and you will not have to give a path to the file.

See section 14.5 for more information about using interfaces from Python and C.

11.2 Restrictions

An interface must be a struct (in the DML or C sense) of function pointers, or methods. Each of the methods should have a conf_object_t *obj as its first argument, which is used to pass the called object to the implementing function.

When you call an interface method from DML, you should not specify the obj argument, as it is provided automatically.

In C, you must use the SIM_INTERFACE(name) macro to declare the interface. You must also define a macro called NAME_INTERFACE to be equal to the string "name". For example:

  SIM_INTERFACE(test) {
          void (*method)(conf_object_t *obj, int arg);
  };
  #define TEST_INTERFACE "test"

This defines a C data type called test_interface_t, which is a struct test_interface, containing one function pointer member, method. The TEST_INTERFACE macro should be used whenever you need to refer to the name of the interface from C, as it helps avoiding typos in the interface name:

  test_interface_t *iface = SIM_get_interface(obj, TEST_INTERFACE);

If you forget to define the TEST_INTERFACE macro, you will get a C compiler error. If the value of TEST_INTERFACE isn't "test", you will get a runtime (assertion) error when your module is loaded.

The TEST_INTERFACE macro is mostly applicable for C models. In DML models the connect construct should be used instead.

The code that handles the Python conversion is created by first running the C preprocessor, CPP, on the interface's C header file. The preprocessed file is then fed to a tool, pywrapgen, which creates the necessary wrapper code.

Not all data types are allowed when exporting an interface to Python. In essence, only the following data types are allowed:

If your interface (or a user-defined struct) uses a type not supported in Python, you will need to prevent that argument or field from being processed by the Python wrapper generator. This is done using the PYWRAP CPP define, which only is defined when creating the indata for pywrapgen.

For example, this is how you can prevent pywrapgen from processing the bar method, which has an unsupported int * argument:

  SIM_INTERFACE(test) {
          void (*foo)(conf_object_t *obj, int a);
  #ifndef PYWRAP
          void (*bar)(conf_object_t *obj, int *result);
  #endif
  };

If you do not do this, you may get somewhat cryptic error messages from pywrapgen, as it has limited knowledge about types defined outside the Simics header files.

You can also let users create new instances of user-defined structs from Python (this is automatically allowed for interfaces), by using the SIM_PY_ALLOCATABLE(type) macro, where type needs to be a typedef. For example, you might add the following to the my-bus-interface example above:

  typedef struct {
          char *name;
          unsigned count;
  } mytype_t;
  SIM_PY_ALLOCATABLE(mytype_t);

The Python wrappings of each header file in an interface module appear as a Python module in the simmod package of the corresponding Simics module; in this example, the Python bindings for mytype_t appear in the module simmod.my_bus_interface.my_bus_interface. This can be used to create new instances of mytype_t from Python:

simics> load-module my-bus-interface      # load Simics module
simics> @from simmod.my_bus_interface import my_bus_interface
simics> @x = my_bus_interface.mytype_t()  # create struct
simics> @x.count = 4711                   # assign to field
simics> @print('0x%x' % x.count)          # read from field
0x1267

You can also initiate the fields in the constructor:

simics> x = my_bus_interface.mytype_t(name = "Arthur", count = 42)

An enumeration type can be used as argument to interface methods and its members will be wrapped to Python as integers. In Python each member of an enumeration can be accessed as an attribute of the interface module, named as the member. The type of an enumeration will not be wrapped by name, only the members. SIM_PY_ALLOCATABLE should not be used for enumeration types.

If you add an enumeration to the previous example:

  typedef enum {
          First = 1,
          Second,
          Last = 100,
  } my_enum_t;

The members First, Second and Last will be accessible from Python as integers, but the enumerator type my_enum_t will not be wrapped. After creating my_bus_interface in the same way as in the previous example, enumeration members can be accessed as follows:

simics> @my_bus_interface.First   # read enum member First
1
simics> @my_bus_interface.Second  # read enum member Second
2
simics> @my_bus_interface.Last    # read enum member Last
100

11.3 Callback Functions in Interfaces

If you have an interface method that takes a callback function as an argument, it is often very useful to also have generic user-supplied data that is passed unmodified to the callback function.

In C, the traditional way to do this is to use a void * parameter for this. However, this does not work well enough for interfaces. Interfaces are available to be called or implemented by Python functions, and therefore need to handle Python objects in the place of the void * parameter. As Python objects need to be explicitly reference counted, some additional work is required.

The solution is to use cbdata_t objects as arguments instead of void *. These are typed and have a deallocation function that is used to free the object when the last reference is used.

The types cbdata_register_t and cbdata_call_t are only aliases for cbdata_t, used to annotate whether the object is passed to a registration function or a callback function. This is used by the automatic Python wrapping to ensure that the callback data is freed correctly.

These are the rules that must be followed when defining an interface method that takes a callback function as argument:

  1. The interface method takes a callback function and a cbdata_register_t as arguments.
  2. The callback function must take a cbdata_call_t argument.
  3. The interface method may call the callback function zero or more times.
  4. When the callback function will not be called again, a C interface implementation must free the callback data by calling SIM_free_cbdata.
  5. The interface user must make no assumptions about when or whether the callback data is deallocated.

When the interface method is implemented in or called from Python, the Python reference counter dictates when the deallocation function is called.

The cbdata_t feature was added in Simics 4.4 and legacy interfaces do typically not use it; instead they rely on using lang_void arguments. While those support being called from Python, they do not support implementing the interface in Python.

For detailed documentation on cbdata_t, its related data types, and API functions, see its entry in the API Reference Manual.

10 Example Models 12 Using Python in a Simics module