diff options
-rw-r--r-- | lib/codegen.py | 82 | ||||
-rwxr-xr-x | test/test_codegen.py | 20 |
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() |