ScEpTIC Configuration
ScEpTIC configuration resides in the ScEpTICConfig
class, which acts as container for the configuration of all ScEpTIC components.
You can import and initialize ScEpTICConfig
as follows:
from ScEpTIC.config import ScEpTICConfig
config = ScEpTICConfig()
The variable config
is required to initialize ScEpTIC and allows you to configure ScEpTIC components through the following attributes:
config.logging
exposes the logger configuration for ScEpTIC actionsconfig.program
exposes the emulated program configurationconfig.register_file
exposes the emulated register file configurationconfig.register_allocation
exposes the register allocation step configurationconfig.memory
exposes the emulated memory configurationconfig.result_output
exposes the results output configurationconfig.state_retention
exposes the state retention configurationconfig.ast_transformations
exposes custom AST transformations configurationconfig.deprecated
exposes deprecated configuration that will be removed in future releases of ScEpTICconfig.analysis
exposes the configuration for the analysis ScEpTIC executesconfig.custom_metrics
exposes custom metrics configurationconfig.interrupts
exposes interrupts configuration
Each configuration attribute exposes two main methods, set_config(config_name, config_value)
and add_config(config_name, config_value)
that you can use to configure the various options of the component.
set_config()
sets a given configuration parameter, such as the logging level, whereas add_config()
add multiple configurations to the component, such as multiple analysis to execute.
In each one of the sections below, we report the configuration parameters of each component and we show how to configure them. Each table reports the parameter name, its description, the configuration method to be used, the value type for the configuration, and its default value. Note: if you do not specify any configuration, the default configuration will be applied. You only need to provide a configuration for components where the default settings do not meet your requirements.
Logging
The config.logging
attribute allows you to configure the logging behavior of ScEpTIC through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies if logging is enabled |
|
|
|
|
Specifies the logging level for the LLVM IR parser inside ScEpTIC |
|
Logging level values from python’s logging library |
|
|
Specifies whether the LLVM IR parser should also log program’s section content |
|
|
|
|
Specifies logging level of ScEpTIC emulation |
|
Logging level values from python’s logging library |
|
The default configuration is the following:
import logging
config.logging.set_config('enabled', False)
config.logging.set_config('parser_log_level', logging.WARNING)
config.logging.set_config('log_section_content', False)
config.logging.set_config('log_level', logging.WARNING)
Program
The config.program
attribute allows you to configure the emulated program information through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies the function prefix in LLVM IR used by ScEpTIC LLVM IR parser |
|
|
|
|
Specifies the main function name |
|
|
|
|
Specifies the source file relative path |
|
|
|
The default configuration is the following:
config.program.set_config('ir_function_prefix', '@')
config.program.set_config('main_function_name', 'main')
config.program.set_config('file', 'source.ll')
Register File
The config.register_file
attribute allows you to configure the emulated register file through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies whether ScEpTIC simulates a physical or uses the virtual unlimited register file of LLVM IR |
|
|
|
|
Specifies the number of general purpose physical registers available in the target MCU |
|
|
|
|
Specifies the number of physical registers that can be used to pass parameters during function calls. Note that in ARM and MSP430 platforms, this is usually 4 |
|
|
|
|
Specifies the text prefix for physycal registers (e.g., R0 has R as prefix). Used for the textual representation of the register file |
|
|
|
|
Specifies the prefix of virtual registers that will contain the stack address of spilled registers, that are, registers saved in the stack before call operations. Used for textual representation of the register file |
|
|
|
|
Specifies the LLVM IR first-class type of the virtual registers used for spilling physical registers. Used for stack size calculation on spilling operations |
|
|
|
The default configuration is the following:
config.register_file.set_config('use_physical_registers', False)
config.register_file.set_config('n_physical_registers', 10)
config.register_file.set_config('n_param_registers', 4)
config.register_file.set_config('physical_registers_prefix', 'R')
config.register_file.set_config('spill_virtual_registers_prefix', '%spill_')
config.register_file.set_config('spill_virtual_registers_type', 'i32')
Register Allocation
The config.register_allocation
attribute allows you to configure the register allocation when using a physycal register file through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies if the register allocation module is enabled. Note that if set to |
|
|
|
|
Specifies the python path of the register file allocation module |
|
|
|
|
Specifies the name of the module containing the register allocation algorithm. ScEpTIC already implements the linear scan register allocation algorithm |
|
|
|
|
Specifies the name of the register allocation function that executes the register allocation algorithm. |
|
|
|
The default configuration is the following:
config.register_allocation.set_config('enabled', False)
config.register_allocation.set_config('module_location', 'ScEpTIC.AST.register_allocation')
config.register_allocation.set_config('module_name', 'linear_scan')
config.register_allocation.set_config('allocation_function_name', 'allocate_registers')
Implementing your register allocation algorithm
ScEpTIC already implements the linear scan register allocation algorithm, which you can check out at ScEpTIC/AST/register_allocation/linear_scan/
if you want to implement your register allocation algorithm.
Our implementation uses the allocate_registers()
function to apply the register allocation algorithm, which is already pre-configured into ScEpTIC:
def allocate_registers(functions, registers_number, config)
where the parameters are the following:
functions
is a dictionary that maps functions name to their AST
registers_number
is the number of physycal registers
config
is the register file configuration
Note that your implementation should provide a function that takes the same parameters.
Memory
ScEpTIC divides memory into three segments: GST (Global Symbol Table, i.e., global variables), stack, and heap. ScEpTIC allows you to allocate portions of the GST across volatile and non-volatile memory, wheras the stack and the heap must reside entirely onto volatile or non-volatile memory, independently. ScEpTIC emulates all these address spaces to be separate.
The config.memory
attribute allows you to configure the emulated memory through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies the bit size of addresses |
|
|
|
|
Specifies if ScEpTIC should verify that the program execution does not exceed the maximum available memory |
|
|
|
|
Specifies the starting address of the stack |
|
|
|
|
Specifies the prefix for stack addresses. Used for textual representation of the stack |
|
|
|
|
Specifies the starting address of the heap |
|
|
|
|
Specifies the prefix for heap addresses. Used for textual representation of the heap |
|
|
|
|
Specifies the default memory for global variables. Variables without a section attribute specified will be allocated to this memory |
|
|
|
|
Specifies the memory section attribute that indicates to allocate the variable onto the other memory. |
|
|
|
To config.memory.volatile
and config.memory.non_volatile
attributes allows you to configure volatile and non-volatile memory through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies if the memory is enabled |
|
|
|
|
Specifies if the memory size |
|
|
|
|
Specifies if the memory contains the stack. Note that this parameter can be set to |
|
|
|
|
Specifies if the memory contains the heap. Note that this parameter can be set to |
|
|
|
|
Specifies if the memory contains global variables. Note that this parameter can be set to |
|
|
|
|
Specifies the prefix for GST addresses. Used for textual representation of the GST |
|
|
|
|
Specifies the starting address of the GST |
|
|
|
The default configuration is the following:
# Generic memory configuration
config.memory.set_config('address_size', 32)
config.memory.set_config('check_memory_size', True)
config.memory.set_config('stack_base_address', 0)
config.memory.set_config('stack_prefix', 'S')
config.memory.set_config('heap_base_address', 0)
config.memory.set_config('heap_prefix', 'H')
config.memory.set_config('gst_default_memory', 'volatile')
config.memory.set_config('gst_other_memory_section', '.DATA,.NVM')
# Volatile memory configuration
config.memory.volatile.set_config('enabled', True)
config.memory.volatile.set_config('max_size', 0)
config.memory.volatile.set_config('contains_stack', True)
config.memory.volatile.set_config('contains_heap', True)
config.memory.volatile.set_config('contains_gst', True)
config.memory.volatile.set_config('gst_prefix', 'VGST')
config.memory.volatile.set_config('gst_base_address', 0)
# Non-volatile memory configuration
config.memory.non_volatile.set_config('enabled', True)
config.memory.non_volatile.set_config('max_size', 0)
config.memory.non_volatile.set_config('contains_stack', False)
config.memory.non_volatile.set_config('contains_heap', False)
config.memory.non_volatile.set_config('contains_gst', True)
config.memory.non_volatile.set_config('gst_prefix', 'NGST')
config.memory.non_volatile.set_config('gst_base_address', 0)
Global Variables Allocation
By default, ScEpTIC allocates global variables to volatile memory, as specified in the parameter gst_default_memory
of config.memory
.
To manually allocate selected global variables to non-volatile memory, you need to declare for each of such variable an attribute section equal to the parameter gst_other_memory_section
of config.memory
, which by default is set to .DATA,.NVM
.
This can be achieved as follows:
int your_variable __attribute__((section(".DATA,.NVM"))) = ...;
Note that if you want to allocate all global variables to non-volatile memory, you can set the following configuration parameter:
config.memory.set_config('gst_default_memory', 'non_volatile')
Then, you can manually allocate selected global variables to -volatile memory by setting an attribute section equal to the parameter gst_other_memory_section
of config.memory
, as previously specified.
State Retention
The config.state_retention
attribute allows you to configure the state retention strategy through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies the state retention type (i.e., checkpoint-based or task-based). If the type is task, all the other configuration parameters are ignored. |
|
|
|
|
Specifies the name of the routine called to saved the device state, if any |
|
|
|
|
Specifies the name of the routine called to restore the device state, if any |
|
|
|
|
Specifies the name of a function that ScEpTIC calls before calling the state-save routine. Note: this is ignored if the function does not exist in the code |
|
|
|
|
Specifies the name of a function that ScEpTIC calls after calling the state-save routine. Note: this is ignored if the function does not exist in the code |
|
|
|
|
Specifies the name of a function that ScEpTIC calls before calling the state-restore routine. Note: this is ignored if the function does not exist in the code |
|
|
|
|
Specifies the name of a function that ScEpTIC calls after calling the state-restore routine. Note: this is ignored if the function does not exist in the code |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the register file content. |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the stack content. |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the heap content. |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the GST allocated onto volatile memory. |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the GST allocated onto non-volatile memory. |
|
|
|
|
Specifies if the state-save (state-restore) routine saves (restores) the environment state (e.g. actuators). |
|
|
|
|
Specifies if state-save operations are statically placed inside the program’s code or if they happen due to external interrupts, based on energy buffer voltage level |
|
|
|
|
Specifies if state-save routine directly save the state upon its execution (execute) or verifies the energy buffer level and then decides whether to save the state (probe) |
|
|
|
|
Specifies if probing operations use the platform ADC |
|
|
|
|
Specifies if ScEpTIC should calculate the optimal energy buffer voltages at which the state should be saved and at which the MCU should resume the computation. This is considered only when |
|
|
|
|
Specifies the additional clock cycles ScEpTIC should account for state-save operations when calculating energy buffer voltages |
|
|
|
|
Specifies the additional clock cycles ScEpTIC should account for state-restore operations when calculating energy buffer voltages |
|
|
|
|
Specifies the minimum clock cycles the MCU should be able to execute when resuming the computation after an energy failure. This is used when calculating energy buffer voltages |
|
|
|
|
Specifies the voltage at which the MCU should save its state. This is considered only when |
|
|
|
|
Specifies the voltage at which the MCU should resume the execution. This is considered only when |
|
|
|
|
Specifies if the MCU enters a low-power mode after saving the state |
|
|
|
The default configuration is the following:
config.state_retention.set_config('type', 'checkpoint')
config.state_retention.set_config('state_save_function_name', 'checkpoint')
config.state_retention.set_config('state_restore_function_name', 'restore')
config.state_retention.set_config('before_save_function_name', 'sceptic_before_save')
config.state_retention.set_config('after_save_function_name', 'sceptic_after_save')
config.state_retention.set_config('before_restore_function_name', 'sceptic_before_restore')
config.state_retention.set_config('after_restore_function_name', 'sceptic_after_restore')
config.state_retention.set_config('restore_register_file', True)
config.state_retention.set_config('restore_stack', True)
config.state_retention.set_config('restore_heap', True)
config.state_retention.set_config('restore_volatile_gst', True)
config.state_retention.set_config('restore_non_volatile_gst', False)
config.state_retention.set_config('restore_environment', False)
config.state_retention.set_config('state_save_strategy', 'static')
config.state_retention.set_config('state_save_behaviour', 'execute')
config.state_retention.set_config('probe_with_adc', False)
config.state_retention.set_config('calculate_v_save_state', False)
config.state_retention.set_config('calculate_v_save_state_additional_cc', 0)
config.state_retention.set_config('calculate_v_resume_additional_cc', 0)
config.state_retention.set_config('calculate_v_restore_min_cc', 0)
config.state_retention.set_config('v_save_state', 0.0)
config.state_retention.set_config('v_resume', 0.0)
config.state_retention.set_config('hibernate_after_save', False)
Note: ScEpTIC does not require you to specify the content of state-save and state-restore routines, as it will take care of saving the corresponding memory locations, as specified in the configuration. Therefore, ScEpTIC ignores user-specified implementation of state-save and state-restore routines in the source code.
Pre-configured Systems
ScEpTIC provides a config.load_system_defaults()
method to reproduce the memory and state-retention configuration of the following state-of-the-art systems:
The configurations of pre-defined systems are available in ScEpTIC/config/system_defaults/
and can be applied using:
config.load_system_defaults(system_name)
where system_name
is the lower-case name of the system configuration.
For example, to apply Mementos
configuration:
config.load_system_defaults('mementos')
AST Transformations
ScEpTIC allows you to apply code transformations to the AST it executes, enabling you to reproduce the effects of compile-time techniques.
The config.ast_transformations
attribute allows you to enable AST transformations by calling the method add_config('transformations', transformation_name)
as follows:
config.ast_transformations.add_config('transformation', 'transformation_name')
ScEpTIC implements the following AST transformations:
Once a transformation is enabled, config.ast_transformations.transformation_name
attribute allows you to configure some transformation parameters, if available.
By default, ScEpTIC has a base
AST transformation enabled, which maps custom AST instructions to C functions.
More details are available at Custom AST Instructions.
AST Transformations - Ratchet
Ratchet transformation provides the following configuration paramets:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies function names whose execution access memory locations outside their stack frame |
add_config() |
str |
|
|
Specifies if state-save operations save only live registers |
|
|
|
For example, if you have two functions ext1()
and ext2()
accessing data outside their stack frames (e.g. global variables), you can specify them as follows:
# Enable Ratchet transformation
config.ast_transformations.add_config('transformations', 'ratchet')
# Configure Ratchet transformation
config.ast_transformations.ratchet.add_config('functions_with_outside_frame_accesses', 'ext1')
config.ast_transformations.ratchet.add_config('functions_with_outside_frame_accesses', 'ext2')
AST Transformations - ALFRED Virtual Memory
ALFRED transformation provides the following configuration paramets:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies the function that computes ALFRED |
set_config() |
FunctionType |
|
|
Specifies if state-save operations save only live registers |
|
|
|
|
Specifies if the transformation should apply memory optimizations |
|
|
|
A configuration example is the following:
# Enable ALFRED Virtual Memory transformation
config.ast_transformations.add_config('transformations', 'virtual_memory')
# Configure ALFRED Virtual Memory transformation
config.ast_transformations.virtual_memory.set_config('n_min_function', lambda x: x > 2)
Custom AST Transformations
You can define new program transformations by creating a new directory in ScEpTIC/AST/transformations/
.
A custom program transformation require two files, config.py
and main.py
.
config.py
provides a custom configuration for the new defined transformation and must define a function get_config()
that returns an object extending ScEpTICBaseConfig as follows:
from ScEpTIC.config.base_config import ScEpTICBaseConfig
class MyTransformationConfig(ScEpTICBaseConfig):
pass
def get_config(main_config):
return MyTransformationConfig(main_config)
where main_config
is a reference to the main configuration object.
main.py
must define a apply_transformation()
function that applies the transformation to all the program’s code as follows:
def apply_transformation(functions, vmstate, f_declarations):
...
where functions
is a directory that maps functions name to their AST, vmstate
is a reference to ScEpTIC state, and f_declarations
is a list of function declarations, consisting of functions used in the program but not defined.
For example, say you want to create a new transformation called my_transformation
.
You need to create a new directory in ScEpTIC/AST/transformations/my_transformation
, with two files inside, config.py
and main.py
.
config.py
content:
from ScEpTIC.config.base_config import ScEpTICBaseConfig
class MyTransformationConfig(ScEpTICBaseConfig):
"""
Configuration for your configuration
"""
_config_domain = {
'my_number': {'class': int, 'mode': 'set'},
}
def __init__(self, main_config):
super().__init__(main_config)
## Init here your params
self.my_number = 0
# Returns the configuration
def get_config(main_config):
return MyTransformationConfig(main_config)
You can check out how to create custom configurations for your transformation by looking at existing transformations inside ScEpTIC/AST/transformations
and at configuration parameters inside ScEpTIC/config
.
main.py
content:
def apply_transformation(functions, vmstate, f_declarations):
for f_name, f_body in functions.items():
# Do something
Then, to use the new transformation, you configure it as follows:
# Enable
config.ast_transformations.add_config('transformations', 'my_transformation')
# Configure ALFRED Virtual Memory transformation
config.ast_transformations.my_transformation.set_config('my_number', 100)
You can check out how to transform ScEpTIC AST by looking at existing transformations inside ScEpTIC/AST/transformations
.
Results Output
The config.result_output
attribute allows you to configure how ScEpTIC provides you analysis results through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies if ScEpTIC should save the analysis results |
|
|
|
|
Specifies the directly local path where analysis results are saved |
|
|
|
|
Specifies the name of the test executed. The analysis results will be saved in the |
|
|
|
|
Specifies if ScEpTIC should append the date and time to the test name directory |
|
|
|
|
Specifies if ScEpTIC should save the analysis results |
|
|
|
|
Specifies if ScEpTIC should save the emulated program’s code in the result directory |
|
|
|
|
Specifies if ScEpTIC should save the emulated platform state (register file, memory) in the result directory |
|
|
|
|
Specifies whether ScEpTIC will save its state and analysis result if an exception is raised during the analysis execution |
|
|
|
The default configuration is the following:
config.result_output.set_config('enabled', True)
config.result_output.set_config('save_directory', 'analysis_results')
config.result_output.set_config('test_name', 'generic_test')
config.result_output.set_config('dir_append_datetime', True)
config.result_output.set_config('save_analysis_output', True)
config.result_output.set_config('save_code', True)
config.result_output.set_config('save_state', True)
config.result_output.set_config('save_results_after_exception', True)
After executing an analysis, ScEpTIC saves both a .txt
and a .json
file with the state, the program’s code, and the analysis result’ metrics.
Custom Metrics
ScEpTIC analysis collects pre-configured metrics, such as the device energy consumption, the execution time, and the number of energy failures. ScEpTIC allows you to define custom metrics through the insertion of special function calls inside your program. Every custom metric is an integer and it is initialized to 0. Every time the function call executes, the metric is incremented.
ScEpTIC allows you to define new metrics through the config.custom_metrics
attribute by calling the add_custom_metric()
method, which takes the following arguments:
Argument Name |
Description |
Value Type |
---|---|---|
|
Specifies the numeric ID for the custom metric |
|
|
Specifies the metric name, which will be stored in the analysis output file |
|
|
Specifies whether to record the current energy consumption alongside the metric |
|
|
Specifies whether to record the elapsed time alongside the metric |
|
|
Specifies whether to record the executed clock cycles alongside the metric |
|
|
Specifies if the collected data is the absolute value or the difference since the last metric collection (diff) |
|
|
Specifies if ScEpTIC prints metrics during program simulation |
|
Then, inside your program you need to call:
sceptic_increment_custom_metric(metric_id, step)
where metric_id
is the metric ID specified as below and step
is the increment step for the metric. This second parameter is optional and by default it is 1.
For example, if you want to record the number of times a branch executes, you can define a new metric as follows:
config.custom_metrics.add_custom_metric(0, 'BRANCH_ENTER', True, True, True, False, False)
Here you specified also that the metric records the energy, time, and clock cycles when it executes.
Then, in your program, you call the metric increment function:
sceptic_increment_custom_metric(0);
Libraries
The LLVM IR generated in Target Program Compilation does not include builtin libraries code, such as mathematical or memory management functions. ScEpTIC provides an implementation of the most used C libraries, such as:
math.h
: ScEpTIC provides the functionalities of abs, acos, asin, atan, atan2, ceil, cos, cosh, exp, fabs, floor, fmod, log, log10, pow, sin, sinh, sqrt, tan, and tanhstdio.h
: ScEpTIC provides the functionalities of memcmp, strncmp, rand, srand, exit, strlen, and of a custom debug_print; note that printf() is implemented using a custom ScEpTIC AST instruction to avoid variable arguments issuesstdlib.h
: ScEpTIC provides the functionalities of calloc, free, malloc, and realloc
These functionaliteis are defined inside ScEpTIC/AST/builtins/libs/
.
ScEpTIC automatically links the implemented functionalities to the corresponding function calls inside the LLVM IR.
Custom Functions and Instructions
Despite you can define functions in the program’s source code, sometimes you may need to access simulation elements that are only available inside ScEpTIC. Therefore, ScEpTIC provides two ways to define custom builtin functions.
Custom Builtin Functions
The first option to create a custom function consists of exploting the mechanism ScEpTIC uses to define library funtions.
Note that with this method, ScEpTIC automatically creates the function body and executes the builtin as a normal function call.
You can specify new functions as a python class that extends the Builtin
base class, which must have:
an attribute
tick_count
that specifies the amount of clock cycles the function takesa method
get_val()
that returns the result of the function execution
The builtin can access funtion parameters using self.args
.
For example, you can define a new function int test_builtin(double x, int a, int b, int c);
that prints all its parameters and returns the latest one as follows:
from ScEpTIC.AST.builtins.builtin import Builtin
class TestBuiltin(Builtin):
tick_count = 10
def get_val(self):
for i in self.args:
print(i)
return self.args[3]
Once you defined the builtin, you need to specify to ScEpTIC to create the corresponding bindings, function body, and function declaration, so that it can maps the new builtin function to the source LLVM IR.
The Builtin
class offers a class method that does exaclty that:
define_builtin(function_name, LLVM_IR_parameters, LLVM_IR_return_type)
- where:
function_name
is the function nameLLVM_IR_parameters
is the textual representation of the list of LLVM IR types for the parameters of the functionLLVM_IR_return_type
is the textual representation of the return type expressed in LLVM IR type
Therefore, to complete the definition of TestBuiltin
you can proceed as follows:
TestBuiltin.define_builtin('test_builtin', 'double, i32, i32, i32', 'i32')
Now you can use the test_builtin()
function inside the progmram’s source code without declaring the function in the source file.
ScEpTIC will link the builtin to the function call and execute it when encountered.
Custom AST Instructions
A second option to create a custom function consists of creating a new AST Instruction that then ScEpTIC executes. Note that with this method, ScEpTIC does not create the function body and executes the builtin as a custom ISA function that allows you to not interfere with emulation timings and energy consumption.
Custom AST instruction are located in ScEpTIC/AST/transformations/base/instructions
and extends the CustomInstruction
base class.
To define a new custom isntruction you need to follow two steps.
First, you need to define the custom instruction code by creating a new file in ScEpTIC/AST/transformation/base/instructions
for your custom instruction, which must include a class that extends CustomInstruction
and override the run()
method.
For example, to define the MyCustomInstruction
AST instruction, you can create a my_custom_instruction.py
file with the following content:
from ScEpTIC.AST.transformations.instructions import CustomInstruction
class MyCustomInstruction(CustomInstruction):
def run(self, update_program_counter=True):
# Your code here
print("MyCustomInstruction executed!")
# Call extended class run method
super().run(update_program_counter)
Note that your custom instruction can take any number of arguments, which are accessible in MyCustomInstruction
through self.get_arg(arg_number)
.
Second, you need to modify ScEpTIC/AST/transformations/base/main.py
, including the import to your custom instruction and updating the function_map
dictionary that maps the function call name to the AST instruction.
For example, to map MyCustomInstruction
to the my_custom_instruction()
function, you can proceed as follows:
...
# ADD
from ScEpTIC.AST.transformations.instructions.my_custom_instruction import MyCustomInstruction
...
function_map = {
...
# ADD:
f"{config.__ir_prefix}my_custom_instruction": MyCustomInstruction,
...
}
Now you can call your custom instruction in your C source file as:
my_custom_instruction();
If you want a more advanced configuration and include the possibility of configuring the instruction name, you can proceed as follows.
First, you need to define the function name for the custom instruction inside config.py
located in ScEpTIC/AST/transformations/base/config.py
by adding a corresponding line in the _config_domain
dictionary and in the __init__()
method.
For example, if you want to define a my_custom_instruction
function, you can proceed as follows:
_config_domain = {
# ADD:
'my_custom_instruction_function_name': {'class': str, 'mode': 'set'},
...
}
def __init__(self, main_config):
super().__init__(main_config)
self.__ir_prefix = f"{main_config.program.ir_function_prefix}"
# ADD:
self.my_custom_instruction_function_name = f"{self.__ir_prefix}my_custom_instruction"
...
Next, when modifying the function_map
dictionary in ScEpTIC/AST/transformations/base/main.py
, you can use config.my_custom_instruction_function_name
as follows:
...
# ADD
from ScEpTIC.AST.transformations.instructions.my_custom_instruction import MyCustomInstruction
...
function_map = {
...
# ADD:
config.my_custom_instruction_function_name: MyCustomInstruction,
...
}
For more advanced information on custom instructions, you can check the ones already implemented in ScEpTIC/AST/transformations/base/instructions
.
Environment Modeling
ScEpTIC allows you to model environment interactions through custom input and output functions, which represents sensors and actuators.
Inputs
ScEpTIC models input elements, such as sensors, as custom built-in functions that you can call in your source file to retrieve sensor data.
You can define simple input elements inside your configuration file by calling the create_input()
class method of the InputManager
class:
from ScEpTIC.emulator.io.input import InputManager
InputManager.create_input(name, function_name, LLVM_IR_return_type)
where name
is a string representing the sensor name, function_name
is the name of the function you call in the C source code to access the sensor data, and LLVM_IR_return_type
is the return type in LLVM IR textual format.
To set the value of a given input function, you can use the set_input_value()
class method of the InputManager
class as follows:
InputManager.set_input(name, value)
where name
is the sensor name and value
is the sensor value.
For example, you can model a sensor named PIR
that always returns an integer (i.e. 10) as follows:
from ScEpTIC.emulator.io.input import InputManager
InputManager.create_input('PIR', 'pir_input', 'i32')
InputManager.set_input_value('PIR', '10')
You can retrieve the input value by calling the pir_input()
function inside your C source code.
Note that such function takes no parameter.
ScEpTIC allows you to define more complex input functions, which also support function parameters.
For doing so, you need to create a class that extends the InputSkeleton
base class and override the get_val()
method.
The get_val()
method must set the return value in the self.value
attribute and then must return super().get_val()
Similarly to the definition of a custom built-in, as described in :ref:custom-builtin-label
, you need to call the class method define_input(name, function_name, LLVM_IR_parameters, LLVM_IR_return_type)
on the created class.
For example, you can define a distance sensor that returns the value of its second parameter as follows:
from ScEpTIC.emulator.io.input import InputSkeleton
class MyDistanceSensor(InputSkeleton):
def get_val(self):
self.value = self.arg[1]
return super().get_val()
MyDistanceSensor.define_input('Distance', 'my_distance', 'i32, i32', 'float')
Similarly to before, you can now use my_distance()
function inside your C source code.
Note that ScEpTIC executes the get_val()
when encounters the corresponding C function.
Outputs
Similarly to inputs, ScEpTIC models output elements, such as actuators, as custom built-in functions that you can call in your source file to set actuators state.
You can define simple output elements inside your configuration file by calling the create_output()
class method of the OutputManager
class as follows:
from ScEpTIC.emulator.io.output import OutputManager
OutputManager.create_output(name, function_name, LLVM_IR_parameters)
where name
is a string representing the output element name, function_name
is the name of the function you call in the C source code to set the output element state, and LLVM_IR_parameters
is the function parameters in LLVM IR textual format.
For example, you can model an output element named motor
as follows:
from ScEpTIC.emulator.io.output import OutputManager
OutputManager.create_output('motor', 'motor_run', 'void')
Now, you can call the motor_run()
function inside your C source code to simulate the sending of a start signal to a motor.
Note that such function returns no value.
Similarly to inputs, ScEpTIC allows you to define more complex output functions, which support return values and internal state.
This may be useful to model complex output functions whose state depends on their current state.
For doing so, you need to create a class that extends the OutputSkeleton
base class and override the get_val()
method.
The OutputSkeleton
offers the following methos that you can call inside get_val()
:
set_output_val(value)
that sets the state of the output element tovalue
get_output_value()
that returns the current state of the output element
Moreover, if you need to initialize the output state at a given value on output element creation, you can do so by overriding the method output_init()
, which is called on object creation.
Finally, similarly to the definition of a custom built-in, you need to call the class method define_output(name, function_name, LLVM_IR_parameters, LLVM_IR_return_type)
on the created class.
For example, you can define a servo as follows:
from ScEpTIC.emulator.io.output import OutputSkeleton
class Servo(OutputSkeleton):
# set initial servo position to 0
def output_init(self):
self.set_output_val(0)
def get_val(self):
value = self.get_output_value() + self.args[0]
self.set_output_val(value)
return value
Servo.define_output('servo', 'move_servo', 'void', 'i32')
Similarly to before, you can now use move_servo()
function inside your C source code.
Note that ScEpTIC executes the get_val()
when encounters the corresponding C function.
Interrupts and Timers
ScEpTIC supports interrupts triggered by timers through two classes:
InterruptsManager
located inScEpTIC/emualtor/io/interrupts_manager.py
and initialized instate.interrupts_manager
TimersManager
located inScEpTIC/emulator/timers/
and initialized instate.timers_manager
You can define an interrupt using the register_isr(interrupt_name, function_name)
method, where interrupt_name
is the name of the interrupt and function_name
is the name of the function that will execute when the interrupt fires.
Note that function_name
is the name of a function inside your source code.
ScEpTIC allows you to set up timers that trigger interrupts using the create_timer(timer_name, interrupt_name, period, periodic)
method, where:
timer_name
is the timer nameinterrupt_name
is the name of the interrupt to trigger when the timer expiresperiod
is the timer period, in secondsperiodic
indicates if the timer is periodic
The following example indicates how to set up an interrupts that executes my_isr
, defined in the source code, and timers:
vm = ScEpTIC.init(config)
# Create ISR
vm.state.interrupts_manager.register_isr('MY_INTERRUPT', 'my_isr')
# Create a timer of 1s
vm.state.timers_manager.create_timer('MY_TIMER', 'MY_INTERRUPT', 1, True)
# Start the timer
vm.state.timers_manager.start_timer('MY_TIMER')
Analysis
The config.analysis
attribute allows you to configure the analysis that ScEpTIC will run through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Sepecifies whether ScEpTIC will save any analysis result |
|
|
|
|
Sepecifies the analysis result format |
|
|
|
|
Specifies the analysis that ScEpTIC will run |
|
|
|
The default configuration is the following:
from ScEpTIC.analysis.options import AnalysisResultFormat
config.analysis.set_config('save_results', True)
config.analysis.add_config('results_formats', AnalysisResultFormat.TEXT)
config.analysis.add_config('results_formats', AnalysisResultFormat.JSON)
For now, ScEpTIC supports two analysis types: min_capacitor_size
and energy
.
You can find below their description and their configuration parameters.
min_capacitor_size
The min_capacitor_size
analysis identifies the minimum capacitor size required to execute your given program.
ScEpTIC starts the analysis at a given capacitance and verifies if it is sufficient to complete the program without incurring in the non-termination path bug.
Otherwise, it increases the capacitance and repeats the test until the bug is no longer present, that is, the program completes successfully.
The config.analysis.min_capacitor_size
attribute allows you to configure the analysis parameters through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Sepecifies the system energy model used by ScEpTIC to simulate energy consumption and timing |
|
|
|
|
Sepecifies the minimum capacitance size to consider in the analysis |
|
|
|
|
Sepecifies the capacitance size increase to consider in each step of the analysis |
|
|
|
The default configuration is the following:
config.analysis.add_config('analysis', 'min_capacitor_size')
config.analysis.min_capacitor_size.set_config('system_model', None)
config.analysis.min_capacitor_size.set_config('min_capacitor_size', '10u')
config.analysis.min_capacitor_size.set_config('capacitance_size_step', '5u')
To configure the system_model
parameter, check the System Model Configuration page.
energy
The energy
analysis executes the program, reproduces a given system model, and simulates its energy consumption and timing.
Note that, if configured, ScEpTIC caches the values for v_save_state
and v_resume
so that multiple executions of a program does not require to recaculate these parameters.
The config.analysis.energy
attribute allows you to configure the analysis parameters through the following elements:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies the maximum simulated time after which ScEpTIC automatically stops the simulation. Note that this does not correspond to the time required to run the simulation, but to the time simulated by ScEpTIC |
|
|
|
|
Sepecifies the system energy model used by ScEpTIC to simulate energy consumption and timing |
|
|
|
|
Sepecifies whether to ignore energy failures occurring during state-save operations, meaning that ScEpTIC always execute state-save operations |
|
|
|
|
Sepecifies whether ScEpTIC should calculate only the v_resume and v_save_state. This is useful if you need to execute multiple parallel analysis |
|
|
|
The default configuration is the following:
config.analysis.add_config('analysis', 'energy')
config.analysis.energy.set_config('system_model', None)
config.analysis.energy.set_config('ignoirepo', False)
config.analysis.energy.set_config('calculate_cache_only', False)
To configure the system_model
parameter, check the System Model Configuration page.
Create your custom analysis
ScEpTIC analysis resides inside a dedicated folder in ScEpTIC.analysis
.
To create a custom analysis, you need to create a folder in ScEpTIC.analysis
and place two files, config.py
and main.py
.
For example:
ScEpTIC/
analysis/
my_analysis/
main.py
config.py
Similarly to Custom AST Transformations, config.py
provides a custom configuration for the new defined transformation and must define a function get_config()
that returns an object extending ScEpTICBaseConfig as follows:
from ScEpTIC.config.base_config import ScEpTICBaseConfig
class MyAnalysisConfig(ScEpTICBaseConfig):
pass
def get_config(main_config):
return MyAnalysisConfig(main_config)
where main_config
is a reference to the main configuration object.
main.py
instead must provide a get_analysis()
function that returns a class extending ScEpTICAnalysis
which overrides the get_result()
and run()
methods as follows:
from ScEpTIC.analysis.base_analysis import ScEpTICAnalysis
from ScEpTIC.analysis.options import AnalysisResultFormat
class MyAnalysis(ScEpTICAnalysis):
def __init__(self, vm):
super().__init__(vm)
# add your initialization code
def get_result(self, result_format):
if result_format == AnalysisResultFormat.JSON:
return your_result_in_json
elif result_format == AnalysisResultFormat.TEXT:
return your_result_in_text
else:
raise Exception(f"Invalid result format {result_format}")
def run(self):
# do stuff
def get_analysis(vm):
return MyAnalysisConfig(vm)
where vm
is a reference to ScEpTICVM class, which allows you to access ScEpTIC state through vm.state
.
Then, you can configure ScEpTIC to execute your analysis:
config.analysis.add_config('analysis', 'my_analysis')
and you can access your analysis configuration through:
config.analysis.my_analysis.set_config(...)
config.analysis.my_analysis.add_config(...)
For more implementation details, you can check the existing analysis in ScEpTIC/analysis/
Deprecated Configuration
ScEpTIC current release does not include intermittence anomalies checks through the new analysis mechanism, and it uses the previous intermittent execution mechanism, which can be configured through the config.deprecated
attribute.
These configuration parameters will no longer be available in a future release of ScEpTIC, as we aim to port all these functionality to ScEpTIC new analysis mechanism.
The deprecated parameters are:
Parameter Name |
Description |
Method |
Value Type |
Default |
---|---|---|---|---|
|
Specifies to execute the program sequentially, without emulating energy failures |
|
|
|
|
Specifies to execute the analysis to locate memory anomalies |
|
|
|
|
Specifies to execute the analysis to evaluate the effects of memory anomalies |
|
|
|
|
Specifies to execute the analysis to evaluate environment input anomalies |
|
|
|
|
Specifies to execute the analysis to evaluate environment outputs behavior |
|
|
|
|
Specifies to execute the analysis for debugging specific intermittent executions |
|
|
|
|
Specifies to execute the analysis for identifying the program’s memory usage |
|
|
|
|
Specifies to execution_depth parameter for |
|
|
|
|
Specifies to stop the analysis after an anomaly is found |
|
|
|
|
Specifies if the memory size accounts for a double buffer to avoid anomalies for |
|
|
|
|
Specifies the default memory location for global variables for |
|
|
|
The default configuration is the following:
from ScEpTIC.AST.misc.virtual_memory_enum import VirtualMemoryEnum
config.deprecated.set_config('run_continuous', False)
config.deprecated.set_config('run_locate_memory_test', False)
config.deprecated.set_config('run_evaluate_memory_test', False)
config.deprecated.set_config('run_input_consistency_test', False)
config.deprecated.set_config('run_output_profiling_test', False)
config.deprecated.set_config('run_profiling', False)
config.deprecated.set_config('run_memory_size_measure', False)
config.deprecated.set_config('execution_depth', 50000)
config.deprecated.set_config('stop_on_fist_anomaly', False)
config.deprecated.set_config('double_buffer_for_anomalies', True)
config.deprecated.set_config('global_variables_default_memory', VirtualMemoryEnum.VOLATILE)
Inputs Access Model
When run_input_consistency_test
is enabled, you need to specify the input access model for each input function, created as described in Inputs.
To set the consistency model of an input, you need to use the set_consistency_model()
class method of the InputManager
, which takes two parameters:
input_name
corresponds to the name of the input function
consistency_model
corresponds to the consistency model you want for the input
for a most recent access model, this must be set to
InputManager.MOST_RECENT
for a long term access model, this must be set to
InputManager.LONG_TERM
For example, you can specify a most recent access model for a temperature sensor as follows:
from ScEpTIC.emulator.io.input import InputManager
InputManager.create_input('temperature_sensor', 'get_temperature', 'i32')
InputManager.set_input_value('temperature_sensor', '23')
InputManager.set_consistency_model('temperature_sensor', InputManager.MOST_RECENT)
Output Access Model
When run_output_profiling_test
is enabled, you need to specify the output idempotency model for each output function, created as described in Outputs.
To set the idempotency model of an output, you can use two class methods of the OutputManager
:
set_default_idempotent(default_idempotent)
sets if the default idempotency model is idempotent (default_idempotent = True
) or non-idempotent (default_idempotent = False
). Note that ScEpTIC sets the output idempotency when an output function is created, so you need to specify the default output idempotency before defining output functions
set_idempotency(output_name, idempotency_model)
sets the idempotency model of the considered output
output_name
corresponds to the name of the output function
idempotency_model
corresponds to the idempotency model you want for the output
for a idempotent model, this must be set to
OutputManager.IDEMPOTENT
for a non-idempotent model, this must be set to
OutputManager.NON_IDEMPOTENT
For example, you can specify a non-idempotent access model for a servo as follows:
from ScEpTIC.emulator.io.output import OutputManager, OutputSkeleton
OutputManager.set_default_idempotent(True)
class Servo(OutputSkeleton):
def output_init(self):
self.set_output_val(0)
def get_val(self):
value = self.get_output_val() + self.args[0]
if value >= 360:
value = 0
self.set_output_val(value)
return None
Servo.define_output('SERVO', 'move_servo', 'i32', 'void')
OutputManager.set_idempotency('SERVO', OutputManager.NON_IDEMPOTENT)
Custom Intermittent Execution
When run_profiling
is set, ScEpTIC allows you to profile and debug specific intermittent executions of a program, by exposting C function calls that enable the reproduction of specific energy failures, events logging, and variables dump.
This analysis produces an execution trace with the events of interest, which you can then inspect to identify errors or unexpected behaviours.
ScEpTIC exposes the following builtins that you can use in your program’s source code when the run_profiling
mode is active.
Log Events
sceptic_log()
allows you to track events and the evolution of variable contents.:
sceptic_log(identifier, element);
where identifier
is a string representing an arbitrary information on the event of interest and element
is a variable you want to log.
Note that element
is optional and can be omitted.
For example, you can log the content of a variable a
with:
sceptic_log("a", a);
or you can signal the execution of a portion of code with:
sceptic_log("branch A executed");
Generate Energy Failures
sceptic_reset()
allows you to reproduce specific energy failures.:
sceptic_reset(mode, val);
where mode
is a string that indices how ScEpTIC controls the simulation of the energy failure and value
specifies the optional conditions on whether to simulate the energy failure.
mode
can be set to:
once: ScEpTIC simulates the energy failure only the first time it encounters the function call
clock: ScEpTIC simulates the energy failure if the reset logical clock is equal to the specified
value
. Note that ScEpTIC re-initializes the reset logical clock at system startup and every time it encounters a state-save operation. Moreover, ScEpTIC increments the logical clock every time it simulates a energy failure.conditional: ScEpTIC simulates the energy failure only if the
value
parameter evaluates toTrue
. Note that ScEpTIC generates this energy failure only once
For example, let us consider the following program:
01. checkpoint();
02. for(int i = 0; i < 10; i++) {
03. // BLOCK A
04. sceptic_reset("clock", 1);
05.
06. // BLOCK B
07. sceptic_reset("once");
08.
09. // BLOCK C
10. sceptic_reset("conditional", i > 8);
11. }
Here ScEpTIC generates an energy failure at line 7, then another at line 4, and finally the last one at line 10, but only when i
is equal to 9
.
Change Input Values
sceptic_change_input()
allows you to change the value of an input during the program execution.:
sceptic_change_input(input_name, value);
where input_name
is the name of the input and value
is the new value of the input.
Note that you can call sceptic_change_input()
inside sceptic_before_restore()
, sceptic_after_restore()
, sceptic_before_checkpoint()
, or sceptic_after_checkpoint()
to change the value of an input upon a given state-save event.
For example, you can change the input value of a temperature sensor whenever an energy failure happens as follows:
void sceptic_after_restore() {
sceptic_change_input("temperature", rand());
}
Suppress Printf Output
You can suppress pritntf output without removing them from your code as follows:
from ScEpTIC.AST.transformations.base.instructions.printf import Printf
Printf.stdout_enabled = False