from ScEpTIC.emulator.energy.mcu import MCUClockCycleAction, ADCPowerState from ScEpTIC.emulator.energy.options import OpModeName from ScEpTIC.emulator.energy.state_retention import StateRetentionEnergyModel class InternalNVMCheckpointEnergyModel(StateRetentionEnergyModel): """ Energy model of a generic checkpoint-based technique that: 1) Places checkpoints or trigger-calls inside the program 2) Relies on an NVM space internal and directly-addressable from the MCU """ def __init__(self): super().__init__() # Saved memory cells self.n_memory_cells = 0 self.mcu_clock_cycle_action = MCUClockCycleAction.NON_VOLATILE_MEMORY_ACCESS self.additional_op_mode_name = None def use_physical_memory(self, val): if val: self.mcu_clock_cycle_action = MCUClockCycleAction.PHYSICAL_MEMORY_ACCESS self.additional_op_mode_name = 'STATE_RETENTION_MEMORY' else: self.mcu_clock_cycle_action = MCUClockCycleAction.NON_VOLATILE_MEMORY_ACCESS self.additional_op_mode_name = None def execute_probe_energy_buffer(self): """ Probes the energy buffer to identify if a checkpoint is required :return: the number of executed clock cycles """ n_ticks = 0 op_mode_name = OpModeName.PROBE_ENERGY_BUFFER # Turn on ADC # - Run instructions # - ADCPowerState -> ON for _ in range(self.system_model.mcu.adc_instructions['on']): n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) self.system_model.mcu.set_adc_state(ADCPowerState.ON) # Wait for startup and data to be ready for _ in range(self.system_model.mcu.get_ADC_wait_cycles()): n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name, True) # Transfer data to reg for _ in range(self.system_model.mcu.adc_instructions['transfer']): n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # Turn off ADC n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) self.system_model.mcu.set_adc_state(ADCPowerState.OFF) # Compare result # 1. SUB Rx, ADC_DATA, THRESHOLD # 2. BNEQ CHECKPOINT, RETURN n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) return n_ticks def execute_save_state(self, save_only_pc, n_registers, n_memory_cells): """ Calculate the cost of saving the state :param save_only_pc: if the checkpoint saves only the program counter :param n_registers: number of general purpose registers saved :param n_memory_cells: number of memory cells saved :return: number of executed clock cycles """ n_ticks = 0 op_mode_name = OpModeName.STATE_SAVE self.n_state_save += 1 # Checkpoint memory structure - Double buffered for 2-phase-commit (2PC) # 0xFFFF -> contains version offset (0x0000 or 0xF000) # [Version 0] # 0x0000 -> PC # 0x0001 -> other reserved registers # ... # 0x0005 -> register file (reg0, ...) # ... # 0x0010 -> main memory # ... # [Version 1 - offset 0xF000] # 0xF000 -> PC # 0xF001 -> other reserved registers # ... # 0xF005 -> register file (reg0, ...) # ... # 0xF010 -> main memory # ... # The checkpoint saves only the PC (no 2PC required) if save_only_pc: # -> Get the current version offset and overwrite the saved PC # 1. LOAD VERSION_OFFSET_LOCATION, REG0 (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 2. STORE PC, REG0 (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) self.set_max_regs(1) return n_ticks # The checkpoint saves some registers and/or memory # [STEP 1] # Calculate the new offset the checkpoint that is being saved # -> Get the current version offset and calculate the new offset # 1. LOAD 0xFFFF, REG0 (0xFFFF = VERSION_OFFSET_LOCATION) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 2. ADD REG0, REG0, NEXT_VERSION_LOCATION (assume optimized) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # [STEP 2] # Save registers at a predefined location # 3. STORE REG_x, REG_x_LOCATION(REG0) -- REG0 contains current version offset and REG_x_LOCATION constant n_regs = self.system_model.mcu.reserved_registers + n_registers self.set_max_regs(n_regs) for _ in range(n_regs): n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) self.n_memory_cells = n_memory_cells self.set_max_memory_cells(n_memory_cells) # [STEP 3] # Save memory # - Set the memory saved flag (necessary to restore) # - Increment REG0 to point to memory area (REG0 is now the offset that points to 0x00 of memory) # - Loop that saves memory # - Read memory cell at address X from volatile memory # - Save value at non-volatile memory address X+REG0 if n_memory_cells > 0: # 4. STORE REG0(MEMORY_SAVED_FLAG), 1 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 5. ADD REG0, REG0, REGISTER_AREA_SPACE n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 6. REG1 = 0x00 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) for _ in range(n_memory_cells): # 7. LOAD REG1, REG2 (volatile memory) n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, op_mode_name) # 8. STORE REG2, REG1(REG0) (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 9. ADD REG1, REG1, 4 -- (REG1++) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 10. BLT REG1, ESP, #7. -- (jump to 7 if REG1 < stack pointer) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 11. SUB REG0, REG0, REGISTER_AREA_SPACE -- (remove REGISTER_AREA_SPACE to get back new version offset) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # [STEP 4] # Update saved version offset # 12. STORE REG0, VERSION_OFFSET_LOCATION (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) return n_ticks def execute_restore_state(self): """ Calculate the cost of restoring the state :return: the number of executed clock cycles """ n_ticks = 0 op_mode_name = OpModeName.STATE_RESTORE self.n_state_restore += 1 # [STEP 1] # Load the current version offset # 1. LOAD 0xFFFF, REG0 (0xFFFF = VERSION_OFFSET_LOCATION) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # [STEP 2] # Restore memory # - Restore stack pointer to identify last memory cell # - Check memory saved flag # - Set REG1 to point to memory area (REG1 is now the offset that points to 0x00 of memory) # - Loop that restores memory # - Read memory cell at address X+REG0 from non-volatile memory # - Save value at volatile memory address X # 2. LOAD ESP, REG0(ESP_ADDRESS) (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 3. LOAD REG1, REG0(MEMORY_SAVED_FLAG) (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 4. BZ REG1, #.11 (jump to 11 if REG1 == 0, that is, MEMORY_SAVED_FLAG not set) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) if self.n_memory_cells > 0: # 5. ADD REG1, REG0, REGISTER_AREA_SPACE n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 6. REG2 = 0x00 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) for _ in range(self.n_memory_cells): # 7. LOAD REG3, REG1(REG2) (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 8. STORE REG2, REG3 (volatile memory) n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, op_mode_name) # 9. ADD REG3, REG3, 4 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 10. BLT REG3, ESP, #7. (jump to 7. if REG3 < ESP) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # [STEP 3] # Restore registers # Restore program counter to resume execution for _ in range(self.system_model.mcu.registers): # 11. LOAD REG_x_LOCATION(REG0), REG_x (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) # 12. LOAD PC_LOCATION(REG0), PC (non-volatile memory) n_ticks += self.system_model.run_step(self.mcu_clock_cycle_action, op_mode_name, additional_op_mode_name=self.additional_op_mode_name) return n_ticks def reset(self): """ Resets statistics """ super().reset() self.n_memory_cells = 0