2026-07-10 10:38:57 +02:00

240 lines
11 KiB
Python

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