390 lines
17 KiB
Python
390 lines
17 KiB
Python
import copy
|
|
import json
|
|
|
|
from ScEpTIC.emulator.memory import utils
|
|
from ScEpTIC.emulator.energy.memory.physical_memory_energy_model import PhysicalMemoryEnergyModel
|
|
from ScEpTIC.emulator.memory.physical.physical_cell_state_enum import PhysicalCellStateEnum
|
|
from ScEpTIC.emulator.memory.physical.physical_memory_array import PhysicalMemoryArray
|
|
from ScEpTIC.emulator.memory.physical.physical_memory_management_unit import PhysicalMemoryManagementUnit
|
|
from ScEpTIC.emulator.register_file.program_counter import ProgramCounter
|
|
|
|
|
|
class PhysicalStateRetention:
|
|
"""
|
|
State retention on physical memory
|
|
"""
|
|
|
|
def __init__(self, n_registers=12, register_size=4, gst_size=4096, stack_size=4096, version_bytes=1):
|
|
"""
|
|
:param n_registers: number of registers
|
|
:param register_size: size of registers in bytes
|
|
:param gst_size: size of global symbol table in bytes
|
|
:param stack_size: size of stack in bytes
|
|
"""
|
|
self.version_bytes = version_bytes
|
|
self.n_registers = n_registers
|
|
self.register_size = register_size
|
|
ProgramCounter._word_size = register_size
|
|
self.gst_size = gst_size
|
|
self.stack_size = stack_size
|
|
self.state_size = self.n_registers * self.register_size + self.gst_size + self.stack_size
|
|
|
|
self.write_counter = 0
|
|
|
|
self.mmu = None
|
|
self.energy_model = None
|
|
|
|
state_map = {
|
|
# 0 -> register_size-1
|
|
'pc': 0,
|
|
# register_size -> 2*register_size - 1
|
|
'esp': self.register_size,
|
|
# 2*register_size -> n_registers*register_size - 1
|
|
'registers': [x for x in range(self.register_size*2, self.register_size*(2+(self.n_registers-2)), self.register_size)],
|
|
# n_registers*register_size -> n_registers*register_size + gst_size - 1
|
|
'gst': self.register_size * self.n_registers,
|
|
# n_registers*register_size + gst_size
|
|
'stack': self.register_size * self.n_registers + self.gst_size,
|
|
}
|
|
|
|
self.state_map = {
|
|
'version': 0,
|
|
'data': {
|
|
0: {},
|
|
1: {},
|
|
}
|
|
}
|
|
|
|
self.dumps = {0: None, 1: None}
|
|
|
|
for element, addr in state_map.items():
|
|
for version in [0, 1]:
|
|
offset = self.version_bytes + version * self.state_size
|
|
|
|
if isinstance(addr, list):
|
|
self.state_map['data'][version][element] = [x+offset for x in addr]
|
|
else:
|
|
self.state_map['data'][version][element] = addr + offset
|
|
|
|
def init_memory(self, model_name, regulator_efficiency=0.8, read_before_write=True, wear_only_if_change=False, refresh_on_read=True, die_on_last_cycle=False):
|
|
"""
|
|
Initializes the physical memory
|
|
:param model_name: the memory model name
|
|
:param regulator_efficiency: the voltage regulator efficiency for the MMU
|
|
:param read_before_write: read data before write; the MMU writes only cells whose value will change
|
|
"""
|
|
size = self.version_bytes + 2 * self.state_size
|
|
name = 'STATE_RETENTION_MEMORY'
|
|
|
|
self.energy_model = PhysicalMemoryEnergyModel(model_name, regulator_efficiency)
|
|
self.energy_model.set_name(name)
|
|
self.mmu = PhysicalMemoryManagementUnit(0, size, name, read_before_write)
|
|
cell_array = PhysicalMemoryArray.from_energy_model(self.energy_model, size, wear_only_if_change, refresh_on_read, die_on_last_cycle)
|
|
|
|
self.mmu.attach_cell_array(cell_array)
|
|
self.mmu.attach_state_map(self.state_map)
|
|
|
|
def __get_version(self):
|
|
return int(self.mmu.memory_read(self.state_map['version'], self.version_bytes*8, sync_memory=False)[-1])
|
|
|
|
def __update_version(self, version):
|
|
self.mmu.memory_write(version, self.state_map['version'], self.version_bytes*8, sync_memory=False)
|
|
if version != self.__get_version():
|
|
self.mmu.log_data("VERSION_UPDATE", self.state_map['version'], self.version_bytes*8, "CHECKPOINT_VERSION", self.__get_version(), version)
|
|
elif PhysicalMemoryArray.log_all_operations:
|
|
self.mmu.log_data("VERSION_UPDATE", self.state_map['version'], self.version_bytes*8, "CHECKPOINT_VERSION", self.__get_version(), version)
|
|
|
|
def __get_virtual_addr(self, address):
|
|
# remove version offset
|
|
address -= self.version_bytes
|
|
|
|
if address >= self.state_size:
|
|
address -= self.state_size
|
|
|
|
return address
|
|
|
|
def get_mmu(self):
|
|
"""
|
|
Returns the mmu
|
|
"""
|
|
return self.mmu
|
|
|
|
def get_energy_model(self):
|
|
"""
|
|
Returns the energy model
|
|
"""
|
|
return self.energy_model
|
|
|
|
def write(self, dump):
|
|
"""
|
|
Execute the memory write of the saved state
|
|
:param dump: state to write
|
|
"""
|
|
self.mmu.log_data('STATE_SAVE_START', None, None, None, None, None)
|
|
version = (self.__get_version() + 1) % 2
|
|
self.dumps[version] = copy.deepcopy(dump)
|
|
#print(f"Writing dump into version #{version}")
|
|
self.__save_registers(dump, version)
|
|
self.__save_stack(dump, version)
|
|
self.__save_gst(dump, version)
|
|
self.__update_version(version)
|
|
self.__save_mem_json()
|
|
self.mmu.log_data('STATE_SAVE_END', None, None, None, None, None)
|
|
self.write_counter = self.write_counter + 1
|
|
|
|
def read(self):
|
|
"""
|
|
Execute the memory read of the saved state and synchronizes the dump state accordingly to the underlying physical memory state
|
|
"""
|
|
self.mmu.log_data('STATE_RESTORE_START', None, None, None, None, None)
|
|
version = self.__get_version()
|
|
dump = copy.deepcopy(self.dumps[version])
|
|
print(f"Reading dump from version #{version}")
|
|
self.__restore_registers(dump, version)
|
|
self.__restore_stack(dump, version)
|
|
self.__restore_gst(dump, version)
|
|
self.__trigger_restore_log()
|
|
self.mmu.log_data('STATE_RESTORE_END', None, None, None, None, None)
|
|
return dump
|
|
|
|
def __save_registers(self, dump, version):
|
|
"""
|
|
Save registers
|
|
:param dump: state to write
|
|
:param version: memory version
|
|
"""
|
|
if 'register_file' not in dump or dump['register_file'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
# Save PC
|
|
pc_address = state_map['pc']
|
|
#pc_val = {'function_name': dump['register_file']['pc'].function_name, 'instruction_number': dump['register_file']['pc'].instruction_number}
|
|
pc_val = dump['register_file']['pc'].get_address()
|
|
self.mmu.memory_write(pc_val, pc_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(pc_address))
|
|
|
|
# Save ESP
|
|
esp_address = state_map['esp']
|
|
self.mmu.memory_write(dump['stack']['top_address'], esp_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(esp_address))
|
|
|
|
reg_id = 0
|
|
for reg_stack_id in reversed(sorted(dump['register_file']['reg_tracker'].keys())):
|
|
regs = dump['register_file']['reg_tracker'][reg_stack_id]['reg_list']
|
|
for reg_name in regs:
|
|
reg_val = dump['register_file']['reg_tracker'][reg_stack_id][reg_name].value
|
|
reg_address = state_map['registers'][reg_id]
|
|
self.mmu.memory_write(reg_val, reg_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(reg_address))
|
|
reg_id += 1
|
|
|
|
if reg_id >= (self.n_registers - 2):
|
|
return
|
|
|
|
def __restore_registers(self, dump, version):
|
|
"""
|
|
Restores registers
|
|
:param dump: state to read
|
|
:param version: memory version
|
|
"""
|
|
if 'register_file' not in dump or dump['register_file'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
version_status = self.mmu.check_status(self.state_map['version'], self.version_bytes * 8)
|
|
|
|
# Read PC and check if it is consistent
|
|
pc_address = state_map['pc']
|
|
pc = self.mmu.memory_read(pc_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(pc_address))
|
|
#pc_val = {'function_name': dump['register_file']['pc'].function_name, 'instruction_number': dump['register_file']['pc'].instruction_number}
|
|
pc_val = dump['register_file']['pc'].get_address()
|
|
match, _, _ = utils.check_data(pc_val, pc)
|
|
|
|
# Invalidate PC
|
|
if not match:
|
|
pc = utils.from_binary(pc, int)
|
|
new_pc = dump['register_file']['pc'].get_pc_str_from_address(pc)
|
|
|
|
expected_pc = {'function_name': dump['register_file']['pc'].function_name, 'instruction_number': dump['register_file']['pc'].instruction_number}
|
|
|
|
status = self.mmu.check_status(pc_address, self.register_size*8)
|
|
if status == PhysicalCellStateEnum.OK:
|
|
status = f"Version status: {version_status.value}; Cell status: {status.value}"
|
|
else:
|
|
status = status.value
|
|
|
|
print(f"State saved for PC degraded ({status}): {hex(pc)} ({new_pc}) instead of {hex(pc_val)} ({expected_pc})")
|
|
self.mmu.log_data("RESTORE", pc_address, self.register_size*8, "CHECKPOINT_PC", pc, pc_val)
|
|
|
|
dump['register_file']['pc'].function_name = new_pc['function_name']
|
|
dump['register_file']['pc'].instruction_number = new_pc['instruction_number']
|
|
elif PhysicalMemoryArray.log_all_operations:
|
|
pc = utils.from_binary(pc, int)
|
|
self.mmu.log_data("RESTORE", pc_address, self.register_size*8, "CHECKPOINT_PC", pc, pc)
|
|
|
|
# Read ESP and check if it is consistent
|
|
esp_address = state_map['esp']
|
|
esp = self.mmu.memory_read(esp_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(esp_address))
|
|
match, _, bits_val = utils.check_data(dump['stack']['top_address'], esp)
|
|
|
|
# Update ESP
|
|
if not match:
|
|
status = self.mmu.check_status(esp_address, self.register_size*8)
|
|
if status == PhysicalCellStateEnum.OK:
|
|
status = f"Version status: {version_status.value}; Cell status: {status.value}"
|
|
else:
|
|
status = status.value
|
|
|
|
print(f"State saved for EBP degraded ({status}): {bits_val} instead of {dump['stack']['top_address']}")
|
|
self.mmu.log_data("RESTORE", esp_address, self.register_size*8, "CHECKPOINT_ESP", bits_val, dump['stack']['top_address'])
|
|
dump['stack']['top_address'] = bits_val
|
|
elif PhysicalMemoryArray.log_all_operations:
|
|
self.mmu.log_data("RESTORE", esp_address, self.register_size*8, "CHECKPOINT_ESP", esp, esp)
|
|
|
|
# Read registers and check if they are consistent
|
|
reg_id = 0
|
|
for reg_stack_id in reversed(sorted(dump['register_file']['reg_tracker'].keys())):
|
|
regs = dump['register_file']['reg_tracker'][reg_stack_id]['reg_list']
|
|
for reg_name in regs:
|
|
reg_val = dump['register_file']['reg_tracker'][reg_stack_id][reg_name].value
|
|
reg_address = state_map['registers'][reg_id]
|
|
physical_reg_val = self.mmu.memory_read(reg_address, self.register_size*8, sync_memory=True, sync_addr=self.__get_virtual_addr(reg_address))
|
|
match, _, bits_val = utils.check_data(reg_val, physical_reg_val)
|
|
|
|
if not match:
|
|
status = self.mmu.check_status(reg_address, self.register_size*8)
|
|
if status == PhysicalCellStateEnum.OK:
|
|
status = f"Version status: {version_status.value}; Cell status: {status.value}"
|
|
else:
|
|
status = status.value
|
|
|
|
print(f"Saved state for register {reg_name} degraded ({status}): {bits_val} instead of {reg_val}")
|
|
self.mmu.log_data("RESTORE", reg_address, self.register_size*8, f"CHECKPOINT_{reg_name}", bits_val, reg_val)
|
|
|
|
if reg_stack_id == len(dump['register_file']['reg_tracker']):
|
|
dump['register_file']['registers'][reg_name].value = bits_val
|
|
else:
|
|
dump['register_file']['reg_stack'][reg_stack_id][reg_name].value = bits_val
|
|
|
|
elif PhysicalMemoryArray.log_all_operations:
|
|
self.mmu.log_data("RESTORE", reg_address, self.register_size*8, f"CHECKPOINT_{reg_name}", reg_val, reg_val)
|
|
|
|
reg_id += 1
|
|
|
|
if reg_id >= (self.n_registers - 2):
|
|
return
|
|
|
|
def __save_memory(self, address, memory_cells):
|
|
"""
|
|
Save memory
|
|
:param address: memory start address
|
|
:param memory_cells: memory cells list
|
|
"""
|
|
for memory_cell in memory_cells.values():
|
|
val = memory_cell.get_content()
|
|
size = memory_cell.get_bit_size()
|
|
self.mmu.memory_write(val, address, size, sync_memory=True, sync_addr=self.__get_virtual_addr(address))
|
|
|
|
# Update next address
|
|
address += int(size / 8)
|
|
|
|
def __restore_memory(self, address, memory_cells):
|
|
"""
|
|
Restore memory and synchronizes the dump state accordingly to the underlying physical memory state
|
|
:param address: memory start address
|
|
:param memory_cells: memory cells list
|
|
"""
|
|
version_status = self.mmu.check_status(self.state_map['version'], self.version_bytes * 8)
|
|
|
|
for memory_cell in memory_cells.values():
|
|
val = memory_cell.get_content()
|
|
size = memory_cell.get_bit_size()
|
|
physical_val = self.mmu.memory_read(address, size, sync_memory=True, sync_addr=self.__get_virtual_addr(address))
|
|
|
|
match, _, bits_val = utils.check_data(val, physical_val)
|
|
|
|
if not match:
|
|
status = self.mmu.check_status(address, size)
|
|
if status == PhysicalCellStateEnum.OK:
|
|
status = f"Version status: {version_status.value}; Cell status: {status.value}"
|
|
else:
|
|
status = status.value
|
|
|
|
print(f"State saved for memory cell {memory_cell.absolute_address} degraded ({status}): {bits_val} instead of {val}")
|
|
self.mmu.log_data("RESTORE", address, size, f"{memory_cell.absolute_address}", bits_val, val)
|
|
memory_cell.set_content(bits_val)
|
|
elif PhysicalMemoryArray.log_all_operations:
|
|
self.mmu.log_data("RESTORE", address, size, f"{memory_cell.absolute_address}", val, val)
|
|
|
|
# Update next address
|
|
address += int(size / 8)
|
|
|
|
def __save_stack(self, dump, version):
|
|
"""
|
|
Saves the stack
|
|
:param dump: state to write
|
|
:param version: memory version
|
|
"""
|
|
if 'stack' not in dump or dump['stack'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
address = state_map['stack']
|
|
self.__save_memory(address, dump['stack']['memory'])
|
|
|
|
def __restore_stack(self, dump, version):
|
|
"""
|
|
Restores the stack and synchronizes the dump state accordingly to the underlying physical memory state
|
|
:param dump: state to read
|
|
:param version: memory version
|
|
"""
|
|
if 'stack' not in dump or dump['stack'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
address = state_map['stack']
|
|
self.__restore_memory(address, dump['stack']['memory'])
|
|
|
|
def __save_gst(self, dump, version):
|
|
"""
|
|
Saves the gst
|
|
:param dump: state to write
|
|
:param version: memory version
|
|
"""
|
|
if 'volatile_gst' not in dump or dump['volatile_gst'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
address = state_map['gst']
|
|
self.__save_memory(address, dump['volatile_gst']['memory'])
|
|
|
|
def __restore_gst(self, dump, version):
|
|
"""
|
|
Restores the gst and synchronizes the dump state accordingly to the underlying physical memory state
|
|
:param dump: state to read
|
|
:param version: memory version
|
|
"""
|
|
if 'volatile_gst' not in dump or dump['volatile_gst'] is None:
|
|
return
|
|
|
|
# get state map of current data version
|
|
state_map = self.state_map['data'][version]
|
|
|
|
address = state_map['gst']
|
|
self.__restore_memory(address, dump['volatile_gst']['memory'])
|
|
|
|
def __save_mem_json(self):
|
|
self.mmu.write_save_trace(self.write_counter)
|
|
|
|
def __trigger_restore_log(self):
|
|
self.mmu.write_restore_trace(self.write_counter - 1)
|
|
|
|
|