summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/harness.py165
-rw-r--r--lib/loader.py176
-rw-r--r--lib/runner.py49
3 files changed, 337 insertions, 53 deletions
diff --git a/lib/harness.py b/lib/harness.py
index ae9c28c..51013e1 100644
--- a/lib/harness.py
+++ b/lib/harness.py
@@ -33,6 +33,7 @@ class TransitionHarness:
log_return_values=False,
repeat=0,
post_transition_delay_us=0,
+ energytrace_sync=None,
):
"""
Create a new TransitionHarness
@@ -53,6 +54,7 @@ class TransitionHarness:
self.log_return_values = log_return_values
self.repeat = repeat
self.post_transition_delay_us = post_transition_delay_us
+ self.energytrace_sync = energytrace_sync
self.reset()
def copy(self):
@@ -63,6 +65,7 @@ class TransitionHarness:
log_return_values=self.log_return_values,
repeat=self.repeat,
post_transition_delay_us=self.post_transition_delay_us,
+ energytrace_sync=self.energytrace_sync,
)
new_object.traces = self.traces.copy()
new_object.trace_id = self.trace_id
@@ -138,9 +141,7 @@ class TransitionHarness:
def start_trace(self):
"""Prepare a new trace/run in the internal `.traces` structure."""
- self.traces.append(
- {"id": self.trace_id, "trace": list(),}
- )
+ self.traces.append({"id": self.trace_id, "trace": list()})
self.trace_id += 1
def append_state(self, state_name, param):
@@ -151,7 +152,7 @@ class TransitionHarness:
:param param: parameter dict
"""
self.traces[-1]["trace"].append(
- {"name": state_name, "isa": "state", "parameter": param,}
+ {"name": state_name, "isa": "state", "parameter": param}
)
def append_transition(self, transition_name, param, args=[]):
@@ -175,21 +176,16 @@ class TransitionHarness:
"""Return C++ code used to start a new run/trace."""
return "ptalog.reset();\n"
- def _pass_transition_call(self, transition_id):
- if self.gpio_mode == "bar":
- barcode_bits = Code128("T{}".format(transition_id), charset="B").modules
- if len(barcode_bits) % 8 != 0:
- barcode_bits.extend([1] * (8 - (len(barcode_bits) % 8)))
- barcode_bytes = [
- 255 - int("".join(map(str, reversed(barcode_bits[i : i + 8]))), 2)
- for i in range(0, len(barcode_bits), 8)
- ]
- inline_array = "".join(map(lambda s: "\\x{:02x}".format(s), barcode_bytes))
- return 'ptalog.startTransition("{}", {});\n'.format(
- inline_array, len(barcode_bytes)
- )
- else:
- return "ptalog.startTransition();\n"
+ def _get_barcode(self, transition_id):
+ barcode_bits = Code128("T{}".format(transition_id), charset="B").modules
+ if len(barcode_bits) % 8 != 0:
+ barcode_bits.extend([1] * (8 - (len(barcode_bits) % 8)))
+ barcode_bytes = [
+ 255 - int("".join(map(str, reversed(barcode_bits[i : i + 8]))), 2)
+ for i in range(0, len(barcode_bits), 8)
+ ]
+ inline_array = "".join(map(lambda s: "\\x{:02x}".format(s), barcode_bytes))
+ return inline_array, len(barcode_bytes)
def pass_transition(
self, transition_id, transition_code, transition: object = None
@@ -201,7 +197,12 @@ class TransitionHarness:
`post_transition_delay_us` is set.
"""
ret = "ptalog.passTransition({:d});\n".format(transition_id)
- ret += self._pass_transition_call(transition_id)
+ if self.gpio_mode == "bar":
+ ret += """ptalog.startTransition("{}", {});\n""".format(
+ *self._get_barcode(transition_id)
+ )
+ else:
+ ret += "ptalog.startTransition();\n"
if (
self.log_return_values
and transition
@@ -373,6 +374,7 @@ class OnboardTimerHarness(TransitionHarness):
pta=self.pta,
log_return_values=self.log_return_values,
repeat=self.repeat,
+ energytrace_sync=self.energytrace_sync,
)
new_harness.traces = self.traces.copy()
new_harness.trace_id = self.trace_id
@@ -398,24 +400,57 @@ class OnboardTimerHarness(TransitionHarness):
]
def global_code(self):
- ret = '#include "driver/counter.h"\n'
- ret += "#define PTALOG_TIMING\n"
+ ret = "#define PTALOG_TIMING\n"
ret += super().global_code()
+ if self.energytrace_sync == "led":
+ # TODO Make nicer
+ ret += """\nvoid runLASync(){
+ // ======================= LED SYNC ================================
+ ptalog.passTransition(0);
+ ptalog.startTransition();
+ gpio.led_toggle(0);
+ gpio.led_toggle(1);
+ ptalog.stopTransition();
+
+ for (unsigned char i = 0; i < 4; i++) {
+ arch.sleep_ms(250);
+ }
+
+ ptalog.passTransition(0);
+ ptalog.startTransition();
+ gpio.led_toggle(0);
+ gpio.led_toggle(1);
+ ptalog.stopTransition();
+ // ======================= LED SYNC ================================
+ arch.sleep_ms(250);
+}\n\n"""
return ret
def start_benchmark(self, benchmark_id=0):
- ret = "counter.start();\n"
- ret += "counter.stop();\n"
- ret += "ptalog.passNop(counter);\n"
+ ret = ""
+ if self.energytrace_sync == "led":
+ ret += "runLASync();\n"
+ ret += "ptalog.passNop();\n"
ret += super().start_benchmark(benchmark_id)
return ret
+ def stop_benchmark(self):
+ ret = ""
+ if self.energytrace_sync == "led":
+ ret += "runLASync();\n"
+ ret += super().stop_benchmark()
+ return ret
+
def pass_transition(
self, transition_id, transition_code, transition: object = None
):
ret = "ptalog.passTransition({:d});\n".format(transition_id)
- ret += self._pass_transition_call(transition_id)
- ret += "counter.start();\n"
+ if self.gpio_mode == "bar":
+ ret += """ptalog.startTransition("{}", {});\n""".format(
+ *self._get_barcode(transition_id)
+ )
+ else:
+ ret += "ptalog.startTransition();\n"
if (
self.log_return_values
and transition
@@ -424,14 +459,13 @@ class OnboardTimerHarness(TransitionHarness):
ret += "transition_return_value = {}\n".format(transition_code)
else:
ret += "{}\n".format(transition_code)
- ret += "counter.stop();\n"
if (
self.log_return_values
and transition
and len(transition.return_value_handlers)
):
ret += "ptalog.logReturn(transition_return_value);\n"
- ret += "ptalog.stopTransition(counter);\n"
+ ret += "ptalog.stopTransition();\n"
return ret
def _append_nondeterministic_parameter_value(
@@ -453,11 +487,26 @@ class OnboardTimerHarness(TransitionHarness):
res.group(1), res.group(2)
)
)
- if re.match(r"\[PTA\] benchmark stop", line):
+ match = re.match(r"\[PTA\] benchmark stop, cycles=(\S+)/(\S+)", line)
+ if match:
self.repetitions += 1
self.synced = False
if self.repeat > 0 and self.repetitions == self.repeat:
self.done = True
+ prev_state_cycles = int(match.group(1))
+ prev_state_overflow = int(match.group(2))
+ prev_state_duration_us = (
+ prev_state_cycles * self.one_cycle_in_us
+ + prev_state_overflow * self.one_overflow_in_us
+ - self.nop_cycles * self.one_cycle_in_us
+ )
+ final_state = self.traces[self.trace_id]["trace"][-1]
+ if "offline_aggregates" not in final_state:
+ final_state["offline_aggregates"] = {"duration": list()}
+ final_state["offline_aggregates"]["duration"].append(
+ prev_state_duration_us
+ )
+
print("[HARNESS] done")
return
# May be repeated, e.g. if the device is reset shortly after start by
@@ -473,14 +522,20 @@ class OnboardTimerHarness(TransitionHarness):
self.current_transition_in_trace = 0
if self.log_return_values:
res = re.match(
- r"\[PTA\] transition=(\S+) cycles=(\S+)/(\S+) return=(\S+)", line
+ r"\[PTA\] transition=(\S+) prevcycles=(\S+)/(\S+) cycles=(\S+)/(\S+) return=(\S+)",
+ line,
)
else:
- res = re.match(r"\[PTA\] transition=(\S+) cycles=(\S+)/(\S+)", line)
+ res = re.match(
+ r"\[PTA\] transition=(\S+) prevcycles=(\S+)/(\S+) cycles=(\S+)/(\S+)",
+ line,
+ )
if res:
transition_id = int(res.group(1))
- cycles = int(res.group(2))
- overflow = int(res.group(3))
+ prev_state_cycles = int(res.group(2))
+ prev_state_overflow = int(res.group(3))
+ cycles = int(res.group(4))
+ overflow = int(res.group(5))
if overflow >= self.counter_max_overflow:
self.abort = True
raise RuntimeError(
@@ -493,11 +548,28 @@ class OnboardTimerHarness(TransitionHarness):
transition_id,
)
)
+ if prev_state_overflow >= self.counter_max_overflow:
+ self.abort = True
+ raise RuntimeError(
+ "Counter overflow ({:d}/{:d}) in benchmark id={:d} trace={:d}: state before transition #{:d} (ID {:d})".format(
+ prev_state_cycles,
+ prev_state_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.nop_cycles * self.one_cycle_in_us
)
+ prev_state_duration_us = (
+ prev_state_cycles * self.one_cycle_in_us
+ + prev_state_overflow * self.one_overflow_in_us
+ - self.nop_cycles * self.one_cycle_in_us
+ )
if duration_us < 0:
duration_us = 0
# self.traces contains transitions and states, UART output only contains transitions -> use index * 2
@@ -505,6 +577,14 @@ class OnboardTimerHarness(TransitionHarness):
log_data_target = self.traces[self.trace_id]["trace"][
self.current_transition_in_trace * 2
]
+ if self.current_transition_in_trace > 0:
+ prev_state_data = self.traces[self.trace_id]["trace"][
+ self.current_transition_in_trace * 2 - 1
+ ]
+ elif self.current_transition_in_trace == 0 and self.trace_id > 0:
+ prev_state_data = self.traces[self.trace_id - 1]["trace"][-1]
+ else:
+ prev_state_data = None
except IndexError:
transition_name = None
if self.pta:
@@ -531,6 +611,17 @@ class OnboardTimerHarness(TransitionHarness):
log_data_target["isa"],
)
)
+ if prev_state_data and prev_state_data["isa"] != "state":
+ self.abort = True
+ raise RuntimeError(
+ "Log mismatch in benchmark id={:d} trace={:d}: state before transition #{:d} (ID {:d}): Expected state, got {:s}".format(
+ 0,
+ self.trace_id,
+ self.current_transition_in_trace,
+ transition_id,
+ prev_state_data["isa"],
+ )
+ )
if self.pta:
transition = self.pta.transitions[transition_id]
if transition.name != log_data_target["name"]:
@@ -601,4 +692,10 @@ class OnboardTimerHarness(TransitionHarness):
if "offline_aggregates" not in log_data_target:
log_data_target["offline_aggregates"] = {"duration": list()}
log_data_target["offline_aggregates"]["duration"].append(duration_us)
+ if prev_state_data is not None:
+ if "offline_aggregates" not in prev_state_data:
+ prev_state_data["offline_aggregates"] = {"duration": list()}
+ prev_state_data["offline_aggregates"]["duration"].append(
+ prev_state_duration_us
+ )
self.current_transition_in_trace += 1
diff --git a/lib/loader.py b/lib/loader.py
index 57b3d30..fcd5490 100644
--- a/lib/loader.py
+++ b/lib/loader.py
@@ -11,6 +11,7 @@ import struct
import tarfile
import hashlib
from multiprocessing import Pool
+
from .utils import running_mean, soft_cast_int
logger = logging.getLogger(__name__)
@@ -107,7 +108,14 @@ def _preprocess_mimosa(measurement):
def _preprocess_etlog(measurement):
setup = measurement["setup"]
- etlog = EnergyTraceLog(
+
+ energytrace_class = EnergyTraceWithBarcode
+ if measurement["sync_mode"] == "la":
+ energytrace_class = EnergyTraceWithLogicAnalyzer
+ elif measurement["sync_mode"] == "timer":
+ energytrace_class = EnergyTraceWithTimer
+
+ etlog = energytrace_class(
float(setup["voltage"]),
int(setup["state_duration"]),
measurement["transition_names"],
@@ -406,7 +414,7 @@ class RawData:
processed_data["error"] = "; ".join(processed_data["datasource_errors"])
return False
- # Note that the low-level parser (EnergyTraceLog) already checks
+ # Note that the low-level parser (EnergyTraceWithBarcode) already checks
# whether the transition count is correct
return True
@@ -909,6 +917,10 @@ class RawData:
new_filenames = list()
with tarfile.open(filename) as tf:
ptalog = self.ptalog
+ if "sync" in ptalog["opt"]["energytrace"]:
+ sync_mode = ptalog["opt"]["energytrace"]["sync"]
+ else:
+ sync_mode = "bar"
# Benchmark code may be too large to be executed in a single
# run, so benchmarks (a benchmark is basically a list of DFA runs)
@@ -963,13 +975,16 @@ class RawData:
"state_duration": ptalog["opt"]["sleep"],
}
)
- for repeat_id, etlog_file in enumerate(ptalog["files"][j]):
- member = tf.getmember(etlog_file)
+ for repeat_id, etlog_files in enumerate(ptalog["files"][j]):
+ members = list(map(tf.getmember, etlog_files))
offline_data.append(
{
- "content": tf.extractfile(member).read(),
+ "content": list(
+ map(lambda f: tf.extractfile(f).read(), members)
+ ),
+ "sync_mode": sync_mode,
"fileno": j,
- "info": member,
+ "info": members[0],
"setup": self.setup_by_fileno[j],
"repeat_id": repeat_id,
"expected_trace": ptalog["traces"][j],
@@ -1161,7 +1176,7 @@ def pta_trace_to_aggregate(traces, ignore_trace_indexes=[]):
return by_name, parameter_names, arg_count
-class EnergyTraceLog:
+class EnergyTraceWithBarcode:
"""
EnergyTrace log loader for DFA traces.
@@ -1184,7 +1199,7 @@ class EnergyTraceLog:
with_traces=False,
):
"""
- Create a new EnergyTraceLog object.
+ Create a new EnergyTraceWithBarcode object.
:param voltage: supply voltage [V], usually 3.3 V
:param state_duration: state duration [ms]
@@ -1226,7 +1241,7 @@ class EnergyTraceLog:
)
return list()
- lines = log_data.decode("ascii").split("\n")
+ lines = log_data[0].decode("ascii").split("\n")
data_count = sum(map(lambda x: len(x) > 0 and x[0] != "#", lines))
data_lines = filter(lambda x: len(x) > 0 and x[0] != "#", lines)
@@ -1599,6 +1614,149 @@ class EnergyTraceLog:
return None, None, None, None
+class EnergyTraceWithLogicAnalyzer:
+ def __init__(
+ self,
+ voltage: float,
+ state_duration: int,
+ transition_names: list,
+ with_traces=False,
+ ):
+
+ """
+ Create a new EnergyTraceWithLogicAnalyzer object.
+
+ :param voltage: supply voltage [V], usually 3.3 V
+ :param state_duration: state duration [ms]
+ :param transition_names: list of transition names in PTA transition order.
+ Needed to map barcode synchronization numbers to transitions.
+ """
+ self.voltage = voltage
+ self.state_duration = state_duration * 1e-3
+ self.transition_names = transition_names
+ self.with_traces = with_traces
+ self.errors = list()
+
+ def load_data(self, log_data):
+ from data.timing.SigrokInterface import SigrokResult
+ from data.energy.EnergyInterface import EnergyInterface
+
+ # Daten laden
+ self.sync_data = SigrokResult.fromString(log_data[0])
+ self.energy_data = EnergyInterface.getDataFromString(str(log_data[1]))
+
+ pass
+
+ def analyze_states(self, traces, offline_index: int):
+ u"""
+ Split log data into states and transitions and return duration, energy, and mean power for each element.
+
+ :param traces: expected traces, needed to synchronize with the measurement.
+ traces is a list of runs, traces[*]['trace'] is a single run
+ (i.e. a list of states and transitions, starting with a transition
+ and ending with a state).
+ :param offline_index: This function uses traces[*]['trace'][*]['online_aggregates']['duration'][offline_index] to find sync codes
+
+ :param charges: raw charges (each element describes the charge in pJ transferred during 10 µs)
+ :param trigidx: "charges" indexes corresponding to a trigger edge, see `trigger_edges`
+ :param ua_func: charge(pJ) -> current(µA) function as returned by `calibration_function`
+
+ :returns: returns list of states and transitions, starting with a transition and ending with astate
+ Each element is a dict containing:
+ * `isa`: 'state' or 'transition'
+ * `W_mean`: Mittelwert der Leistungsaufnahme
+ * `W_std`: Standardabweichung der Leistungsaufnahme
+ * `s`: Dauer
+ if isa == 'transition, it also contains:
+ * `W_mean_delta_prev`: Differenz zwischen W_mean und W_mean des vorherigen Zustands
+ * `W_mean_delta_next`: Differenz zwischen W_mean und W_mean des Folgezustands
+ """
+
+ names = []
+ for trace_number, trace in enumerate(traces):
+ for state_or_transition in trace["trace"]:
+ names.append(state_or_transition["name"])
+ # print(names[:15])
+ from data.processing.DataProcessor import DataProcessor
+
+ dp = DataProcessor(sync_data=self.sync_data, energy_data=self.energy_data)
+ dp.run()
+ energy_trace_new = list()
+ energy_trace_new.extend(dp.getStatesdfatool(state_sleep=self.state_duration))
+ dp.plot()
+ # dp.plot(names)
+ energy_trace_new = energy_trace_new[4:]
+
+ energy_trace = list()
+ expected_transitions = list()
+
+ # Print for debug purposes
+ # for number, name in enumerate(names):
+ # if "P15_8MW" in name:
+ # print(name, energy_trace_new[number]["W_mean"])
+
+ # add next/prev state W_mean_delta
+ for number, item in enumerate(energy_trace_new):
+ if item["isa"] == "transition" and 0 < number < len(energy_trace_new) - 1:
+ item["W_mean_delta_prev"] = energy_trace_new[number - 1]
+ item["W_mean_delta_next"] = energy_trace_new[number + 1]
+
+ # st = ""
+ # for i, x in enumerate(energy_trace_new[-10:]):
+ # #st += "(%s|%s|%s)" % (energy_trace[i-10]["name"],x['W_mean'],x['s'])
+ # st += "(%s|%s|%s)\n" % (energy_trace[i-10]["s"], x['s'], x['W_mean'])
+
+ # print(st, "\n_______________________")
+ # print(len(self.sync_data.timestamps), " - ", len(energy_trace_new), " - ", len(energy_trace), " - ", ",".join([str(x["s"]) for x in energy_trace_new[-6:]]), " - ", ",".join([str(x["s"]) for x in energy_trace[-6:]]))
+ # if len(energy_trace_new) < len(energy_trace):
+ # return None
+
+ return energy_trace_new
+
+
+class EnergyTraceWithTimer(EnergyTraceWithLogicAnalyzer):
+ def __init__(
+ self,
+ voltage: float,
+ state_duration: int,
+ transition_names: list,
+ with_traces=False,
+ ):
+
+ """
+ Create a new EnergyTraceWithLogicAnalyzer object.
+
+ :param voltage: supply voltage [V], usually 3.3 V
+ :param state_duration: state duration [ms]
+ :param transition_names: list of transition names in PTA transition order.
+ Needed to map barcode synchronization numbers to transitions.
+ """
+
+ self.voltage = voltage
+ self.state_duration = state_duration * 1e-3
+ self.transition_names = transition_names
+ self.with_traces = with_traces
+ self.errors = list()
+
+ super().__init__(voltage, state_duration, transition_names, with_traces)
+
+ def load_data(self, log_data):
+ from data.timing.SigrokInterface import SigrokResult
+ from data.energy.EnergyInterface import EnergyInterface
+
+ # Daten laden
+ self.sync_data = None
+ self.energy_data = EnergyInterface.getDataFromString(str(log_data[0]))
+
+ pass
+
+ def analyze_states(self, traces, offline_index: int):
+ from data.timing.SigrokInterface import SigrokResult
+
+ self.sync_data = SigrokResult.fromTraces(traces)
+ return super().analyze_states(traces, offline_index)
+
+
class MIMOSA:
"""
MIMOSA log loader for DFA traces with auto-calibration.
diff --git a/lib/runner.py b/lib/runner.py
index 71ca799..96627cf 100644
--- a/lib/runner.py
+++ b/lib/runner.py
@@ -9,7 +9,7 @@ Functions:
get_monitor -- return Monitor class suitable for the selected multipass arch
get_counter_limits -- return arch-specific multipass counter limits (max value, max overflow)
"""
-
+import json
import os
import re
import serial
@@ -17,6 +17,7 @@ import serial.threaded
import subprocess
import sys
import time
+from data.timing.SigrokCLIInterface import SigrokCLIInterface
class SerialReader(serial.threaded.Protocol):
@@ -156,6 +157,7 @@ class EnergyTraceMonitor(SerialMonitor):
self._start_energytrace()
def _start_energytrace(self):
+ print("[%s] Starting Measurement" % type(self).__name__)
cmd = ["msp430-etv", "--save", self._output, "0"]
self._logger = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
@@ -166,17 +168,19 @@ class EnergyTraceMonitor(SerialMonitor):
super().close()
self._logger.send_signal(subprocess.signal.SIGINT)
stdout, stderr = self._logger.communicate(timeout=15)
+ print("[%s] Stopped Measurement" % type(self).__name__)
# Zusätzliche Dateien, die mit dem Benchmark-Log und -Plan abgespeichert werden sollen
# (hier: Die von msp430-etv generierten Logfiles)
def get_files(self) -> list:
+ print("[%s] Getting files" % type(self).__name__)
return [self._output]
- #
+ # Benchmark-Konfiguration. Hier: Die (konstante) Spannung.
+ # MSP430FR5969: 3,6V (wird aktuell nicht unterstützt)
+ # MSP430FR5994: 3,3V (default)
def get_config(self) -> dict:
- return {
- "voltage": self._voltage,
- }
+ return {"voltage": self._voltage}
class EnergyTraceLogicAnalyzerMonitor(EnergyTraceMonitor):
@@ -185,6 +189,35 @@ class EnergyTraceLogicAnalyzerMonitor(EnergyTraceMonitor):
def __init__(self, port: str, baud: int, callback=None, voltage=3.3):
super().__init__(port=port, baud=baud, callback=callback, voltage=voltage)
+ # TODO Max length
+ options = {"length": 90, "fake": False, "sample_rate": 1_000_000}
+ self.log_file = "logic_output_log_%s.json" % (time.strftime("%Y%m%d-%H%M%S"))
+
+ # Initialization of Interfaces
+ self.sig = SigrokCLIInterface(
+ sample_rate=options["sample_rate"],
+ sample_count=options["length"] * options["sample_rate"],
+ fake=options["fake"],
+ )
+
+ # Start Measurements
+ self.sig.runMeasureAsynchronous()
+
+ def close(self):
+ super().close()
+ # Read measured data
+ # self.sig.waitForAsynchronousMeasure()
+ self.sig.forceStopMeasure()
+ time.sleep(0.2)
+ sync_data = self.sig.getData()
+ with open(self.log_file, "w") as fp:
+ json.dump(sync_data.getDict(), fp)
+
+ def get_files(self) -> list:
+ files = [self.log_file]
+ files.extend(super().get_files())
+ return files
+
class MIMOSAMonitor(SerialMonitor):
"""MIMOSAMonitor captures serial output and MIMOSA energy data for a specific amount of time."""
@@ -261,11 +294,7 @@ class MIMOSAMonitor(SerialMonitor):
return [self.mim_file]
def get_config(self) -> dict:
- return {
- "offset": self._offset,
- "shunt": self._shunt,
- "voltage": self._voltage,
- }
+ return {"offset": self._offset, "shunt": self._shunt, "voltage": self._voltage}
class ShellMonitor: