aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJ08nY2020-02-04 20:02:22 +0100
committerJ08nY2020-02-04 20:02:22 +0100
commit2c4b3d2c50c6acff1e5db988183040c0f616054a (patch)
tree603246bea3f833d8b3dc9e72dfcd37fc49f79164
parent228dfa8c785b8b9ae47ac1712f5fef71cd1e5c90 (diff)
downloadpyecsca-2c4b3d2c50c6acff1e5db988183040c0f616054a.tar.gz
pyecsca-2c4b3d2c50c6acff1e5db988183040c0f616054a.tar.bz2
pyecsca-2c4b3d2c50c6acff1e5db988183040c0f616054a.zip
Add support for PicoScope scopes, sketch out scope interface.
-rw-r--r--pyecsca/ec/formula.py29
-rw-r--r--pyecsca/sca/__init__.py14
-rw-r--r--pyecsca/sca/scope/__init__.py11
-rw-r--r--pyecsca/sca/scope/base.py5
-rw-r--r--pyecsca/sca/scope/chipwhisperer.py5
-rw-r--r--pyecsca/sca/scope/picoscope.py232
-rw-r--r--pyecsca/sca/target/__init__.py0
-rw-r--r--pyecsca/sca/trace/__init__.py9
-rw-r--r--pyecsca/sca/trace/align.py (renamed from pyecsca/sca/align.py)0
-rw-r--r--pyecsca/sca/trace/combine.py (renamed from pyecsca/sca/combine.py)0
-rw-r--r--pyecsca/sca/trace/edit.py (renamed from pyecsca/sca/edit.py)0
-rw-r--r--pyecsca/sca/trace/filter.py (renamed from pyecsca/sca/filter.py)0
-rw-r--r--pyecsca/sca/trace/match.py (renamed from pyecsca/sca/match.py)0
-rw-r--r--pyecsca/sca/trace/process.py (renamed from pyecsca/sca/process.py)0
-rw-r--r--pyecsca/sca/trace/sampling.py (renamed from pyecsca/sca/sampling.py)0
-rw-r--r--pyecsca/sca/trace/test.py (renamed from pyecsca/sca/test.py)0
-rw-r--r--pyecsca/sca/trace/trace.py (renamed from pyecsca/sca/trace.py)0
-rw-r--r--pyecsca/sca/trace_set/__init__.py3
-rw-r--r--setup.py2
19 files changed, 298 insertions, 12 deletions
diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula.py
index 8e09f71..2da6ad5 100644
--- a/pyecsca/ec/formula.py
+++ b/pyecsca/ec/formula.py
@@ -1,4 +1,4 @@
-from ast import parse, Expression
+from ast import parse, Expression, Mult, Add, Sub, Pow, Div
from typing import List, Set, Any, ClassVar, MutableMapping
from itertools import product
@@ -25,6 +25,7 @@ class Formula(object):
@property
def input_index(self):
+ """The starting index where this formula reads its inputs."""
raise NotImplementedError
@property
@@ -34,12 +35,38 @@ class Formula(object):
@property
def inputs(self) -> Set[str]:
+ """The input variables of the formula."""
raise NotImplementedError
@property
def outputs(self) -> Set[str]:
+ """The output variables of the formula."""
raise NotImplementedError
+ @property
+ def num_operations(self) -> int:
+ """Number of operations."""
+ return len(list(filter(lambda op: op.operator is not None, self.code)))
+
+ @property
+ def num_multiplications(self) -> int:
+ """Number of multiplications."""
+ return len(list(filter(lambda op: isinstance(op.operator, Mult), self.code)))
+
+ @property
+ def num_inversions(self) -> int:
+ """Number of inversions."""
+ return len(list(filter(lambda op: isinstance(op.operator, Div), self.code)))
+
+ @property
+ def num_squarings(self) -> int:
+ """Number of squarings."""
+ return len(list(filter(lambda op: isinstance(op.operator, Pow), self.code)))
+
+ @property
+ def num_addsubs(self) -> int:
+ """Number of additions and subtractions."""
+ return len(list(filter(lambda op: isinstance(op.operator, (Add, Sub)), self.code)))
class EFDFormula(Formula):
diff --git a/pyecsca/sca/__init__.py b/pyecsca/sca/__init__.py
index 6a6b163..4e5c985 100644
--- a/pyecsca/sca/__init__.py
+++ b/pyecsca/sca/__init__.py
@@ -1,12 +1,4 @@
-from .align import *
-from .combine import *
-from .edit import *
-from .filter import *
-from .match import *
-from .process import *
-from .sampling import *
-from .test import *
+from .scope import *
+from .target import *
from .trace import *
-from .trace_set.base import *
-from .trace_set.inspector import *
-from .trace_set.chipwhisperer import *
+from .trace_set import *
diff --git a/pyecsca/sca/scope/__init__.py b/pyecsca/sca/scope/__init__.py
new file mode 100644
index 0000000..25cd0ca
--- /dev/null
+++ b/pyecsca/sca/scope/__init__.py
@@ -0,0 +1,11 @@
+try:
+ import picosdk
+ from .picoscope import *
+except ImportError:
+ pass
+
+try:
+ import chipwhisperer
+ from .chipwhisperer import *
+except ImportError:
+ pass
diff --git a/pyecsca/sca/scope/base.py b/pyecsca/sca/scope/base.py
new file mode 100644
index 0000000..3bade40
--- /dev/null
+++ b/pyecsca/sca/scope/base.py
@@ -0,0 +1,5 @@
+
+
+class Scope(object):
+ """An oscilloscope."""
+ pass
diff --git a/pyecsca/sca/scope/chipwhisperer.py b/pyecsca/sca/scope/chipwhisperer.py
new file mode 100644
index 0000000..2eb2747
--- /dev/null
+++ b/pyecsca/sca/scope/chipwhisperer.py
@@ -0,0 +1,5 @@
+from .base import Scope
+
+class ChipWhispererScope(Scope):
+ """A ChipWhisperer based scope."""
+ pass \ No newline at end of file
diff --git a/pyecsca/sca/scope/picoscope.py b/pyecsca/sca/scope/picoscope.py
new file mode 100644
index 0000000..f9f34f2
--- /dev/null
+++ b/pyecsca/sca/scope/picoscope.py
@@ -0,0 +1,232 @@
+import ctypes
+from enum import IntEnum
+from math import log2, floor
+from typing import Mapping, Optional, MutableMapping, Union
+
+import numpy as np
+from picosdk.functions import assert_pico_ok
+from picosdk.library import Library
+from picosdk.ps4000 import ps4000
+from picosdk.ps6000 import ps6000
+from public import public
+
+from .base import Scope
+
+
+class TriggerType(IntEnum):
+ ABOVE = 1
+ BELOW = 2
+ RISING = 3
+ FALLING = 4
+
+
+def adc2volt(adc: Union[np.ndarray, ctypes.c_int16], volt_range: float, adc_minmax: int) -> Union[
+ np.ndarray, float]:
+ if isinstance(adc, ctypes.c_int16):
+ adc = adc.value
+ return (adc / adc_minmax) * volt_range
+
+
+def volt2adc(volt: Union[np.ndarray, float], volt_range: float, adc_minmax: int) -> Union[
+ np.ndarray, ctypes.c_int16]:
+ if isinstance(volt, float):
+ return ctypes.c_int16(int((volt / volt_range) * adc_minmax))
+ return (volt / volt_range) * adc_minmax
+
+
+class PicoScope(Scope):
+ """A PicoScope based scope."""
+ MODULE: Library
+ PREFIX: str
+ CHANNELS: Mapping
+ RANGES: Mapping
+ MAX_ADC_VALUE: int
+ MIN_ADC_VALUE: int
+ COUPLING: Mapping
+ TIME_UNITS: Mapping
+
+ def __init__(self):
+ self.handle: ctypes.c_int16 = ctypes.c_int16()
+ self.frequency: Optional[int] = None
+ self.samples: Optional[int] = None
+ self.timebase: Optional[int] = None
+ self.buffers: MutableMapping = {}
+ self.ranges: MutableMapping = {}
+
+ def open(self):
+ assert_pico_ok(self.__dispatch_call("OpenUnit", ctypes.byref(self.handle)))
+
+ # channel setup (ranges, coupling, which channel is scope vs trigger)
+ def set_channel(self, channel: str, enabled: bool, coupling: str, range: float):
+ assert_pico_ok(
+ self.__dispatch_call("SetChannel", self.handle, self.CHANNELS[channel], enabled,
+ self.COUPLING[coupling], self.RANGES[range]))
+ self.ranges[channel] = range
+
+ # frequency setup
+ def set_frequency(self, frequency: int, samples: int):
+ period = 1 / frequency
+ if period <= 3.2e-9:
+ tb = log2(5_000_000_000) - log2(frequency)
+ tb = floor(tb)
+ if tb > 4:
+ tb = 4
+ actual_frequency = 5_000_000_000 / 2 ** tb
+ else:
+ tb = floor(156_250_000 / frequency + 4)
+ actual_frequency = 156_250_000 / (tb - 4)
+ max_samples = ctypes.c_int32()
+ assert_pico_ok(self.__dispatch_call("GetTimebase", self.handle, tb, samples, None, 0,
+ ctypes.byref(max_samples), 0))
+ if max_samples.value < samples:
+ samples = max_samples
+ self.frequency = actual_frequency
+ self.samples = samples
+ self.timebase = tb
+ return actual_frequency, samples
+
+ # triggering setup
+ def set_trigger(self, type: TriggerType, enabled: bool, value: float, channel: str,
+ range: float, delay: int, timeout: int):
+ assert_pico_ok(
+ self.__dispatch_call("SetSimpleTrigger", self.handle, enabled,
+ self.CHANNELS[channel],
+ volt2adc(value, range, self.MAX_ADC_VALUE),
+ type.value, delay, timeout))
+
+ # buffer setup
+ def set_buffer(self, channel: str):
+ if self.samples is None:
+ raise ValueError
+ buffer = (ctypes.c_int16 * self.samples)()
+ self.buffers[channel] = buffer
+ assert_pico_ok(self.__dispatch_call("SetDataBuffer", self.handle, self.CHANNELS[channel],
+ ctypes.byref(buffer), self.samples))
+
+ # collection
+ def collect(self):
+ if self.samples is None or self.timebase is None:
+ raise ValueError
+ assert_pico_ok(
+ self.__dispatch_call("RunBlock", self.handle, 0, self.samples, self.timebase, 0,
+ None,
+ 0, None, None))
+ ready = ctypes.c_int16(0)
+ check = ctypes.c_int16(0)
+ while ready.value == check.value:
+ assert_pico_ok(self.__dispatch_call("IsReady", self.handle, ctypes.byref(ready)))
+
+ # get the data
+ def retrieve(self, channel: str):
+ if self.samples is None:
+ raise ValueError
+ actual_samples = ctypes.c_int32(self.samples)
+ overflow = ctypes.c_int16()
+ assert_pico_ok(
+ self.__dispatch_call("GetValues", self.handle, 0, ctypes.byref(actual_samples), 1,
+ 0, 0,
+ ctypes.byref(overflow)))
+ arr = np.array(self.buffers[channel], dtype=np.int16)
+ return adc2volt(arr, self.ranges[channel], self.MAX_ADC_VALUE)
+
+ # stop
+ def stop(self):
+ assert_pico_ok(self.__dispatch_call("Stop"))
+
+ def close(self):
+ assert_pico_ok(self.__dispatch_call("CloseUnit", self.handle))
+
+ def __dispatch_call(self, name, *args, **kwargs):
+ method = getattr(self.MODULE, self.PREFIX + name)
+ if method is None:
+ raise ValueError
+ return method(*args, **kwargs)
+
+
+@public
+class PS4000Scope(PicoScope):
+ MODULE = ps4000
+ PREFIX = "ps4000"
+ CHANNELS = {
+ "A": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_A"],
+ "B": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_B"],
+ "C": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_C"],
+ "D": ps4000.PS4000_CHANNEL["PS4000_CHANNEL_D"]
+ }
+
+ RANGES = {
+ 0.01: ps4000.PS4000_RANGE["PS4000_10MV"],
+ 0.02: ps4000.PS4000_RANGE["PS4000_20MV"],
+ 0.05: ps4000.PS4000_RANGE["PS4000_50MV"],
+ 0.10: ps4000.PS4000_RANGE["PS4000_100MV"],
+ 0.20: ps4000.PS4000_RANGE["PS4000_200MV"],
+ 0.50: ps4000.PS4000_RANGE["PS4000_500MV"],
+ 1.00: ps4000.PS4000_RANGE["PS4000_1V"],
+ 2.00: ps4000.PS4000_RANGE["PS4000_2V"],
+ 5.00: ps4000.PS4000_RANGE["PS4000_5V"],
+ 10.0: ps4000.PS4000_RANGE["PS4000_10V"],
+ 20.0: ps4000.PS4000_RANGE["PS4000_20V"],
+ 50.0: ps4000.PS4000_RANGE["PS4000_50V"],
+ 100.0: ps4000.PS4000_RANGE["PS4000_100V"]
+ }
+
+ MAX_ADC_VALUE = 32764
+ MIN_ADC_VALUE = -32764
+
+ COUPLING = {
+ "AC": ps4000.PICO_COUPLING["AC"],
+ "DC": ps4000.PICO_COUPLING["DC"]
+ }
+
+
+@public
+class PS6000Scope(PicoScope):
+ MODULE = ps6000
+ PREFIX = "ps6000"
+ CHANNELS = {
+ "A": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_A"],
+ "B": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_B"],
+ "C": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_C"],
+ "D": ps6000.PS6000_CHANNEL["PS6000_CHANNEL_D"]
+ }
+
+ RANGES = {
+ 0.01: ps6000.PS6000_RANGE["PS6000A_10MV"],
+ 0.02: ps6000.PS6000_RANGE["PS6000_20MV"],
+ 0.05: ps6000.PS6000_RANGE["PS6000_50MV"],
+ 0.10: ps6000.PS6000_RANGE["PS6000_100MV"],
+ 0.20: ps6000.PS6000_RANGE["PS6000_200MV"],
+ 0.50: ps6000.PS6000_RANGE["PS6000_500MV"],
+ 1.00: ps6000.PS6000_RANGE["PS6000_1V"],
+ 2.00: ps6000.PS6000_RANGE["PS6000_2V"],
+ 5.00: ps6000.PS6000_RANGE["PS6000_5V"],
+ 10.0: ps6000.PS6000_RANGE["PS6000_10V"],
+ 20.0: ps6000.PS6000_RANGE["PS6000_20V"],
+ 50.0: ps6000.PS6000_RANGE["PS6000_50V"]
+ }
+
+ MAX_ADC_VALUE = 32512
+ MIN_ADC_VALUE = -32512
+
+ COUPLING = {
+ "AC": ps6000.PS6000_COUPLING["PS6000_AC"],
+ "DC": ps6000.PS6000_COUPLING["PS6000_DC_1M"]
+ }
+
+ def open(self):
+ assert_pico_ok(ps6000.ps6000OpenUnit(ctypes.byref(self.handle), None))
+
+ def set_channel(self, channel: str, enabled: bool, coupling: str, range: str):
+ assert_pico_ok(ps6000.ps6000SetChannel(self.handle, self.CHANNELS[channel], enabled,
+ self.COUPLING[coupling], self.RANGES[range], 0,
+ ps6000.PS6000_BANDWIDTH_LIMITER["PS6000_BW_FULL"]))
+
+ def set_buffer(self, channel: str):
+ if self.samples is None:
+ raise ValueError
+ buffer = (ctypes.c_int16 * self.samples)()
+ self.buffers[channel] = buffer
+ assert_pico_ok(
+ ps6000.ps6000SetDataBuffer(self.handle, self.CHANNELS[channel],
+ ctypes.byref(buffer),
+ self.samples, 0))
diff --git a/pyecsca/sca/target/__init__.py b/pyecsca/sca/target/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pyecsca/sca/target/__init__.py
diff --git a/pyecsca/sca/trace/__init__.py b/pyecsca/sca/trace/__init__.py
new file mode 100644
index 0000000..62f2864
--- /dev/null
+++ b/pyecsca/sca/trace/__init__.py
@@ -0,0 +1,9 @@
+from .align import *
+from .combine import *
+from .edit import *
+from .filter import *
+from .match import *
+from .process import *
+from .sampling import *
+from .test import *
+from .trace import * \ No newline at end of file
diff --git a/pyecsca/sca/align.py b/pyecsca/sca/trace/align.py
index ff586ab..ff586ab 100644
--- a/pyecsca/sca/align.py
+++ b/pyecsca/sca/trace/align.py
diff --git a/pyecsca/sca/combine.py b/pyecsca/sca/trace/combine.py
index c484748..c484748 100644
--- a/pyecsca/sca/combine.py
+++ b/pyecsca/sca/trace/combine.py
diff --git a/pyecsca/sca/edit.py b/pyecsca/sca/trace/edit.py
index f01a0dc..f01a0dc 100644
--- a/pyecsca/sca/edit.py
+++ b/pyecsca/sca/trace/edit.py
diff --git a/pyecsca/sca/filter.py b/pyecsca/sca/trace/filter.py
index bc221ce..bc221ce 100644
--- a/pyecsca/sca/filter.py
+++ b/pyecsca/sca/trace/filter.py
diff --git a/pyecsca/sca/match.py b/pyecsca/sca/trace/match.py
index ba69026..ba69026 100644
--- a/pyecsca/sca/match.py
+++ b/pyecsca/sca/trace/match.py
diff --git a/pyecsca/sca/process.py b/pyecsca/sca/trace/process.py
index 8e983b2..8e983b2 100644
--- a/pyecsca/sca/process.py
+++ b/pyecsca/sca/trace/process.py
diff --git a/pyecsca/sca/sampling.py b/pyecsca/sca/trace/sampling.py
index 29dc251..29dc251 100644
--- a/pyecsca/sca/sampling.py
+++ b/pyecsca/sca/trace/sampling.py
diff --git a/pyecsca/sca/test.py b/pyecsca/sca/trace/test.py
index e192048..e192048 100644
--- a/pyecsca/sca/test.py
+++ b/pyecsca/sca/trace/test.py
diff --git a/pyecsca/sca/trace.py b/pyecsca/sca/trace/trace.py
index 5324a37..5324a37 100644
--- a/pyecsca/sca/trace.py
+++ b/pyecsca/sca/trace/trace.py
diff --git a/pyecsca/sca/trace_set/__init__.py b/pyecsca/sca/trace_set/__init__.py
index e69de29..517b3b7 100644
--- a/pyecsca/sca/trace_set/__init__.py
+++ b/pyecsca/sca/trace_set/__init__.py
@@ -0,0 +1,3 @@
+from .base import *
+from .inspector import *
+from .chipwhisperer import * \ No newline at end of file
diff --git a/setup.py b/setup.py
index 6fc1695..91ac056 100644
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,8 @@ setup(
"asn1crypto"
],
extras_require={
+ "picoscope": ["picosdk", "ctypes"],
+ "chipshiwperer": ["chipwhisperer"],
"typecheck": ["mypy"],
"test": ["nose2", "parameterized", "green", "coverage"]
}