summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/cycles_to_energy.py96
-rw-r--r--lib/data_parameters.py61
-rw-r--r--lib/size_to_radio_energy.py51
-rw-r--r--lib/utils.py22
4 files changed, 229 insertions, 1 deletions
diff --git a/lib/cycles_to_energy.py b/lib/cycles_to_energy.py
new file mode 100644
index 0000000..bfbac75
--- /dev/null
+++ b/lib/cycles_to_energy.py
@@ -0,0 +1,96 @@
+"""
+Convert CPU cycle count to energy.
+
+Contains classes for some embedded CPUs/MCUs. Given a configuration, each
+class can convert a cycle count to an energy consumption.
+"""
+
+def get_class(cpu_name):
+ """Return model class for cpu_name."""
+ if cpu_name == 'MSP430':
+ return MSP430
+ if cpu_name == 'ATMega168':
+ return ATMega168
+ if cpu_name == 'ATTiny88':
+ return ATTiny88
+
+def _param_list_to_dict(device, param_list):
+ param_dict = dict()
+ for i, parameter in enumerate(sorted(device.parameters.keys())):
+ param_dict[parameter] = param_list[i]
+ return param_dict
+
+class MSP430:
+ name = 'MSP430'
+ parameters = {
+ 'cpu_freq': [1e6, 4e6, 8e6, 12e6, 16e6],
+ 'memory' : ['unified', 'fram0', 'fram50', 'fram66', 'fram75', 'fram100', 'ram'],
+ 'voltage': [2.2, 3.0],
+ }
+ default_params = {
+ 'cpu_freq': 4e6,
+ 'memory' : 'unified',
+ 'voltage': 3
+ }
+
+ current_by_mem = {
+ 'unified' : [210, 640, 1220, 1475, 1845],
+ 'fram0' : [370, 1280, 2510, 2080, 2650],
+ 'fram50' : [240, 745, 1440, 1575, 1990],
+ 'fram66' : [200, 560, 1070, 1300, 1620],
+ 'fram75' : [170, 480, 890, 1155, 1420],
+ 'fram100' : [110, 235, 420, 640, 730],
+ 'ram' : [130, 320, 585, 890, 1070],
+ }
+
+ def get_current(params):
+ if type(params) != dict:
+ return MSP430.get_current(_param_list_to_dict(MSP430, params))
+ cpu_freq_index = MSP430.parameters['cpu_freq'].index(params['cpu_freq'])
+
+ return MSP430.current_by_mem[params['memory']][cpu_freq_index] * 1e-6
+
+class ATMega168:
+ name = 'ATMega168'
+ parameters = {
+ 'cpu_freq': [1e6, 4e6, 8e6],
+ 'voltage': [2, 3, 5]
+ }
+ default_params = {
+ 'cpu_freq': 4e6,
+ 'voltage': 3
+ }
+
+ def get_current(params):
+ if type(params) != dict:
+ return ATMega168.get_current(_param_list_to_dict(ATMega168, params))
+ if params['cpu_freq'] == 1e6 and params['voltage'] <= 2:
+ return 0.5e-3
+ if params['cpu_freq'] == 4e6 and params['voltage'] <= 3:
+ return 3.5e-3
+ if params['cpu_freq'] == 8e6 and params['voltage'] <= 5:
+ return 12e-3
+ return None
+
+class ATTiny88:
+ name = 'ATTiny88'
+ parameters = {
+ 'cpu_freq': [1e6, 4e6, 8e6],
+ 'voltage': [2, 3, 5]
+ }
+ default_params = {
+ 'cpu_freq' : 4e6,
+ 'voltage' : 3
+ }
+
+ def get_current(params):
+ if type(params) != dict:
+ return ATTiny88.get_current(_param_list_to_dict(ATTiny88, params))
+ if params['cpu_freq'] == 1e6 and params['voltage'] <= 2:
+ return 0.2e-3
+ if params['cpu_freq'] == 4e6 and params['voltage'] <= 3:
+ return 1.4e-3
+ if params['cpu_freq'] == 8e6 and params['voltage'] <= 5:
+ return 4.5e-3
+ return None
+
diff --git a/lib/data_parameters.py b/lib/data_parameters.py
index 4dde850..ef463bc 100644
--- a/lib/data_parameters.py
+++ b/lib/data_parameters.py
@@ -6,6 +6,7 @@ length of lists, ane more.
"""
from protocol_benchmarks import codegen_for_lib
+import cycles_to_energy, size_to_radio_energy, utils
import numpy as np
import ubjson
@@ -175,7 +176,7 @@ class Protolog:
['text_serdes', 'text_size_serdes', idem],
]
- def __init__(self, logfile):
+ def __init__(self, logfile, cpu_conf = None, cpu_conf_str = None, radio_conf = None, radio_conf_str = None):
"""
Load and enrich raw protobench log data.
@@ -267,6 +268,64 @@ class Protolog:
except KeyError:
pass
+ if cpu_conf_str:
+ cpu_conf = utils.parse_conf_str(cpu_conf_str)
+
+ if cpu_conf:
+ cpu = cycles_to_energy.get_class(cpu_conf['model'])
+ for key, value in cpu.default_params.items():
+ if not key in cpu_conf:
+ cpu_conf[key] = value
+ for key in self.aggregate.keys():
+ for arch in self.aggregate[key].keys():
+ for lib, val in self.aggregate[key][arch].items():
+ try:
+ val['energy_enc'] = int(val['cycles_enc'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+ try:
+ val['energy_ser'] = int(val['cycles_ser'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+ try:
+ val['energy_encser'] = int(val['cycles_encser'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+ try:
+ val['energy_des'] = int(val['cycles_des'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+ try:
+ val['energy_dec'] = int(val['cycles_dec'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+ try:
+ val['energy_desdec'] = int(val['cycles_desdec'] * cpu.get_current(cpu_conf) * cpu_conf['voltage'] / cpu_conf['cpu_freq'] * 1e9)
+ except KeyError:
+ pass
+
+ if radio_conf_str:
+ radio_conf = utils.parse_conf_str(radio_conf_str)
+
+ if radio_conf:
+ radio = size_to_radio_energy.get_class(radio_conf['model'])
+ for key, value in radio.default_params.items():
+ if not key in radio_conf:
+ radio_conf[key] = value
+ for key in self.aggregate.keys():
+ for arch in self.aggregate[key].keys():
+ for lib, val in self.aggregate[key][arch].items():
+ try:
+ radio_conf['txbytes'] = val['serialized_size']
+ if radio_conf['txbytes'] > 0:
+ val['energy_tx'] = int(radio.get_energy(radio_conf) * 1e9)
+ else:
+ val['energy_tx'] = 0
+ val['energy_encsertx'] = val['energy_encser'] + val['energy_tx']
+ except KeyError:
+ pass
+
+
def add_datapoint(self, arch, lib, key, value, aggregate_label, data_label, getter):
"""
Set self.aggregate[key][arch][lib][aggregate_Label] = getter(value[data_label]['v']).
diff --git a/lib/size_to_radio_energy.py b/lib/size_to_radio_energy.py
new file mode 100644
index 0000000..0b5cfd2
--- /dev/null
+++ b/lib/size_to_radio_energy.py
@@ -0,0 +1,51 @@
+"""
+Convert data length to radio TX/RX energy.
+
+Contains classes for some embedded CPUs/MCUs. Given a configuration, each
+class can convert a cycle count to an energy consumption.
+"""
+
+import numpy as np
+
+def get_class(radio_name: str):
+ """Return model class for radio_name."""
+ if radio_name == 'CC1200tx':
+ return CC1200tx
+
+def _param_list_to_dict(device, param_list):
+ param_dict = dict()
+ for i, parameter in enumerate(sorted(device.parameters.keys())):
+ param_dict[parameter] = param_list[i]
+ return param_dict
+
+class CC1200tx:
+ name = 'CC1200tx'
+ parameters = {
+ 'symbolrate' : [6, 12, 25, 50, 100, 200, 250], # ksps
+ 'txbytes' : [],
+ 'txpower' : [10, 20, 30, 40, 47], # dBm = f(txpower)
+ }
+ default_params = {
+ 'symbolrate' : 100,
+ 'txpower' : 47,
+ }
+
+ def get_energy(params):
+ if type(params) != dict:
+ return CC1200tx.get_energy(_param_list_to_dict(CC1200tx, params))
+
+ power = 8.18053941e+04
+ power -= 1.24208376e+03 * np.sqrt(params['symbolrate'])
+ power -= 5.73742779e+02 * np.log(params['txbytes'])
+ power += 1.76945886e+01 * (params['txpower'])**2
+ power += 2.33469617e+02 * np.sqrt(params['symbolrate']) * np.log(params['txbytes'])
+ power -= 6.99137635e-01 * np.sqrt(params['symbolrate']) * (params['txpower'])**2
+ power -= 3.31365158e-01 * np.log(params['txbytes']) * (params['txpower'])**2
+ power += 1.32784945e-01 * np.sqrt(params['symbolrate']) * np.log(params['txbytes']) * (params['txpower'])**2
+
+ duration = 3.65513500e+02
+ duration += 8.01016526e+04 * 1/(params['symbolrate'])
+ duration -= 7.06364515e-03 * params['txbytes']
+ duration += 8.00029860e+03 * 1/(params['symbolrate']) * params['txbytes']
+
+ return power * 1e-6 * duration * 1e-6
diff --git a/lib/utils.py b/lib/utils.py
index 64f1780..02c1d26 100644
--- a/lib/utils.py
+++ b/lib/utils.py
@@ -31,6 +31,21 @@ def float_or_nan(n):
except ValueError:
return np.nan
+def soft_cast_int(n):
+ """
+ Convert to int, if possible.
+
+ If it is empty, returns None.
+ If it is not numeric, it is left unchanged.
+ """
+ if n == None or n == '':
+ return None
+ try:
+ return int(n)
+ except ValueError:
+ return n
+
+
def flatten(somelist):
"""
Flatten a list.
@@ -39,6 +54,13 @@ def flatten(somelist):
"""
return [item for sublist in somelist for item in sublist]
+def parse_conf_str(conf_str):
+ conf_dict = dict()
+ for option in conf_str.split(','):
+ key, value = option.split(':')
+ conf_dict[key] = soft_cast_int(value)
+ return conf_dict
+
def param_slice_eq(a, b, index):
"""
Check if by_param keys a and b are identical, ignoring the parameter at index.