summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rwxr-xr-xbin/analyze-archive.py9
-rwxr-xr-xbin/generate-dfa-benchmark.py7
-rw-r--r--lib/harness.py38
-rw-r--r--lib/loader.py312
-rw-r--r--lib/runner.py36
6 files changed, 385 insertions, 18 deletions
diff --git a/.gitignore b/.gitignore
index 25b1be5..91b6250 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
*.pyc
/htmlcov/
/.coverage*
+.idea/
diff --git a/bin/analyze-archive.py b/bin/analyze-archive.py
index 5c7c97e..10fe304 100755
--- a/bin/analyze-archive.py
+++ b/bin/analyze-archive.py
@@ -104,6 +104,9 @@ Options:
--export-energymodel=<model.json>
Export energy model. Works out of the box for v1 and v2 logfiles. Requires --hwmodel for v0 logfiles.
+
+--no-cache
+ Do not load cached measurement results
"""
import getopt
@@ -304,7 +307,7 @@ if __name__ == "__main__":
try:
optspec = (
- "info "
+ "info no-cache "
"plot-unparam= plot-param= plot-traces= show-models= show-quality= "
"ignored-trace-indexes= discard-outliers= function-override= "
"export-traces= "
@@ -369,7 +372,9 @@ if __name__ == "__main__":
sys.exit(2)
raw_data = RawData(
- args, with_traces=("export-traces" in opt or "plot-traces" in opt)
+ args,
+ with_traces=("export-traces" in opt or "plot-traces" in opt),
+ skip_cache=("no-cache" in opt),
)
if "info" in opt:
diff --git a/bin/generate-dfa-benchmark.py b/bin/generate-dfa-benchmark.py
index 1410c28..6540702 100755
--- a/bin/generate-dfa-benchmark.py
+++ b/bin/generate-dfa-benchmark.py
@@ -393,7 +393,7 @@ def run_benchmark(
os.remove(filename)
harness.undo(i)
else:
- files.extend(monitor.get_files())
+ files.append(monitor.get_files())
i += 1
harness.restart()
@@ -636,8 +636,10 @@ if __name__ == "__main__":
elif "energytrace" in opt:
# Use barcode sync by default
gpio_mode = "bar"
+ energytrace_sync = None
if "sync" in opt["energytrace"] and opt["energytrace"]["sync"] != "bar":
gpio_mode = "around"
+ energytrace_sync = "led"
harness = OnboardTimerHarness(
gpio_pin=timer_pin,
gpio_mode=gpio_mode,
@@ -645,6 +647,7 @@ if __name__ == "__main__":
counter_limits=runner.get_counter_limits_us(opt["arch"]),
log_return_values=need_return_values,
repeat=1,
+ energytrace_sync=energytrace_sync,
)
elif "timing" in opt:
harness = OnboardTimerHarness(
@@ -677,7 +680,7 @@ if __name__ == "__main__":
"files": list(map(lambda x: x[3], results)),
"configs": list(map(lambda x: x[2].get_config(), results)),
}
- extra_files = flatten(json_out["files"])
+ extra_files = flatten(map(flatten, json_out["files"]))
if "instance" in pta.codegen:
output_prefix = (
opt["data"] + time.strftime("/%Y%m%d-%H%M%S-") + pta.codegen["instance"]
diff --git a/lib/harness.py b/lib/harness.py
index c251ec8..833807e 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
@@ -373,6 +376,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
@@ -400,13 +404,45 @@ class OnboardTimerHarness(TransitionHarness):
def global_code(self):
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 = "ptalog.passNop();\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
):
diff --git a/lib/loader.py b/lib/loader.py
index c35eb4c..531559f 100644
--- a/lib/loader.py
+++ b/lib/loader.py
@@ -107,7 +107,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"],
@@ -242,7 +249,7 @@ class RawData:
file system, making subsequent loads near-instant.
"""
- def __init__(self, filenames, with_traces=False):
+ def __init__(self, filenames, with_traces=False, skip_cache=False):
"""
Create a new RawData object.
@@ -321,7 +328,7 @@ class RawData:
self.pta = self.ptalog["pta"]
self.set_cache_file()
- if not with_traces:
+ if not with_traces and not skip_cache:
self.load_cache()
def set_cache_file(self):
@@ -406,7 +413,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 +916,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 +974,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 +1175,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 +1198,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 +1240,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 +1613,282 @@ 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
+ """
+
+ # Tatsächlich Daten auswerten
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ # remove Dirty Data from previously running program (happens if logic Analyzer Measurement starts earlier than
+ # the HW Reset from energytrace)
+ use_data_after_index = 0
+ for x in range(1, len(self.sync_data.timestamps)):
+ if self.sync_data.timestamps[x] - self.sync_data.timestamps[x - 1] > 1.3:
+ use_data_after_index = x
+ break
+
+ time_stamp_data = self.sync_data.timestamps[use_data_after_index:]
+
+ start_offset = None
+ start_timestamp = None
+ end_offset = None
+ end_timestamp = None
+
+ last_values = [] # maybe needed to flatten the data
+ plot_data_x = []
+ plot_data_y = []
+ last_data = [0, 0, 0, 0]
+
+ power_sync_watt = 0.0162
+
+ # MAIN ENERGY DATA ITERATION
+ for energytrace_dataset in self.energy_data:
+ usedtime = energytrace_dataset[0] - last_data[0]
+ usedenergy = energytrace_dataset[3] - last_data[3]
+ power = usedenergy / usedtime * 10 ** -3 # in watts
+
+ if power > 0:
+ if power > power_sync_watt and start_offset is None:
+ # automatic START offset calculation
+ start_offset = energytrace_dataset[0] / 1_000_000 - time_stamp_data[0]
+ start_timestamp = energytrace_dataset[0] / 1_000_000
+ if power > power_sync_watt and start_offset is not None: # TODO BETTER
+ # automatic END offset calculation (Drift)
+ end_offset = energytrace_dataset[0] / 1_000_000 - time_stamp_data[-1] - start_offset
+ end_timestamp = energytrace_dataset[0] / 1_000_000
+ if len(last_values) > 10:
+ last_values.pop(0)
+ last_values.append(power)
+ # print("%s \tUsed Energy: %i \tUsed Time: %i \tPower: %f \t l_10: %f" % (now_data[0], usedenergy, usedtime, power, sum(last_values) / len(last_values)))
+ plot_data_x.append(energytrace_dataset[0] / 1_000_000)
+ plot_data_y.append(power)
+
+ last_data = energytrace_dataset
+
+ # --------------------
+ # add start offset to the data
+ modified_timestamps_with_offset = []
+ for x in time_stamp_data:
+ if x + start_offset >= 0:
+ modified_timestamps_with_offset.append(x + start_offset)
+
+ # --------------------
+ # Add drift to datapoints
+ modified_timestamps_with_drift = []
+ endFactor = (end_timestamp + end_offset - start_timestamp) / (end_timestamp - start_timestamp)
+ for x in modified_timestamps_with_offset:
+ modified_timestamps_with_drift.append(((x - start_timestamp) * endFactor) + start_timestamp)
+
+ self.start_offset = 0
+ def getPowerBetween(start, end, base_power=0.001469):
+ first_index = 0
+ all_power = 0
+ all_power_count = 0
+ for ind in range(self.start_offset, len(plot_data_x)):
+ first_index = ind
+ if plot_data_x[ind] > start:
+ break
+
+ for ind in range(first_index, len(plot_data_x)):
+ all_power += plot_data_y[ind]
+ all_power_count += 1
+ if plot_data_x[ind] > end:
+ self.start_offset = ind - 1
+ break
+ return (all_power / all_power_count) - base_power
+
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+ energy_trace = list()
+
+ expected_transitions = list()
+ for trace_number, trace in enumerate(traces):
+ for state_or_transition_number, state_or_transition in enumerate(
+ trace["trace"]
+ ):
+ if state_or_transition["isa"] == "transition":
+ try:
+ expected_transitions.append(
+ (
+ state_or_transition["name"],
+ state_or_transition["online_aggregates"]["duration"][
+ offline_index
+ ]
+ * 1e-6,
+ )
+ )
+ except IndexError:
+ self.errors.append(
+ 'Entry #{} ("{}") in trace #{} has no duration entry for offline_index/repeat_id {}'.format(
+ state_or_transition_number,
+ state_or_transition["name"],
+ trace_number,
+ offline_index,
+ )
+ )
+ return energy_trace
+
+
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+ end_transition_ts = None
+ timestamps_sync_start = 0
+ energy_trace_new = list()
+
+ for ts_index in range(0 + timestamps_sync_start, int(len(modified_timestamps_with_drift) / 2)):
+ start_transition_ts = modified_timestamps_with_drift[ts_index * 2]
+ if end_transition_ts is not None:
+ power = getPowerBetween(end_transition_ts, start_transition_ts)
+ #print("STATE", end_transition_ts * 10 ** 6, start_transition_ts * 10 ** 6, (start_transition_ts - end_transition_ts) * 10 ** 6, power)
+ if (start_transition_ts - end_transition_ts) * 10 ** 6 > 900_000 and power > power_sync_watt * 0.9 and ts_index > 10:
+ #remove last transition and stop (upcoming data only sync)
+ del energy_trace_new[-1]
+ break
+ pass
+
+ state = {
+ "isa": "state",
+ "W_mean": power,
+ "W_std": 0.0001,
+ "s": (start_transition_ts - end_transition_ts), #* 10 ** 6,
+ }
+ energy_trace_new.append(state)
+
+ energy_trace_new[-2]["W_mean_delta_next"] = (
+ energy_trace_new[-2]["W_mean"] - energy_trace_new[-1]["W_mean"]
+ )
+
+ # get energy end_transition_ts
+ end_transition_ts = modified_timestamps_with_drift[ts_index * 2 + 1]
+ power = getPowerBetween(start_transition_ts, end_transition_ts)
+ #print("TRANS", start_transition_ts * 10 ** 6, end_transition_ts * 10 ** 6, (end_transition_ts - start_transition_ts) * 10 ** 6, power)
+ transition = {
+ "isa": "transition",
+ "W_mean": power,
+ "W_std": 0.0001,
+ "s": (end_transition_ts - start_transition_ts), #* 10 ** 6,
+ }
+
+ if (end_transition_ts - start_transition_ts) * 10 ** 6 > 2_000_000:
+ # TODO Last data set corrupted? HOT FIX!!!!!!!!!!!! REMOVE LATER
+ #for x in range(4):
+ # del energy_trace_new[-1]
+ #break
+ pass
+
+ energy_trace_new.append(transition)
+ # print(start_transition_ts, "-", end_transition_ts, "-", end_transition_ts - start_transition_ts)
+
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+ energy_trace_new = energy_trace_new[4:]
+
+
+
+ for number, item in enumerate(expected_transitions):
+ name, duration = item
+ transition = {
+ "isa": "transition",
+ "W_mean": max(np.random.normal(0.023, 0.002), 0),
+ "W_std": 0.0001,
+ "s": duration,
+ "name": name,
+ }
+
+ energy_trace.append(transition)
+
+ if len(energy_trace) > 1:
+ energy_trace[-1]["W_mean_delta_prev"] = (
+ energy_trace[-1]["W_mean"] - energy_trace[-2]["W_mean"]
+ )
+
+ state = {
+ "isa": "state",
+ "W_mean": max(np.random.normal(0.023, 0.002), 0),
+ "W_std": 0.0001,
+ "s": self.state_duration,
+ "name": "state",
+ }
+
+ energy_trace.append(state)
+
+ energy_trace[-2]["W_mean_delta_next"] = (
+ energy_trace[-2]["W_mean"] - energy_trace[-1]["W_mean"]
+ )
+
+ st = ""
+ for i, x in enumerate(energy_trace_new[-10:]):
+ st += "(%s|%s|%s)" % (energy_trace[i-10]["name"],x['W_mean'],x['s'])
+
+ #print(st)
+ 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):
+ pass
+
+
class MIMOSA:
"""
MIMOSA log loader for DFA traces with auto-calibration.
diff --git a/lib/runner.py b/lib/runner.py
index aeb8600..3092d5a 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("EnergyTrace Start")
cmd = ["msp430-etv", "--save", self._output, "0"]
self._logger = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
@@ -163,6 +165,7 @@ class EnergyTraceMonitor(SerialMonitor):
# Benchmark fertig -> externe Hilfsprogramme beenden
def close(self):
+ print("EnergyTrace Close")
super().close()
self._logger.send_signal(subprocess.signal.SIGINT)
stdout, stderr = self._logger.communicate(timeout=15)
@@ -170,9 +173,12 @@ class EnergyTraceMonitor(SerialMonitor):
# 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("EnergyTrace Get Files")
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,
@@ -185,6 +191,32 @@ 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."""