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 actions

  • config.program exposes the emulated program configuration

  • config.register_file exposes the emulated register file configuration

  • config.register_allocation exposes the register allocation step configuration

  • config.memory exposes the emulated memory configuration

  • config.result_output exposes the results output configuration

  • config.state_retention exposes the state retention configuration

  • config.ast_transformations exposes custom AST transformations configuration

  • config.deprecated exposes deprecated configuration that will be removed in future releases of ScEpTIC

  • config.analysis exposes the configuration for the analysis ScEpTIC executes

  • config.custom_metrics exposes custom metrics configuration

  • config.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:

Logging Parameters

Parameter Name

Description

Method

Value Type

Default

enabled

Specifies if logging is enabled

set_config()

bool

False

parser_log_level

Specifies the logging level for the LLVM IR parser inside ScEpTIC

set_config()

Logging level values from python’s logging library

logging.WARNING

log_section_content

Specifies whether the LLVM IR parser should also log program’s section content

set_config()

bool

False

log_level

Specifies logging level of ScEpTIC emulation

set_config()

Logging level values from python’s logging library

logging.WARNING

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:

Program Parameters

Parameter Name

Description

Method

Value Type

Default

ir_function_prefix

Specifies the function prefix in LLVM IR used by ScEpTIC LLVM IR parser

set_config()

str

@

main_function_name

Specifies the main function name

set_config()

str

main

file

Specifies the source file relative path

set_config()

str

source.ll

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:

Register File Parameters

Parameter Name

Description

Method

Value Type

Default

use_physical_registers

Specifies whether ScEpTIC simulates a physical or uses the virtual unlimited register file of LLVM IR

set_config()

bool

False

n_physical_registers

Specifies the number of general purpose physical registers available in the target MCU

set_config()

int > 0

10

n_param_registers

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

set_config()

int > 0

4

physical_registers_prefix

Specifies the text prefix for physycal registers (e.g., R0 has R as prefix). Used for the textual representation of the register file

set_config()

str

R

spill_virtual_registers_prefix

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

set_config()

str

%spill_

spill_virtual_registers_type

Specifies the LLVM IR first-class type of the virtual registers used for spilling physical registers. Used for stack size calculation on spilling operations

set_config()

str

i32

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:

Register Allocation Parameters

Parameter Name

Description

Method

Value Type

Default

enabled

Specifies if the register allocation module is enabled. Note that if set to True, it overrides ScEpTIC register file configuration, enabling the physycal register file.

set_config()

bool

False

module_location

Specifies the python path of the register file allocation module

set_config()

str

ScEpTIC.AST.register_allocation

module_name

Specifies the name of the module containing the register allocation algorithm. ScEpTIC already implements the linear scan register allocation algorithm

set_config()

str

linear_scan

allocation_function_name

Specifies the name of the register allocation function that executes the register allocation algorithm.

set_config()

str

allocate_registers

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:

Memory Parameters

Parameter Name

Description

Method

Value Type

Default

address_size

Specifies the bit size of addresses

set_config()

int

32

check_memory_size

Specifies if ScEpTIC should verify that the program execution does not exceed the maximum available memory

set_config()

bool

True

stack_base_address

Specifies the starting address of the stack

set_config()

int

0

stack_prefix

Specifies the prefix for stack addresses. Used for textual representation of the stack

set_config()

str

S

heap_base_address

Specifies the starting address of the heap

set_config()

int

0

heap_prefix

Specifies the prefix for heap addresses. Used for textual representation of the heap

set_config()

str

H

gst_default_memory

Specifies the default memory for global variables. Variables without a section attribute specified will be allocated to this memory

set_config()

volatile or non_volatile

volatile

gst_other_memory_section

Specifies the memory section attribute that indicates to allocate the variable onto the other memory.

set_config()

str

.DATA,.NVM

To config.memory.volatile and config.memory.non_volatile attributes allows you to configure volatile and non-volatile memory through the following elements:

Volatile/Non-volatile Memory Parameters

Parameter Name

Description

Method

Value Type

Default

enabled

Specifies if the memory is enabled

set_config()

bool

True

max_size

Specifies if the memory size

set_config()

int

0

contains_stack

Specifies if the memory contains the stack. Note that this parameter can be set to True either for volatile or non-volatile memory, but not both at the same time

set_config()

bool

True for config.memory.volatile and False for config.memory.non_volatile

contains_heap

Specifies if the memory contains the heap. Note that this parameter can be set to True either for volatile or non-volatile memory, but not both at the same time

set_config()

bool

