37 Transactions VII Extending Simics
Model Builder User's Guide  /  VI Simics API  / 

38 Checkpoint Compatibility

As your device models evolve, you may feel the need to change attributes or devices in ways that might prevent older checkpoints from loading correctly. To mitigate this problem Simics includes an API that supports handling checkpoint compatibility while keeping the freedom of changing your models as you deem necessary.

Checkpoint compatibility is based on the concept of build-id:

As an example, Simics has a single build-id for each distributed build (it started at 1500 for the Simics 3.2 release). This build-id is used to update checkpoints when standard Simics models are changed.

When loading an old checkpoint, Simics will look at the build-id that each object claims. Simics will then run the appropriate update functions until all objects in the checkpoint are up-to date with the current models. Let us have a look at some examples.

Simics maintains backward compatibility for checkpoints one major version older than the oldest of the currently supported API versions. Older checkpoints might load successfully anyway. If they fail to do so, however, it is possible to update them in steps by using intermediate versions of Simics where compatibility was still maintained.

38.1 First Example: A Simple Case

Assume that you have modeled a device with, among other properties, three pins called pin0, pin1 and pin2. The first version of this model was delivered at build-id 100 (an arbitrarily chosen number). A checkpoint with this device would look like:

OBJECT foo TYPE my_device {
        build_id: 100
        pin0: 1
        pin1: 0
        pin2: 1
        […]
}

A few months later, you extend that model to handle more pins, to the point where it would make more sense to have a single pins attribute where bit 0 would represent the state of pin0, and so on. The new checkpoint at build-id 110 is the following:

OBJECT foo TYPE my_device {
        build_id: 110
        pins: 0x5            # 0101b
        […]
}

So far so good, but older checkpoints still contain the three pinn attributes so they will fail to load with the newer model. To preserve compatibility, you need to write an upgrade function that will convert the three pin attributes to the new pins attribute layout. In the global commands file, simics_start.py, of your device model, write the following:

from configuration import *
from update_checkpoint import *

def upgrade_pin_to_pins(obj):
    obj.pins = (obj.pin2 << 2) | (obj.pin1 << 1) | obj.pin0
    remove_attr(obj, "pin0")
    remove_attr(obj, "pin1")
    remove_attr(obj, "pin2")

SIM_register_class_update(110, "my_device", upgrade_pin_to_pins)

The first two lines will import the necessary classes and functions from Simics. The upgrade_pin_to_pins() function takes an object of class my_device and computes the new pins attribute from the values of pin0, pin1, and pin2. It then removes the obsolete attributes, leaving the object in a valid state for build-id 110. The last function call registers the upgrade_pin_to_pins() function as a class upgrade function, to be run for all objects of class my_device with a build-id lower than 110.

When loading a checkpoint with objects of class my_device with a build-id inferior to 110, Simics will loop over all matching objects to apply upgrade_pin_to_pins() and make sure that they are all updated.

38.2 Second Example: Changing the Class of an Object

Although this should be a rare occurrence, you might want to change the class of an object. This can be done in a class upgrade function as above, by setting the __class_name__ attribute:

from configuration import *
from update_checkpoint import *

def upgrade_to_new_device(obj):
    obj.__class_name__ = "my_new_device"

SIM_register_class_update(120, "my_device", upgrade_to_new_device)

Do not forget that when an object class has changed, no upgrade function registered on the former class will apply any longer.

38.3 Third Example: Creating New Objects

Class upgrade functions are usually enough for simple changes, like adding or removing an attribute, but you might need to perform more changes to the checkpoint before it can be loaded.

As an example, let us take a two-port Ethernet controller modeled in a single class:

OBJECT foo TYPE two-port-ctrl {
        build-id: 450
        port0_status: 0xCFF
        port0_control: 0x10
        port1_status: 0xC0F
        port1_control: 0x0
        […]
}

Later in the development, you need to improve the Ethernet controller to handle up to four ports. It might then make more sense to have a class per port, rather than a class for the whole controller:

OBJECT foo_port0 TYPE port-ctrl {
        build-id: 480
        status: 0xCFF
        control: 0x10
        […]
}
OBJECT foo_port1 TYPE port-ctrl {
        build-id: 480
        status: 0xC0F
        control: 0x0
        […]
}

Converting older checkpoint is a bit more difficult though, and class upgrade functions do not give you enough control to do this properly. You will need a more generic upgrade function:

from configuration import *
from update_checkpoint import *

def upgrade_two_ports_ctrl(config):
    deleted = []
    changed = []
    added   = []
    
    for obj in all_objects(config, "two-port-ctrl"):
        # build a port 0 object
        obj_port0 = pre_conf_object(obj.name + "_port0", "port-ctrl")
        obj_port0.status = obj.port0_status
        obj_port0.control = obj.port0_control

        # build a port 1 object
        obj_port1 = pre_conf_object(obj.name + "_port1", "port-ctrl")
        obj_port1.status = obj.port1_status
        obj_port1.control = obj.port1_control

        # delete the old object
        del config[obj.name]
        deleted.append(obj)

        # add the two new objects
        config[obj_port0.name] = obj_port0
        config[obj_port1.name] = obj_port1
        added.append(obj_port0)
        added.append(obj_port1)

    return (deleted, changed, added)

SIM_register_generic_update(480, upgrade_two_ports_ctrl)

A generic upgrade function is called with the complete set of configuration objects constituting the checkpoint as argument. The variable config is a Python dictionary indexed by the object names. The function is required to keep track of all changes it does, in three lists representing the objects deleted, changed and added to the configuration.

