5.3 Targets and parameters 6 Configuration and Checkpointing
Simics User's Guide  /  II Feature Overview  /  5 Simics Scripting Environment  / 

5.4 Scripting Using Python

The Intel® Simics® simulator provides support for the script language Python (http://www.python.org). By using Python the user can extend the simulator, and control it in greater detail. Python code can use functions from the simulator's API.

5.4.1 Python in the Intel® Simics® Simulator

The Intel® Simics® simulator always contains a Python environment. The simulator's command line interface (CLI) and all commands are implemented in Python. To find the code executed by a command, one can search the Python - .py - files of the corresponding module for the calls to the new_command, new_info_command, and new_status_command functions.

To execute Python code directly from the simulator's CLI, Python code that is to be executed should be prefixed with the @ character:

simics> @print("This is a Python line")
This is a Python line
simics>

For code spanning more than one line, the prompt will change to ....... and more code can be inserted until an empty line is entered. The full code block will then be executed (note that whitespace indentation is significant in Python):

simics> @if SIM_number_processors() > 1:
.......     print("Wow, an MP system!")
....... else:
.......     print("Only single pro :-(")
.......
Wow, an MP system!
simics>

Entering more than one line is useful for defining Python functions. It is also possible to execute Python code from a file, which is done with the run-python-file command.

If the Python code is an expression that should return a value to the CLI, the python command can be used, or the expression can be back-quoted. The following example selects a file with Python commands to execute depending on the number of processors in the system:

simics> run-python-file `"abc-%d.py" % SIM_number_processors()`

If the system has 2 processors, the file abc-2.py will be executed.

One can switch the simulator's CLI to Python mode with the python-mode command. In Python mode, indicated by

simics>>>

prompt, all input is interpreted as Python code, not as simulator's CLI commands. In Python mode, Python code lines should not be prefixed with the @ character, since that is only used in CLI mode to interpret input as Python code. Example:

simics> python-mode
Entering Python mode. Use cli_mode() or Ctrl-D to return to CLI.
simics>>>

Now Python code can be entered directly:

simics>>> print(f"Simics base package version: {SIM_version_base()}")
Simics base package version: Simics ...
None
simics>>>

As it was pointed above, to exit Python mode, one can use the cli_mode function or simply enter Ctrl-D:

simics>>> cli_mode()  # we run cli_mode here. Alternatively, enter Ctrl-D
Command line is now in CLI mode.
None
simics>

5.4.2 Accessing CLI Variables from Python

CLI variables can be accessed from Python via the simenv namespace, for example:

simics> $cpu = "processor"
simics> @simenv.cpu = simenv.cpu.capitalize()
simics> $cpu
Processor

As we could see in the example, simenv is imported into global namespace by default, but if it is needed elsewhere, it can be imported from the cli module.

5.4.3 Accessing the Configuration from Python

5.4.3.1 Configuration Objects

All configuration objects are visible as objects in Python. The global Python module conf holds the top level namespace, which contains all top level objects. The objects contain all their subobjects as attributes.

The configuration objects also expose their attributes as Python attributes. The attributes can be accessed via the attr attribute, or, in a legacy way, directly as Python object attributes. Thus the attributes can be both read and written as Python attributes. The following example prints the size attribute from the board.mb.rom_image object of the image class:

simics> @conf.board.mb.rom_image.attr.size
262144

Here is a legacy way to access the same attribute:

simics> @conf.board.mb.rom_image.size
262144

We recommend to access Simics objects' attributes via the attr attribute, except for a few commonly used attributes like name and classname.

If an object contains a subobject and an attribute with the same name, the subobject takes precedence and hides the attribute.

To try the previous example in an arbitrary configuration, run list-objects -all image to find available objects of the image class to use instead of the board.mb.rom_image object.

Any '-' (dash) character in the object name, or in an attribute name, is replaced by '_' (underscore). This substitution is performed because Python always treats the dash character as the minus operator. To avoid confusion the recommendation is to always use underscore.

Indexed attributes can be accessed using [] indexing in Python. It is also possible to index other list attributes this way, but it might be inefficient since the full list is converted to a Python list before the element is extracted. Here are some examples of indexed attributes access (a pcie-bus object, and a memory-space object):

simics> @conf.board.mb.nb.pci_bus.attr.pci_devices[0]
[29, 1, <the ich10_usb_uhci 'board.mb.sb.uhci[1]'>, 1]

simics> @conf.board.mb.phys_mem.attr.memory[0x100:0x10f]
(89, 236, 0, 240, 61, 0, 0, 158, 83, 255, 0, 240, 144, 37, 0)

simics> @conf.board.mb.phys_mem.attr.memory[0x10000:0x10003] = Line break
(100, 101, 102)

If the attribute contains a list, dictionary or data, then an access returns a reference instead of the actual value. This is similar to how container objects such as lists and dictionaries work in Python and allows constructs such as:

@conf.board.mb.phys_mem.attr.map[0][0] = 0x1000

The example modifies the attribute at position [0][0]. To get the copy of the attribute value, the following can be used:

@memory_map = conf.board.mb.phys_mem.attr.map.copy()

Note that there is a difference in how references to Simics attributes work compared to ordinary Python objects: if the attribute access returns a list, dictionary or tuple, then a reference to the full attribute is used and not only to the referenced container objects. The reason is that internally in Simics, the attribute is treated as a single value.

Consider a list of lists, such as a = [[1, 2, 3], [4, 5, 6]]. If this was a Python list, then the following applies:

b = a[0]       # b is a reference to the [1, 2, 3] list.
a[0][1] = 9    # b will now change to [1, 9, 3].
a[0] = [7, 8]  # b still references the [1, 9, 3] list, only a will change.

If a instead is a Simics attribute:

b = a[0]       # b is a reference to the first list in a, i.e. [1, 2, 3].
a[0][1] = 9    # b will now change to [1, 9, 3].
a[0] = [7, 8]  # b is still a reference to the first list in a, i.e [7, 8].

As we see, only the last line of the examples differs. The most common situation where this difference is visible is when doing list duplication. In Python a list can be duplicated in whole or part by using slicing to produce a shallow copy. In Simics, that would simply produce a reference to the same list if any of the items in the list is a container object. In this case the .copy() method has to be used.

5.4.3.2 Interfaces

From Python, the iface attribute of a configuration object can be used to access the interfaces it exports. Use obj.iface.name.method to accesses the method function in the name interface of the obj object. Example:

simics> @conf.board.mb.cpu0.core[0][0].iface
<interfaces of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].iface.processor_info
<processor_info_interface_t interface of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].iface.processor_info.get_program_counter
<built-in method logical_address_t (*)([conf_object_t *]) of interface Line break
method object at 0x5cb2070>
simics> @hex(conf.board.mb.cpu0.core[0][0].iface.processor_infoLine break
.get_program_counter())
'0xfff0'

