This chapter describes the major programming concepts in Simics. It is intended for anyone who wants to write extensions or modules for Simics.
A Simics module is just some executable code loaded dynamically into the simulator, so it could perform virtually any operation. To be of any practical use however, a module has to interact with Simics, other modules, or the user. Simics provides a set of functions (the Simics API) for the modules to work within the simulator framework. The API defines a number of concepts like classes, objects, interfaces, haps, and events, which will be presented below.
Simics modules can be written using different programming languages:
This chapter will cover the concepts that the Simics API defines. It will also present how to use them, first in DML, then in Python and C/C++.
Each device is represented by an object whose attributes correspond to the state of the device.
Most Simics modules work the same way: they define classes describing the properties of an object. This includes its attributes, interfaces and the class name. The objects are created by instantiating a class and setting the required attributes.
Note that modules and classes are not the same thing. A module can define two or more classes, or none at all. But many modules define a single class with the same name as the module.
When registering an attribute, a type definition should be provided for Simics to check that the attribute is always set properly. This type definition is a string that is defined according to the following rules:
i
(integer),
f
(floating), s
(string), b
(boolean), d
(data), o
(object), D
(dictionary), n
(nil) or
a
(all).|
like in
s|o
(string OR object).[
and ]
. Lists can
be specified in three ways:
[iffsb]
matches a list of five
elements; an integer, two floating-point values, a string and a boolean
value.[i*]
and
[i+]
both match any list containing only integers, with
the difference that [i*]
will match an empty list, while
[i+]
requires at least one list element.[i{1:4}]
matches lists of
integers with 1 to 4 elements. [i{4}]
is the same as
[i{4:4}]
or [iiii]
, i.e., a list of four integers.
|
operator has higher precedence than juxtaposition in the
list type definitions, which means that [i|si|s]
matches any
two-elements list with integer or string elements. For clarity you can use a
comma anywhere in the type definition, it will be ignored. The example could
be written as [i|s,i|s]
As an example, the "[s*]|s"
string defines an attribute
that can hold either a string or a (possibly empty) list of strings.
When writing complex attributes, you may notice that the type description
strings do not cover all the possibilities offered by the attribute structure
definition. If you need to register attributes that cannot be described in a
type string (like complex variable size lists), you will need to use the
a
type and perform the type checking by yourself in the
set() function. You may also want to review your attribute and
change its definition to match a possible type description, or divide your
attribute into several simpler attributes.
For example, an attribute accepting a list composed of one object and one or
more integers can not be described in a type string (list definition with
variable size can only have one element). You may want to rewrite your
attribute to use a sub-list for the integers: [o[i+]]
, or you can
perform type checking yourself.
Interfaces allow objects to interact with each other. An interface defines a number of functions. Every class that implements an interface (by registering it) must define those functions. In some cases, it is sufficient to implement just a subset of the functions. An object A may then ask for an interface registered by an object B, and thus use the interface's functions to communicate with B. Simics comes with many predefined interfaces but it is also possible to define new interfaces.
One of the most important interfaces for a device class is probably the
io_memory
interface. Implementing the io_memory
interface enables a class to be mapped into a memory space and be accessed by
the simulated machine. In C or DML, it is defined as:
typedef struct io_memory_interface { int (*_deprecated_map)(conf_object_t *NOTNULL obj, addr_space_t memory_or_io, map_info_t map_info); exception_type_t (*operation)(conf_object_t *NOTNULL obj, generic_transaction_t *NOTNULL mem_op, map_info_t map_info); } io_memory_interface_t;
Other interfaces can be used for things like raising processor interrupts or implementing PCI device functionality.
Interfaces are either implemented by the device class itself, or by one of its ports. This way, a device can have several ports implementing the same interface, for example interrupt controllers with several sources or memory mapped devices with separate banks of registers.
A device model should log its activities so that it is possible to see what happens. It is useful both while developing the model and while developing target software, or to help finding problems in the system. Logging should not be used in attribute accessor functions or in CLI commands where there are other ways to report errors. The Simics API provides logging facilities that classifies log messages based on the following criteria:
info
error
-werror
command line flag, then Simics
will exit with an error code when a log message of the error type is
generated.critical
unimplemented
spec_violation
When a device model wants some action to be taken at a later time, it posts an event and returns control to the simulation engine. This will cause a callback to be called after the requested simulated time has passed.
For example, a device that wants to raise a time interrupt every
The events are posted on a clock, which is usually one of the processor cores in the system. Typically, each device is associated with one clock on which it posts all its events, but it is possible to choose which clock to post on, as long as it is in the same simulation cell as the device.
Another way that models may react to Simics events is to use haps. (The term was chosen because happenstance was considered too long.) To react to a hap, a module can register a callback function to the hap. This callback function will then be called every time the hap occurs. A complete list of all haps, descriptions of what parameters the callback functions should take and all functions used to register callbacks and manipulate haps can be found in chapter 12.
In general, haps are slower than notifiers, and therefore less suitable for communication between models. However, haps allow sending additional parameters to the callback function.
A user interface for a module can be created by adding new commands to the Simics command line interface. Commands can be bound to a specific namespace (i.e., a class or an interface) or to the global namespace.