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

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]