diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/harness.py | 81 | ||||
-rw-r--r-- | lib/runner.py | 81 |
2 files changed, 141 insertions, 21 deletions
diff --git a/lib/harness.py b/lib/harness.py index 529a77e..1390e03 100644 --- a/lib/harness.py +++ b/lib/harness.py @@ -11,18 +11,19 @@ import re # generated otherwise and it should also work with AnalyticModel (which does # not have states) class TransitionHarness: - def __init__(self, gpio_pin = None): + def __init__(self, gpio_pin = None, pta = None): self.gpio_pin = gpio_pin + self.pta = pta + self.reset() + + def reset(self): self.traces = [] self.trace_id = 1 - pass + self.synced = False def start_benchmark(self): pass - def stop_benchmark(self): - pass - def global_code(self): ret = '' if self.gpio_pin != None: @@ -34,8 +35,8 @@ class TransitionHarness: ret += 'PTALog ptalog;\n' return ret - def start_benchmark(self): - return 'ptalog.startBenchmark(0);\n' + def start_benchmark(self, benchmark_id = 0): + return 'ptalog.startBenchmark({:d});\n'.format(benchmark_id) def start_trace(self): self.traces.append({ @@ -62,15 +63,15 @@ class TransitionHarness: def start_run(self): return 'ptalog.reset();\n' - def pass_transition(self, transition_id, transition_code, transition: object = None, parameter: dict = dict()): + def pass_transition(self, transition_id, transition_code, transition: object = None): ret = 'ptalog.passTransition({:d});\n'.format(transition_id) ret += 'ptalog.startTransition();\n' ret += '{}\n'.format(transition_code) ret += 'ptalog.stopTransition();\n' return ret - def stop_run(self, trace_id = 0): - return 'ptalog.dump({:d});\n'.format(trace_id) + def stop_run(self, num_traces = 0): + return 'ptalog.dump({:d});\n'.format(num_traces) def stop_benchmark(self): return '' @@ -96,8 +97,11 @@ class TransitionHarness: pass class OnboardTimerHarness(TransitionHarness): - def __init__(self, gpio_pin = None): - super().__init__(gpio_pin = gpio_pin) + def __init__(self, counter_limits, **kwargs): + super().__init__(**kwargs) + self.trace_id = 0 + self.trace_length = 0 + self.one_cycle_in_us, self.one_overflow_in_us, self.counter_max_overflow = counter_limits def global_code(self): ret = '#include "driver/counter.h"\n' @@ -105,18 +109,63 @@ class OnboardTimerHarness(TransitionHarness): ret += super().global_code() return ret - def start_benchmark(self): + def start_benchmark(self, benchmark_id = 0): ret = 'counter.start();\n' ret += 'counter.stop();\n' ret += 'ptalog.passNop(counter);\n' - ret += super().start_benchmark() + ret += super().start_benchmark(benchmark_id) return ret - def pass_transition(self, transition_id, transition_code, transition: object = None, parameter: dict = dict()): + def pass_transition(self, transition_id, transition_code, transition: object = None): ret = 'ptalog.passTransition({:d});\n'.format(transition_id) ret += 'ptalog.startTransition();\n' ret += 'counter.start();\n' ret += '{}\n'.format(transition_code) ret += 'counter.stop();\n' ret += 'ptalog.stopTransition(counter);\n' - return ret
\ No newline at end of file + return ret + + def parser_cb(self, line): + #print('[HARNESS] got line {}'.format(line)) + if re.match(r'\[PTA\] benchmark start, id=(.*)', line): + self.synced = True + print('[HARNESS] synced') + if self.synced: + res = re.match(r'\[PTA\] trace=(.*) count=(.*)', line) + if res: + self.trace_id = int(res.group(1)) + self.trace_length = int(res.group(2)) + self.current_transition_in_trace = 0 + #print('[HARNESS] trace {:d} contains {:d} transitions. Expecting {:d} transitions.'.format(self.trace_id, self.trace_length, len(self.traces[self.trace_id]['trace']) // 2)) + res = re.match(r'\[PTA\] transition=(.*) cycles=(.*)/(.*)', line) + if res: + transition_id = int(res.group(1)) + # TODO Handle Overflows (requires knowledge of arch-specific max cycle value) + cycles = int(res.group(2)) + overflow = int(res.group(3)) + 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 + # self.traces contains transitions and states, UART output only contains trnasitions -> use index * 2 + try: + log_data_target = self.traces[self.trace_id]['trace'][self.current_transition_in_trace * 2] + except IndexError: + transition_name = None + if self.pta: + transition_name = self.pta.transitions[transition_id].name + print('[HARNESS] benchmark id={:d} trace={:d}: transition #{:d} (ID {:d}, name {}) is out of bounds'.format(0, self.trace_id, self.current_transition_in_trace, transition_id, transition_name)) + print(' Offending line: {}'.format(line)) + return + if log_data_target['isa'] != 'transition': + raise RuntimeError('Log mismatch: Expected transition, got {:s}'.format(log_data_target['isa'])) + if self.pta: + transition = self.pta.transitions[transition_id] + if transition.name != log_data_target['name']: + raise RuntimeError('Log mismatch: Expected transition {:s}, got transition {:s}'.format(log_data_target['name'], transition.name)) + #print('[HARNESS] Logging data for transition {}'.format(log_data_target['name'])) + if 'offline_aggregates' not in log_data_target: + log_data_target['offline_aggregates'] = { + 'duration' : list() + } + log_data_target['offline_aggregates']['duration'].append(duration_us) + self.current_transition_in_trace += 1
\ No newline at end of file diff --git a/lib/runner.py b/lib/runner.py index baf4d16..fe76459 100644 --- a/lib/runner.py +++ b/lib/runner.py @@ -10,6 +10,7 @@ Functions: get_counter_limits -- return arch-specific multipass counter limits (max value, max overflow) """ +import os import re import serial import serial.threaded @@ -39,12 +40,16 @@ class SerialReader(serial.threaded.Protocol): str_data = data.decode('UTF-8') self.recv_buf += str_data - lines = list(map(str.strip, self.recv_buf.split('\n'))) + # We may get anything between \r\n, \n\r and simple \n newlines. + # We assume that \n is always present and use str.strip to remove leading/trailing \r symbols + # Note: Do not call str.strip on lines[-1]! Otherwise, lines may be mangled + lines = self.recv_buf.split('\n') if len(lines) > 1: - self.lines.extend(lines[:-1]) + self.lines.extend(map(str.strip, lines[:-1])) self.recv_buf = lines[-1] if self.callback: - self.callback(lines[:-1]) + for line in lines[:-1]: + self.callback(str.strip(line)) except UnicodeDecodeError: pass @@ -117,6 +122,51 @@ class SerialMonitor: self.worker.stop() self.ser.close() + +class MIMOSAMonitor(SerialMonitor): + def __init__(self, port: str, baud: int, callback = None, offset = 130, shunt = 330, voltage = 3.3): + super().__init__(port = port, baud = baud, callback = callback) + self._offset = offset + self._shunt = shunt + self._voltage = voltage + self._start_mimosa() + + def _mimosacmd(self, opts): + cmd = ['MimosaCMD'] + cmd.extend(opts) + res = subprocess.run(cmd) + if res.returncode != 0: + raise RuntimeError('MimosaCMD returned ' + res.returncode) + + def _start_mimosa(self): + self._mimosacmd(['--start']) + self._mimosacmd(['--parameter', 'offset', str(self._offset)]) + self._mimosacmd(['--parameter', 'shunt', str(self._shunt)]) + self._mimosacmd(['--parameter', 'voltage', str(self._voltage)]) + self._mimosacmd(['--mimosa-start']) + + def _stop_mimosa(self): + self._mimosacmd(['--mimosa-stop']) + mtime_changed = True + mim_file = None + time.sleep(1) + for filename in os.listdir(): + if re.search(r'[.]mim$', filename): + mim_file = filename + break + while mtime_changed: + mtime_changed = False + if time.time() - os.stat(mim_file).st_mtime < 3: + mtime_changed = True + time.sleep(1) + self._mimosacmd(['--stop']) + return mim_file + + def close(self): + super().close() + mim_file = self._stop_mimosa() + os.remove(mim_file) + class ShellMonitor: """SerialMonitor runs a program and captures its output for a specific amount of time.""" def __init__(self, script: str, callback = None): @@ -140,7 +190,8 @@ class ShellMonitor: stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines = True) if self.callback: - self.callback(res.stdout.split('\n')) + for line in res.stdout.split('\n'): + self.callback(line) return res.stdout.split('\n') def monitor(self): @@ -193,12 +244,15 @@ def get_info(arch, opts: list = []) -> list: return res.stdout.split('\n') def get_monitor(arch: str, **kwargs) -> object: - """Return a SerialMonitor or ShellMonitor.""" + """Return a SerialMonitor or ShellMonitor, depending on "make info" output of arch.""" for line in get_info(arch): if 'Monitor:' in line: _, port, arg = line.split(' ') if port == 'run': return ShellMonitor(arg, **kwargs) + elif 'mimosa' in kwargs: + mimosa_kwargs = kwargs.pop('mimosa') + return MIMOSAMonitor(port, arg, **mimosa_kwargs, **kwargs) else: return SerialMonitor(port, arg, **kwargs) raise RuntimeError('Monitor failure') @@ -212,3 +266,20 @@ def get_counter_limits(arch: str) -> tuple: max_overflow = int(match.group(2)) return overflow_value, max_overflow raise RuntimeError('Did not find Counter Overflow limits') + +def get_counter_limits_us(arch: str) -> tuple: + """Return duration of one counter step and one counter overflow in us.""" + cpu_freq = 0 + overflow_value = 0 + max_overflow = 0 + for line in get_info(arch): + match = re.match(r'CPU\s+Freq:\s+(.*)\s+Hz', line) + if match: + cpu_freq = int(match.group(1)) + match = re.match(r'Counter Overflow:\s+([^/]*)/(.*)', line) + if match: + overflow_value = int(match.group(1)) + max_overflow = int(match.group(2)) + if cpu_freq and overflow_value: + return 1000000 / cpu_freq, overflow_value * 1000000 / cpu_freq, max_overflow + raise RuntimeError('Did not find Counter Overflow limits') |