Previous - Up - Next

12.3   Implementing Components

12.3.1   Component Class

A component file contains Python classes that define all the component classes in a component collection. These classes inherit from the component base class component_object, or from one of its sub classes.

Component classes are registered with Simics using the register_component_class() function. When a component class is registered, a corresponding Simics class is created with the supplied list of attributes. The Simics class uses the name stored in the classname data member of the Python class.

def register_component_class(python_class,
                             attributes, checkpoint_attributes = [],
                             top_level = False)
The first argument, python_class, is the Python component class, attributes is a list of attributes that can be used to configure the component at creation time. All attributes in this list will be available as command arguments in the create- and new- CLI commands. The format of each attribute list entry is [<name>, <kind>, <type>, <description>]. See the documentation of SIM_register_typed_attribute() for an explanation of these entries.

The checkpoint_attributes is a list with the same format as attributes. The difference is that checkpoint_attributes is only intended for attributes representing internal state that has to be checkpointed. The attributes in this list are not visible as arguments to the creation commands. This means that the attributes can not be of the pseudo or required kinds.

The last argument, top_level, should be set to True for top level components; the default value is False. The register function will add some additional attributes for top level components, and also check that the component class implements all top-level-only methods.

12.3.2   Component Class Data Members

There are a number of data members required in the Python component class that are used by the generic component system.

The name of the Simics configuration class representing the component class.
Default name for instances of this class if none is given when the user creates a component. The complete default instance name is <basename>_cmp<number>.
A description of the component class. This will be the help text for the configuration class in Simics.
A list of all connectors for components of this class. The format of the list is described in the next sub-section.

Example of a component class:

class ide_disk_component(component_object):
    classname = 'std-ide-disk'
    basename = 'ide_disk'
    description = ('The "std-ide-disk" component represents an IDE disk.')
    connectors = {
        'ide-slot' : {'type' : 'ide-slot', 'direction' : 'up',
                      'empty_ok' : False, 'hotplug' : False, 'multi' : False}}

    ... <class method code removed> ...

    [['size', Sim_Attr_Required, 'i',
      'The size of the IDE disk in bytes.'],
     ['file', Sim_Attr_Optional, 's',
      'File with disk contents for the full disk Either a raw file '
      'or a CRAFF file.']])

12.3.3   Defining Connectors

The connectors member is a dictionary indexed by connector name. For each connector there is another dictionary describing it with the keywords type, direction, empty_ok, hotplug and multi:

The name of the connector type. The connector can only be connected to other connectors of the same type.
One of up, down and any. Defines the direction of the connector. A connector can only be connected with connectors of a different direction; this is checked by the generic component connection code.
True if the connector may be empty after instantiation. The component system will not allow instantiation of components that have empty connectors with empty_ok set to False.
True if the connector supports connecting and disconnecting after the component has been instantiated. A hotplug connector must have empty_ok set to True. A component where all connectors are either hotplug or empty_ok is called a standalone component.
True if the connector supports multiple connections. A few connectors allow several other connectors to be connected to them. One such example is the ISA bus in legacy PC systems.

12.3.4   Required Class Methods

There are a few class methods that have to be implemented by the component class since they are used by the base class when the component is created:

Responsible for adding pre-configuration objects to the component. The pre-configuration objects are Python objects that represent the configuration objects before the component has been instantiated and the actual configuration objects are created. The pre-configuration objects allow attributes to be set without any side-effects and simplifies adding and removing objects before the instantiation. A more detailed description of how to add objects follows in the 12.3.7 section. This method is not called when a checkpoint is loaded, since then the object references will be set with real objects directly.
Adds dynamic information about all connectors, such as what objects that are involved in the connection and other connector specific data, for example the slot number for PCI buses. This dynamic connector information is sent as arguments to the corresponding connect method (12.3.9) in the component being connected to. The information is stored in the connector_info data member that is a dictionary indexed with the connector name, and described in section 12.3.8.
connect_(self, connector, )
For each connector type that the component implements, there must be a corresponding connect method. The second argument is the name of the actual connector, and any following arguments are connector-type specific and provided by the add_connector_info() method of the other component in the connection. If the connector has hot-plug support, it must support connections for both the instantiated and non-instantiated component.
disconnect_(self, connector)
For each connector type that has hot-plug support that the component implements, there must be a corresponding disconnect method. The second argument is the name of the actual connector. The method must support disconnections for both the instantiated and non-instantiated component.

12.3.5   Optional Class Methods

