11 Defining New Interface Types 13 Adding New Commands
Model Builder User's Guide  /  II Device Modeling  / 

12 Using Python in a Simics module

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:

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.

12.1 Python in older versions of Simics

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:

12.2 Recommendations on style

12.2.1 Importing Python modules within a Simics module

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.

12.2.2 Sharing Python code between Simics modules

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.

12.2.3 Side-effects in modules

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.

11 Defining New Interface Types 13 Adding New Commands