summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/codegen.py82
-rwxr-xr-xtest/test_codegen.py20
2 files changed, 92 insertions, 10 deletions
diff --git a/lib/codegen.py b/lib/codegen.py
index 98eeafe..75c5102 100644
--- a/lib/codegen.py
+++ b/lib/codegen.py
@@ -98,6 +98,7 @@ class SimulatedAccountingMethod:
* timer counter size (e.g. a 16-bit timer at 1MHz will overflow after 65us)
* variable size for accounting of durations, power and energy values
"""
+
def __init__(self, pta: PTA, timer_freq_hz, timer_type, ts_type, power_type, energy_type, ts_granularity = 1e-6, power_granularity = 1e-6, energy_granularity = 1e-12):
"""
Simulate Online Accounting for a given PTA.
@@ -121,50 +122,101 @@ class SimulatedAccountingMethod:
self.power_granularity = power_granularity
self.energy_granularity = energy_granularity
+ """Energy in pJ."""
self.energy = self.energy_class(0)
+ def _energy_from_power_and_time(self, power, time):
+ """
+ Return energy (=power * time), accounting for configured granularity.
+
+ Does not use Module types and therefore does not consider overflows or data-type limitations"""
+ if self.energy_granularity == self.power_granularity * self.ts_granularity:
+ return power * time
+ return int(power * self.power_granularity * time * self.ts_granularity / self.energy_granularity)
+
def _sleep_duration(self, duration_us):
u"""
- Return the sleep duration a timer with the configured timer frequency would measure, in µs
+ Return the sleep duration a timer with the configured timer frequency would measure, according to the configured granularity.
- I.e., for a 35us sleep with a 50kHz timer (-> one tick per 20us), the OS would likely measure one tick == 20us.
+ I.e., for a 35us sleep with a 50kHz timer (-> one tick per 20us) and 1us time resolution, the OS would likely measure one tick == 20us.
This is based on the assumption that the timer is reset at each transition, so the duration of states may be under-, but not over-estimated
"""
us_per_tick = 1000000 / self.timer_freq_hz
ticks = self.timer_class(int(duration_us // us_per_tick))
- return int(ticks.val * us_per_tick)
+ time_units_per_tick = 1 / (self.timer_freq_hz * self.ts_granularity)
+ return int(ticks.val * time_units_per_tick)
def sleep(self, duration_us):
pass
def pass_transition(self, transition: Transition):
+ """Updates current state to `transition.destination`."""
self.current_state = transition.destination
def get_energy(self):
- """
- Return total energy in pJ
- """
+ """Return total energy in pJ."""
return self.energy.val * self.energy_granularity * 1e12
class SimulatedStaticStateOnlyAccountingImmediateCalculation(SimulatedAccountingMethod):
+ """
+ Simulated state-only energy accounting with immediate calculation.
+
+ Does not use functions or LUTs, only static (median) state power.
+ Transitions are assumed to be immediate and have negligible energy overhead.
+
+ Keeps track of the current state and the time it is active. On each
+ transition, current state power and duration is used to update the
+ total energy spent.
+ """
+
def __init__(self, pta: PTA, *args, **kwargs):
super().__init__(pta, *args, **kwargs)
def sleep(self, duration_us):
- self.energy += self.ts_class(self._sleep_duration(duration_us)) * self.power_class(int(self.current_state.power))
+ time = self._sleep_duration(duration_us)
+ power = int(self.current_state.power)
+ energy = self._energy_from_power_and_time(time, power)
+ self.energy += energy
class SimulatedStaticAccountingImmediateCalculation(SimulatedAccountingMethod):
+ """
+ Simulated energy accounting with states and transitions, immediate calculation.
+
+ Does not use functions or LUTs, only static (median) state power and transition energ.
+
+ Keeps track of the current state and the time it is active. On each
+ transition, current state power and duration is used to calculate the
+ energy spent in the state, which is used in conjunction with the
+ transition's energy cost to update the total energy spent.
+ """
+
def __init__(self, pta: PTA, *args, **kwargs):
super().__init__(pta, *args, **kwargs)
def sleep(self, duration_us):
- self.energy += self.ts_class(self._sleep_duration(duration_us)) * self.power_class(int(self.current_state.power))
+ time = self._sleep_duration(duration_us)
+ print('sleep duration is {}'.format(time))
+ power = int(self.current_state.power)
+ print('power is {}'.format(power))
+ energy = self._energy_from_power_and_time(time, power)
+ print('energy is {}'.format(energy))
+ self.energy += energy
def pass_transition(self, transition: Transition):
self.energy += int(transition.energy)
super().pass_transition(transition)
class SimulatedStaticAccounting(SimulatedAccountingMethod):
+ """
+ Simulated energy accounting with states and transitions, deferred energy calculation.
+
+ Does not use functions or LUTs, only static (median) state power and transition energ.
+
+ Keeps track of the time spent in each state and the number of calls for
+ each transition. This data is update whenever passing a transition and used
+ to calculate total energy spent on-demand: E = sum(P_q * t_q) + sum(E_t * n_t).
+ """
+
def __init__(self, pta: PTA, *args, **kwargs):
super().__init__(pta, *args, **kwargs)
self.time_in_state = dict()
@@ -185,13 +237,23 @@ class SimulatedStaticAccounting(SimulatedAccountingMethod):
pta = self.pta
energy = self.energy_class(0)
for state in pta.state.values():
- energy += self.time_in_state[state.name] * int(state.power)
+ energy += self._energy_from_power_and_time(self.time_in_state[state.name], int(state.power))
for i, transition in enumerate(pta.transitions):
energy += self.transition_count[i] * int(transition.energy)
return energy.val
class SimulatedStaticStateOnlyAccounting(SimulatedAccountingMethod):
+ """
+ Simulated energy accounting with states and transitions, deferred energy calculation.
+
+ Does not use functions or LUTs, only static (median) state power and transition energ.
+
+ Keeps track of the time spent in each state and the number of calls for
+ each transition. This data is update whenever passing a transition and used
+ to calculate total energy spent on-demand: E = sum(P_q * t_q) + sum(E_t * n_t).
+ """
+
def __init__(self, pta: PTA, *args, **kwargs):
super().__init__(pta, *args, **kwargs)
self.time_in_state = dict()
@@ -205,7 +267,7 @@ class SimulatedStaticStateOnlyAccounting(SimulatedAccountingMethod):
pta = self.pta
energy = self.energy_class(0)
for state in pta.state.values():
- energy += self.time_in_state[state.name] * int(state.power)
+ energy += self._energy_from_power_and_time(self.time_in_state[state.name], int(state.power))
return energy.val
class AccountingMethod:
diff --git a/test/test_codegen.py b/test/test_codegen.py
index 2d54158..e1bb942 100755
--- a/test/test_codegen.py
+++ b/test/test_codegen.py
@@ -110,6 +110,26 @@ class TestCG(unittest.TestCase):
cg.sleep(90)
self.assertEqual(cg.get_energy(), 900 % 256)
+ cg = get_simulated_accountingmethod('static_statetransition_immediate')(pta, 100000, 'uint8_t', 'uint8_t', 'uint8_t', 'uint16_t')
+ cg.current_state = pta.state['IDLE']
+ cg.sleep(7)
+ self.assertEqual(cg.get_energy(), 0)
+ cg.sleep(15)
+ self.assertEqual(cg.get_energy(), 90)
+ cg.sleep(90)
+ self.assertEqual(cg.get_energy(), 900)
+
+ pta.state['IDLE'].power = 9 # -> 90 uW
+ pta.transitions[1].energy = 1 # -> 100 pJ
+ cg = get_simulated_accountingmethod('static_statetransition_immediate')(pta, 1000000, 'uint8_t', 'uint8_t', 'uint8_t', 'uint8_t', 1e-5, 1e-5, 1e-10)
+ cg.current_state = pta.state['IDLE']
+ cg.sleep(10) # 10 us
+ self.assertEqual(cg.get_energy(), 90 * 10)
+ cg.pass_transition(pta.transitions[1])
+ self.assertAlmostEqual(cg.get_energy(), 90 * 10 + 100, places=0)
+ cg.pass_transition(pta.transitions[1])
+ self.assertAlmostEqual(cg.get_energy(), 90 * 10 + 100 + 100, places=0)
+
def test_statetransition(self):
pta = PTA.from_json(example_json_1)
pta.set_random_energy_model()