612 lines
21 KiB
Python
612 lines
21 KiB
Python
import math
|
|
|
|
from ScEpTIC.emulator.energy import energy_utils
|
|
from ScEpTIC.emulator.energy.mcu.options import MCUClockCycleAction, ADCPowerState, MCUPowerState
|
|
from ScEpTIC.emulator.energy.mcu.lookup_tables import LookupTable
|
|
from ScEpTIC.emulator.energy.mcu_peripheral import MCUPeripheral
|
|
from ScEpTIC.emulator.energy.mcu_peripheral.external_nvm import ExternalNVM
|
|
from ScEpTIC.emulator.energy.mcu_peripheral.options import NVMPowerState
|
|
from ScEpTIC.emulator.energy.power_state_event import PowerStateEvent
|
|
from ScEpTIC.emulator.energy.voltage_drawner import VoltageDrawner
|
|
from ScEpTIC.exceptions import ConfigurationException
|
|
|
|
|
|
class MCUEnergyModel(VoltageDrawner):
|
|
"""
|
|
MCU Energy Model
|
|
"""
|
|
|
|
# min, max, avg
|
|
ADC_I_TO_CONSIDER = 'min'
|
|
|
|
def __init__(self, mcu_name, load_lookup_table=False, instruction_cache_hit_ratio=0.75):
|
|
try:
|
|
datasheet_module = __import__(f'ScEpTIC.emulator.energy.mcu.datasheets.{mcu_name}', fromlist=[''])
|
|
except ModuleNotFoundError:
|
|
mcu_module_name = mcu_name.split('-')[0]
|
|
datasheet_module = __import__(f'ScEpTIC.emulator.energy.mcu.datasheets.{mcu_module_name}', fromlist=[''])
|
|
|
|
self.datasheet = getattr(datasheet_module, 'datasheet')
|
|
self.family = self.datasheet['family']
|
|
|
|
self._check_mcu_family(mcu_name)
|
|
|
|
# program cache hit rate
|
|
self.cache_hit = energy_utils.str_to_float(instruction_cache_hit_ratio)
|
|
|
|
# general purpose regs
|
|
self.registers = self.datasheet['registers']
|
|
self.reserved_registers = self.datasheet['reserved_registers']
|
|
|
|
# dimensions
|
|
self.register_size = math.ceil(self.datasheet['register_size'] / 8)
|
|
self.memory_cell_size = math.ceil(self.datasheet['memory_cell_size'] / 8)
|
|
self.volatile_memory_size = energy_utils.str_to_int(self.datasheet['volatile_memory_size'])
|
|
self.non_volatile_memory_size = energy_utils.str_to_int(self.datasheet['non_volatile_memory_size'])
|
|
|
|
self.has_adc = self.datasheet['has_adc']
|
|
self.has_nvm = self.datasheet['has_nvm']
|
|
|
|
# Non-volatile memory wait cycles (i.e., extra clock cycles to complete the memory access)
|
|
self.nvm_wait_cycles = {}
|
|
|
|
# MCU equivalent resistance
|
|
self._mcu_equivalent_r = {}
|
|
# MCU minimum voltage per frequency
|
|
self.mcu_min_v = {}
|
|
# MCU supported frequencies
|
|
self.frequencies = {}
|
|
|
|
# Default frequency
|
|
self.default_frequency = self.datasheet['default_frequency']
|
|
|
|
# ADC wait cycles (i.e., clock cycles to turn adc on and access it)
|
|
self.adc_wait_cycles = {}
|
|
# ADC instructions (ADC on, get data, ADC off)
|
|
self.adc_instructions = {"on": 0, "transfer": 0, "off": 0}
|
|
# ADC equivalent resistance
|
|
self.adc_equivalent_r = 0.0
|
|
# ADC minimum voltage
|
|
self.adc_min_v = 0.0
|
|
|
|
# LPM data
|
|
self.lpm_data = {}
|
|
self.lpm_r = 0.0
|
|
self.lpm_set = False
|
|
self.lpm_v_min = 0.0
|
|
self.lpm_wait_cycles = {}
|
|
|
|
# Populate variables with datasheet data
|
|
self._populate_data()
|
|
|
|
self.adc_power_state = ADCPowerState.OFF
|
|
self.mcu_power_state = MCUPowerState.OFF
|
|
|
|
self.frequency = None
|
|
|
|
self.peripherals = set()
|
|
self.has_exeternal_nvm = False
|
|
|
|
self.v_on = 0.0
|
|
|
|
# Custom nominal frequency (e.g. when frequency scaling is enabled)
|
|
self.dfs_enabled = False
|
|
self.custom_frequency_name = None
|
|
|
|
if load_lookup_table:
|
|
lookup_table = LookupTable(mcu_name)
|
|
self.set_lookup_table(lookup_table)
|
|
else:
|
|
self.set_lookup_table(None)
|
|
|
|
# ticks and time tracking since latest power failure
|
|
self.__ticks = 0
|
|
self.__time = 0.0
|
|
|
|
def get_clock_cycles(self):
|
|
"""
|
|
Returns current active cycle clock cycles count
|
|
"""
|
|
return self.__ticks
|
|
|
|
def get_time(self):
|
|
"""
|
|
Returns the mcu time
|
|
"""
|
|
return self.__time
|
|
|
|
def set_time(self, t):
|
|
"""
|
|
Sets the mcu time
|
|
"""
|
|
self.__time = t
|
|
|
|
def power_failure_callback(self):
|
|
"""
|
|
Sets the last time and ticks when a power failure occurred
|
|
"""
|
|
self.__ticks = 0
|
|
self.__time = 0.0
|
|
|
|
def update_stats(self, time, ticks):
|
|
"""
|
|
Update stats
|
|
"""
|
|
if self.mcu_power_state == MCUPowerState.ON:
|
|
self.__time += time
|
|
self.__ticks += ticks
|
|
|
|
|
|
def reset(self):
|
|
"""
|
|
Resets the MCU state
|
|
"""
|
|
self._last_power_failure_ticks = 0
|
|
self._last_power_failure_time = 0.0
|
|
|
|
def set_lookup_table(self, lookup_table):
|
|
"""
|
|
Sets the lookup table for the MCU energy model.
|
|
:param lookup_table: instance of LookupTable
|
|
"""
|
|
if lookup_table is None:
|
|
self.get_mcu_equivalent_r = self._get_mcu_equivalent_r_datasheet
|
|
self.lookup_table = None
|
|
|
|
elif isinstance(lookup_table, LookupTable):
|
|
self.get_mcu_equivalent_r = self._get_mcu_equivalent_r_lookup_table
|
|
self.lookup_table = lookup_table
|
|
|
|
else:
|
|
raise ConfigurationException(f"set_lookup_table() requires a LookupTable, {lookup_table.__class__.__name__} was given.")
|
|
|
|
|
|
def set_v_on(self, v_on):
|
|
"""
|
|
Sets the minimum voltage to turn the MCU on
|
|
:param v_on: minimum voltage to turn the MCU on
|
|
"""
|
|
self.v_on = v_on
|
|
|
|
|
|
def get_power_state_events(self):
|
|
"""
|
|
:return: a list of PowerStateEvent events that are occurring
|
|
"""
|
|
events = []
|
|
|
|
voltage = self.get_voltage()
|
|
|
|
# ADC ON but voltage < min voltage
|
|
if self.adc_power_state == ADCPowerState.ON and voltage < self.adc_min_v:
|
|
events.append(PowerStateEvent.ADC_OFF)
|
|
self.adc_power_state = ADCPowerState.OFF
|
|
|
|
# MCU ON but voltage < min voltage
|
|
if (self.mcu_power_state == MCUPowerState.ON or self.mcu_power_state == MCUPowerState.LPM) and voltage < self.get_min_v():
|
|
events.append(PowerStateEvent.MCU_OFF)
|
|
self.mcu_power_state = MCUPowerState.OFF
|
|
|
|
return events
|
|
|
|
|
|
def set_target_lpm(self, lpm_name):
|
|
"""
|
|
Sets the parameters of the MCU LPM used during the simulation
|
|
:param lpm_name: the name of the MCU LPM
|
|
"""
|
|
if lpm_name not in self.lpm_data:
|
|
raise ConfigurationException(f"LPM '{lpm_name}' not found.")
|
|
|
|
self.lpm_set = True
|
|
|
|
self.lpm_r = self.lpm_data[lpm_name]['R']
|
|
self.lpm_v_min = self.lpm_data[lpm_name]['V_min']
|
|
|
|
# Wakeup wait cycles (t_wait * MCU frequency)
|
|
for f_name, f_val in self.frequencies.items():
|
|
self.lpm_wait_cycles[f_name] = math.ceil(float(self.lpm_data[lpm_name]['t_wakeup']) * float(f_val))
|
|
|
|
|
|
def set_frequency(self, f):
|
|
"""
|
|
Sets MCU frequency
|
|
:param f: current frequency (string)
|
|
"""
|
|
|
|
# set default frequency
|
|
if f is None:
|
|
self.frequency = self.default_frequency
|
|
return
|
|
|
|
if f not in self.frequencies:
|
|
raise ConfigurationException(f"Wrong frequency value {f}")
|
|
|
|
self.frequency = f
|
|
|
|
|
|
def get_frequency(self):
|
|
"""
|
|
:return: current MCU clock frequency
|
|
"""
|
|
return self.frequencies[self.frequency]
|
|
|
|
|
|
def get_nominal_frequency(self):
|
|
"""
|
|
:return: a string representation of the MCU frequency
|
|
"""
|
|
if self.custom_frequency_name is None:
|
|
return self.frequency
|
|
|
|
return self.custom_frequency_name
|
|
|
|
|
|
def get_voltage(self):
|
|
"""
|
|
:return: the voltage applied to the MCU
|
|
"""
|
|
return self.voltage_source.get_voltage()
|
|
|
|
|
|
def get_min_v(self, ignore_current_frequency=False):
|
|
"""
|
|
:param ignore_current_frequency: returns the absolute minimum voltage, regardless of current frequency
|
|
:return: the minimum voltage required to compute with current frequency
|
|
"""
|
|
if self.mcu_power_state == MCUPowerState.LPM:
|
|
return self.lpm_v_min
|
|
|
|
if ignore_current_frequency:
|
|
return min(self.mcu_min_v.values())
|
|
|
|
return self.mcu_min_v[self.frequency]
|
|
|
|
|
|
def get_mcu_cycle_time(self):
|
|
"""
|
|
:return: the time of a single clock cycle (seconds)
|
|
"""
|
|
return 1.0 / self.frequencies[self.frequency]
|
|
|
|
|
|
def get_ADC_wait_cycles(self):
|
|
"""
|
|
:return: ADC wait cycles (turn ADC on + wait for sample)
|
|
"""
|
|
return self.adc_wait_cycles[self.frequency]
|
|
|
|
|
|
def get_LPM_wait_cycles(self):
|
|
"""
|
|
:return: LPM to ON wait cycles
|
|
"""
|
|
return self.lpm_wait_cycles[self.frequency]
|
|
|
|
|
|
def get_NVM_wait_cycles(self):
|
|
"""
|
|
:return: NVM wait cycles
|
|
"""
|
|
return self.nvm_wait_cycles[self.frequency]
|
|
|
|
|
|
def get_action_wait_cycles(self, mcu_clock_action):
|
|
"""
|
|
:param mcu_clock_action: Clock cycle action
|
|
:return: the number of wait cycles that the MCU executed due to the execution of mcu_clock_action
|
|
"""
|
|
|
|
# NVM wait cycles
|
|
if mcu_clock_action == MCUClockCycleAction.NON_VOLATILE_MEMORY_ACCESS:
|
|
return self.get_NVM_wait_cycles()
|
|
|
|
# LPM exit wait cycles
|
|
if mcu_clock_action == MCUClockCycleAction.LPM_EXIT:
|
|
return self.get_LPM_wait_cycles()
|
|
|
|
return 0
|
|
|
|
|
|
def _get_mcu_equivalent_r_lookup_table(self, mcu_clock_action):
|
|
"""
|
|
Implementation of get_mcu_equivalent_r that uses a LookupTable
|
|
:param mcu_clock_action: current MCUCLockCycleAction
|
|
:return: the equivalent resistance
|
|
"""
|
|
return self.lookup_table.get_value(self.frequency, mcu_clock_action, self.get_voltage())
|
|
|
|
|
|
def _get_mcu_equivalent_r_datasheet(self, mcu_clock_action):
|
|
"""
|
|
:param mcu_clock_action: current MCUCLockCycleAction
|
|
:return: the equivalent resistance
|
|
"""
|
|
if mcu_clock_action == MCUClockCycleAction.PHYSICAL_MEMORY_ACCESS:
|
|
mcu_clock_action = MCUClockCycleAction.NO_MEMORY_ACCESS
|
|
|
|
try:
|
|
return self._mcu_equivalent_r[self.frequency][mcu_clock_action]
|
|
|
|
except KeyError:
|
|
raise ConfigurationException(f"This MCU does not support {mcu_clock_action} clock cycle action")
|
|
|
|
|
|
def get_drained_energy(self, mcu_clock_action, elapsed_time=None):
|
|
"""
|
|
Calculates the energy consumed by the MCU.
|
|
:param mcu_clock_action: Clock cycle action
|
|
:return: the consumed energy
|
|
"""
|
|
if mcu_clock_action == MCUClockCycleAction.NOP_OFF_RECHARGE:
|
|
return 0.0
|
|
|
|
if self.mcu_power_state == MCUPowerState.OFF:
|
|
raise Exception(f"Cannot execute {mcu_clock_action} - MCU is off!")
|
|
|
|
if self.mcu_power_state == MCUPowerState.LPM:
|
|
# When MCU in LPM -> No instruction can be executed (only NOP)
|
|
if mcu_clock_action != MCUClockCycleAction.LPM_NOP and mcu_clock_action != MCUClockCycleAction.LPM_EXIT:
|
|
raise Exception(f"Operation {mcu_clock_action} not supported while MCU is in LPM!")
|
|
|
|
r_mcu = self.lpm_r
|
|
|
|
else:
|
|
r_mcu = self.get_mcu_equivalent_r(mcu_clock_action)
|
|
|
|
# Compute energy
|
|
voltage = self.get_voltage()
|
|
if elapsed_time is None:
|
|
e_mcu = energy_utils.energy_from_R_f(voltage, r_mcu, self.frequencies[self.frequency])
|
|
else:
|
|
e_mcu = energy_utils.energy_from_R_t(voltage, r_mcu, elapsed_time)
|
|
|
|
# If ADC on -> add its energy consumption
|
|
if self.adc_power_state == ADCPowerState.ON:
|
|
r_adc = self.adc_equivalent_r
|
|
if elapsed_time is None:
|
|
e_adc = energy_utils.energy_from_R_f(voltage, r_adc, self.frequencies[self.frequency])
|
|
else:
|
|
e_adc = energy_utils.energy_from_R_t(voltage, r_adc, elapsed_time)
|
|
|
|
e_mcu += e_adc
|
|
|
|
# Enter LPM
|
|
if mcu_clock_action == MCUClockCycleAction.LPM_ENTER:
|
|
self.set_mcu_state(MCUPowerState.LPM)
|
|
|
|
# Exit LPM
|
|
if mcu_clock_action == MCUClockCycleAction.LPM_EXIT:
|
|
self.set_mcu_state(MCUPowerState.LPM_WAKEUP)
|
|
|
|
return e_mcu
|
|
|
|
|
|
def set_adc_state(self, adc_state):
|
|
"""
|
|
Sets the current ADC state to adc_state
|
|
:param adc_state: ADCPowerState state
|
|
"""
|
|
if not self.has_adc:
|
|
raise ConfigurationException("MCU does not have an ADC!")
|
|
|
|
if not isinstance(adc_state, ADCPowerState):
|
|
raise ConfigurationException("Variable adc_state must be an ADCPowerState enum!")
|
|
|
|
self.adc_power_state = adc_state
|
|
|
|
|
|
def get_adc_state(self):
|
|
"""
|
|
:return: The ADC state (instance of ADCPowerState)
|
|
"""
|
|
return self.adc_power_state
|
|
|
|
|
|
def set_mcu_state(self, mcu_state):
|
|
"""
|
|
Sets the current MCU state to mcu_state
|
|
:param mcu_state: MCUPowerState state
|
|
"""
|
|
if not isinstance(mcu_state, MCUPowerState):
|
|
raise ConfigurationException("Variable mcu_state must be a MCUPowerState enum!")
|
|
|
|
self.mcu_power_state = mcu_state
|
|
|
|
# Turn off ADC
|
|
if mcu_state != MCUPowerState.ON and self.has_adc:
|
|
self.set_adc_state(ADCPowerState.OFF)
|
|
|
|
# Update external NVM power state, if present
|
|
if self.has_exeternal_nvm:
|
|
nvm_state = NVMPowerState.ON if mcu_state == MCUPowerState.ON else NVMPowerState.OFF
|
|
self.get_external_nvm().set_power_state(nvm_state)
|
|
|
|
|
|
def get_mcu_state(self):
|
|
"""
|
|
:return: The mcu state (instance of MCUPowerState)
|
|
"""
|
|
return self.mcu_power_state
|
|
|
|
|
|
def attach_peripheral(self, peripheral):
|
|
"""
|
|
Attaches a peripheral to the MCU
|
|
:param peripheral: the peripheral
|
|
"""
|
|
if not isinstance(peripheral, MCUPeripheral):
|
|
raise ConfigurationException(f"Wrong peripheral model {peripheral.__class__.__name__}. MCUPeripheral expected")
|
|
|
|
# Only one NVM supported (for the moment)
|
|
if isinstance(peripheral, ExternalNVM):
|
|
for p in self.peripherals:
|
|
if isinstance(p, ExternalNVM):
|
|
raise ConfigurationException(f"ScEpTIC system model currently supports only one external NVM attached to the MCU")
|
|
|
|
self.has_exeternal_nvm = True
|
|
|
|
self.peripherals.add(peripheral)
|
|
|
|
|
|
def get_external_nvm(self):
|
|
"""
|
|
:return: an external NVM, if attached to the MCU; otherwise None
|
|
"""
|
|
|
|
for peripheral in self.peripherals:
|
|
if isinstance(peripheral, ExternalNVM):
|
|
return peripheral
|
|
|
|
return None
|
|
|
|
|
|
def _check_mcu_family(self, mcu_name):
|
|
"""
|
|
Verifies the correctness of the MCU family
|
|
"""
|
|
used_family = str(self.__class__.__name__).replace('EnergyModel', '')
|
|
|
|
if used_family != self.family:
|
|
raise ConfigurationException(f"Wrong EnergyModel: {mcu_name} has family {self.datasheet['family']}, but you are using the EnergyModel of {used_family} family!")
|
|
|
|
|
|
def _populate_data(self):
|
|
"""
|
|
Populates the datasheet-related variables in a correct order.
|
|
"""
|
|
if self.has_adc:
|
|
adc_data = self.datasheet['ADC']
|
|
self.adc_instructions = self._get_ADC_instructions(adc_data)
|
|
self.adc_min_v = self._get_ADC_min_v(adc_data)
|
|
self.adc_equivalent_r = self._calculate_ADC_equivalent_r(adc_data)
|
|
|
|
# Frequency data for various operating modes
|
|
for frequency, frequency_data in self.datasheet['frequencies'].items():
|
|
self.mcu_min_v[frequency] = self._get_MCU_min_v(frequency_data)
|
|
self.frequencies[frequency] = self._get_MCU_frequency(frequency_data)
|
|
self._mcu_equivalent_r[frequency] = self._calculate_MCU_equivalent_r(frequency, frequency_data)
|
|
|
|
if self.has_nvm:
|
|
self.nvm_wait_cycles[frequency] = self._get_NVM_wait_cycles(frequency_data)
|
|
|
|
if self.has_adc:
|
|
self.adc_wait_cycles[frequency] = self._calculate_ADC_wait_cycles(self.datasheet['ADC'], self.frequencies[frequency])
|
|
|
|
# LPM data
|
|
for lpm_name, lpm_data in self.datasheet['LPM'].items():
|
|
self.lpm_data[lpm_name] = {
|
|
'R': self._calculate_MCU_LPM_R(lpm_data),
|
|
'V_min': self._get_MCU_LPM_V_min(lpm_data),
|
|
't_wakeup': self._get_MCU_LPM_t_wakeup(lpm_data),
|
|
}
|
|
|
|
|
|
def _calculate_MCU_equivalent_r(self, frequency_name, frequency_data):
|
|
"""
|
|
:param frequency_name: operating frequency name
|
|
:param frequency_data: MCU datasheet information of a specific clock frequency
|
|
:return: the equivalent resistance of the MCU in the various operating conditions.
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _calculate_MCU_equivalent_r()")
|
|
|
|
|
|
def _get_MCU_frequency(self, frequency_data):
|
|
"""
|
|
:param frequency_data: MCU datasheet information of a specific clock frequency
|
|
:return: clock frequency
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_MCU_frequency()")
|
|
|
|
|
|
def _get_MCU_min_v(self, frequency_data):
|
|
"""
|
|
:param frequency_data: MCU datasheet information of a specific clock frequency
|
|
:return: the minimum operating voltage of the MCU at a given clock frequency
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_MCU_min_v()")
|
|
|
|
|
|
def _get_NVM_wait_cycles(self, frequency_data):
|
|
"""
|
|
:param frequency_data: MCU datasheet information of a specific clock frequency
|
|
:return: the wait cycles of NVM at a given clock frequency
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_NVM_wait_cycles()")
|
|
|
|
|
|
def _calculate_MCU_LPM_R(self, lpm_data):
|
|
"""
|
|
:param lpm_data: LPM datasheet information
|
|
:return: the equivalent resistance of the MCU in LPM
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _calculate_MCU_LPM_R()")
|
|
|
|
|
|
def _get_MCU_LPM_V_min(self, lpm_data):
|
|
"""
|
|
:param lpm_data: LPM datasheet information
|
|
:return: the minimum voltage required in LPM
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_MCU_LPM_V_min()")
|
|
|
|
|
|
def _get_MCU_LPM_t_wakeup(self, lpm_data):
|
|
"""
|
|
:param lpm_data: LPM datasheet information
|
|
:return: the wakeup time from LPM to active mode
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_MCU_LPM_t_wakeup()")
|
|
|
|
|
|
def _calculate_ADC_equivalent_r(self, adc_data):
|
|
"""
|
|
:param adc_data: ADC datasheet information
|
|
:return: the equivalent resistance of the ADC
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _calculate_ADC_equivalent_r()")
|
|
|
|
|
|
def _get_ADC_min_v(self, adc_data):
|
|
"""
|
|
:param adc_data: ADC datasheet information
|
|
:return: the minimum operating voltage of the ADC
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_ADC_min_v()")
|
|
|
|
|
|
def _calculate_ADC_wait_cycles(self, adc_data, frequency):
|
|
"""
|
|
Calculates the number of cycles to activate the ADC, wait for its operativity, retrieve data, and turn it off
|
|
:param adc_data: ADC datasheet information
|
|
:param frequency: MCU frequency
|
|
:return: the wait cycles
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _calculate_ADC_wait_cycles()")
|
|
|
|
|
|
def _get_ADC_instructions(self, adc_data):
|
|
"""
|
|
:param adc_data: ADC datasheet information
|
|
:return: the instructions executed to turn on the ADC, retrieve data, and turn it off
|
|
"""
|
|
raise NotImplementedError(f"{self.__class__.__name__} does not implement _get_ADC_instructions()")
|
|
|
|
|
|
def ALFRED_n_min(self, n_writes):
|
|
"""
|
|
Function to calculate the minimum number of read instructions required to create a volatile copy of a memory location.
|
|
Necessary for virtual_memory transformation (ALFRED).
|
|
:param n_writes: number of writes required for creating a volatile copy
|
|
:return: number of minimum reads
|
|
"""
|
|
volatile_access = self.get_drained_energy(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS)
|
|
non_volatile_access = self.get_drained_energy(MCUClockCycleAction.NON_VOLATILE_MEMORY_ACCESS)
|
|
|
|
numerator = volatile_access * float(n_writes)
|
|
denominator = non_volatile_access * float(1 + self.nvm_wait_cycles[self.frequency]) - volatile_access
|
|
|
|
value = float(numerator) / float(denominator)
|
|
|
|
return math.floor(value)
|