There are several methods that have a default implementation in the base component class, but that the component sub class may override.

__init__(self, parse_obj)
The standard Python object constructor. Called when the object is created, it may be used to initialize some state before any other method in the object is called. If this method is implemented, it has to call the __init__() method of the parent class.
Called when the component object in Simics has been created. The first thing finalize_instance(self) in the base class does is to call add_objects() if the component is not instantiated. If this method is implemented, it has to call the finalize_instance() method of the parent class.
Called when the component is instantiated. Note that this method is not called when loading a checkpoint with already instantiated components. If this method is implemented, it has to call the instantiation_done() method of the parent class.

12.3.6   Top-level Class Methods

Top-level components need two additional methods that are used by the component system to handle clock domain configuration and to keep track of all processors in the component hierarchy. There is also one optional method that is used to restore the processor list from a checkpoint.

Should return the processor object that is the default clock in the component hierarchy, that is, in the top-level component and all components below it. All configuration objects within this component hierarchy, also called a timing domain, will have the same clock object set as queue.
Similar to get_clock() but should return a list of all processors in the component hierarchy.
set_processors(self, cpu_list)
Optional method that is called when a checkpoint is loaded with a list of all processors previously returned by get_processors(). If this method is not implemented, the component should make sure that that it can recreate the processor list after a checkpoint for use in get_processors().

12.3.7   Adding Configuration Objects

When the component object has been created, the add_objects() method is called. Its responsibility is to create pre-configuration objects representing the configuration objects that implement the functionality of the component. The pre_obj() Python class is typically used instead of pre_conf_object(), since it provides improved name handling, as described in 12.3.11.

The pre-configuration objects should be added to the o namespace of the component. When the component later is instantiated, all object references in this namespace are converted to configuration objects.

The o namespace may contain simple pre-configuration objects, and also arrays of objects.

Example from a Serengeti processor board component:

def add_objects(self):
    for i in range(self.num_cores):
        self.o.mmu[i] = pre_obj('mmu(x + %d)' % i, self.mmuclass)
        self.o.cpu[i] = pre_obj('cpu(x + %d)' % i, self.cpuclass)
        self.o.cpu[i].processor_number = get_next_cpu_number()
        self.o.cpu[i].mmu = self.o.mmu[i]
        self.o.cpu[i].control_registers = [['mid', i]]
        self.o.cpu[i].freq_mhz = self.freq_mhz

After instantiation, the o namespace will contain references to the newly created Simics configuration objects instead.

12.3.8   Providing Connection Information

For each connector, some information must be supplied to the other component of the connection. This usually includes a configuration object that implements a Simics interface, which is needed by some configuration object in the other component. The information is stored in the connector_info data member of the component, and passed to the other component in the call to its connect method. The connector_info dictionary is indexed by connector name. This dictionary should be filled in by the add_connector_info() method that is called directly after add_objects(), and is also called when restoring an instantiated component from a checkpoint.

The format of the values in the connector_info dictionary is a list in the common case. It may be a tuple as well, described in 12.3.10. The length of the list, and the types of the values, depends on the kind of the connector. The up and the down part of a connector may have different arguments in the list.

At instantiation, any pre-configuration references in connect_info will be converted to configuration object references.

A dual-function PCI device with two SCSI controllers may have the following add_connector_info() method. The first controller is implemented by configuration object sym0 and the second by sym1.

def add_connector_info(self):
    self.connector_info['pci-bus'] = [[[0, self.o.sym0], [1, self.o.sym1]]]
    self.connector_info['scsi-bus0'] = [self.o.sym0, 7]
    self.connector_info['scsi-bus1'] = [self.o.sym1, 7]

For the pci-bus connector type, the argument is a list with PCI function number and object implementing the PCI_DEVICE_INTERFACE interface. The scsi-bus connector type have two arguments, the object implementing the SCSI_TARGET_INTERFACE and the initial SCSI ID of the component on the SCSI bus.

12.3.9   Connection Methods

For each connector type that a component implements, it must also have a corresponding connect_<connection-type>() method.   Arguments

The first argument (excluding the Python self reference) to the connect method is the name of the connector. The following arguments come from the connector_info list of the other component in the connection. The connector method typically sets attributes in the objects of the component that are related to the connection.

In the example in the previous section, the connector_info for an up-directed pci-bus connector in a PCI device was shown. The corresponding connect method in the component that has a down-directed pci-bus connector may look like the following. The component in the example has four pci-bus connectors, called pci-slot0, etc.

