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:
project> bin\project-setup.bat --interface my-bus
project$ ./bin/project-setup --interface my-bus
This creates a module directory in [project]/modules/my-bus-interface
(note the added -interface
suffix), containing the following files:
.dml
file. Now, you will have to enter the DML and C definitions of your interface into the generated .dml
and .h
files.
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.
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:
Integral types
These include bool
, signed and unsigned variants of char
, short
, int
, long
, long long
, the Simics-defined intN_t
types (where N is a size in bits), intptr_t
, uintptr_t
, and enumerations.
Floating-point types
float
and double
.
Strings
The C types char *
and const char *
are automatically converted to and from Python strings.
Structs
Most structs that are part of the Simics API are available in Python. Within those structs, only fields that are of supported types are available.
User-defined structs can also be made available. These should then be defined in, or included from, the same header file as where the C interface definition is.
Simics-specific types
For example, conf_object_t
and attr_value_t
. The complete list of supported types can be found in [simics]/host/bin/pywrap/py-typemaps.c
. That file contains all data types known to pywrapgen
, as well as %typemap
entries for the Simics-specific types. This file must not be edited by the user.
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
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:
cbdata_register_t
as arguments. cbdata_call_t
argument. SIM_free_cbdata
. 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.