Note: When called from Python, the first conf_object_t * argument for interface methods should not be used: it is passed automatically.
The last command corresponds to the following C code (with no error-checking):

conf_object_t *obj = SIM_get_object("board.mb.cpu0.core[0][0]");
processor_info_interface_t *iface = 
        SIM_get_interface(obj, PROCESSOR_INFO_INTERFACE);
logical_address_t pc = iface->get_program_counter(obj);
printf("0x%llx", pc);

5.4.3.3 Ports and Banks

Port objects in Simics represent ports and banks. For scripting access to objects they work exactly as a sub-object. By convention they are placed in the port and bank namespaces under the object.

Use obj.port.portname.iface.interfacename.method or obj.bank.bankname.iface.interfacename.method to access interface methods in a port object.

simics> @conf.board.mb.sb.com[0].port
<the namespace 'board.mb.sb.com[0].port'>
simics> @conf.board.mb.sb.com[0].port.HRESET
<the NS16550.HRESET 'board.mb.sb.com[0].port.HRESET'>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface
<interfaces of board.mb.sb.com[0].port.HRESET>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal
<signal_interface_t interface of board.mb.sb.com[0].port.HRESET>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal.signal_raise
<built-in method void (*)([conf_object_t *NOTNULL]) of interface method object at 0x7f90f32f5310>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal.signal_raise()

