While the majority of this guide focuses on DML, there is also support for writing modules in C and Python. DML is recommended for device models as DML allows the author to focus on the functionality of the device without having to worry about the mechanics of interacting with Simics. Components must be written in Python which is also useful for high level modules which do not model devices directly. C is useful for integrating with legacy code.
This chapter describes how to create modules written in C focusing on the mechanics of interacting with Simics. The general guidelines presented earlier apply to C as well as to DML. Each section focuses on one concept and describes how to implement it in C. It may be useful to review section Classes and Objects in API Reference Manual to clarify the differences between objects, classes, and modules.
Most modules need to do some work when initially loaded into Simics. Typically this work includes registering the classes implemented by the module, and their attributes, with Simics.
A module written in C/C++ must implement the function init_local(). It must
exist, even if it is empty. The function will be run by Simics when the module
is loaded into Simics. The function is defined as:
void
init_local(void)
{
}
If the module is written in C++, this function must be declared extern “C” for C linkage.
Each Simics class implemented by a module must be registered with Simics.
Remember that classes registered in a module should be listed in the
MODULE_CLASSES variable in the module’s Makefile. This allows Simics to
automatically load the required modules when reading a configuration file.
Similarly, components registered in the module should be listed in
MODULE_COMPONENTS.
Registering a class is done by creating and filling a class_info_t structure,
and then calling the function SIM_create_class with the new class name and the
class_info_t structure. The members in the class_info_t structure are:
init
A function called when creating an instance of the class.
finalize This function is called once init has
returned, and all attributes on the object being created have been set.
kind The class kind tells Simics whether objects
of this class should be saved when a checkpoint is created. Valid values are:
Sim_Class_Kind_Vanilla class instances will be
saved as part of checkpoints (this is the default if kind is not given
any value.)Sim_Class_Kind_Pseudoshort_desc A short description of the class,
preferably a one-liner. This is for example used by some commands that list
classes in table form.
description A string that should contain a,
potentially longer, description of the class.
In C/C++, registration of classes is usually done from within the mandatory
init_local() function. The C definition of class_info_t and
SIM_create_class() is the following:
typedef struct class_info {
conf_object_t *(*alloc)(conf_class_t *cls);
lang_void *(*init)(conf_object_t *obj);
void (*finalize)(conf_object_t *obj);
void (*objects_finalized)(conf_object_t *obj);
void (*deinit)(conf_object_t *obj);
void (*dealloc)(conf_object_t *obj);
const char *description;
const char *short_desc;
class_kind_t kind;
} class_info_t;
conf_class_t *SIM_create_class(
const char *NOTNULL name, const class_info_t *NOTNULL class_info);
SIM_create_class() returns a pointer to a conf_class_t structure which is
used internally by Simics to keep track of the class information. This pointer
can be used when referring to the class in calls to other functions.
A simple init_local() initialization function could look like this:
void
init_local(void)
{
class_info_t cdata;
conf_class_t *my_class;
memset(&cdata, 0, sizeof(cdata));
cdata.init = my_init;
cdata.kind = Sim_Class_Kind_Pseudo;
cdata.short_desc = "One-liner";
cdata.description = "This is my class";
my_class = SIM_create_class("my-class", &cdata);
// Other initializations...
}
A configuration class implemented in C typically maintains the state of each
object in a structure that is co-allocated with the object’s conf_object_t
structure, which is used both as a general reference to the object and to keep
track of information for the configuration system.
This is done by putting the conf_object_t first in a C struct that also
contains the object state:
typedef struct {
conf_object_t obj; // Always first!
// Object-specific state follows.
float temperature;
} my_object_t;
Converting between a pointer to my_object_t and its corresponding
conf_object_t then becomes a cast or member dereference:
// my_object_t -> conf_object_t
conf_object_t *obj = &myobj->obj;
// conf_object_t -> my_object_t
my_object_t *myobj = (my_object_t *)obj;
Of course, the code casting from a conf_object_t pointer must know for certain
that it really is an object of the right type. This knowledge is usually
implicit in the function interfaces.
The allocation of the combined structure is done in the alloc method in the
class_info_t value passed to SIM_create_class:
static conf_object_t *
my_alloc(conf_class_t *cls)
{
my_object_t *myobj = MM_ZALLOC(1, my_object_t);
return &myobj->obj;
}
The method init should be defined to initialize the structure:
static void *
my_init(conf_object_t *obj)
{
my_object_t *myobj = (conf_object_t *)obj;
myobj->temperature = 20.0;
return myobj;
}
Configuration classes written in C++ may use the same techniques as in C, or
simpler by subclassing simics::SimicsObject; see the C++ Device API
programming guide for details.
Attributes are linked to the class definition, usually just after the class has
been declared, with the SIM_register_attribute() function. It has the
following declaration in C:
int SIM_register_attribute(
conf_class_t *cls, const char *name,
attr_value_t (*get_attr)(conf_object_t *),
set_error_t (*set_attr)(conf_object_t *, attr_value_t *),
attr_attr_t attr,
const char *type,
const char *doc);
The parameters of SIM_register_attribute() are:
clsSIM_create_class().)nameget_attr, set_attr The get() and set()
functions for the attribute. If one of these operations is not supported,
NULL (or None in Python) can be used.attr The properties of the attribute, a
combination of the configuration type, an optional index type and
initialization order. It tells Simics how the attribute will be saved and
addressed, and is specified using the constants described below.The configuration type of an attribute must be selected from one of the following values:
<div class="dl">
- <span class="term">`Sim_Attr_Required`</span> The attribute has to be set
when creating the object. It will also be saved during checkpointing.
- <span class="term">`Sim_Attr_Optional`</span> If a value is not specified,
the attribute will keep its default value when creating an object. It will
be saved during checkpointing.
- <span class="term">`Sim_Attr_Pseudo`</span> The attribute does not really
represent any internal state. It may work instead as a command in
disguise, or as a redundant way of accessing internal state. It will not
be saved during checkpointing.
</div>
Attributes may also have the following additional kinds added (using a bitwise or operation).
<div class="dl">
- <span class="term">`Sim_Attr_Persistent`</span> Attribute represents a
persistent value and is included in persistent files, created with the
`save-persistent-state` command. Persistent attributes are used for data
that survives power-cycling.
- <span class="term">`Sim_Attr_Internal`</span> Indicates that the attribute
is internal to the object and should not be accessed directly by other
users.
</div>
In addition the order in which the attribute will be initialized can be defined by adding (also using a bitwise or operation) with one of the following values:
<div class="dl">
- <span class="term">`Sim_Init_Phase_0`</span>
Early initialization (default)
- <span class="term">`Sim_Init_Phase_1`</span>
Late initialization
</div>
Attributes with Sim_Init_Phase_1 will be initialized after attributes with
Sim_Init_Phase_0, but no other order is guaranteed.
Let us use a simple counter attribute as an example.
In C, we’ll have an object declared as:
typedef struct my_object {
conf_object_t obj;
int foo;
} my_object_t;
We want to implement an attribute called counter, thus we need a pair of
set/get functions. counter will internally use foo to keep its value.
The pair of get/set functions could be defined as:
static attr_value_t
get_counter(conf_object_t *obj)
{
my_object_t *mo = (my_object_t *)obj;
return SIM_make_attr_uint64(mo->foo);
}
static set_error_t
set_counter(conf_object_t *obj, attr_value_t *val)
{
my_object_t *mo = (my_object_t *)obj;
mo->foo = SIM_attr_integer(*val);
return Sim_Set_Ok;
}
In the get_counter() function, obj is the object that owns the attribute and
arg is the user information that was registered along with the attribute. Note
that obj can be safely cast to my_object_t (conf_object_t is used as a
“base type” here). The function creates an attr_value_t variable that will be
of integer type and contain the value foo. It then returns this attribute
value.
The set_counter() function on the other hand takes a val argument which
contains the value to be written. The return value is of type set_error_t,
which is defined as below. Descriptions of the values can be found in the API
Reference Manual.
typedef enum {
Sim_Set_Ok,
Sim_Set_Object_Not_Found,
Sim_Set_Interface_Not_Found,
Sim_Set_Illegal_Value,
Sim_Set_Illegal_Type,
Sim_Set_Illegal_Index,
Sim_Set_Attribute_Not_Found,
Sim_Set_Not_Writable
} set_error_t;
Registering the counter attribute is just a matter of calling
SIM_register_attribute():
SIM_register_attribute(my_class,
"counter",
get_counter,
set_counter,
Sim_Attr_Required,
"i",
"A counter");
In the previous example, the attribute counter provides a direct
representation of the value foo inside the object. Now let us add an attribute
called add_counter that will increase foo by a given value when the
attribute is set, and do nothing when the attribute is read. This would give us
the following code:
static set_error_t
set_add_counter(conf_object_t *obj, attr_value_t *val)
{
my_object_t *mo = (my_object_t *)obj;
mo->foo += SIM_attr_integer(*val);
return Sim_Set_Ok;
}
There is no need for a get function since this attribute only can be written.
The semantics of set_add_counter() are also slightly different, since the
function actually adds a value to foo.
It is thus possible to create real attributes whose value corresponds to a real variable in an object, and pseudo attributes which are only used as object “methods”.
Registering the add_counter attribute is straightforward:
SIM_register_attribute(class_name, "add_counter",
NULL,
set_add_counter,
Sim_Attr_Pseudo,
"i",
"A sample pseudo attribute.");
Errors in attribute getter methods are signalled by calling
SIM_attribute_error with an error description, and then returning an arbitrary
value. An explicitly “invalid” value is usually returned in these cases, as
generated by SIM_make_attr_invalid, but any value will do. In Python, None
is a good choice.
Errors in attribute setter methods are signalled by returning a set_error_t
value other than Sim_Set_Ok. In most cases, returning Sim_Set_Illegal_Value
is the appropriate way of indicating that the given value is not valid for the
attribute. Optionally, SIM_attribute_error may be called in order to provide a
more detailed error description, prior to returning.
Attribute errors generally give rise to frontend exceptions. If the error occurred during the reading or writing of a checkpoint, that activity is aborted; any partially written checkpoint or created objects are removed.
An object that wants to interact with another through an interface uses the
SIM_get_interface() function to retrieve the interface structure. It can then
call the other object using the functions defined in the structure.
conf_object_t *obj = SIM_get_object("phys_mem");
const memory_space_interface_t *ifc;
attr_value_t val;
ifc = SIM_get_interface(obj, "memory_space");
val = ifc->read(obj, SIM_get_object("cpu0"), 0x1234, 4, 0);
When you are using interfaces inside an object, you will often need to define
which object you want to talk to via an attribute. Typically this is done by
defining an attribute with type o|n, and checking if the object passed as
argument implements the necessary interface:
static attr_value_t
get_an_object(conf_object_t *obj)
{
my_object_t *mo = (my_object_t *)obj;
return SIM_make_attr_object(mo->an_object);
}
static set_error_t
set_an_object(conf_object_t *obj, attr_value_t *val)
{
my_object_t *mo = (my_object_t *)obj;
if (SIM_attr_is_nil(*val)) {
mo->an_object = NULL;
} else {
foo_interface_t *foo_ifc =
(foo_interface_t *)SIM_get_interface(
SIM_attr_object(*val), "foo");
if (SIM_clear_exception() != SimExc_No_Exception)
return Sim_Set_Interface_Not_Found;
mo->an_object = SIM_attr_object(*val);
}
return Sim_Set_Ok;
}
[…]
SIM_register_attribute(class_name, "an_object",
get_an_object,
set_an_object,
Sim_Attr_Optional,
"o|n",
"An object implementing the 'foo' interface");
A limitation of interfaces as described above is that each class can only
implement each interface once, but you may need to implement an interface
several times in the same device model. For example, consider an interrupt
controller that has several interrupt inputs having to implement the signal
interface once for each input.
To get around this a device will usually use port objects. These are sub objects
of the main device object that can be used to specify a particular interface
instance when connecting objects, see 35. In this case using a
port interface is identical to using a regular interface. You use
SIM_get_interface() to get the interface implementation and do not have to
care whether the object is a regular object or port object.
However, if your model is required to interact with old models implemented in C or Python you may find that you also have to support an older port mechanism. In this case a port is identified by an object and a port name string.
Your attribute then has to accept either an object or a list with an object and
a name string. If a port name is specified you have to use
SIM_get_port_interface() instead of SIM_get_interface() to get the interface
implementation.
The example code from 15.5.1 below is extended with support for the old port mechanism:
static attr_value_t
get_an_object(conf_object_t *obj)
{
my_object_t *mo = (my_object_t *)obj;
if (mo->a_portname != NULL)
return SIM_make_attr_list(
2,
SIM_make_attr_object(mo->an_object),
SIM_make_attr_object(mo->n_portname));
return SIM_make_attr_object(mo->an_object);
}
static set_error_t
set_an_object(conf_object_t *obj, attr_value_t *val)
{
my_object_t *mo = (my_object_t *)obj;
if (SIM_attr_is_nil(*val)) {
mo->an_object = NULL;
} else if (SIM_attr_is_obj(*val)) {
foo_interface_t *foo_ifc =
(foo_interface_t *)SIM_get_interface(
SIM_attr_object(*val), "foo");
if (SIM_clear_exception() != SimExc_No_Exception)
return Sim_Set_Interface_Not_Found;
mo->an_object = SIM_attr_object(*val);
} else if (SIM_attr_is_list(*val)) {
conf_object_t *foo_obj =
SIM_attr_object(SIM_attr_list_item(*val, 0));
const char *foo_port =
SIM_attr_string(SIM_attr_list_item(*val, 1));
foo_interface_t *foo_ifc =
(foo_interface_t *)SIM_get_port_interface(
foo_obj, "foo", foo_port);
if (SIM_clear_exception() != SimExc_No_Exception)
return Sim_Set_Interface_Not_Found;
mo->an_object = foo_obj;
}
MM_FREE(mo->a_portname);
mo->a_portname = foo_port ? MM_STRDUP(PORT) : NULL;
return Sim_Set_Ok;
}
[…]
SIM_register_attribute(class_name, "an_object",
get_an_object,
set_an_object,
Sim_Attr_Optional,
"o|n|[os]",
"An object implementing the 'foo' interface");
The implementation of an existing interface requires the population of all the
function pointers that are listed in the interface definition with the functions
that should be called. The interface should then be registered using the
SIM_register_interface() function:
SIM_register_interface(conf_class_t *class, char *name, void *iface)
Where the parameters are:
This gives us the following code:
static cycles_t
my_operate(conf_object_t *mem_hier, conf_object_t *space,
map_list_t *map, generic_transaction_t *mem_op)
{
// do something
}
static conf_class_t *my_class;
static timing_model_interface_t ifc;
void
init_local(void)
{
[…]
ifc.operate = my_operate;
SIM_register_interface(my_class, "timing_model", (void *) &ifc);
[…]
}
If there is no predefined interface that suits your needs, you will need to define your own interface type. This is described in section 11.
If your model needs to implement the same interface more than once, for example, consider the case of an interrupt controller with multiple interrupt inputs, it should do that by implementing the interface on multiple port objects.
Port objects and how to use them from C are described in 35.
Logging in C is handled by SIM_log_register_group() and the
SIM_LOG_* macros.
A single call to SIM_log_register_groups() registers all groups for the class.
The function is used as:
SIM_log_register_groups(conf_class_t *cls, const char **gnames)
where the parameters are:
classnamegroupnamesAn example:
static char *groupnames[] = { "config", "request", "response", NULL };
SIM_log_register_groups(my_class, &groupnames);
The log group values will be defined by the order of the strings in the tuple as
a power of 2 series, so in the example above config corresponds to 1,
request corresponds to 2 and response corresponds to 4.
Log outputs are handled with SIM_LOG_INFO() and corresponding macros. They
take the following parameters:
SIM_LOG_INFO(int level, conf_object_t *obj, uint64 groups, const char *msg);
with the parameters meaning:
level An integer from 1 through 4, determining the
lowest verbosity level at which the message will be logged. The level does not
exist for the SIM_log_error() function.objgroups The bitwise or:ed values of one or several
log-groups. A value of 0 equals any group.msgLogging from a Simics module written in C/C++ should be done with the following
macros: SIM_LOG_INFO(), SIM_LOG_ERROR(), SIM_LOG_UNDEFINED(),
SIM_LOG_SPEC_VIOLATION(), SIM_LOG_WARNING() and SIM_LOG_UNIMPLEMENTED().
These macros use the corresponding SIM_log_<type>() function internally, and
should always be used instead for performance reasons.
Note that the macros take a variable number of arguments to allow you to write
printf()-like strings.
A small example:
static attr_value_t
get_counter_array(conf_object_t *obj, attr_value_t *idx)
{
my_object_t *mo = (my_object_t *)obj;
SIM_LOG_INFO(4, obj, 0, "get_counter_array");
if (!SIM_att_is_nil(*idx)) {
if (!SIM_attr_is_integer(*idx))
SIM_LOG_ERROR(obj, 0,
"Index must be integer");
return SIM_make_attr_invalid();
return SIM_make_attr_uint64(
mo->foo[SIM_attr_integer(*idx)]);
}
else {
attr_value_t ret = SIM_alloc_attr_list(10);
int i;
for (i = 0; i < 10; i++) {
SIM_attr_list_set_item(ret, i,
SIM_make_attr_uint64(mo->foo[i]);
}
return ret;
}
}
Event classes are created with the SIM_register_event() function which should
be called at module initialization time. The event class holds a pointer to the
callback function. Here is an example from an UART model:
static event_class_t *transmit_event;
static void
uart_transmit(conf_object_t *obj, void *param)
{
uart_device_t *uart = (uart_device_t *)obj;
SIM_LOG_INFO(4, &uart->log, 0, "event called");
}
void
init_local(void)
{
conf_class_t *uart_class;
[…]
uart_class = SIM_create_class(…);
[…]
transmit_event = SIM_register_event(
"transmit character", uart_class, 0, uart_transmit,
0, 0, 0, 0);
[…]
}
To post an event in the future, based on time, the SIM_event_post_time() and
SIM_event_post_cycle() functions are used. An example:
SIM_event_post_time(&uart->log.obj, transmit_event,
&uart->log.obj, 0.00001, NULL);
It is possible to specify an argument to be passed to the callback function, as
the sixth parameter of SIM_event_post_time. In order for checkpointing to work
correctly, you must in this case also specify user-data to attr_value_t
conversion functions in the call to SIM_register_event.
If for some reason you do want to remove a posted but not yet handled event, you
can cancel it with a call to SIM_event_cancel_time(), specifying the object,
event class and optionally a parameter.
SIM_event_cancel_time(obj, transmit_event, obj, 0, NULL);
You can also check how long time is left until an event occurs using
SIM_event_find_next_cycle(), again specifying the object, event class and
parameter. The time left to the event is returned in cycles.
If you want to post an event a number of simulated CPU steps into the future it
should instead post to the step queue. Posting to the step queue is very similar
to posting to the time queue, but the functions SIM_event_post_step(),
SIM_event_cancel_step() and SIM_event_find_next_step() should be used
instead.
Refer to the API Reference Manual for more information on the function prototypes and their parameters.
As the Simics profiling and event viewing systems are based on listening to haps it can be useful for a device to directly trigger haps rather than relying on haps built into the memory, link, and processor models. In these situations the model has to choose between a standard Simics hap and a user defined hap. Standard haps have the benefit of documentation and predefined hap handlers. User defined haps have the advantage of specificity.
Before handlers can be notified of a new hap, the hap must be known. A new hap
type is made known through registration. Registering a new hap type is done with
the function SIM_hap_add_type(). The signature is:
hap_type_t
SIM_hap_add_type(const char *hap,
const char *params,
const char *param_desc,
const char *index,
const char *desc,
int old_hap_obj);
where the parameters are:
hap
The name of the hap, which must be unique
params A string specifying the number of
parameters of the hap and their types. The return value and the first two
parameters of the callback function are always the same and are not included
in the list. A valid parameter description string contains only the following
type description characters:
i – intI – int64 (64 bit integer)e – exception_type_to – object (i.e., void* in C and Python object in Python)s – stringm – memory transaction (generic_transaction_t * in C)c – configuration object (conf_object_t * in C)param_desc space separated list of descriptive
parameter names (in the same order as params, so that the first word is the
name of the first parameter. If param is the empty string, param_desc may
be None.
index A string describing the index value for the
hap, or None if there is no index value. The meaning of indexes is up to you
to define.
desc
A human readable description of the hap.
old_hap_obj
Always 0.
The return value is a handle that must be saved for operations on the hap.
Example:
void
init_local()
{
[…]
hap_handle = SIM_hap_add_type("My_Special_Hap",
"ii",
"val1 val2",
NULL,
"Triggered when something special"
" happens in my module.",
0);
if (hap_handle <= 0) {
/× error handling ×/
[…]
}
}
Whenever the condition for the hap is met, the handlers for the hap should be
notified. Triggering a hap incurs some overhead; if it occurs in
performance-sensitive code, it may be beneficial to use one of the
SIM_hap_is_active_obj or SIM_hap_is_active_obj_idx functions to check if
there are any handlers prior to calling the notification function.
bool SIM_hap_is_active_obj(hap_type_t hap, conf_object_t *NOTNULL obj);
bool SIM_hap_is_active_obj_idx(hap_type_t hap, conf_object_t *NOTNULL obj,
int64 index);
where the parameter hap is the value returned from SIM_hap_add_type() or
from SIM_hap_get_number() if using a standard hap type. These predicates are
approximate, but if they return false, there is no need to trigger the hap since
no installed functions would be called.
The notification to handlers is normally done by calling one of
SIM_hap_occurred(), SIM_hap_occurred_vararg(), SIM_hap_occurred_always(),
and SIM_hap_occurred_always_vararg(). See the API Reference Manual for
information about the differences.
int
SIM_c_hap_occurred_always(hap_type_t hap,
conf_object_t *obj,
int64 value,
...);
The parameters are:
In C, hap parameters will be provided as additional parameters to the function. A short example:
static void
some_func(conf_object_t *obj, int v1, int v2)
{
if (some_condition) {
SIM_c_hap_occurred(hap_handle, obj, 0, v1, v2)
}
}
If you want to interact with the Python interpreter built into Simics from C,
you will need to use the Python/C API. The Python/C API is defined by the
simics/python-header.h header file. This file needs to be included before any
other Simics API header files.
In order to use the header file, you will need to include the following two
statements in your module’s Makefile:
MODULE_CFLAGS = $(PYTHON_INCLUDE)
MODULE_LDFLAGS = $(PYTHON_LDFLAGS)
These need to be set before the include $(MODULE_MAKEFILE) line and must use
the lazy evaluation assignment operator (=) rather than := or +=.
See http://docs.python.org for more information about the Python/C API.
You should not include the standard
Python.hheader file directly as it contains errors whichsimics/python-header.hworks around.