183 lines
7.1 KiB
Python
183 lines
7.1 KiB
Python
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()
|