from ScEpTIC import ConfigurationException from ScEpTIC.AST.elements.instructions.memory_operations import LoadOperation, StoreOperation from ScEpTIC.AST.elements.instructions.other_operations import CallOperation from ScEpTIC.AST.elements.instructions.termination_instructions import ReturnOperation from ScEpTIC.analysis.utils.saved_state_calculator import SavedStateCalculator from ScEpTIC.analysis.utils.settings_cache import SettingsCache from ScEpTIC.emulator.energy.mcu import MCUPowerState, MCUClockCycleAction from ScEpTIC.emulator.energy.options import OpModeName class MaxCyclesBetweenStateSaveIdentifier: """ Utility that identifies the clock cycles between two state-save operations with the highest energy consumption. """ cache_element = "max_cycles_between_state_save" def __init__(self, vm, system_model): """ :param vm: the ScEpTIC vm :param system_model: the system model """ self.vm = vm self.system_model = system_model self.max_cycles = [] self.state_calc = SavedStateCalculator(self.vm) # Cache file_name = self.vm.state.config.program.file mcu_frequency = self.system_model.mcu.get_nominal_frequency() mcu_v_on = self.system_model.mcu.v_on cache_target = "{}_static_{}_{}".format(file_name, mcu_frequency, mcu_v_on) self._cache = SettingsCache(self.cache_element, cache_target) def _load_cache(self): """ Load data from the cache file """ self._cache.load() try: # Note: de-serialization from string is required self.max_cycles = [MCUClockCycleAction[x] for x in self._cache.data['max_cycles']] print(f"Max clock cycles between two state-saving operations loaded - {len(self.max_cycles)}") return True except KeyError: return False def _save_cache(self): """ Save data into the cache file """ # Note: serialization to string is required self._cache.data = { 'max_cycles': [str(x) for x in self.max_cycles], } self._cache.save() def identify(self): """ Identifies the sequence of clock cycles (between two state-saving operations) that lead to the maximum energy consumption """ if not self.vm.state_retention.static_placement: raise ConfigurationException(f"The system does not have a static placement of state-saving operations!") if not self.vm.state_retention.probe_energy_buffer: raise ConfigurationException(f"The system does not probe the energy buffer before saving the state!") if self._load_cache(): return print(f"Identifying maximum clock cycles between two state-saving operations") tmp_v = self.system_model.energy_buffer.get_voltage() tmp_discharge = self.system_model.energy_buffer.discharge_enabled self.system_model.energy_buffer.discharge_enabled = False tmp_source = self.system_model.energy_source.enabled self.system_model.energy_source.enabled = False self._identify() # Reset VM self.vm.reset() # Set voltage self.system_model.energy_buffer.set_voltage(tmp_v) # Re-initialize custom devices self.system_model.init(custom_devices_print_enabled=False) # Re-set voltage self.system_model.energy_buffer.set_voltage(tmp_v) # Reset self.system_model.reset() self.system_model.energy_buffer.discharge_enabled = tmp_discharge self.system_model.energy_source.enabled = tmp_source print(f" -> Max clock cycles {len(self.max_cycles)}") self._save_cache() def _identify(self): """ Runs the program and identifies the cycles that lead to the maximum energy consumption """ # Reset self.system_model.energy_buffer.set_voltage(self.system_model.mcu.v_on) self.system_model.reset() self.vm.reset() # Init custom devices and MCU self.system_model.init(custom_devices_print_enabled=False) op_mode_name = OpModeName.PROGRAM_EXECUTION state_save_function_name = self.vm.state_retention.routine_names['save'] max_energy = 0.0 current_cycles = [] # Run program while not self.vm.state.program_end_reached: current_instruction = self.vm.state.current_instruction op_ticks = current_instruction.tick_count # Execute instruction self.vm.state.run_step() if self.vm.state.global_clock % 100000 == 0: print(f"Still going: {self.vm.state.register_file.pc} / {self.vm.state.global_clock}") # Load / Store if isinstance(current_instruction, LoadOperation) or isinstance(current_instruction, StoreOperation): op_type, additional_op_mode_name = self.state_calc.get_memory_access_mcu_action(current_instruction) self.system_model.run_step(op_type, op_mode_name, additional_op_mode_name=additional_op_mode_name) current_cycles.append(op_type) # State-save elif isinstance(current_instruction, CallOperation) and current_instruction.resolve_function_name() == state_save_function_name: energy = self.system_model.get_drained_energy() if energy > max_energy: del self.max_cycles max_energy = energy self.max_cycles = current_cycles print(f" New sequence: {self.vm.state.global_clock}cc / {len(current_cycles)} with {energy}J") current_cycles = [] # Charge capacitor and reset self.system_model.energy_buffer.set_voltage(self.system_model.mcu.v_on) self.system_model.reset(False) # Init custom devices and MCU self.system_model.init(custom_devices_print_enabled=False) # Call / Return elif isinstance(current_instruction, CallOperation) or isinstance(current_instruction, ReturnOperation): mem_op_type, additional_op_mode_name = self.state_calc.get_call_ret_mcu_action(current_instruction) memory_ticks = current_instruction.memory_tick_count normal_ticks = op_ticks - memory_ticks for _ in range(memory_ticks): self.system_model.run_step(mem_op_type, op_mode_name, additional_op_mode_name=additional_op_mode_name) current_cycles.append(mem_op_type) for _ in range(normal_ticks): self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) current_cycles.append(MCUClockCycleAction.NO_MEMORY_ACCESS) # All the other instructions else: self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) current_cycles.append(MCUClockCycleAction.NO_MEMORY_ACCESS) print(f"Max sequence consumes {max_energy} and has {len(self.max_cycles)} cycles") self.vm.reset()