diff options
author | Daniel Friesel <daniel.friesel@uos.de> | 2019-09-26 16:53:29 +0200 |
---|---|---|
committer | Daniel Friesel <daniel.friesel@uos.de> | 2019-09-26 16:53:29 +0200 |
commit | bdd9fa8b2c08c833d398247c4e8fa9806e759c13 (patch) | |
tree | ae8bacc3c519f16917da96a4099929504e01c2ab /lib | |
parent | 8aa9bdc2ec7832d49a7dde8ee92641df43a37398 (diff) |
Use individuel MIMOSA log files per repetition; documentation
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/automata.py | 86 | ||||
-rwxr-xr-x | lib/dfatool.py | 47 | ||||
-rw-r--r-- | lib/harness.py | 87 | ||||
-rw-r--r-- | lib/runner.py | 2 |
4 files changed, 163 insertions, 59 deletions
diff --git a/lib/automata.py b/lib/automata.py index 0cc7143..2387734 100755 --- a/lib/automata.py +++ b/lib/automata.py @@ -27,10 +27,9 @@ class State: u""" Create a new PTA state. - arguments: - name -- state name - power -- static state power in µW - power_function -- parameterized state power in µW + :param name: state name + :param power: static state power in µW + :param power_function: parameterized state power in µW """ self.name = name self.power = power @@ -45,19 +44,25 @@ class State: u""" Return energy spent in state in pJ. - arguments: - duration -- duration in µs - param_dict -- current parameters + :param duration: duration in µs + :param param_dict: current parameters + :returns: energy spent in pJ """ if self.power_function: return self.power_function.eval(_dict_to_list(param_dict)) * duration return self.power * duration def set_random_energy_model(self, static_model = True): + """Set a random static energy value.""" self.power = int(np.random.sample() * 50000) def get_transition(self, transition_name: str) -> object: - """Return Transition object for outgoing transtion transition_name.""" + """ + Return Transition object for outgoing transtion transition_name. + + :param transition_name: transition name + :returns: `Transition` object + """ return self.outgoing_transitions[transition_name] def has_interrupt_transitions(self) -> bool: @@ -73,8 +78,8 @@ class State: Must only be called if has_interrupt_transitions returned true. - arguments: - parameters -- current parameter values + :param parameters: current parameter values + :returns: Transition object """ interrupts = filter(lambda x: x.is_interrupt, self.outgoing_transitions.values()) interrupts = sorted(interrupts, key = lambda x: x.get_timeout(parameters)) @@ -84,14 +89,15 @@ class State: """ Return a generator object for depth-first search over all outgoing transitions. - arguments: - depth -- search depth - with_arguments -- perform dfs with function+argument transitions instead of just function transitions. - trace_filter -- list of lists. Each sub-list is a trace. Only traces matching one of the provided sub-lists are returned. + :param depth: search depth + :param with_arguments: perform dfs with function+argument transitions instead of just function transitions. + :param trace_filter: list of lists. Each sub-list is a trace. Only traces matching one of the provided sub-lists are returned. E.g. trace_filter = [['init', 'foo'], ['init', 'bar']] will only return traces with init as first and foo or bar as second element. trace_filter = [['init', 'foo', '$'], ['init', 'bar', '$']] will only return the traces ['init', 'foo'] and ['init', 'bar']. - sleep -- if set and non-zero: include sleep pseudo-states with <sleep> us duration + Note that `trace_filter` takes precedence over `depth`: traces matching `trace_filter` are generated even if their length exceeds `depth` + :param sleep: if set and non-zero: include sleep pseudo-states with <sleep> us duration For the [['init', 'foo', '$'], ['init', 'bar', '$']] example above, sleep=10 results in [(None, 10), 'init', (None, 10), 'foo'] and [(None, 10), 'init', (None, 10), 'bar'] + :returns: Generator object for depth-first search. Each access yields a list of (Transition, (arguments)) elements describing a single run through the PTA. """ # A '$' entry in trace_filter indicates that the trace should (successfully) terminate here regardless of `depth`. @@ -170,7 +176,28 @@ class State: return ret class Transition: - """A single PTA transition with one origin and one destination state.""" + u""" + A single PTA transition with one origin and one destination state. + + :param name: transition name, corresponds to driver function name + :param origin: origin `State` + :param destination: destination `State` + :param energy: static energy needed to execute this transition, in pJ + :param energy_function: parameterized transition energy `AnalyticFunction`, returning pJ + :param duration: transition duration, in µs + :param duration_function: parameterized duration `AnalyticFunction`, returning µs + :param timeout: transition timeout, in µs. Only set for interrupt transitions. + :param timeout_function: parameterized transition timeout `AnalyticFunction`, in µs. Only set for interrupt transitions. + :param is_interrupt: Is this an interrupt transition? + :param arguments: list of function argument names + :param argument_values: list of argument values used for benchmark generation. Each entry is a list of values for the corresponding argument + :param argument_combination: During benchmark generation, should arguments be combined via `cartesian` or `zip`? + :param param_update_function: Setter for parameters after a transition. Gets current parameter dict and function argument values as arguments, must return the new parameter dict + :param arg_to_param_map: dict mapping argument index to the name of the parameter affected by its value + :param set_param: dict mapping parameter name to their value (set as side-effect of executing the transition, not parameter-dependent) + :param return_value_handlers: todo + :param codegen: todo + """ def __init__(self, orig_state: State, dest_state: State, name: str, energy: float = 0, energy_function: AnalyticFunction = None, @@ -183,14 +210,14 @@ class Transition: param_update_function = None, arg_to_param_map: dict = None, set_param = None, - return_value_handlers: list = []): + return_value_handlers: list = [], + codegen = dict()): """ Create a new transition between two PTA states. - arguments: - orig_state -- origin state - dest_state -- destination state - name -- transition name, typically the same as a driver/library function name + :param orig_state: origin `State` + :param dest_state: destination `State` + :param name: transition name, typically the same as a driver/library function name """ self.name = name self.origin = orig_state @@ -209,18 +236,21 @@ class Transition: self.arg_to_param_map = arg_to_param_map self.set_param = set_param self.return_value_handlers = return_value_handlers + self.codegen = codegen for handler in self.return_value_handlers: if 'formula' in handler: handler['formula'] = NormalizationFunction(handler['formula']) + def get_duration(self, param_dict: dict = {}, args: list = []) -> float: u""" Return transition duration in µs. - arguments: - param_dict -- current parameter values - args -- function arguments + :param param_dict: current parameter values + :param args: function arguments + + :returns: transition duration in µs """ if self.duration_function: return self.duration_function.eval(_dict_to_list(param_dict), args) @@ -307,6 +337,14 @@ class PTA: A parameterized priced timed automaton. All states are accepting. Suitable for simulation, model storage, and (soon) benchmark generation. + + :param state: dict mapping state name to `State` object + :param accepting_states: list of accepting state names + :param parameters: current parameters + :param parameter_normalization: TODO + :param codegen: TODO + :param initial_param_values: TODO + :param transitions: list of `Transition` objects """ def __init__(self, state_names: list = [], diff --git a/lib/dfatool.py b/lib/dfatool.py index 68146ae..ade685c 100755 --- a/lib/dfatool.py +++ b/lib/dfatool.py @@ -648,7 +648,7 @@ class RawData: # state_duration is stored as ms, not us return offline['us'] > state_duration * 1500 - def _measurement_is_valid_01(self, processed_data, repeat = 0): + def _measurement_is_valid_01(self, processed_data): """ Check if a dfatool v0 or v1 measurement is valid. @@ -693,8 +693,6 @@ class RawData: sched_trigger_count = 0 for run in traces: sched_trigger_count += len(run['trace']) - if repeat: - sched_trigger_count *= repeat if sched_trigger_count != processed_data['triggers']: processed_data['error'] = 'got {got:d} trigger edges, expected {exp:d}'.format( got = processed_data['triggers'], @@ -931,20 +929,8 @@ class RawData: elif version == 1: - traces_by_file = list() - mim_files_by_file = list() with tarfile.open(filename) as tf: - # Relies on generate-dfa-benchmark placing the .mim files - # in the order they were created (i.e., lexically sorted) - for member in tf.getmembers(): - _, extension = os.path.splitext(member.name) - if extension == '.mim': - mim_files_by_file.append({ - 'content' : tf.extractfile(member).read(), - 'info' : member, - }) - elif extension == '.json': - ptalog = json.load(tf.extractfile(member)) + ptalog = json.load(tf.extractfile(tf.getmember('ptalog.json'))) # ptalog['traces'] is a list of lists. # The first level corresponds to the individual .mim files: @@ -954,17 +940,22 @@ class RawData: # sub-benchmark, so ptalog['traces'][0][0] is the first # run, ptalog['traces'][0][1] the second, and so on - traces_by_file.extend(ptalog['traces']) - self.setup_by_fileno.append({ - 'mimosa_voltage' : ptalog['configs'][0]['voltage'], - 'mimosa_shunt' : ptalog['configs'][0]['shunt'], - 'state_duration' : ptalog['opt']['sleep'] - }) - for j, mim_file in enumerate(mim_files_by_file): - mim_file['setup'] = self.setup_by_fileno[i] - mim_file['expected_trace'] = ptalog['traces'][j] - mim_file['fileno'] = i - mim_files.extend(mim_files_by_file) + for j, traces in enumerate(ptalog['traces']): + self.traces_by_fileno.append(traces) + self.setup_by_fileno.append({ + 'mimosa_voltage' : ptalog['configs'][j]['voltage'], + 'mimosa_shunt' : ptalog['configs'][j]['shunt'], + 'state_duration' : ptalog['opt']['sleep'], + }) + for mim_file in ptalog['files'][j]: + member = tf.getmember(mim_file) + mim_files.append({ + 'content' : tf.extractfile(member).read(), + 'fileno' : i, + 'info' : member, + 'setup' : self.setup_by_fileno[j], + 'expected_trace' : ptalog['traces'][j], + }) with Pool() as pool: measurements = pool.map(_preprocess_measurement, mim_files) @@ -983,7 +974,7 @@ class RawData: measurement['energy_trace'].pop(0) repeat = ptalog['opt']['repeat'] - if self._measurement_is_valid_01(measurement, repeat): + if self._measurement_is_valid_01(measurement): self._merge_online_and_offline(measurement) num_valid += 1 else: diff --git a/lib/harness.py b/lib/harness.py index d3ea481..f39b28c 100644 --- a/lib/harness.py +++ b/lib/harness.py @@ -11,15 +11,35 @@ import re # generated otherwise and it should also work with AnalyticModel (which does # not have states) class TransitionHarness: - """Foo.""" + """ + TODO + + :param done: True if the specified amount of iterations have been logged. + :param synced: True if `parser_cb` has synchronized with UART output, i.e., the benchmark has successfully started. + :param traces: List of annotated PTA traces from benchmark execution. This list is updated during UART logging and should only be read back when `done` is True. + Uses the standard dfatool trace format: `traces` is a list of `{'id': ..., 'trace': ...}` dictionaries, each of which represents a single PTA trace (AKA + run). Each `trace` is in turn a list of state or transition dictionaries with the + following attributes: + * `isa`: 'state' or 'transition' + * `name`: state or transition name + * `parameter`: currently valid parameter values. If normalization is used, they are already normalized. Each parameter value is either a primitive + int/float/str value (-> constant for each iteration) or a list of + primitive values (-> set by the return value of the current run, not necessarily constan) + * `args`: function arguments, if isa == 'transition' + """ def __init__(self, gpio_pin = None, pta = None, log_return_values = False, repeat = 0, post_transition_delay_us = 0): """ Create a new TransitionHarness - :param gpio_pin: multipass GPIO Pin used for transition synchronization, e.g. `GPIO::p1_0`. Optional. + :param gpio_pin: multipass GPIO Pin used for transition synchronization with an external measurement device, e.g. `GPIO::p1_0`. Optional. The GPIO output is high iff a transition is executing - :param pta: PTA object + :param pta: PTA object. Needed to map UART output IDs to states and transitions :param log_return_values: Log return values of transition function calls? + :param repeat: How many times to run the benchmark until setting `one`, default 0. + When 0, `done` is never set. + :param post_transition_delay_us: If set, inject `arch.delay_us` after each transition, before logging the transition as completed (and releasing + `gpio_pin`). This artificially increases transition duration by the specified time and is useful if an external measurement device's resolution is + lower than the expected minimum transition duration. """ self.gpio_pin = gpio_pin self.pta = pta @@ -29,19 +49,35 @@ class TransitionHarness: self.reset() def copy(self): - new_object = __class__(gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat) + new_object = __class__(gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat, post_transition_delay_us = self.post_transition_delay_us) new_object.traces = self.traces.copy() new_object.trace_id = self.trace_id return new_object def reset(self): + """ + Reset harness for a new benchmark. + + Truncates `traces`, `trace_id`, `done`, and `synced`. + """ self.traces = [] self.trace_id = 0 self.repetitions = 0 self.done = False self.synced = False + def restart(self): + """ + Reset harness for a new execution of the current benchmark. + + Resets `done` and `synced`. + """ + self.repetitions = 0 + self.done = False + self.synced = False + def global_code(self): + """Return global (pre-`main()`) C++ code needed for tracing.""" ret = '' if self.gpio_pin != None: ret += '#define PTALOG_GPIO {}\n'.format(self.gpio_pin) @@ -56,9 +92,11 @@ class TransitionHarness: return ret def start_benchmark(self, benchmark_id = 0): + """Return C++ code to signal benchmark start to harness.""" return 'ptalog.startBenchmark({:d});\n'.format(benchmark_id) def start_trace(self): + """Prepare a new trace/run in the internal `.traces` structure.""" self.traces.append({ 'id' : self.trace_id, 'trace' : list(), @@ -66,6 +104,12 @@ class TransitionHarness: self.trace_id += 1 def append_state(self, state_name, param): + """ + Append a state to the current run in the internal `.traces` structure. + + :param state_name: state name + :param param: parameter dict + """ self.traces[-1]['trace'].append({ 'name': state_name, 'isa': 'state', @@ -73,6 +117,13 @@ class TransitionHarness: }) def append_transition(self, transition_name, param, args = []): + """ + Append a transition to the current run in the internal `.traces` structure. + + :param transition_name: transition name + :param param: parameter dict + :param args: function arguments (optional) + """ self.traces[-1]['trace'].append({ 'name': transition_name, 'isa': 'transition', @@ -81,9 +132,16 @@ class TransitionHarness: }) def start_run(self): + """Return C++ code used to start a new run/trace.""" return 'ptalog.reset();\n' def pass_transition(self, transition_id, transition_code, transition: object = None): + """ + Return C++ code used to pass a transition, including the corresponding function call. + + Tracks which transition has been executed and optionally its return value. May also inject a delay, if + `post_transition_delay_us` is set. + """ ret = 'ptalog.passTransition({:d});\n'.format(transition_id) ret += 'ptalog.startTransition();\n' if self.log_return_values and transition and len(transition.return_value_handlers): @@ -170,14 +228,22 @@ class TransitionHarness: self.current_transition_in_trace += 1 class OnboardTimerHarness(TransitionHarness): - """Bar.""" + """TODO + + Additional parameters / changes from TransitionHarness: + + :param traces: Each trace element (`.traces[*]['trace'][*]`) additionally contains + the dict `offline_aggregates` with the member `duration`. It contains a list of durations (in us) of the corresponding state/transition for each + benchmark iteration. + I.e. `.traces[*]['trace'][*]['offline_aggregates']['duration'] = [..., ...]` + """ def __init__(self, counter_limits, **kwargs): super().__init__(**kwargs) self.trace_length = 0 self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow = counter_limits def copy(self): - new_harness = __class__((self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow), gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values) + new_harness = __class__((self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow), gpio_pin = self.gpio_pin, pta = self.pta, log_return_values = self.log_return_values, repeat = self.repeat) new_harness.traces = self.traces.copy() new_harness.trace_id = self.trace_id return new_harness @@ -217,8 +283,14 @@ class OnboardTimerHarness(TransitionHarness): def parser_cb(self, line): #print('[HARNESS] got line {}'.format(line)) if re.match(r'\[PTA\] benchmark start, id=(\S+)', line): + if self.repeat > 0 and self.repetitions == self.repeat: + self.done = True + self.synced = False + print('[HARNESS] done') + return self.synced = True - print('[HARNESS] synced') + self.repetitions += 1 + print('[HARNESS] synced, {}/{}'.format(self.repetitions, self.repeat)) if self.synced: res = re.match(r'\[PTA\] trace=(\S+) count=(\S+)', line) if res: @@ -237,6 +309,7 @@ class OnboardTimerHarness(TransitionHarness): if overflow >= self.counter_max_overflow: raise RuntimeError('Counter overflow ({:d}/{:d}) in benchmark id={:d} trace={:d}: transition #{:d} (ID {:d})'.format(cycles, overflow, 0, self.trace_id, self.current_transition_in_trace, transition_id)) duration_us = cycles * self.one_cycle_in_us + overflow * self.one_overflow_in_us + # TODO subtract 'nop' cycles # self.traces contains transitions and states, UART output only contains transitions -> use index * 2 try: log_data_target = self.traces[self.trace_id]['trace'][self.current_transition_in_trace * 2] diff --git a/lib/runner.py b/lib/runner.py index 588bd4d..25e3519 100644 --- a/lib/runner.py +++ b/lib/runner.py @@ -167,6 +167,8 @@ class MIMOSAMonitor(SerialMonitor): self._mimosactl('connect') def _stop_mimosa(self): + # Make sure the MIMOSA daemon has gathered all needed data + time.sleep(2) self._mimosacmd(['--mimosa-stop']) mtime_changed = True mim_file = None |