.. |linear-scan| raw:: html linear scan register allocation algorithm .. |msp430| raw:: html MSP430 .. |alfred| raw:: html ALFRED .. |mementos| raw:: html Mementos .. |hibernus| raw:: html Hibernus .. |dino| raw:: html DINO .. |ratchet| raw:: html Ratchet .. |quickrecall| raw:: html QuickRecall .. |non-termination| raw:: html non-termination path bug .. _configuration-label: 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: .. list-table:: Logging Parameters :header-rows: 1 * - 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: .. list-table:: Program Parameters :header-rows: 1 * - 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: .. list-table:: Register File Parameters :header-rows: 1 * - 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: .. list-table:: Register Allocation Parameters :header-rows: 1 * - 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| - ``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|, 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: .. list-table:: Memory Parameters :header-rows: 1 * - 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: .. list-table:: Volatile/Non-volatile Memory Parameters :header-rows: 1 * - 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: .. list-table:: State Retention Parameters :header-rows: 1 * - 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: - |mementos| - |ratchet| - |hibernus| - |quickrecall| - |dino| 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 :ref:`custom-ast-label`. AST Transformations - Ratchet ***************************** Ratchet transformation provides the following configuration paramets: .. list-table:: Ratchet Transformation Parameters :header-rows: 1 * - 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: .. list-table:: ALFRED Virtual Memory Transformation Parameters :header-rows: 1 * - 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-tr-label: 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: .. list-table:: Result Output Parameters :header-rows: 1 * - 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: .. list-table:: ``add_custom_metric`` Parameters :header-rows: 1 * - 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 :ref:`compilation-label` 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-label: 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-label: 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. .. _configuration-input-label: 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. .. _configuration-output-label: 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: .. list-table:: Analysis Parameters :header-rows: 1 * - 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|. 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: .. list-table:: min_capacitor_size Analysis Parameters :header-rows: 1 * - 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 :ref:`system-model-label` 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: .. list-table:: energy Analysis Parameters :header-rows: 1 * - 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 :ref:`system-model-label` 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 :ref:`custom-ast-tr-label`, ``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: .. list-table:: energy Analysis Parameters :header-rows: 1 * - 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 :ref:`configuration-input-label`. 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 :ref:`configuration-output-label`. 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) .. _profiling-label: 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