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 in DML, Python, C and 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:
A simple type is represented by a single letter: i (integer), f (floating), s (string), b (boolean), d (data), o (object), D (dictionary), n (nil) or a (all).
You can match several types by using the operator | like in s|o (string OR object).
Lists are defined by square brackets: [ 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.The | operator has higher precedence than juxtaposition in the list type definitions, which means that [i|si|s] matches any two-element 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 cannot 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 find 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 classify log messages based on the following criteria:
The verbosity level ranging from 1 through 4 in decreasing importance order. The terms ‘high’ and ‘low’ for log-level are based on priority, thus 1 is the highest log-level.
Each log message belongs to a specific category:
infowarningerrorsim->fail_on_warnings attribute is set to true, for example if Simics was started with the -werror command line flag, then Simics will exit with an error code when a log message of the error type is generated.criticalunimplementedspec_violationA bit-field, defined and registered by the module, used to separate different parts of the device, for example to distinguish between the configuration of a device and using the device’s services, or the PCI interface and the Ethernet interface, etc.
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 timer interrupt every 10 ms would post an event with a callback that raises the interrupt and then reposts the event with a 10 ms delay.
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.
Some clocks, in particular the processor cores, also allow posting events on a step queue which doesn’t count time, but CPU steps. This is usually not used by device models, but can be used for profiling extensions etc.
You can print out the current event queue by using the print-event-queue command (or peq for short).
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. The API to register hap callbacks is in the simulator API.
Each package can register new hap types. The base package defines several common hap types.
To register new hap types and to trigger haps use the device API.
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.
info command that prints static information about the device and a status command that prints the state of the device.output-radix or print.