14 Modeling with C 16 Writing Model Tests
Model Builder User's Guide  /  II Device Modeling  / 

15 Modeling with Python

This section describes modeling devices in pyobj Python module.

15.1 Creating a New Python Device

A new Python device module can be created using the project-setup utility:

This will create skeleton code for a new device in the [project]/modules/my-py-device/ directory, with all files needed to build it as a Simics module.

The entry point for a module written in Python is the file module_load.py, which is executed when the Simics module is loaded. This file is normally small, containing something like:

from . import my_py_device
my_py_device.my_py_device.register()

The rest of the implementation is given in another file, (in this case my_py_device.py).

15.2 The pyobj Python Module

Simics includes the pyobj Python module that greatly simplifies writing device in Python. The module contains all functionality needed for creating a Python device.

The pyobj module contains the ConfObject class that should be used as base class when creating new device. The class contains a lot of useful methods to define a new device.

Here is an example how to create a simple Python device:

import simics
import pyobj
class foo(pyobj.ConfObject):
    """This is the long-winded documentation for this Simics class.
    It can be as long as you want."""
    _class_desc = 'a one-line description of the class'

In the example we create the device class foo. The first string in the class is a long description of the device that can be several sentences. The _class_desc is a short class description beginning with lower case, without trailing dot, and at most 50 characters long. The longer description is used in the help commands and reference manuals, while the shorter description is used for example in the GUI to describe the device.

Upon registration, the newly defined device registered itself as a common Simics class which allows instances of it to be created like any other Simics object. The device will automatically get info and status commands.

Please see the chapter pyobj Python Module in API Reference Manual for more detailed information about the pyobj module.

15.3 ConfObject Class Basic Methods

The ConfObject class is defined in the pyobj Python module. This section describes some of the basic methods in the ConfObject class.

The _initialize method is called when the class is instantiated as a device object. This method is used to define values that must be set before the device defines any attributes or interfaces. This method is rarely defined as very few devices require any values before object definition. A device class that redefines this method must always call the _initialize method in the parent class.

class foo(pyobj.ConfObject):
    """The Foo Python device."""
    _class_desc = 'a foo device'

    def _initialize(self):
        super()._initialize()
        self.my_val = 4711

The _finalize method is called after all attributes of a device object's have been set.

The _initialize and _finalize methods corresponds to the init and finalize fields in the class_info_t struct that is passed to the SIM_create_class function, as documented in the Simics API Reference Manual.

15.4 ConfObject Class Parameters

The ConfObject class in the pyobj module includes parameters that can be set to control the device behavior.

15.5 Attributes

Like all Simics objects, a Python device can have attributes for parametrization or for saving its internal state in checkpoints. This section describes how to create state attributes that store the device state.

A device that inherits the ConfObject class creates a new attribute by defining a new class in the device class that inherits the Attribute class. The Attribute class uses the SIM_register_attribute API function to register the attribute to the class.

class wee(pyobj.Attribute):
    """Documentation for the attribute goes here."""
    attrattr = simics.Sim_Attr_Pseudo
    attrtype = 'i'
    def _initialize(self):
        self.val = 4711
    def getter(self):
        self.val += 3
        return self.val
    def setter(self, val):
        self.val = val

The Attribute has three standard methods; _initialize, getter, and setter. The _initialize method is similar to the _initialize method in the ConfObject class; it is used for setting default values for the attribute. The getter method is called when someone reads the attribute. The setter method is called when someone writes the attribute. The class also has two parameters; attrattr and attrtype. The attrattr parameter defines if the attribute is optional, required, or pseudo, see the attr_attr_t type. The default value of the attrattr parameter is Sim_Attr_Optional. The attrtype parameter defines the type of the attribute; see the type argument to the SIM_register_attribute function in the API Reference Manual.

In the example we choose to store the attribute value in the val member.

Most attributes are simple attributes with uncomplicated functionality. The SimpleAttribute function can be used when defining a simple attribute. The function defines an Attribute class instance and returns it. The first argument to the function is the default value. The second argument, attrtype, is the type of the attribute. The last argument, attrattr, defines if the attribute is optional, required, or pseudo. As attributes are optional by default, the last argument can be left out.

class woot(pyobj.SimpleAttribute(0, 'i|n')):
    """A four-letter attribute"""

Note that the attribute value will be automatically stored in the val parameter when using the SimpleAttribute function.

15.6 Class Attributes