The function loops over all objects of class two-port-ctrl in config. For each of them, it creates two port-ctrl objects that will represent the port 0 and 1 of the original controller. It updates all necessary attributes, then proceeds to delete the old object from the configuration, before adding the new ones.

When it is finished, the function returns a tuple with the three lists containing the deleted, changed and added objects. The last line registers the upgrade_two_ports_ctrl() function as a generic update function to be run at build-id 480.

38.4 Last Example: Changing the Name of an Object

If you need to change the name of an object, you will be forced to do so in a generic update function, as you will have to update the checkpoint set itself:

from configuration import *
from update_checkpoint import *

def upgrade_to_new_name(config):
    deleted = []
    changed = []
    added   = []
    
    i = 0
    for obj in all_objects(config, "port-ctrl"):
        del config[obj.name]
        obj.name = "new-controller%d" % i
        config[obj.name] = obj
        changed.append(obj)
        i = i + 1

    return (deleted, changed, added)

SIM_register_generic_update(500, upgrade_to_new_name)

Do not forget to report the changed object in the changed object list.

38.5 Upgrade Functions in More Details

38.5.1 Choosing and Setting a Build-Id

Simics has a unique build-id for each distributed build. It does not make sense to follow this setting, as your own development might not match the pace at which new builds of Simics are released.

You can set a custom build-id in a project using the USER_BUILD_ID make variable. USER_BUILD_ID is not a simple number: it is composed of a build-id namespace followed by a build-id. The build-id namespace is there to define which modules are following the same convention for build-id versioning. The build-id sets what the current version is.

For example, assuming you are delivering a complete set of devices to simulate a board, associated to a Simics processor. You might want to set the build-id as company.board_name:0, where company is your company name, and board-name is the current project. The newly defined build-id namespace will start its versioning at 0, and increase for each release.

Simics packages reserve the simics namespace for official packages. Setting USER_BUILD_ID to use the namespace simics will result in an error. Similarly, the namespace __simics_project__ is reserved to disable the user build-id mechanism, when USER_BUILD_ID is left unset.

38.5.2 Checkpoint Format

Objects provided in the configuration set are Python pre_conf_object identical to those handled in components. Each object has a name and classname attribute. Other attributes depend on the checkpoint contents.

New objects can be created using the pre_conf_object Python class. Adding attributes is just a matter of assigning them a value. Other utility functions, described below, are provided for renaming and removing attributes.

The class of an object can be changed by setting the __class_name__ attribute to the name of the new class. Both class and generic functions are allowed to change the class of an object.

The name of an object can be changed by setting the name attribute of the object. However, the update function should make sure that the checkpoint set is updated to contain the object under its new name, so only generic update functions are allowed to perform this change.

38.5.3 Utility Functions

Some common functions are provided by update_checkpoint to make manipulating a checkpoint easy. They are described in detail in the Reference Manual:

38.5.4 Class Upgrade Functions

A class upgrade function is the simplest form of upgrade function. It takes as parameter a pre_conf_object Python object of the class it was registered for.

The function is allowed to change all attributes, including the object class, but not its name. It can not destroy or add new objects, since it does not have access to the complete configuration.

A class upgrade function has no return value. If an error occurs, it can raise a CliError exception.

Class upgrade functions should generally be declared in the simics_start.py of the module in which they are declared.

38.5.5 Generic Upgrade Functions

A generic upgrade function allows for larger changes in the checkpoint. It takes as parameter the complete set of configuration objects. It is called only once, at a time where all functions registered for previous build-ids have already been called.

The function is allowed to perform any operations on the objects of the checkpoint, including deleting them, changing their attributes and adding new objects. It can also rename objects, but it is responsible for update the checkpoint set properly to point at the object under its new name. Renamed objects should be listed in the changed objects list.

The function must return a tuple of three lists: the list of deleted objects, the list of changed objects (only changed attributes) and the list of added objects. If an error occurs, it can raise a CliError exception.

Generic update functions should be declared in a relevant simics_start.py file. If the function is linked to a specific class or module, the global commands file of that module is appropriate. In case of functions dealing with several classes, the function should be declared in the most relevant component's global commands file.

38.5.6 Manipulating Standard Simics Objects

In general, you should refrain from touching objects provided with Simics in a checkpoint, as they have their own upgrade functions and this might create conflicts. However, you may need to add such objects to update your checkpoint, for example, if you wish to add a recorder to one of your models that was not present before. In that case, you must be careful to indicate the Simics build-id of the object you are adding, so future upgrade functions can be applied properly. Let us look at an example:

from configuration import *
from update_checkpoint import *

def get_available_object_name(config, prefix):
    i = 0
    while ("rec%d" % i) in config:
        i = i + 1
    return "rec%d" % i

def add_recorder(config):
    changed = []
    added = []

    port_ctrl_objects = all_objects(config, "port-ctrl")
    if not port_ctrl_objects:
        return ([], [], [])

    recorders = all_objects(config, "recorder")
    if not recorders:
        rec_name = get_available_object_name(config, "rec")
        rec = pre_conf_object(rec_name, "recorder", build_id = 1500)
        config[rec_name] = rec
        added.append(rec)
    else:
        rec = recorders[0]

    for x in port_ctrl_objects:
        if not "recorder" in dir(x):
            setattr(x, "recorder", rec)
            changed.append(x)

    return ([], changed, added)

SIM_register_generic_update(570, add_recorder)

The upgrade function adds a recorder if none exists in the checkpoint, then it makes sure that all objects of class port-ctrl have a recorder attribute pointing at this recorder. Note the code creating the recorder object if none is found: it specifies that the recorder created is of build-id 1500 (corresponding to Simics 3.2.0). This will allow future upgrade functions to be applied correctly to this new recorder object later on.

37 Transactions VII Extending Simics