288 lines
12 KiB
Python
288 lines
12 KiB
Python
from ScEpTIC.emulator.custom_devices import CustomDevice
|
|
from ScEpTIC.emulator.custom_devices.D2VFS.d2vfs_energy_model import D2VFSChangepointDetectorEnergyModel
|
|
from ScEpTIC.emulator.energy.mcu.options import MCUClockCycleAction, MCUPowerState
|
|
from ScEpTIC.emulator.energy.options import ComponentVoltageSource, OpModeName
|
|
from ScEpTIC.emulator.energy.voltage_regulator.TPS62740x import TPS62740x
|
|
|
|
|
|
class D2VFSSystemModel(CustomDevice):
|
|
"""
|
|
D2VFS system model
|
|
"""
|
|
|
|
# Operational zones extracted from paper
|
|
operational_zones = {
|
|
3.3: '16MHz',
|
|
2.8: '12MHz',
|
|
2.2: '8MHz',
|
|
1.8: '1MHz',
|
|
}
|
|
|
|
# Voltage detector sensitivity
|
|
sensitivity = 0.005
|
|
|
|
op_mode_name = OpModeName.D2VFS_ISR
|
|
|
|
name = 'D2VFS'
|
|
|
|
def __init__(self):
|
|
# voltages of the operational zones (reverse ordered for logic purposes)
|
|
self.v_operational_zones = sorted(list(self.operational_zones.keys()), reverse=True)
|
|
|
|
# current operational zone
|
|
self.current_operational_zone = None
|
|
|
|
# Signal
|
|
self.current_signal = None
|
|
|
|
|
|
def setup(self, system_model):
|
|
"""
|
|
Setup the D2VFS device. Note that this is a callback called by the system model
|
|
:param system_model: the system model
|
|
"""
|
|
super().setup(system_model)
|
|
|
|
# Initialize devices
|
|
d2vfs_detector = D2VFSChangepointDetectorEnergyModel()
|
|
voltage_regulator = TPS62740x()
|
|
|
|
# Attach to energy manager
|
|
system_model.attach_voltage_regulator(voltage_regulator)
|
|
system_model.attach_component('D2VFS', d2vfs_detector, ComponentVoltageSource.ENERGY_BUFFER)
|
|
system_model.mcu.custom_frequency_name = 'D2VFS'
|
|
system_model.mcu.dfs_enabled = True
|
|
|
|
|
|
def init(self, print_enabled=True):
|
|
"""
|
|
Initializes the D2VFS circuit
|
|
:param print_enabled: enables/disables print messages
|
|
:return: the number of clock cycles executed by the MCU during the initialization of custom device operations
|
|
"""
|
|
# Init to default frequency
|
|
self.system_model.mcu.set_frequency(None)
|
|
|
|
self.current_signal = "INIT"
|
|
|
|
voltage = self.system_model.energy_buffer.get_voltage()
|
|
|
|
# Hardcoded first operational zone
|
|
# Adjust hardcoded first operational zone w.r.t. different system init settings
|
|
for zone_voltage in self.v_operational_zones:
|
|
if voltage >= zone_voltage + self.sensitivity:
|
|
self.current_operational_zone = zone_voltage
|
|
break
|
|
|
|
first_zone = self.current_operational_zone
|
|
|
|
# Low voltage (may happen in voltage identifier analysis)
|
|
if first_zone is None:
|
|
first_zone = min(self.v_operational_zones)
|
|
|
|
# On start -> voltage regulator outputs the energy buffer voltage
|
|
self.system_model.voltage_regulator.set_output_voltage(voltage)
|
|
|
|
# Set voltage (hardcoded)
|
|
n_ticks = self._simulate_set_new_voltage(first_zone, False)
|
|
|
|
# Set frequency (hardcoded)
|
|
n_ticks += self._simulate_set_new_frequency(first_zone, False, print_enabled=print_enabled)
|
|
|
|
# 01. Set frequency identifier to max value
|
|
# frequency_id = max_frequency
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
return n_ticks
|
|
|
|
|
|
def run_logic(self):
|
|
"""
|
|
Runs the D2VFS logic (detects if a changepoint is reached and changes voltage and frequency accordingly
|
|
:return: the number of clock cycles executed by the MCU during custom device operations
|
|
"""
|
|
current_voltage = self.system_model.energy_buffer.get_voltage()
|
|
|
|
n_ticks = 0
|
|
|
|
self.current_signal = None
|
|
|
|
# No logic when MCU off or in LPM
|
|
if self.system_model.mcu.get_mcu_state() != MCUPowerState.ON:
|
|
return n_ticks
|
|
|
|
for operational_zone in self.v_operational_zones:
|
|
# identify operational zone from current voltage
|
|
if current_voltage >= operational_zone + self.sensitivity:
|
|
|
|
# No changes required (operational zone did not change)
|
|
if self.current_operational_zone == operational_zone:
|
|
return n_ticks
|
|
|
|
# Going up
|
|
if operational_zone > self.current_operational_zone:
|
|
n_ticks = self._simulate_interrupt_for_increase(operational_zone)
|
|
self.current_signal = "UP"
|
|
|
|
# Going down
|
|
else:
|
|
n_ticks = self._simulate_interrupt_for_decrease(operational_zone)
|
|
self.current_signal = "DOWN"
|
|
|
|
self.current_operational_zone = operational_zone
|
|
break
|
|
|
|
return n_ticks
|
|
|
|
|
|
def _simulate_interrupt_for_increase(self, new_vreg_voltage):
|
|
"""
|
|
Simulates the interrupt that signals the MCU to increase its clock frequency
|
|
The MCU changes first sets the new output voltage of the voltage regulator and then changes its clock frequency.
|
|
:param new_vreg_voltage: the new operational zone voltage to be set as output of voltage regulator
|
|
"""
|
|
n_ticks = self._simulate_isr_call()
|
|
n_ticks += self._simulate_set_new_voltage(new_vreg_voltage)
|
|
n_ticks += self._simulate_set_new_frequency(new_vreg_voltage)
|
|
n_ticks += self._simulate_isr_return()
|
|
return n_ticks
|
|
|
|
|
|
def _simulate_interrupt_for_decrease(self, new_vreg_voltage):
|
|
"""
|
|
Simulates the interrupt that signals the MCU to decrease its clock frequency.
|
|
The MCU changes first its clock frequency and then sets the new output voltage of the voltage regulator.
|
|
:param new_vreg_voltage: the new operational zone voltage to be set as output of voltage regulator
|
|
"""
|
|
n_ticks = self._simulate_isr_call()
|
|
n_ticks += self._simulate_set_new_frequency(new_vreg_voltage)
|
|
n_ticks += self._simulate_set_new_voltage(new_vreg_voltage)
|
|
n_ticks += self._simulate_isr_return()
|
|
|
|
return n_ticks
|
|
|
|
|
|
def _simulate_set_new_frequency(self, new_vreg_voltage, retrieve_val=True, print_enabled=True):
|
|
"""
|
|
Simulates the execution of the instructions that change the MCU frequency
|
|
:param new_vreg_voltage: the new operational zone voltage to be set as output of voltage regulator
|
|
:param retrieve_val: True if the frequency value is not hardcoded in the simulated operation and must be loaded from memory
|
|
:param print_enabled: enables/disables print messages
|
|
:return: the number of executed instructions
|
|
"""
|
|
# Get the new frequency value
|
|
# R1 = operating_frequency[frequency_id] (frequency_id in R0)
|
|
# 01. LOAD R1, R0(operating_frequency)
|
|
n_ticks = 0
|
|
|
|
if retrieve_val:
|
|
n_ticks = self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
# Set the operating frequency
|
|
# MSP430FR:
|
|
# 02. CSCTL1 = R1
|
|
# 03. CSCTL2 = SELA__VLOCLK | SELS__DCOCLK | SELM__DCOCLK; // Set SMCLK = MCLK = DCO
|
|
# 04. CSCTL3 = DIVA__1 | DIVS__1 | DIVM__1; // Set all dividers
|
|
# MSP430G:
|
|
# 02. DCOCTL = 0; // Select lowest DCOx and MODx settings
|
|
# 03. BCSCTL1 = R1; // Set range
|
|
# 04. DCOCTL = R1; // Set DCO step + modulation */
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
if print_enabled:
|
|
print(f"D2VFS: frequency {self.system_model.mcu.frequency} -> {self.operational_zones[new_vreg_voltage]} / voltage set to {new_vreg_voltage}V (buffer: {self.system_model.energy_buffer.get_voltage():.3}V)")
|
|
|
|
# Update MCU frequency for the simulation
|
|
self.system_model.mcu.set_frequency(self.operational_zones[new_vreg_voltage])
|
|
|
|
# MSP430FR - Set FRAM WAIT CYCLES
|
|
if self.system_model.mcu.family == 'MSP430FR':
|
|
# Get the number of wait cycles
|
|
# R1 = fram_wait_cycles[frequency_id]
|
|
# 05. LOAD R1, R0(fram_wait_cycles)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
# Set NWAITS
|
|
# 06. FRCTL0 = R1
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
return n_ticks
|
|
|
|
|
|
def _simulate_set_new_voltage(self, new_vreg_voltage, retrieve_val=True):
|
|
"""
|
|
Simulates the execution of the instructions that change the Voltage Regulator output voltage
|
|
:param new_vreg_voltage: the new operational zone voltage to be set as output of voltage regulator
|
|
:param retrieve_val: True if the voltage value is not hardcoded in the simulated operation and must be loaded from memory
|
|
:return: the number of executed instructions
|
|
"""
|
|
# Get the new voltage value
|
|
# R1 = operating_voltage[frequency_id]
|
|
# 01. LOAD R1, R0(operating_voltage)
|
|
n_ticks = 0
|
|
if retrieve_val:
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
# Set the voltage to the output pin group to change voltage
|
|
# 02. MOV R1, PIN_GROUP_1
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
# Update output of voltage regulator for the simulation
|
|
self.system_model.voltage_regulator.set_output_voltage(new_vreg_voltage)
|
|
|
|
return n_ticks
|
|
|
|
|
|
def _simulate_isr_call(self):
|
|
"""
|
|
Simulates the execution of the ISR call
|
|
:return: the number of executed instructions
|
|
"""
|
|
# Interrupt fires -> call ISR
|
|
# 01. save PC
|
|
# 02. save EBP
|
|
# 03. jump ISR
|
|
n_ticks = self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
# Increment frequency identifier
|
|
# frequency_id = frequency_id + 1
|
|
# 04. LOAD frequency_id, R0
|
|
# 05. ADD R0, R0, 1
|
|
# 06. STORE R0, frequency_id
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.NO_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
return n_ticks
|
|
|
|
def _simulate_isr_return(self):
|
|
"""
|
|
Simulates the execution of the return from ISR
|
|
:return: the number of executed instructions
|
|
"""
|
|
# Return from ISR
|
|
# 01. pop EBP
|
|
# 02. pop PC
|
|
n_ticks = self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
n_ticks += self.system_model.run_step(MCUClockCycleAction.VOLATILE_MEMORY_ACCESS, self.op_mode_name, from_custom_device=self.name)
|
|
|
|
return n_ticks
|
|
|
|
|
|
def get_signals_strings(self):
|
|
"""
|
|
:return: a list with the names of the signals that may be collected from this device
|
|
"""
|
|
return ['D2VFS Logic Signal']
|
|
|
|
|
|
def get_signals(self):
|
|
"""
|
|
:return: a list with the signals collected from this device
|
|
"""
|
|
return [self.current_signal]
|