The last command corresponds to the following C code (with no error-checking):

conf_object_t *obj = SIM_get_object("board.mb.sb.com[0].port.HRESET");
signal_interface_t *iface = 
        SIM_get_interface(obj, SIGNAL_INTERFACE);
iface->signal_raise(obj);

5.4.3.4 Port Interfaces

Simics also has a legacy mechanism for providing named entry points to objects, called port interfaces. This mechanism is still used by some models. Named port interfaces are referenced from other objects using a list of [object, portname] instead of just a single object reference. Port interfaces are accessed from Python in a similar way to interfaces.

Use obj.ports.portname.interfacename.method to access the interfacename interface in port portname of the object obj. Example:

simics> @conf.board.mb.cpu0.core[0][0].ports
<ports of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET
<interfaces of port RESET of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.signal
<signal_interface_t interface of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.Line break
signal.signal_raise
<built-in method void (*)([conf_object_t * NOTNULL]) of Line break
interface method object at 0x5cb22d0>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.Line break
signal.signal_raise()

The last command corresponds to the following C code:

conf_object_t *obj = SIM_get_object("board.mb.cpu0.core[0][0]");
signal_interface_t *iface = 
        SIM_get_port_interface(obj, SIGNAL_INTERFACE, "RESET");
iface->signal_raise(obj);

5.4.4 Accessing Command Line Commands from Python

At times, it can be useful to access command line commands from a Python script file. This can be done in two ways which are described below.

The first way to access command-line commands from Python is to use the run_command(cli_string) function, which takes a string which is then evaluated by the command line front-end. For example, write run_command("print-processor-registers") to execute the print-processor-registers command. Any return value from the command is returned to Python. There is also the quiet_run_command function, which captures any output produced by the command and returns a tuple with the command return value as first entry, and the text output as the second. More details about both functions can be found in the Simics Reference Manual.

The second way to access command line commands from Python is to use wrapper functions from two special namespaces. The first namespace is global_cmds (provided by the cli module). It allows to run commands which are not tied to any Simics object. For example, the print-processor-registers command can be executed as global_cmds.print_processor_registers(). The commands which are tied to Simics objects (namespace commands) can be executed via the cli_cmds namespaces which can be accessed through a Python object representing a Simics objects. For example, the sim.info command can be executed as conf.sim.cli_cmds.info().

Please note that Simics command-line commands are defined to only be executed in the Simics API execution context known as Global Context. If a command is executed while not in Global Context then Simics will stop with an error message. Though, this doesn't happen often in practice; usually only when CLI commands are be called directly from user callbacks installed for haps or notifiers, or from device models. For more information about Simics execution contexts and how to get to Global Context, please see section "API Execution Contexts" in the Simics API reference manual.

5.4.5 The Script Branch API

Script branches can be manipulated from Python using the script branch API. A script branch can be created using sb_create, passing a Python function that defines the script branch.

From the script branch Python function, one can use sb_wait to suspend the script branch. From somewhere else, the branch can be woken up again by calling sb_signal_waiting. Thus one can make the script branch wait on arbitrary conditions. Example:

def script_branch(data):
    print("Branch start")
    wait_id = cli.sb_get_wait_id()
    data.append(wait_id)
    cli.sb_wait("wait", wait_id)
    print("Branch done")

data = []
print("Create branch")
cli.sb_create(lambda: script_branch(data))
wait_id = data[0]
time.sleep(1)
print("Signal branch")
cli.sb_signal_waiting(wait_id)

One can also access the existing script wait functionality using the ability to run any CLI command, described in 5.4.4. As an example:

# Sample script branch stopping simulation after 5 cycles
# executed on the clock object.
def script_branch():
    clock.cli_cmds.wait_for_cycle(cycle = 5, _relative = True)
    cli.global_cmds.stop()

cli.sb_create(script_branch)

5.4.6 The Simics API

The Simics API is a set of functions that provide access to Simics functionality from loadable modules (i.e., devices and extensions), and Python scripts. All functions in the Simics API have a name that starts with "SIM_". They are described in details in the Simics Reference Manual.

By using the api-help and api-search commands you can get the declarations for API functions and data types. api-help identifier will print the declaration of identifier. api-search identifier lists all declarations where identifier appears.

