Most Simics modules contain some code written in Python. The most common uses of Python are to extend Simics with new CLI commands, as discussed in chapter 13, and to write component modules, as discussed in chapter 4.4.
Python and Simics use conflicting nomenclature around the module concept. A Python module is a single Python file, and a Python package is a directory that contains multiple related Python modules. A Simics module can contain multiple Python files; thereby, it roughly corresponds to a Python package. Furthermore, a Python package should not be confused with a Simics add-on package.
Python code is added to a Simics module by adding filenames to the PYTHON_FILES
variable in the module's Makefile
. The listed files are compiled into .pyc
files, placed in the directory [host]/lib/python/simmod/simics_module_name
, where simics_module_name is the name of the Simics module, with hyphens replaced with underscores. The .pyc
files are then available as submodules of the simmod.simics_module_name
Python package. As an example, consider a Simics module named my-module
, which contains the following in its Makefile
:
PYTHON_FILES = common.py module_load.py simics_start.py
The Python module defined by common.py can then be imported from Simics as follows:
import simmod.my_module.common
Two filenames in PYTHON_FILES
have special meaning:
simics_start.py
will be automatically loaded when Simics starts. module_load.py
will be automatically loaded when the Simics module is loaded. In addition, the filename checkpoint_update.py
is reserved for future use, and not permitted.
It is possible to create a nested directory structure in PYTHON_FILES
. This works as in standard Python; i.e., each subdirectory must contain a file __init__.py
. Thus, the following will create a subpackage sub
with a submodule common
:
PYTHON_FILES = sub/__init__.py sub/common.py
The Python module sub/common.py
can then be imported as follows:
import simmod.my_module.sub.common
Module tests that are written in Python should not be included in the PYTHON_FILES
variable; the test framework automatically finds and runs tests directly under the module's source directory.
Before the PYTHON_FILES
variable was supported, there were two different mechanisms for using Python in modules. Both mechanisms are deprecated, and will be removed in future Simics versions. They are briefly explained here because they may appear in existing source code:
gcommands.py
and commands.py
are found in the same directory as the module's Makefile
, then they are run when Simics is started and when the module is loaded, respectively. This mechanism is only supported for modules with SIMICS_API
set to 4.8 or older. SRC_FILES
variable, one Simics module is created for each such source file; module metadata is declared using Python comments on certain forms. It is an error to both specify PYTHON_FILES
and list Python files in SRC_FILES
. When a Python module imports another Python module in the same Simics module, the recommended practice is to use an explicit relative import:
from . import common
from .common import utility
or, alternatively, to use the more verbose absolute import:
import simmod.my_module.common
from simmod.my_module.common import utility
Python permits a third variant of imports, called implicit relative imports, which should be avoided in this case:
import common # BAD!
This notation is bad because an implicit relative import can easily be mistaken for an absolute import. Implicit relative imports have been removed in recent versions of the Python language.
There are two ways to share Python code between modules. In most cases, the best way is to list the shared file in the PYTHON_FILES
variable of all modules. Use the EXTRA_MODULE_VPATH
variable (see 3.5.3) to make sure the file is found by Make. One copy of the shared Python file will be created in each Simics module using it, and each copy will act as an independent Python module in Simics.
Alternatively, a single instance of a Python module can be shared between multiple Simics modules using absolute imports. While this approach may appear simpler, it is also risky, because it places very high compatibility requirements on the ABI exposed by the shared module. Using an absolute import to access shared code from a different module is therefore discouraged in general, and should only be done if you fully understand the consequences. The following example demonstrates how a seemingly innocent change can cause compatibility problems:
Let's say that a configuration attribute is added in a new version of a model A, and that a CLI command defined in shared code reads from this attribute. Furthermore, let's say that the shared code is also used by a model B, distributed in a different Simics add-on package, and that the shared code is distributed in both packages. Now, suppose that we use an old version of package A, where the new attribute has not been added, together with a new version of package B, where the attribute has been added. Simics will pick the version of the shared code from B for both modules; this means that the CLI command defined by the shared code will try to access a nonexisting attribute, causing unintended errors.
It is generally considered good design to avoid side-effects when merely importing a Python module. In Simics, the module_load
and simics_start
modules must have side-effects, usually in the form of class or command registration, in order to be meaningful. However, for large Simics modules it is recommended to keep these files small, and keep most implementation in other Python files. For example, let's say a module defines info
and status
commands for the three devices my-uart
, my-pic
and my-timer
. Then it can make sense to define command callbacks in a separate file, say command_defs.py
:
def uart_status(obj): […]
def uart_info(obj): […]
def pic_status(obj): […]
def pic_info(obj): […]
def timer_status(obj): […]
def timer_info(obj): […]
def register_commands(prefix):
cli.register_status_command(prefix + 'uart', uart_status)
cli.register_info_command(prefix + 'uart', uart_info)
cli.register_status_command(prefix + 'pic', pic_status)
cli.register_info_command(prefix + 'pic', pic_info)
cli.register_status_command(prefix + 'timer', timer_status)
cli.register_info_command(prefix + 'timer', timer_info)
Now module_load.py
can be implemented as follows:
from . import command_defs
command_defs.register_commands('my-')
One advantage of this approach is code reuse: If another Simics module your-module
contains models of similar hardware, say your-uart
, your-pic
and your-timer
, then the command_defs.py
file can be shared between the Simics modules. After editing the PYTHON_FILES
and EXTRA_MODULE_VPATH
variables in the Makefile
of your-module
, as discussed in section 12.2.2, module_load.py
can be implemented just like in my-module
:
from . import command_defs
command_defs.register_commands('your-')
Note that the register_commands
function may have to be parameterized differently, depending on how the your-
and my-
models differ.