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

499 lines
19 KiB
Python

import logging
from ScEpTIC.emulator.memory.virtual_global_symbol_table_unifier import VirtualGlobalSymbolTableUnifier
from ScEpTIC.emulator.memory.virtual_ram import VolatileRAM, NonVolatileRAM
from ScEpTIC.exceptions import MemoryException, ConfigurationException
class Memory:
"""
This class represents the memory and is in charge of interfacing with it.
It initializes both Volatile and Non-Volatile Memory, accordingly to a given configuration.
Stack and Heap must be present and can reside only into one Memory at a time, in any combination.
Global Symbol Table must be present and can reside in Volatile Memory, Non-Volatile Memory, or both.
All the memory spaces between stack, heap and global symbol tables are separated.
Memory has not a maximum dimension, since this simulator is not focused on memory representation.
In order to get a correct memory representation, I should also put all the things in the stack (saved PC, canaries)
and I would be bounded to the compiler back-end conversions and architecture-dependent optimizations.
Not having a memory size, makes difficult to have a continuous memory space such that at a certain address
the Volatile Memory ends and starts the NVM, but makes possible to test the program for a generic architecture and then verify if
it fits the arch. memory requirements.
This is why the Volatile Memory and NVM have overlapping addresses (but will be accessed correctly accordingly to the configuration).
Having variables in both Volatile Memory and NVM will require a sort of "lookup table", or the data layout should be performed.
In this implementation, the lookup table for the variables contains also them and extends the stack with some functionalities.
In this way:
- There is a better separation between global variables and local stack
- With the VirtualGlobalSymbolTableUnifier is easy to access a variable "transparently" (independently if it resides in NVM or Volatile Memory)
- There is no need to differentiate addresses between Volatile Memory and NVM.
- Is possible to compare directly a portion (stack, heap, vars) of the NVM and Volatile memory with a previous dump
Stack and heap are kept as separated objects (and not directly mapped to the same "memory") because I do not have to deal
with overflows that causes the stack or heap to be damaged.
"""
def __init__(self, config):
logging.debug('Initializing main memory.')
self.address_prefixes = {'stack': '', 'heap': '', 'volatile_gst': '', 'non_volatile_gst': ''}
# prefixes
self.address_prefixes['stack'] = f"{config.stack_prefix}-"
self.address_prefixes['heap'] = f"{config.heap_prefix}-"
# Check variables
stack_initialized = False
heap_initialized = False
gst_initialized = False
self.memory_positions = {'stack': None, 'heap': None, 'gst': []}
self.nvm_prefixes = []
self.nvm_content = {'stack': False, 'heap': False, 'gst': False}
self.mmus = {}
self.mmu_targets = {'stack': None, 'heap': None, 'volatile_gst': None, 'non_volatile_gst': None}
if config.stack_mmu is not None:
self.mmus[self.address_prefixes['stack']] = config.stack_mmu
self.mmu_targets['stack'] = config.stack_mmu
if config.heap_mmu is not None:
self.mmus[self.address_prefixes['heap']] = config.heap_mmu
self.mmu_targets['heap'] = config.heap_mmu
# Initialize Volatile Memory
if config.volatile.enabled:
# Address prefix
self.address_prefixes['volatile_gst'] = f"{config.volatile.gst_prefix}-"
# Set check variables
stack_initialized = config.volatile.contains_stack
heap_initialized = config.volatile.contains_heap
gst_initialized = config.volatile.contains_gst
# Initialize memory
volatile_conf = {
'stack': config.volatile.contains_stack,
'stack_base_address': config.stack_base_address,
'stack_prefix': self.address_prefixes['stack'],
'stack_mmu': config.stack_mmu,
'heap': config.volatile.contains_heap,
'heap_base_address': config.heap_base_address,
'heap_prefix': self.address_prefixes['heap'],
'heap_mmu': config.heap_mmu,
'gst': config.volatile.contains_gst,
'gst_base_address': config.volatile.gst_base_address,
'gst_prefix': self.address_prefixes['volatile_gst'],
'gst_mmu': config.volatile.gst_mmu,
'max_size': config.volatile.max_size,
}
self.volatile = VolatileRAM(volatile_conf)
if config.volatile.gst_mmu is not None:
self.mmus[self.address_prefixes['volatile_gst']] = config.volatile.gst_mmu
self.mmu_targets['volatile_gst'] = config.volatile.gst_mmu
if config.volatile.contains_stack:
self.memory_positions['stack'] = 'volatile'
if config.volatile.contains_heap:
self.memory_positions['heap'] = 'volatile'
if config.volatile.contains_gst:
self.memory_positions['gst'].append('volatile')
else:
self.volatile = None
# Initialize Non-Volatile Memory
if config.non_volatile.enabled:
# Address prefix
self.address_prefixes['non_volatile_gst'] = f"{config.non_volatile.gst_prefix}-"
non_volatile_conf = {
'stack': config.non_volatile.contains_stack,
'stack_base_address': config.stack_base_address,
'stack_prefix': self.address_prefixes['stack'],
'stack_mmu': config.stack_mmu,
'heap': config.non_volatile.contains_heap,
'heap_base_address': config.heap_base_address,
'heap_prefix': self.address_prefixes['heap'],
'heap_mmu': config.heap_mmu,
'gst': config.non_volatile.contains_gst,
'gst_base_address': config.non_volatile.gst_base_address,
'gst_prefix': self.address_prefixes['non_volatile_gst'],
'gst_mmu': config.non_volatile.gst_mmu,
'max_size': config.non_volatile.max_size,
}
self.non_volatile = NonVolatileRAM(non_volatile_conf)
if config.non_volatile.gst_mmu is not None:
self.mmus[self.address_prefixes['non_volatile_gst']] = config.non_volatile.gst_mmu
self.mmu_targets['non_volatile_gst'] = config.non_volatile.gst_mmu
if config.non_volatile.contains_stack:
if stack_initialized:
raise ConfigurationException('Stack cannot reside on both Volatile and Non-volatile Memory')
self.memory_positions['stack'] = 'non_volatile'
self.nvm_prefixes.append(self.address_prefixes['stack'])
self.nvm_content['stack'] = True
if config.non_volatile.contains_heap:
if heap_initialized:
raise ConfigurationException('Heap cannot reside on both Volatile and Non-volatile Memory')
self.memory_positions['heap'] = 'non_volatile'
self.nvm_prefixes.append(self.address_prefixes['heap'])
self.nvm_content['heap'] = True
if config.non_volatile.contains_gst:
self.memory_positions['gst'].append('non_volatile')
self.nvm_prefixes.append(self.address_prefixes['non_volatile_gst'])
self.nvm_content['gst'] = True
# adjust initialization variables
stack_initialized |= config.non_volatile.contains_stack
heap_initialized |= config.non_volatile.contains_heap
gst_initialized |= config.non_volatile.contains_gst
else:
self.non_volatile = None
# Verify initializations
if self.non_volatile is None and self.volatile is None:
raise ConfigurationException('Memory not initialized: you need at least one between Volatile and Non-volatile Memory.')
if not stack_initialized:
raise ConfigurationException('Stack not initialized: allocate it either onto Volatile or Non-volatile Memory.')
if not heap_initialized:
raise ConfigurationException('Heap not initialized: allocate it either onto Volatile or Non-volatile Memory.')
if not gst_initialized:
raise ConfigurationException('Global Symbol Table (i.e. global variables) not initialized: allocate it onto Volatile and/or Non-volatile Memory.')
# gst only in one memory location
if len(self.memory_positions['gst']) == 1:
other_section = None
# gst in both
else:
other_section = config.gst_other_memory_section
self.gst = VirtualGlobalSymbolTableUnifier(self, config.gst_default_memory, other_section)
self.address_dimension = config.address_size
logging.info('Main memory initialized.')
@staticmethod
def _is_absolute_address(address):
"""
Returns if an address is a valid absolute one.
"""
return isinstance(address, str) and '-0x' in address
@staticmethod
def extract_prefix_from_address(address):
"""
Extracts the prefix of an absolute address.
"""
if not Memory._is_absolute_address(address):
raise ValueError('Invalid address: {}'.format(address))
prefix = address.split('-0x')
return prefix[0]+'-'
@staticmethod
def _extract_relative_address(address):
"""
Returns the relative address from an absolute address.
"""
if not Memory._is_absolute_address(address):
raise ValueError('Invalid address: {}'.format(address))
address = address.split('-')
return int(address[1], 16)
@staticmethod
def _parse_absolute_address(address):
"""
Returns the prefix and the relative address from an absolute one.
"""
if not Memory._is_absolute_address(address):
raise ValueError('Invalid address: {}'.format(address))
address = address.split('-')
relative_address = int(address[1], 16)
prefix = address[0] + '-'
return prefix, relative_address
@staticmethod
def convert_dimension(dimension, dimension_in_bits):
"""
Converts a dimension from bits to bytes, if dimension_in_bits is True.
"""
if dimension_in_bits:
if dimension % 8 != 0:
raise MemoryException('[Memory] Invalid memory dimension: {} bits. Dimension must be a multiple of a byte (8 bits).'.format(dimension))
# convert dimension in bytes
dimension = dimension // 8 # int division
return dimension
def add_offset(self, address, offset, offset_in_bits = True):
"""
Add an offset to an absolute address
"""
offset = self.stack.convert_dimension(offset, offset_in_bits)
prefix, relative_address = self._parse_absolute_address(address)
relative_address = relative_address + offset
return self._convert_to_absolute_address(prefix, relative_address)
@staticmethod
def _convert_to_absolute_address(prefix, address):
"""
Returns an absolute address, given a prefix
"""
return '{}{}'.format(prefix, hex(address))
def write(self, address, dimension, content, dimension_in_bits = True):
"""
Performs the call to the correct write method.
"""
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.write(address, dimension, content, dimension_in_bits)
elif prefix == self.heap.address_prefix:
return self.heap.write(address, dimension, content, dimension_in_bits)
elif prefix in self.gst.address_prefix:
return self.gst.write(address, dimension, content, dimension_in_bits)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def set_cell_input_lookup(self, address, input_lookup):
"""
Sets the input lookup information of cell in a given address.
"""
# NB: emptiness of input lookup if input_lookup is [] is guaranteed by write operations
# which removes completely the lookup infos.
if len(input_lookup) == 0:
return
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.set_cell_input_lookup(address, input_lookup)
elif prefix == self.heap.address_prefix:
return self.heap.set_cell_input_lookup(address, input_lookup)
elif prefix in self.gst.address_prefix:
return self.gst.set_cell_input_lookup(address, input_lookup)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def get_cell_input_lookup(self, address):
"""
Returns the input lookup information of cell in a given address.
"""
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.get_cell_input_lookup(address)
elif prefix == self.heap.address_prefix:
return self.heap.get_cell_input_lookup(address)
elif prefix in self.gst.address_prefix:
return self.gst.get_cell_input_lookup(address)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def get_cells_from_address(self, address, dimension, dimension_in_bits = True, resolve_address = True):
"""
Performs the call to the correct get_cells_from_address method
"""
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
elif prefix == self.heap.address_prefix:
return self.heap.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
elif prefix in self.gst.address_prefix:
return self.gst.get_cells_from_address(address, dimension, dimension_in_bits, resolve_address)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def simulate_mmu_read_on_cells(self, cells):
if len(cells) == 0:
return
prefix = self.extract_prefix_from_address(cells[0].absolute_address)
if prefix == self.stack.address_prefix:
return self.stack.simulate_mmu_read_on_cells(cells)
elif prefix == self.heap.address_prefix:
return self.heap.simulate_mmu_read_on_cells(cells)
elif prefix in self.gst.address_prefix:
return self.gst.simulate_mmu_read_on_cells(cells)
def set_cells_from_address(self, address, cells, resolve_address = True):
"""
Performs the call to the correct set_cells_from_address method
"""
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.set_cells_from_address(address, cells, resolve_address)
elif prefix == self.heap.address_prefix:
return self.heap.set_cells_from_address(address, cells, resolve_address)
elif prefix in self.gst.address_prefix:
return self.gst.set_cells_from_address(address, cells, resolve_address)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def read_addr(self, address):
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
address = self.stack.get_real_address(address)
return self.stack._memory[address].content
elif prefix == self.heap.address_prefix:
address = self.heap.get_real_address(address)
return self.heap._memory[address].content
elif prefix in self.gst.address_prefix:
gst = self.gst._get_gst_from_address(address)
address = gst.get_real_address(address)
return gst._memory[address].content
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
def read(self, address, dimension, dimension_in_bits = True):
"""
Performs the call to the correct read method.
"""
prefix = self.extract_prefix_from_address(address)
if prefix == self.stack.address_prefix:
return self.stack.read(address, dimension, dimension_in_bits)
elif prefix == self.heap.address_prefix:
return self.heap.read(address, dimension, dimension_in_bits)
elif prefix in self.gst.address_prefix:
return self.gst.read(address, dimension, dimension_in_bits)
raise MemoryException('Invalid address {}: prefix not found in memory!'.format(address))
@property
def stack(self):
"""
Returns transparently the stack
Stack must be in volatile or non-volatile memory.
If volatile memory not initialized or stack not in volatile memory, it must be in nvm, otherwise
the memory has not been initialized and an exception have been raised on init.
"""
if self.volatile is not None and self.volatile.stack is not None:
return self.volatile.stack
return self.non_volatile.stack
@property
def heap(self):
"""
Returns transparently the heap
Heap must be in volatile or nvm.
If volatile memory not initialized or heap not in volatile memory, it must be in nvm, otherwise
the memory has not been initialized and an exception have been raised on init.
"""
if self.volatile is not None and self.volatile.heap is not None:
return self.volatile.heap
return self.non_volatile.heap
def reset(self):
"""
Performs the CPU reset operation
"""
logging.debug('Memory reset.')
if self.volatile is not None:
self.volatile.reset()
def force_nvm_reset(self):
"""
Resets the NVM.
"""
if self.non_volatile is not None:
self.non_volatile.force_reset()
def check_memory_size(self):
"""
Checks memory size
"""
if self.volatile is not None:
self.volatile.check_memory_size()
if self.non_volatile is not None:
self.non_volatile.check_memory_size()