True for config.memory.volatile and False for config.memory.non_volatile

contains_gst

Specifies if the memory contains global variables. Note that this parameter can be set to True for volatile and non-volatile memory at the same time

set_config()

bool

True

gst_prefix

Specifies the prefix for GST addresses. Used for textual representation of the GST

set_config()

str

VGST for config.memory.volatile and NGST for config.memory.non_volatile

gst_base_address

Specifies the starting address of the GST

set_config()

int

0

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:

State Retention Parameters

Parameter Name

Description

Method

Value Type

Default

type

Specifies the state retention type (i.e., checkpoint-based or task-based). If the type is task, all the other configuration parameters are ignored.

set_config()

checkpoint or task

checkpoint

state_save_function_name

Specifies the name of the routine called to saved the device state, if any

set_config()

str

checkpoint

state_save_function_name

Specifies the name of the routine called to restore the device state, if any

set_config()

str

restore

before_save_function_name

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

set_config()

str

sceptic_before_save

after_save_function_name

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

set_config()

str

sceptic_after_save

before_restore_function_name

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

set_config()

str

sceptic_before_restore

after_restore_function_name

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

set_config()

str

sceptic_after_restore

restore_register_file

Specifies if the state-save (state-restore) routine saves (restores) the register file content.

set_config()

bool

True

restore_stack

Specifies if the state-save (state-restore) routine saves (restores) the stack content.

set_config()

bool

True

restore_heap

Specifies if the state-save (state-restore) routine saves (restores) the heap content.

set_config()

bool

True

restore_volatile_gst

Specifies if the state-save (state-restore) routine saves (restores) the GST allocated onto volatile memory.

set_config()

bool

True

restore_non_volatile_gst

Specifies if the state-save (state-restore) routine saves (restores) the GST allocated onto non-volatile memory.

set_config()

bool

False

restore_environment

Specifies if the state-save (state-restore) routine saves (restores) the environment state (e.g. actuators).

set_config()

bool

False

state_save_strategy

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

set_config()

static_placement or interrupt

static_placement

state_save_behaviour

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)

set_config()

execute or probe

execute

probe_with_adc

Specifies if probing operations use the platform ADC

set_config()

bool

True

calculate_v_save_state

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 state_save_strategy is interrupt

set_config()

bool

False

calculate_v_save_state_additional_cc

Specifies the additional clock cycles ScEpTIC should account for state-save operations when calculating energy buffer voltages

set_config()

int

0

calculate_v_resume_additional_cc

Specifies the additional clock cycles ScEpTIC should account for state-restore operations when calculating energy buffer voltages

set_config()

int

0

calculate_v_restore_min_cc

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

set_config()

int

0

v_save_state

Specifies the voltage at which the MCU should save its state. This is considered only when state_save_strategy is interrupt

set_config()

float

0.0

v_resume

Specifies the voltage at which the MCU should resume the execution. This is considered only when state_save_strategy is interrupt

set_config()

float

0.0

hibernate_after_save

Specifies if the MCU enters a low-power mode after saving the state

set_config()

bool

False

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:

  • mementos_memory_map: applies Mementos memory map

  • nv_memory_map: sets all the memory operations to target NVM

  • ratchet: inserts state-save operations using Ratchet strategy.

  • virtual_memory: applies ALFRED technique

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:

Ratchet Transformation Parameters

Parameter Name

Description

Method

Value Type

Default

functions_with_outside_frame_accesses

Specifies function names whose execution access memory locations outside their stack frame

add_config()

str

optimize_saved_registers

Specifies if state-save operations save only live registers

set_config()

bool

True

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:

ALFRED Virtual Memory Transformation Parameters

Parameter Name

Description

Method

Value Type

Default

n_min_function

Specifies the function that computes ALFRED n_min parameter

set_config()

FunctionType

None

optimize_saved_registers

Specifies if state-save operations save only live registers

set_config()

bool

True

optimize_memory

Specifies if the transformation should apply memory optimizations

set_config()

bool

False

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:

Result Output Parameters

Parameter Name

Description

Method

Value Type

Default

enabled

Specifies if ScEpTIC should save the analysis results

set_config()

bool

True

save_directory

Specifies the directly local path where analysis results are saved

set_config()

str

analysis_results

test_name

Specifies the name of the test executed. The analysis results will be saved in the save_directory path, inside a directory named test_name

set_config()

str

generic_test

dir_append_datetime

Specifies if ScEpTIC should append the date and time to the test name directory

set_config()

bool

True

save_analysis_output

Specifies if ScEpTIC should save the analysis results