The pyobj.ClassAttribute class defines an attribute that will be registered for the containing ConfObject class. The attribute will be registered with Simics using the SIM_register_class_attribute function. See documentation for SIM_register_class_attribute for detailed information about class attributes.

The value stored in the class should always be stored in the attribute named val. This is to avoid problems when a class that defines a pyobj.Attribute class is inherited by more than one class.

class wee(pyobj.ClassAttribute):
    """Documentation for the attribute goes here."""
    attrtype = 'i'
    val = 4711
    @classmethod
    def getter(cls): return cls.val
    @classmethod
    def setter(cls, val): cls.val = val

The pyobj.ClassAttribute class is very similar to the pyobj.Attribute class. See the documentation for the pyobj.Attribute class for how to use this class.

15.7 The _up Member

The device class can implement attributes and interfaces. This is done by adding class within the device class. For example an Attribute class can be added to the device class.

class foo(pyobj.ConfObject):
    """The Foo Python Device."""
    _class_desc = 'a foo device'

    def _initialize(self):
        super()._initialize()
        self.my_val = 4711

    class lost(pyobj.Attribute):
        """A pseudo attribute"""
        attrattr = simics.Sim_Attr_Pseudo
        def getter(self):
            return self._up.my_val

The foo contains an attribute lost. To access a class's value or containing class the _up member is used. In the example the _up member makes it possible to access the my_val field from within the lost attribute.

15.8 Interfaces

The pyobj.Interface class implements a Simics interface for the containing ConfObject class. The interface is registered using the SIM_register_interface function. The interface name is taken from the class name.

class signal(pyobj.Interface):
    def signal_raise(self): self.val = True
    def signal_lower(self): self.val = False
    def _initialize(self): self.val = False

The _initialize method can be overridden if special initialization behavior is required.

To implement port interfaces instead of regular interfaces, place one or more pyobj.Interface subclasses inside a pyobj.Port class.

In Python, you can use the iface attribute of a configuration object to access the interface directly:

val = conf.phys_mem.iface.memory_space.read(conf.cpu0, 0x1234, 4, 0)

15.9 Port Objects

If your device needs to provide multiple implementations of the same interface it should use port objects. More general information about port objects can be found in 34.

Below is an example of how port objects are registered in Python using the pyobj framework:

import pyobj, simics

class myclass(pyobj.ConfObject):
    # Define the port object 'myobj.port.RESET'
    class RESET(pyobj.PortObject):
        class signal(pyobj.Interface):
            def signal_raise(self):
                print("signal_raise")

    # Define the port object 'myobj.bus_clock'
    class bus_clock(pyobj.PortObject):
        namespace = ""
        classname = "cycle-counter"

    def _initialize(self):
        super()._initialize()
        simics.SIM_set_attribute_default(
            self.obj.bus_clock, "frequency", 1E6)

15.10 Events

pyobj.Event defines an event that will be registered for the containing ConfObject class. Internally, registration is done with SIM_register_event; see the documentation for that API function for detailed information.

Events are posted with the post(clock, data, <duration>) method. clock determines which clock the event is posted on, and data is the event data. The duration is the number of seconds, cycles, or steps until the event triggers, specified with the appropriate keyword argument:

ev.post(a_clock, some_data, seconds = 4.711)
ev.post(a_clock, some_data, cycles = 4711)
ev.post(a_clock, some_data, steps = 4711)

Events can be cancelled before they trigger with either cancel_time(clock, match_fun) or cancel_step(clock, match_fun) (depending on whether the event duration was specified in steps or not). The match_fun argument is optional: if given, it should be a function that accepts an event data parameter, and returns true for the events that should be cancelled; if not given, all events are cancelled.

A subclass may define the following methods:

Additionally, it may set the flags parameter to Sim_EC_Notsaved, if the event should not be checkpointed. In this case, neither get_value nor set_value should be defined.

Here is an example for different kinds of events.

class foo(pyobj.ConfObject):
    class ev1(pyobj.Event):
        def callback(self, data):
            do_something(data)
    class ev2(pyobj.Event):
        def callback(self, data):
            self.do_something_else(data)
        def get_value(self, data):
            return str(data)
        def set_value(self, val):
            return int(val)
        def describe(self, data):
            return 'ev2 with %s' % data
    class ev3(pyobj.Event):
        flags = simics.Sim_EC_Notsaved
        def callback(self, data):
            self._up.do_this_third_thing(data)
14 Modeling with C 16 Writing Model Tests