from ScEpTIC.emulator.energy.mcu import MCUClockCycleAction, ADCPowerState from ScEpTIC.emulator.energy.mcu_peripheral.protocol_models.external_nvm_protocol import ExternalNVMProtocolAction from ScEpTIC.emulator.energy.options import OpModeName from ScEpTIC.emulator.energy.state_retention import StateRetentionEnergyModel class ExternalNVMCheckpointEnergyModel(StateRetentionEnergyModel): """ Energy model of a generic checkpoint-based technique that: 1) Places checkpoints or trigger-calls inside the program 2) Relies on an external NVM space connected to the MCU """ def __init__(self): super().__init__() # Saved memory cells self.n_memory_cells = 0 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 # External NVM details nvm = self.system_model.mcu.get_external_nvm() register_size = self.system_model.mcu.register_size memory_cell_size = self.system_model.mcu.memory_cell_size # 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. read VERSION_OFFSET_LOCATION from External NVM (non-volatile memory) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 2. Copy buffer data to REG0 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 3. save PC to External NVM at address REG0 (non-volatile memory) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=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. read 0xFFFF from External NVM (0xFFFF = VERSION_OFFSET_LOCATION) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 2. Copy buffer data to REG0 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 3. 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 entire register file (best option due to communication overhead) # Init write (address = REG0; burst write the register file) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_INIT, self.system_model, additional_op_mode_name=op_mode_name) # 4. write REG_x to External NVM -- REG0 contains current version offset and REG_x_LOCATION constant n_regs = self.system_model.mcu.reserved_registers + self.system_model.mcu.registers self.set_max_regs(n_regs) for _ in range(n_regs): n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_BYTE, self.system_model, register_size, additional_op_mode_name=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: # 5. write 1 to External NVM (continue write) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_BYTE, self.system_model, 1, additional_op_mode_name=op_mode_name) # 6. ADD REG0, REG0, REGISTER_AREA_SPACE n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 7. REG1 = 0x00 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) for _ in range(n_memory_cells): # 8. LOAD REG1, REG2 (volatile memory) n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, op_mode_name) # 9. Write REG2 to External NVM (continue write) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_BYTE, self.system_model, memory_cell_size, additional_op_mode_name=op_mode_name) # 10. ADD REG1, REG1, 4 -- (REG1++) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 11. BLT REG1, ESP, #8. -- (jump to 8 if REG1 < stack pointer) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 12. 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 # 13. write REG0 to External NVM at VERSION_OFFSET_LOCATION (re-send header) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_INIT, self.system_model, additional_op_mode_name=op_mode_name) # NB: register_size == address_size (address registers must contain a memory cell address) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.WRITE_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=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 """ op_mode_name = OpModeName.STATE_RESTORE n_ticks = 0 self.n_state_restore += 1 # External NVM details nvm = self.system_model.mcu.get_external_nvm() register_size = self.system_model.mcu.register_size memory_cell_size = self.system_model.mcu.memory_cell_size # [STEP 1] # Load the current version offset # 1. read 0xFFFF from External NVM (0xFFFF = VERSION_OFFSET_LOCATION) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 2. Copy buffer data to REG0 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, 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 # 3. Load ESP from External NVM n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 4. Move buffer data to ESP n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 5. Read MEMORY_SAVED_FLAG from External NVM n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 6. Move buffer data to REG1 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 7. 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: # 8. ADD REG1, REG0, REGISTER_AREA_SPACE n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 9. REG2 = 0x00 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 10. Start read from address REG1 + REGISTER_AREA_SPACE n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) for _ in range(self.n_memory_cells): # 11. Read cell from External NVM n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, memory_cell_size, additional_op_mode_name=op_mode_name) # 12. Save buffer data to REG1(REG2) n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, op_mode_name) # 13. ADD REG3, REG3, 4 n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 14. BLT REG3, ESP, #11. (jump to 11. if REG3 < ESP) n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # [STEP 3] # Restore registers # Restore program counter to resume execution # 15. Set address to REG0 n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) for _ in range(self.system_model.mcu.registers): # 16. LOAD REG_x_LOCATION(REG0), REG_x (non-volatile memory) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) # 17. Save buffer data to reg n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) # 18. read PC from External NVM n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_INIT, self.system_model, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.READ_BYTE, self.system_model, register_size, additional_op_mode_name=op_mode_name) n_ticks += nvm.execute_action(ExternalNVMProtocolAction.STOP, self.system_model, additional_op_mode_name=op_mode_name) # 19. Save buffer data to PC n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, op_mode_name) return n_ticks def reset(self): """ Resets statistics """ super().reset() self.n_memory_cells = 0