def add_connector_info(self):
    for i in range(4):
        self.connector_info['pci-slot%d' % i] = [i, self.o.pcibus]

def connect_pci_bus(self, connector, device_list):
    slot = self.connector_info[connector][0]
    bus = self.connector_info[connector][1]
    for dev in device_list:
        bus.pci_devices += [[slot, dev[0], dev[1]]]
This method first reads some data for the components own connector, to know what PCI slot and bus object it has associated with the connector. It then iterates over device_list, that is the list with PCI functions and objects from the previous example. This list, together with the PCI slot number, is used to set the pci_devices attribute in the bus object.

A connect method should make as few assumptions as possible about the objects that are passed to it. This makes it easier for developers to add new components that use the same standard connector definitions, so that they can be connected to existing components.   Method Invocation Time

The connect methods are called when the components are instantiated, and not when the CLI command <component>.connect, or connect-components, is invoked. This simplifies coding the connect methods, since they are only called once, and implementing disconnection of components before instantiation is simpler.   Method Invocation Order

The connect methods are called starting from the top-level component and working down the hierarchy. The connect method in the lower component of a connection is called while traversing down the hierarchy downwards. When the leaves have been reached, the connect methods are called in reverse order back up the hierarchy. This way components may pass data down the hierarchy which can be used to by lower components to determine what data to pass up the hierarchy.   Connect Failures
A connect method may not fail. If a connection isn't possible, it should be prevented by the standard connector mechanisms, such as connector type and direction checking. In cases where this isn't enough, it is also possible to have a connector check method that is called before the connect method. This is described in section 12.3.10.   Hotplug Support
For connectors that support hotplugging, there must also be a disconnect method. It is similar to the connect method, but only takes the connector name as argument. This method should restore all object attributes related to the connection to their initial state. To properly support hotplugging, the connect and disconnect methods also have to handle the case of objects in the o namespace being real Simics configuration objects and not pre-configuration ones.

Note: Components may only be connected to other components sharing the same instantiation state, i.e., instantiated components may only be connected to other instantiated components, and non-instantiated components may only be connected to other non-instantiated components.

12.3.10   Disallowing Connections

Sometimes the generic connector mechanism does not provide sufficient support for determining when a connection is allowed or not. For example, it may require device specific knowledge. To solve this, the component may have a check method that, unlike the connect method, is called when the connect command is invoked. The check method is called with the same arguments as the connect method by default. Optionally the other component may supply a tuple of two different argument lists. Then the first list is supplied to the connect method, and the second list to the check method.

Check methods are often used for connectors that support multiple connections, i.e. they have the multi attribute set to True. One example is the ISA bus component that has a multi-connector where the check method makes sure that ports of the attached devices do not collide.

If a check method determines that a connection is not allowed, it should raise a Python exception with a message saying why the connection failed.

12.3.11   Naming of Configuration Objects

Components set the names of configuration objects when the corresponding pre_obj object is created, for example:

self.o.cpu = pre_obj('cpu', 'x86-p2')
The x86-p2 processor object will get the name cpu unless there already exist another object with the same name. In that case, an underscore and a unique number will be appended to the name, for example cpu_0 for the first duplicate and cpu_1 for the second.   Automatic Object Name Numbering

The component can decide where in the name the unique number should appear by including a $ sign in the name. Underscores are not added in that case.

self.o.cpu = pre_obj('cpu$', 'x86-p2')
self.o.tlb = pre_obj('cpu$_tlb', 'x86-tlb')
The first set of objects created by this code will be called cpu0 and cpu0_tlb, and the second cpu1 and cpu1_tlb. As with the _<number> suffix, the number for the name is assigned when the pre_obj object is created.   User-defined Object Name Numbering

In some cases, a simple automatic numbering of objects is not sufficient, but object names are still unknown at creation time. A typical example is a large server with processor boards and a backplane, where processors should be numbered depending on what slot of the backplane the processor board is connected to. The component system solves this by allowing simple parentheses-enclosed expressions in the object name, where x is replaced with a supplied value. This expression can be re-evaluated at anytime until the component is instantiated, by calling the rename_component_objects() method.

The following example creates four processors, numbered from 4 up to 7. The non-evaluated name is cpu(x + 0), cpu(x + 1), etc. When rename_component_objects() is called, x is replaced by 4, and the final object names will be cpu4, cpu5, etc.

for i in range(4):
    self.o.cpu[i] = pre_obj('cpu(x + %d)' % i, 'x86-p2')


Previous - Up - Next