This section describes modeling devices in pyobj
Python module.
A new Python device module can be created using the project-setup
utility:
Windows
project> bin\project-setup.bat --py-device my-py-device
Linux
project$ ./bin/project-setup --py-device my-py-device
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
).
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.
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.
The ConfObject
class in the pyobj
module includes parameters that can be set to control the device behavior.
_class_desc
The device's short class description describes what the device does or is. The description is showed in help commands and in the GUI. This is the string passed as short_desc to SIM_create_class
.
_do_not_init
Set it to object()
to make sure the class is not registered as a Simics class. Every class inheriting the ConfObject
class, either directly or indirectly, will be registered as a Simics class. Sometimes it is useful to create a base class that is not registered as a Simics class, but is used only as a base class for classes with similar functionality. This means that no object can be created of that class.
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.
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.
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.
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)
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)
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:
callback(data)
Called when the event triggers. Overriding this method is not optional.
destroy(data)
Called when the event is removed from the queue without being called. The method is not allowed to use any event API calls; it is mainly intended for freeing event data.
get_value(data)
and set_value(val)
Converts the given event data to an attr_value_t
value, and the other way around. If the event carries no data that needs checkpointing, you may omit these methods.
describe(data)
Called to generate a human-readable description of the event to be used in the print-event-queue command. If you do not supply this method, the event's name will be used.
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)