set_config()

bool

True

save_code

Specifies if ScEpTIC should save the emulated program’s code in the result directory

set_config()

bool

True

save_state

Specifies if ScEpTIC should save the emulated platform state (register file, memory) in the result directory

set_config()

bool

True

save_results_after_exception

Specifies whether ScEpTIC will save its state and analysis result if an exception is raised during the analysis execution

set_config()

bool

True

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:

add_custom_metric Parameters

Argument Name

Description

Value Type

metric_id

Specifies the numeric ID for the custom metric

int

metric_name

Specifies the metric name, which will be stored in the analysis output file

str

collect_energy

Specifies whether to record the current energy consumption alongside the metric

bool

collect_time

Specifies whether to record the elapsed time alongside the metric

bool

collect_cc

Specifies whether to record the executed clock cycles alongside the metric

bool

data_diffs

Specifies if the collected data is the absolute value or the difference since the last metric collection (diff)

bool

print_data

Specifies if ScEpTIC prints metrics during program simulation

bool

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 tanh

  • stdio.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 issues

  • stdlib.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 takes

  • a 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 name

  • LLVM_IR_parameters is the textual representation of the list of LLVM IR types for the parameters of the function

  • LLVM_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 to value

  • 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 in ScEpTIC/emualtor/io/interrupts_manager.py and initialized in state.interrupts_manager

  • TimersManager located in ScEpTIC/emulator/timers/ and initialized in state.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 name

  • interrupt_name is the name of the interrupt to trigger when the timer expires

  • period is the timer period, in seconds

  • periodic 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:

Analysis Parameters

Parameter Name

Description

Method

Value Type

Default

save_results

Sepecifies whether ScEpTIC will save any analysis result

set_config()

bool

True

results_formats

Sepecifies the analysis result format

add_config()

AnalysisResultFormat.JSON or AnalysisResultFormat.TEXT

AnalysisResultFormat.JSON

enabled_analysis

Specifies the analysis that ScEpTIC will run

add_config()

str

None

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:

min_capacitor_size Analysis Parameters

Parameter Name

Description

Method

Value Type

Default

system_model

Sepecifies the system energy model used by ScEpTIC to simulate energy consumption and timing

set_config()

SystemEnergyModel

None

min_capacitance

Sepecifies the minimum capacitance size to consider in the analysis

set_config()

str

10u (10 micro F)

capacitance_size_step

Sepecifies the capacitance size increase to consider in each step of the analysis

set_config()

str

5u (5 micro F)

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:

energy Analysis Parameters

Parameter Name

Description

Method

Value Type

Default

max_simulation_time

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

set_config()

int

None

system_model

Sepecifies the system energy model used by ScEpTIC to simulate energy consumption and timing

set_config()

SystemEnergyModel

None

ignore_power_failures_during_state_save

Sepecifies whether to ignore energy failures occurring during state-save operations, meaning that ScEpTIC always execute state-save operations

set_config()

bool

False

calculate_cache_only

Sepecifies whether ScEpTIC should calculate only the v_resume and v_save_state. This is useful if you need to execute multiple parallel analysis

set_config()

bool

False

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:

energy Analysis Parameters

Parameter Name

Description

Method

Value Type

Default

run_continuous

Specifies to execute the program sequentially, without emulating energy failures

set_config()

bool

False

run_locate_memory_test

Specifies to execute the analysis to locate memory anomalies

set_config()

bool

False

run_evaluate_memory_test

Specifies to execute the analysis to evaluate the effects of memory anomalies

set_config()

bool

False

run_input_consistency_test

Specifies to execute the analysis to evaluate environment input anomalies

set_config()

bool

False

run_output_profiling_test

Specifies to execute the analysis to evaluate environment outputs behavior

set_config()

bool

False

run_profiling

Specifies to execute the analysis for debugging specific intermittent executions

set_config()

bool

False

run_memory_size_measure

Specifies to execute the analysis for identifying the program’s memory usage

set_config()

bool

False

execution_depth

Specifies to execution_depth parameter for run_locate_memory_test and run_evaluate_memory_test

set_config()

bool

False

stop_on_first_anomaly

Specifies to stop the analysis after an anomaly is found

set_config()

bool

False

double_buffer_for_anomalies

Specifies if the memory size accounts for a double buffer to avoid anomalies for run_memory_size_measure

set_config()

bool

True

global_variables_default_memory

Specifies the default memory location for global variables for run_memory_size_measure

set_config()

VirtualMemoryEnum

VirtualMemoryEnum.VOLATILE

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 to True. 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