summaryrefslogtreecommitdiff
path: root/lib/dfatool.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dfatool.py')
-rwxr-xr-xlib/dfatool.py126
1 files changed, 120 insertions, 6 deletions
diff --git a/lib/dfatool.py b/lib/dfatool.py
index 657a7b1..3ff38e0 100755
--- a/lib/dfatool.py
+++ b/lib/dfatool.py
@@ -79,7 +79,22 @@ def vprint(verbose, string):
return 1.
def gplearn_to_function(function_str):
-
+ """
+ Convert gplearn-style function string to Python function.
+
+ Takes a function string like "mul(add(X0, X1), X2)" and returns
+ a Python function implementing the specified behaviour,
+ e.g. "lambda x, y, z: (x + y) * z".
+
+ Supported functions:
+ add -- x + y
+ sub -- x - y
+ mul -- x * y
+ div -- x / y if |y| > 0.001, otherwise 1
+ sqrt -- sqrt(|x|)
+ log -- log(|x|) if |x| > 0.001, otherwise 0
+ inv -- 1 / x if |x| > 0.001, otherwise 0
+ """
eval_globals = {
'add' : lambda x, y : x + y,
'sub' : lambda x, y : x - y,
@@ -120,11 +135,7 @@ def append_if_set(aggregate, data, key):
aggregate.append(data[key])
def mean_or_none(arr):
- """
- Compute mean of NumPy array arr.
-
- Return -1 if arr is empty.
- """
+ """Compute mean of NumPy array arr, return -1 if empty."""
if len(arr):
return np.mean(arr)
return -1
@@ -197,11 +208,18 @@ def regression_measures(predicted, actual):
return measures
class KeysightCSV:
+ """Simple loader for Keysight CSV data, as exported by the windows software."""
def __init__(self):
+ """Create a new KeysightCSV object."""
pass
def load_data(self, filename):
+ """
+ Load log data from filename, return timestamps and currents.
+
+ Returns two one-dimensional NumPy arrays: timestamps and corresponding currents.
+ """
with open(filename) as f:
for i, l in enumerate(f):
pass
@@ -1203,8 +1221,24 @@ class EnergyModel:
class MIMOSA:
+ """
+ MIMOSA log loader for DFA traces with auto-calibration.
+
+ Expects a MIMOSA log file generated via dfatool and a dfatool-generated
+ benchmark: There is an automatic calibration step at the start and the
+ trigger pin is high iff a transition is active. The resulting data
+ is a list of state/transition/state/transition/... measurements.
+ """
def __init__(self, voltage, shunt, verbose = True):
+ """
+ Initialize MIMOSA loader for a specific voltage and shunt setting.
+
+ arguments:
+ voltage -- MIMOSA voltage used for measurements
+ shunt -- Shunt value in Ohms
+ verbose -- notify about invalid data and the likes
+ """
self.voltage = voltage
self.shunt = shunt
self.verbose = verbose
@@ -1212,6 +1246,7 @@ class MIMOSA:
self.r2 = 99013 # "100k"
def charge_to_current_nocal(self, charge):
+ u"""Convert charge per 10µs to mean currents without accounting for calibration."""
ua_max = 1.836 / self.shunt * 1000000
ua_step = ua_max / 65535
return charge * ua_step
@@ -1232,20 +1267,32 @@ class MIMOSA:
def load_data(self, raw_data):
+ """Load a MIMOSA log archive from a raw bytestring."""
with io.BytesIO(raw_data) as data_object:
with tarfile.open(fileobj = data_object) as tf:
return self._load_tf(tf)
def load_file(self, filename):
+ """Load a MIMOSA log archive from a filename."""
with tarfile.open(filename) as tf:
return self._load_tf(tf)
def currents_nocal(self, charges):
+ u"""Convert charge per 10µs to mean currents without accounting for calibration."""
ua_max = 1.836 / self.shunt * 1000000
ua_step = ua_max / 65535
return charges.astype(np.double) * ua_step
def trigger_edges(self, triggers):
+ """
+ Return indexes of trigger edges (both 0->1 and 1->0) in log data.
+
+ arguments:
+ triggers -- trigger array as returned by load_data
+
+ Ignores the first 10 seconds, which are used for calibration and may
+ contain bogus triggers due to DUT resets. Returns a list of int.
+ """
trigidx = []
prevtrig = triggers[0]
# the device is reset for MIMOSA calibration in the first 10s and may
@@ -1260,6 +1307,22 @@ class MIMOSA:
return trigidx
def calibration_edges(self, currents):
+ """
+ Return start/stop indexes of calibration measurements.
+
+ arguments:
+ currents -- uncalibrated currents as reported by MIMOSA. For best results,
+ it may help to use a running mean, like so:
+ currents = running_mean(currents_nocal(..., 10))
+
+ Returns six indexes:
+ - Disconnected start
+ - Disconnected stop
+ - R1 (1 kOhm) start
+ - R1 (1 kOhm) stop
+ - R2 (100 kOhm) start
+ - R2 (100 kOhm) stop
+ """
r1idx = 0
r2idx = 0
ua_r1 = self.voltage / self.r1 * 1000000
@@ -1274,6 +1337,32 @@ class MIMOSA:
return r1idx - 180500, r1idx - 500, r1idx + 500, r2idx - 500, r2idx + 500, r2idx + 180500
def calibration_function(self, charges, cal_edges):
+ u"""
+ Calculate calibration function from previously determined calibration phase.
+
+ arguments:
+ charges -- raw charges from MIMOSA
+ cal_edges -- calibration edges as returned by calibration_edges
+
+ returns (calibration_function, calibration_data):
+ calibration_function -- charge in pJ (float) -> current in uA (float).
+ Converts the amount of charge in a 10 µs interval to the
+ mean current during the same interval.
+ calibration_data -- dict containing the following keys:
+ edges -- calibration points in the log file, in µs
+ offset -- ...
+ offset2 -- ...
+ slope_low -- ...
+ slope_high -- ...
+ add_low -- ...
+ add_high -- ..
+ r0_err_uW -- mean error of uncalibrated data at "∞ Ohm" in µW
+ r0_std_uW -- standard deviation of uncalibrated data at "∞ Ohm" in µW
+ r1_err_uW -- mean error of uncalibrated data at 1 kOhm
+ r1_std_uW -- stddev at 1 kOhm
+ r2_err_uW -- mean error at 100 kOhm
+ r2_std_uW -- stddev at 100 kOhm
+ """
dis_start, dis_end, r1_start, r1_end, r2_start, r2_end = cal_edges
if dis_start < 0:
dis_start = 0
@@ -1341,6 +1430,7 @@ class MIMOSA:
return calfunc, caldata
+ """
def calcgrad(self, currents, threshold):
grad = np.gradient(running_mean(currents * self.voltage, 10))
# len(grad) == len(currents) - 9
@@ -1382,8 +1472,32 @@ class MIMOSA:
threshold = np.mean([gradmin, gradmax])
gradidx = self.calcgrad(currents, threshold)
return threshold, gradidx
+ """
def analyze_states(self, charges, trigidx, ua_func):
+ u"""
+ Split log data into states and transitions and return mean power and duration for each element.
+
+ arguments:
+ charges -- raw charges (each element describes the charge transferred during 10 µs)
+ trigidx -- "charges" indexes corresponding to a trigger edge
+ ua_func -- charge -> current function as returned by calibration_function
+
+ returns a list of (alternating) states and transitions.
+ Each element is a dict containing:
+ - isa: 'state' oder 'transition'
+ - clip_rate: range(0..1) Anteil an Clipping im Energieverbrauch
+ - raw_mean: Mittelwert der Rohwerte
+ - raw_std: Standardabweichung der Rohwerte
+ - uW_mean: Mittelwert der (kalibrierten) Leistungsaufnahme
+ - uW_std: Standardabweichung der (kalibrierten) Leistungsaufnahme
+ - us: Dauer
+
+ Nur falls isa == 'transition':
+ - timeout: Dauer des vorherigen Zustands
+ - uW_mean_delta_prev: Differenz zwischen uW_mean und uW_mean des vorherigen Zustands
+ - uW_mean_delta_next: Differenz zwischen uW_mean und uW_mean des Folgezustands
+ """
previdx = 0
is_state = True
iterdata = []