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.
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.
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.
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.
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.
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.
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.
Some common functions are provided by update_checkpoint
to make manipulating a checkpoint easy. They are described in detail in the Reference Manual:
all_objects()
, for_all_objects()
remove_attr()
, rename_attr()
remove_class_attr()
remove_class()
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.
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.
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.