Attributes are linked to the class definition, usually just after the class has been declared, with the SIM_register_typed_attribute() function. It has the following declaration in C, and Python:
// in C int SIM_register_typed_attribute( conf_class_t *cls, const char *name, get_attr_t get_attr, lang_void *user_data_get, set_attr_t set_attr, lang_void *user_data_set, attr_attr_t attr, const char *type, const char *idx_type, const char *doc); # in Python SIM_register_typed_attribute(cls, name, get_attr, user_data_get, set_attr, user_data_set, attr, type, idx_type, doc)
The parameters of SIM_register_typed_attribute() are:
The configuration type of an attribute must be selected from one of the following values:
Attributes may also have the following additional kinds added (using a bitwise or operation).
The index type, if any, is added (using a bitwise or operation) with one or several of the following values:
In addition the order in which the attribute will be initialized can be defined by adding (also using a bitwise or operation) with one of the following values:
Attributes with Sim_Init_Phase_1 will be initialized after attributes with Sim_Init_Phase_0, but no other order is guaranteed.
Let us use a simple counter attribute as an example.
In C, we'll have an object declared as:
typedef struct my_object { conf_object_t obj; int foo; } my_object_t;
In Python, we'll use a data member of the class defining the object (sample-python-class) that we will call foo.
We want to implement an attribute called counter, thus we need a pair of set/get functions. counter will internally use foo to keep its value. The pair of get/set functions could be defined as:
// In C static attr_value_t get_counter(void *arg, conf_object_t *obj, attr_value_t *idx) { my_object_t *mo = (my_object_t *)obj; return SIM_make_attr_integer(mo->foo); } static set_error_t set_counter(void *arg, conf_object_t *obj, attr_value_t *val, attr_value_t *idx) { my_object_t *mo = (my_object_t *)obj; mo->foo = val->u.integer; return Sim_Set_Ok; }
In the get_counter() function, obj is the object that owns the attribute and arg is the user information that was registered along with the attribute. Note that obj can be safely cast to my_object_t (conf_object_t is used as a "base type" here). The function creates an attr_value_t variable that will be of type Sim_Val_Integer and contain the value foo. It then returns this attribute value.
The set_counter() function on the other hand takes a val argument which contains the value to be written. The return value is of type set_error_t, which is defined as below. Descriptions of the values can be found in the Simics Reference Manual.
typedef enum { Sim_Set_Ok, Sim_Set_Need_Integer, Sim_Set_Need_Floating, Sim_Set_Need_String, Sim_Set_Need_List, Sim_Set_Need_Dict, Sim_Set_Need_Boolean, Sim_Set_Need_Data, Sim_Set_Need_Object, Sim_Set_Object_Not_Found, Sim_Set_Interface_Not_Found, Sim_Set_Illegal_Value, Sim_Set_Illegal_Type, Sim_Set_Attribute_Not_Found, Sim_Set_Not_Writable, Sim_Set_Ignored } set_error_t;
In Python, the value used in the get()/set() functions is represented in Python's native types:
# In Python def get_counter(arg, obj, idx): return obj.object_data.counter def set_counter(arg, obj, val, idx): obj.object_data.counter = val return Sim_Set_Ok
Note, however, that the object is referred to by the log object, and not the object created from the new_instance() function that creates object instances. The data of the object is thus referred to via the object_data member of the obj parameter.
Registering the counter attribute is just a matter of calling SIM_register_typed_attribute():
// in C SIM_register_typed_attribute(my_class, "counter", get_counter, NULL, set_counter, NULL, Sim_Attr_Required, "i", NULL, "A counter"); # and in Python SIM_register_typed_attribute(my_class, "counter", get_counter, None, set_counter, None, Sim_Attr_Required, "i", None, "A counter");
In the previous example, the attribute counter was a direct representation of the value foo inside the object. Now let us add an attribute called add_counter that will increase foo by a given value when the attribute is set, and do nothing when the attribute is read. This would give us the following code:
// In C static set_error_t set_add_counter(void *arg, conf_object_t *obj, attr_value_t *val, attr_value_t *idx) { my_object_t *mo = (my_object_t *)obj; mo->foo += val->u.integer; return Sim_Set_Ok; } # and in Python def add_counter(arg, obj, val, idx): obj.object_data.counter += val return Sim_Set_Ok
There is no need for a get function since this attribute only can be written. The semantics of set_add_counter() are also slightly different, since the function actually adds a value to foo.
It is thus possible to create real attributes whose value corresponds to a real variable in an object, and pseudo attributes which are only used as object "methods".
Registering the add_counter attribute is straightforward:
// In C SIM_register_typed_attribute(class_name, "add_counter", NULL, NULL, set_add_counter, NULL, Sim_Attr_Pseudo, "i", NULL, "A sample pseudo attribute.") # and in Python SIM_register_typed_attribute(class_name, "add_counter", None, None, add_counter, None, Sim_Attr_Pseudo, "i", None, "A sample pseudo attribute.")
If an attribute advertises the possibility of being indexed, Simics will allow access using an index of a type specified when registering the attribute: an integer, a string and/or a list. In Python, a slice is also accepted and will be converted to a list as described later in this section. Using indexed attributes in Python would correspond to the following code:
object.attr[i] = a b = object.attr['a string'] c = object.attr[i:j] d = object.attr[[i,j]]
When an indexed access is performed, the idx parameter of the get()/set() functions contains the index value. In C this value will be coded as an attr_value_t representing an integer, a string or a list. In Python, the index will be the corresponding native python type.
Let us add an array of counters to our previous example:
// In C typedef struct my_object { conf_object_t obj; int foo[10]; } my_object_t;
The get() and set() function will be:
// In C static attr_value_t get_counter_array(void *arg, conf_object_t *obj, attr_value_t *idx) { my_object_t *mo = (my_object_t *)obj; if (idx->kind != Sim_Val_Nil) { if (idx->u.integer < 0 || idx->u.integer >= 10) return SIM_make_attr_invalid(); return SIM_make_attr_integer(mo->foo[idx->u.integer]); } else { attr_value_t ret = SIM_alloc_attr_list(10); int i; for (i = 0; i < 10; i++) { ret.u.list.vector[i] = SIM_make_attr_integer(mo->foo[i]); } return ret; } } static set_error_t set_counter_array(void *arg, conf_object_t *obj, attr_value_t *val, attr_value_t *idx) { my_object_t *mo = (my_object_t *)obj; if (idx->kind != Sim_Val_Nil) { if (idx->u.integer < 0 || idx->u.integer >= 10) return Sim_Set_Illegal_Value; mo->foo[idx->u.integer] = val->u.integer; } else { int i; for (i = 0; i < 10; i++) mo->foo[i] = ret.u.list.vector[i].u.integer; } return Sim_Set_Ok; } # And in Python: def get_counter_array(arg, obj, idx): if idx != None: if idx < 0 or idx >= 10: return None return obj.object_data.vcounter[idx] else: return obj.object_data.vcounter def set_counter_array(arg, obj, val, idx): if idx != None: if idx < 0 or idx >= 10: return Sim_Set_Illegal_Value obj.object_data.vcounter[idx] = val else: obj.object_data.vcounter = val return Sim_Set_Ok SIM_register_typed_attribute(class_name, "counter_array", get_counter_array, None, set_counter_array, None, Sim_Attr_Optional | Sim_Attr_Integer_Indexed, "[i{10}]", "i", "Sample array of counters")
These functions handle both indexed and non-indexed access to the attribute. This is important for checkpointing, as all attributes are saved and restored using non-indexed access. Thus, if you wish your attribute to be checkpointed, you will always have to handle non-indexed accesses.
As shown in the example usages above, it is also possible to use strings, lists and Python slices as index. An index corresponding to a slice is provided as a list of two elements, the first and last element that should be included. Note that this means a slight change, since the Python slice refers to the first element included, and the one after the last included. This means that: object.attr[i:j] will be translated to an index parameter with the value [i, j - 1], or in C, an attribute of type Sim_Val_List with two Sim_Val_Integer elements i and j − 1.
As an example, let's consider two attributes, foo and bar. foo is an array of 5 elements that do not support indexing, while bar is a table of 10 elements supporting both indexed and non-indexed accesses:
# non-indexed access to 'foo' attribute simics> @conf.object.foo [0,1,2,3,4] # non-indexed access to 'foo' attribute, later indexed by python simics> @conf.object.foo[2] 2 # indexed access to 'bar' attribute simics> @conf.object.bar[12] 12 # no access performed to 'bar' attribute simics> @conf.object.bar <object bar> # non-indexed access to 'bar' attribute simics> @SIM_get_attribute(conf.object, "bar") [0,1,2,3,4,5,6,7,8,9]