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.
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>
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.
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] = (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.
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 method object at 0x5cb2070> simics> @hex(conf.board.mb.cpu0.core[0][0].iface.processor_info .get_program_counter()) '0xfff0'
conf_object_t *
argument
for interface methods should not be used: it is passed automatically.
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);
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);
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. signal.signal_raise <built-in method void (*)([conf_object_t * NOTNULL]) of interface method object at 0x5cb22d0> simics> @conf.board.mb.cpu0.core[0][0].ports.RESET. 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);
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()
.
_cmd
suffix is
appended to the wrapper function name, and the function parameter gets
the _
suffix.
@help(conf.sim.cli_cmds.info)
. The docstrings complement the CLI
command documentation (to access the documentation of a command one can
run "help command-name" command at Simics CLI).
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.
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)
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.
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.
A complete reference of the haps available in Simics can be found in the Simics Reference Manual.
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] .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, 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: " + ((hex ($cpu.read-reg ia32_feature_control))) } }
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.
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)