Note that while api-help topic does the same thing as help api:topic, the help-search command will not search through the API declarations.

The Simics API functions are available in the simics Python module. This module is imported into the Python environment in the frontend when Simics starts. However, for user-written .py files, the module must be imported explicitly:

    from simics import *

Errors in API functions are reported back to the caller using frontend exceptions. The exception is thrown together with a string that describes the problem more in detail. Examples of exceptions are SimExc_General, SimExc_Memory, SimExc_Index, and SimExc_IOError. In DML and C/C++, these exceptions have to be tested for using SIM_clear_exception or SIM_get_pending_exception. In Python, such exceptions result in regular Python exceptions.

For the Python environment, Simics defines an exception subclass for each of its defined exceptions in the simics module. These are raised to indicate exceptions inside the API functions. When errors occur in the interface between Python and the underlying C API function, the standard Python exceptions are used; e.g., if the C API function requires an int argument, and the Python function is called with a tuple, a Python TypeError exception is raised.

5.4.7 Haps

A hap is an event or occurrence in Simics with some specific semantic meaning, either related to the target or to the internals of the simulator.

Examples of simulation haps are:

There are also haps which are related to the simulator, e.g., (re)starting the simulation or stopping it and returning to prompt.

Note: In the Simics documentation, the word event is used exclusively for events that occur at a specific point in simulated time, and hap for those that happen in response to other specific conditions (like a state change in the simulator or in the simulated machine).
A callback can be invoked for all occurrences of the hap, or for a specified range. This range can be a register number, an address, or an exception number, depending on the hap.

A complete reference of the haps available in Simics can be found in the Simics Reference Manual.

5.4.7.1 Example of Python Callback on a Hap

This example uses functions from the Simics API to install a callback on the hap that occurs when a control register is written. It is intended to be part of a .simics script, that extends an QSP-x86 machine setup. The SIM_hap_add_callback_index() function sets the index of the control register to listen to, in this case the %ia32_feature_control register in an x86-intel64-turbo processor.

@ia32_feature_control_reg_no = conf.board.mb.cpu0.core[0][0]Line break
.iface.int_register.get_number("ia32_feature_control")

# print the new value when %ia32_feature_control is changed
@def ctrl_write_ia32_feature_control(user_arg, cpu, reg, val):
    print("[%s] Write to %%ia32_feature_control: 0x%x" % (cpu.name, val))

# install the callback
@SIM_hap_add_callback_index("Core_Control_Register_Write",
                            ctrl_write_ia32_feature_control, None,Line break
ia32_feature_control_reg_no)

In CLI, the same example would look like:

script-branch {
    local $cpu = (pselect)
    while TRUE {
        $cpu.wait-for-register-write ia32_feature_control
        echo "[" + $cpu + "] Write to %ia32_feature_control: "Line break
+ ((hex ($cpu.read-reg ia32_feature_control)))
    }
}

5.4.7.2 Example of Python Callback on Core_Log_Message_Extended Hap

This example shows how to add a callback to the Core_Log_Message_Extended. This allows for better control when handling log messages. This example writes all log messages to a file that is associated with the cpu object.

Note: The Core_Log_Message_Extended hap will only be triggered for messages with log level less than or equal to the log level setting of the object.
from simics import *

class file_log:

    def __init__(self, fname, obj, level):
        # setup logging
        try:
            self.f = open(fname, 'w')
        except Exception as msg:
            raise Exception("Failed to open file %s, %s" % (fname, msg))
        self.obj = obj
        self.level = level

        # install the callback
        SIM_hap_add_callback_obj(
            "Core_Log_Message_Extended", obj, 0, self.log_callback, None)

    def log_callback(self, not_used, obj, log_type, message, level, group):
        type_str = conf.sim.log_types[log_type]
        if level <= self.level:
            self.f.write("[%s %s] %s,  level=%d, group=%d\n" % (
                    obj.name, type_str, message, level, group))

file_log('log_cpu.out', conf.board.mb.cpu0.core[0][0], 1)

5.3 Targets and parameters 6 Configuration and Checkpointing