Module: secbench.api
¶
The secbench.api
module contains common definitions used across
secbench packages. The primary goal of the package is to define interfaces of
all instruments relevant for hardware security characterization such as
side-channel analysis and fault injections. But the package other important things:
A convenient discovery mechanism for available hardware. This mechanism is configurable and allows loading instruments without hard-coding them in scripts (TTY paths, serial numbers, etc.).
A
Bench
that manages all equipment for experiments.Helpers to implement drivers in few lines of codes for lab equipments, though serial, Ethernet, USB.
Exceptions¶
Most errors that occur in the internal layers of secbench are subclasses of
SecbenchError
.
- exception SecbenchError¶
Base exception for secbench related operations.
- exception NoSuchHardwareError¶
Exception raised when no device of the requested hardware type is available.
- __init__(hw_class, what='')¶
- exception MissingDependency¶
A dependency (e.g., a Python package) is missing for using a feature.
- exception UnsupportedFeature¶
Exception raised when attempting to use a feature that is not supported.
- __init__(pkg_name, usage='')¶
- exception InvalidParameter¶
Invalid parameters requested to an instrument.
Bench¶
A Bench
allows discovery and instantiation of hardware.
You can create a Bench
instance by calling the default constructor
without arguments:
from secbench.api import Bench
bench = Bench()
In security tests, unless you have to run multiple experiments in parallel in
the same scripts, it is recommended to use the get_bench()
function to
create a bench. It returns a common bench instance, acting as a global
variable.
from secbench.api import get_bench
bench = get_bench()
The Bench
class provides methods for requesting different type of
hardware. Note that by default, a bench will cache the components loaded, so
that hardware discovery is not ran every time.
from secbench.api import get_bench
from secbench.api.instrument import Scope
bench = get_bench()
scope = bench.get_scope() # Slow the first time.
scope = bench.get_scope() # Fast: a scope is already in the bench.
scope = bench.get(Scope) # Fast: same as the previous line.
More details on this topic are available in the tutorial Bench and Device Discovery.
- class Bench¶
A class that manages instruments during experiments.
- Parameters:
initial_scan – if
True
, performs an initial scan of all hardware connected. This is disabled by default to avoid any overhead.
- __init__(initial_scan=False)¶
- clear_cache()¶
Release all cached instruments.
Warning
Calling this function only frees internal references to the instruments. If you keep external references, the destructors will not be called by the garbage collector.
- hardware_info()¶
Return the hardware information used for device discovery.
- discover_first(base_cls, policy=DiscoverPolicy.max_weight)¶
Discover the best matching device given discover policy.
- Parameters:
base_cls – base class to be discovered.
policy – policy used for choosing the best match.
- has_hardware(base_cls)¶
Test if a given hardware is available or not.
Note
Doing this test will not put the hardware in the bench internal cache. The reason is that this function only discovers the hardware and does not construct it (which can have a cost).
If you wish to cache the hardware and test its presence, you should rather use:
instrument = bench.get(HardwareType, required=False) if instrument: # Hardware is available. pass
- is_loaded(hw_type)¶
Test if a given hardware type is loaded or not.
This function will work only if you used
cache=True
when requesting hardware withBench.get()
.
- get(hw_type, policy=DiscoverPolicy.single, cache=True, required=True)¶
Discover an instance of a given class.
This method will look for all subclasses of
hw_type
that implement theDiscoverable
interface in the current scope.- Parameters:
hw_type – class to be discovered.
policy – policy for choosing the best match
cache – if True (default), will look if the class was already loaded before attempting a true discovery.
required – if
True
and no hardware is found, an error will be raised.
- get_all(hw_type)¶
Return all instances of a given hardware type.
Note
The instances created will not be cached in the current bench.
- register(hw)¶
Register a custom instrument in the bench.
- get_psu(**kwargs)¶
Look for a
PowerSupply
instance.
- get_bench()¶
Return an instance of secbench’s default bench.
The
get_bench()
function always returns the same object. It is nothing more that a global variable. By using only this method, all hardware loaded will be cached. If you do not want this behavior you have several options:Call
Bench.clear_cache()
to unload all hardware.Create a
Bench()
instance in your module.
Device Discovery¶
Note
Understanding this section is not required for a regular secbench usage. It is indented to developpers and advanced users.
To implement automatic device discovery any class of instrument that is
discoverable (Scope, Afg, …) implements the Discoverable
interface:
- class Discoverable¶
An interface implemented by hardware that is discoverable.
- classmethod is_supported(hardware_info)¶
Test if this class can be discovered.
This is useful for instruments that are platform-specific.
- abstractmethod classmethod discover(hardware_info)¶
Return a generator of possible instantiations of the class.
- Parameters:
hardware_info – information that can be used by implementors for checking hardware availability.
- Returns:
a generator of valid arguments that can be passed to
Discoverable.build()
for constructing instances.
- classmethod discover_weight()¶
A weight that can be used to select the best match when multiple instances of the same hardware are available (e.g., USB Scope over Ethernet).
A higher value give more priority.
- abstractmethod classmethod build(hw_info, args)¶
Instantiate the hardware using specific parameters returned by discover.
- class DiscoverPolicy¶
Policy to use to pick the hardware when several devices are found during the discovery.
first
: select the first element (discarding others). This is not deterministic, as we provide no guarantees on the order on which elements appear.single
: return the first element and raise an exception (secbench.api.NoSuchHardware
) if more than one element exists.max_weight
: return the hardware with the highest weight.
- __new__(value)¶
In very specific use cases, you can directly discover a specific class (without
passing through a Bench) using the discover()
method.
- discover(base_cls, hardware_info=None)¶
A helper for enumerating all subclasses of
base_cls
.- Returns:
an iterator of
(subclass, build_arguments)
Types and Enumerations¶
All enumerations are available in the secbench.api.enums
namespace.
Enumerations used in the control of oscilloscopes:
- class Coupling¶
Common channel coupling modes found on oscilloscopes.
Specific scope implementation may support additional modes.
ac
: Alternating Couplingdc
: Direct Coupling, high resistance termination (typically \(1 M\Omega\))dc_low_impedance
: Direct Coupling, low resistance termination (typically \(50\Omega\))
- __new__(value)¶
- class Slope¶
Trigger slope enumeration.
rising
: trig on rising slopefalling
: trig on falling slopeeither
: trig on either rising or falling slope
- __new__(value)¶
- class Arithmetic¶
Waveform arithmetic enumeration.
off
: don’t apply any transformation (default)envelope
: compute the waveform envelopeaverage
: compute the waveform average
- __new__(value)¶
- class Decimation¶
Decimation method enumeration.
Defines the method to reduce the data stream of the ADC to a stream of waveform points with lower sample rate.
sample
: one of every N samplespeak
: minimum and maximum of N samples (peak detection)highres
: average of N samplesrms
: root mean square of N samples
- __new__(value)¶
Enumerations used in the control of arbitrary function generators:
- class Function¶
Different shapes that a waveform from an AFG can take.
sinus
: Sinusoïdal functionsquare
: Square functionramp
: Ramp functionpulse
: Pulse functionnoise
: Noise functiondc
: continuous offset
- __new__(value)¶
- class OutputLoad¶
This enum allows to define the output load. Infinity is set to > 9E+38
ohms_50
: sets the output load to 50Ohm.ohms_10k
: sets the output load to 10kOhms.inf
: sets the output load to infinity.
- __new__(value)¶
- class Polarity¶
Output polarity of a signal.
normal
: use to set the afg signal polarity to normal.inverted
: use to invert the afg signal polarity.
- __new__(value)¶
- class TriggerSource¶
The trigger source settings stands for:
external
: external trigger is set via this source and allows the afg to detect a TTL pulse. The user must also set the TriggerSlope. (default to rising). Use the external connector on the rear panel.manual
: trigger is done via the afg API using afg.trigger().internal
: use an internal clock to use as a timer trigger.
- __new__(value)¶
Enumerations used in the control of power supplies (PSU):
Scope¶
In order to use a instrument.Scope
you will first obtain
it through a Bench
and the Bench.get_scope()
method.
Then, you can use the following interface implemented by all scope.
Although some scope provide some extra functionalities, we do not recommend to use them as it will reduce the portability of your script.
- class Scope¶
Base abstract class for scope hardware.
- abstract property description¶
The self-description of this instrument. This is typically a string representation of the device serial number.
>>> scope.description "1329.7002K14-100654-Hn"
- abstractmethod channels()¶
Return a mapping of analog channels available.
- abstractmethod horizontal_interval()¶
Duration in seconds between two points.
>>> scope.horizontal_interval() 1e-10
- abstractmethod horizontal_duration()¶
Duration in seconds of the whole acquisition.
>>> scope.horizontal_duration() 1e-07
- abstractmethod horizontal_samples()¶
Number of samples (points) for the whole acquisition.
>>> scope.horizontal_samples() 1000
- abstractmethod bit_resolution()¶
Get the number of bits used to represent a single sample.
Usually this value is 8 or 16 scopes that have high-precision ADCs.
- abstractmethod set_bit_resolution(prec)¶
Set the number of bits to represent a single sample.
The values supported are usually 8, 12 or 16, depending on the scope model.
- abstractmethod trigger_count()¶
When using an acquisition count greater than 1, return the number of triggers that happened since last arming.
- abstractmethod reset()¶
Reset the instrument to factory defaults. This usually resets the timebase and trigger settings to default values and disables all channels. This can be a no-op on some hardware.
>>> scope.reset()
- abstractmethod disable_trigger_out()¶
Disable the trigger out signal.
- abstractmethod sync()¶
Synchronize the class instance with the scope parameters.
- set_trigger(channel, level, slope=Slope.rising, delay=0)¶
Configure the trigger condition.
>>> scope.set_trigger('1', Slope.rising, 0.5)
- Parameters:
channel – the name of the channel used to trigger
slope – the
Slope
to trigger onlevel – the trigger level in volts
delay – the delay in seconds between trigger and start of acquisition.
- enable_trigger_out(slope, length, delay=0)¶
Enable the trigger out signal.
- Parameters:
slope –
Slope.rising
orSlope.falling
length – pulse length in seconds
delay – pulse delay in seconds
- set_horizontal(*, interval=None, duration=None, samples=None)¶
Configure the horizontal (time) parameters. Call this method with any combination of two of the three available parameters. The third, unspecified one, will be computed according to the other two.
>>> # 10k samples during 2 ms >>> scope.horizontal(samples=10e3, duration=2e-3) >>> # 1 µs resolution during 0.5 ms >>> scope.horizontal(interval=1e-6, duration=.5e-3) >>> # 5M samples with 1 µs resolution >>> scope.horizontal(samples=5e6, interval=1e-6)
- Parameters:
interval – duration in seconds between two points
duration – duration in seconds of the whole acquisition
samples – number of samples (points) for the whole acquisition
- Raises:
ValueError – if zero, one or three parameters are given instead of two.
- segmented_acquisition(count)¶
Enable (or disable) segmented acquisition (aka. Ultra segmentation)
- Parameters:
count – If none, disable segmented acquisition, otherwise activate segmented acquisition of
count
frames.
- arm(count=1, iterations=1000, poll_interval=0.001)¶
Arm the instrument for triggering. This command returns immediately and does not wait for an actual trigger to happen. To this end, you need to call
wait()
just afterarm()
.- Parameters:
count – number of triggers constituting a single acquisition. Some hardware only supports
count=1
.iterations – number of iterations for polling trigger state.
poll_interval – delay between state polling iteration if None, will poll without interruptions.
- Returns:
time elapsed.
>>> scope.arm() # arm to receive a single trigger >>> scope.arm(count=20) # arm to receive 20 triggers
- wait(iterations=1000, poll_interval=0.001)¶
Wait for the instrument to complete the latest command sent.
It is particularly useful when called after
arm()
to guarantee that data was actually acquired.>>> scope.wait() # waits 1 second >>> scope.wait(iterations=10, poll_interval=1) # waits 10 second
- Parameters:
iterations – Number of polling iteration before assessing timeout.
poll_interval – Duration of a polling loop.
- Returns:
time elapsed.
- wait_auto()¶
Wait for the instrument to complete the latest command sent. It is particularly useful when called after
arm()
to guarantee that data was actually acquired.This function automatically sets up the iteration and polling parameters of the wait function.
- Returns:
time elapsed.
- set_data_format(bits=8, little_endian=True)¶
Configure the data format returned by
Scope.get_data()
method.- Parameters:
bits – Number of bits
little_endian – When more than one byte is returned per sample (bits > 8) specify the order of bytes (little-endian = least significant bytes first)
- get_data(*channels, volts=False)¶
Retrieve the waveform data for the specified channel(s).
If
volts
is False, no attempt to convert raw samples to volts is made. The returned samples are raw ADC values in the device internal format. Usually this means 8 or 16 bit integer values.If
volts
is True and the device supports it, returned samples are in volts. Usually this means floating point values.
You can retrieve the actual data type using the numpy
dtype
property, eg.print(my_array.dtype)
.This method returns a list of waveform data, each being a single
numpy.array
.>>> c1, = scope.get_data('1') # note the comma (,) >>> c1, c4 = scope.get_data('1', '4') >>> c1_volts, = scope.get_data('1', volts=True)
- Parameters:
channels – the channel(s) to retrieve data from
volts – return volts instead of raw samples (if supported)
- config(channels=None, only_enabled=False)¶
Return a dictionary of scope attributes
EXAMPLES
>>> scope.config(channels=['A', 'B'], only_enabled=True) { 'scope_name': Foo, 'scope_horizontal_samples': 10000, ... }
- clear(pop_errors=False)¶
Clear pending measurements, status flags and optionally pop errors.
Can be used to bring back the scope in a usable mode if something goes wrong.
- calibrate(channel, clip_detection_callback, method=None)¶
Perform automatic calibration of the scope’s vertical range.
- Parameters:
channel – The scope channel to calibrate
clip_detection_callback – A function responsible for detecting whether the scope is clipping or not. This callback takes as input the scope and a channel and returns a boolean.
method – The calibration algorithm to use. See instances of the
Calibrate
abstract method.
- Returns:
The voltage scale found.
- Raises:
ValueError
if the maximum number of iteration is reached.
Added in version 5.1.0.
- Example:
from secbench.scope.util import is_clipping def is_clipping_callback(scope, channel): scope.arm() # send DUT input scope.wait() d, = scope.get_data(channel) return is_clipping(d) # Latter in your code: scope.calibrate('1', is_clipping_callback, method=StepSearchCalibration())
- class ScopeAnalogChannel¶
Represents a scope analog channel.
Use the dict syntax on
Scope
instances to retrieve a channel instance.- disable()¶
Disable the channel.
- abstractmethod setup(range=None, coupling=None, offset=None, decimation=Decimation.sample)¶
Enable the channel and configure it with the given parameters.
For analog channels,
range
andcoupling
are required.
- Parameters:
range – the vertical range, in volt
coupling – the coupling
offset – the offset in volt (default: 0)
decimation – the decimation method (default: sample)
- abstractmethod set_arithmetic(arithmetic, reset=1)¶
Set the channel
arithmetic
function. The function is reset afterreset
triggers.- Parameters:
arithmetic – the arithmetic function to use
reset – the number of triggers after which the function is reset
Scope Vertical Range Calibration¶
Automatic scope calibration can be performed through the calibrate()
.
All calibration algorithms implement the interface Calibration
.
We currently provide the following methods:
instrument.StepSearchCalibration
: rescale the range according to a pre-defined scales. This is the default and recommended method.instrument.BinarySearchCalibration
: use a binary search to optimize the vertical range.instrument.LinearSearchCalibration
: gradually increases the voltage range until the traces do not clip. This method is very slow.
- class Calibration¶
Abstract interface for scope calibration methods.
- abstractmethod run(scope, channel, clip_detection_callback)¶
Launch the calibration on a given channel.
This method has to be reimplemented by the different calibration algorithms.
- class StepSearchCalibration¶
Perform automatic calibration of the scope’s vertical range using a stepwise search.
The algorithm starts from
volts_max
and reduces the voltage range to reach the selected profile. It also sets updates the voltage offset.- __init__(volts_min=0.001, volts_max=20, profile=None)¶
- run(scope, channel, callback)¶
Run the calibration of scope’s voltage range and offset following the provided profile.
- Parameters:
scope – Scope that can be used to perform acquisitions
channel – Scope channel on which calibration should be done.
callback – A function which returns a tuple
(adc_dynamics, adc_offset)
.adc_dynamics
represents the dynamics of the signal when sampled by ADCs (e.g.,abs(max - min)
).adc_offset
represents the offset of the signal when sampled by ADCs (e.g.,(min + max) / 2
).
- class BinarySearchCalibration¶
Perform automatic calibration of the scope vertical range using a binary search.
The initial search window is the voltage range
[volts_min; volts_max]
. The search range is shrunk at each iteration by 2. The algorithm stops if the search window size is belowvolts_prec
.If the algorithm does not find a non-clipping setting after
max_iterations
aValueError
is raised.- __init__(volts_min=0.001, volts_max=1, volts_prec=0.01, max_iterations=15)¶
- run(scope, channel, clip_detection_callback)¶
Launch the calibration on a given channel.
This method has to be reimplemented by the different calibration algorithms.
- class LinearSearchCalibration¶
Perform automatic calibration of the scope’s vertical range using a linear search.
The algorithm starts from
volts_min
and increment the voltage byvolt_prec
until the signal stops clipping. The algorithm stops ifvolts_max
is reached.If the algorithm does not find a non-clipping setting after
max_iterations
aValueError
is raised.- __init__(volts_min=0.001, volts_max=1, volts_prec=0.05, max_iterations=15)¶
- run(scope, channel, clip_detection_callback)¶
Launch the calibration on a given channel.
This method has to be reimplemented by the different calibration algorithms.
Table¶
- class Carto¶
Helper object to iterate over a uniform, rectangular grid of given width and height.
Usage for 8 × 11 grid:
from secbench.table import carto grid = carto.Carto(8, 11) for step in grid: # do acquisition here, for example: scope.arm() dut.trigger() scope.wait() data, = scope.get_data('1') store.store(data)
On the first run of this program, you’ll be asked to interactively position the probe on the top-left, then bottom-right positions using an external program, typically
secbench-axectrl
.On every run of this program, you’ll be asked to interactively set the probe Z position using an external program.
You can set the amount of Z distance the probe is lifted between each position. By default, this is 1mm. For 2.5mm, use:
grid = carto.Carto(8, 11, z_up=2.5)
If you need to start over the top-left/bottom-right positioning after the first run, just delete the
.carto.json
file.You may want to store the X, Y coordinates of each step (eg. in
store()
or for the trace filename). To do so, use thefor
loop iterator variable that hasindex
andposition
tuples:for step in grid: x, y = step.index x_mm, y_mm = step.position print("i am at cell", x, y) print("axes are positioned at", x_mm, y_mm)
index
is the integer coordinates in \([0, width - 1]\), \([0, height - 1]\).position
is the absolute coordinates of the motorized axes in millimeters, starting from the axis hardware zero (“home”).You can use various “visitors” controlling the order in which grid positions are visited:
grid = carto.Carto(8, 11, visitor=carto.visit_random)
See the docstring of each visitor for more information:
visit_spiral()
visit_rows()
visit_random()
- __init__(table, width, height, state_file=None, visitor=<function visit_spiral>, z_up=1.0)¶
Arbitrary Function Generators¶
An arbitrary function generator has one or several channel that allows
generating signals. The instrument.Afg
interface gives general
control on the instrument, while instrument.AfgChannel
generating
signals.
- class Afg¶
Abstract interface for Arbitrary function generators.
- abstract property description¶
A unique string identifying the instrument.
- abstractmethod set_locked(locked)¶
Lock or unlock the instrument.
- Example:
>>> afg.set_locked(True) # Device is locked >>> afg.set_locked(False) # Device is unlocked
- abstractmethod channels()¶
Return a list of channels available on this instrument.
- default_channel()¶
Return a conventional default channel for the AFG.
The default implementation returns the first channel.
- reset()¶
Reset the instrument.
The default implementation invokes the
Afg.clear()
method.
- clear(pop_errors=True)¶
Clear pending measurements, status flags and optionally pop errors.
- class AfgChannel¶
- abstract property parent¶
Return the parent AFG.
- abstractmethod output_state()¶
Return the output state for the current channel.
- abstractmethod trigger_state()¶
Return current trigger configuration.
- abstractmethod force_trigger()¶
Force a manual trigger to a specific channel.
- abstractmethod set_burst_mode(cycles, mode)¶
Enable burst mode (generation of a finite number of shapes).
- Example:
>>> afg_ch1.set_burst_mode(1, BurstMode.triggered)
- abstractmethod burst_count()¶
Gets the number of burst for a specific channel.
- Example:
>>> ncycles = afg_ch1.burst_count() 1
- abstractmethod set_voltage(amplitude, offset=None)¶
Set voltage parameters.
- Example:
>>> afg_ch1.set_voltage(1.5, 0)
- abstractmethod voltage()¶
Return the voltage parameters of the AFG.
- Example:
>>> volt, _ = afg_ch1.voltage() >>> volt, offset = afg_ch1.voltage()
- abstractmethod set_frequency(frequency)¶
Set the signal frequency (in Hz).
- Example:
>>> afg_ch1.set_frequency(1E+5)
- abstractmethod frequency()¶
Return the frequency for the current function (in Hz).
- Example:
>>> frequency = afg_ch1.frequency() 1E+6
- abstractmethod set_function(function)¶
Set the function of the arbitrary function generator.
- Example:
>>> afg_ch1.set_function(Function.sinus) >>> afg_ch1.set_function(Function.pulse)
- abstractmethod function()¶
Return the active function of the arbitrary function generator.
- abstractmethod set_duty_cycle(w)¶
Set the duty cycle in percentage (i.e., from 0 to 100) for the square wave.
- abstractmethod duty_cycle()¶
Returns the duty cycle in percentage (i.e., from 0 to 100) of the square wave.
- abstractmethod set_ratio(w)¶
Sets the ratio for functions that support it (pulse, ramp, etc.).
- abstractmethod ratio()¶
Return the ratio of the ramp function in percentage.
- abstractmethod pulse_width()¶
Returns the pulse width in nanoseconds for the pulse function.
- abstractmethod set_pulse_width(width_ns)¶
Set the pulse width in nanoseconds for the pulse function.
- abstractmethod pulse_edge_time()¶
Return the rising edge time in nanoseconds for pulse function.
- abstractmethod set_pulse_edge_time(edge_time_ns)¶
Set the rising edge time in nanoseconds for pulse function.
- abstractmethod pulse_delay()¶
Retrieve the pulse delay in nanoseconds.
- abstractmethod set_pulse_delay(delay_ns)¶
Set the pulse delay in nanoseconds.
- abstractmethod set_trigger_delay(delay_ns)¶
Set the delay from the trigger signal.
This delay should not be used for precise offset. Usually
Afg.set_pulse_delay()
provides better precision.
- set_combined_delay(delay_ns)¶
Configure an arbitrary delay, keeping a good precision.
This method combines
Afg.set_trigger_delay()
andAfg.set_pulse_delay()
. This is the recommended method to use if you need to set long delays.
- generate_square(frequency, voltage, offset=None, duty_cycle=50)¶
Configure a square waveform in a single call.
- generate_ramp(frequency, voltage, offset=None, ratio=100)¶
Configure a ramp waveform in a single call.
- generate_pulse(frequency, voltage, offset=None, edge_time_ns=5, width_ns=20)¶
Configure a pulse waveform in a single call.
- generate_noise(voltage, offset=None)¶
Configure a noise waveform in a single call.
Controllable Power Supply¶
The secbench framework provides support for remote-controllable power supplies.
Such devices implement the abstract abstract interface
instrument.PowerSupply
. The latter class provide general
instrument control. For doing useful operations like setting voltage, you will
need to query a channel and use the instrument.PowerSupplyChannel
interface.
- class PowerSupply¶
Base abstract class for a Power Supply hardware.
- abstractmethod description()¶
The self-description of this instrument.
This is typically a string representation of the device serial number.
- Example:
>>> alim.description 'SPD3XIDD4R5542,1.01.01.02.05,V3.0'
- abstractmethod get_channel(channel)¶
Return a specific power channel output.
- default_channel()¶
Return a conventional default channel for the AFG.
The default implementation returns the first channel.
- clear(pop_errors=True)¶
Clear pending measurements, status flags and optionally pop errors.
Can be used to bring back the scope in a usable mode if something goes wrong.
- class PowerSupplyChannel¶
Base abstract class for a power supply channel.
- abstractmethod set_output_enabled(enabled)¶
Enable the output of this channel.
- abstractmethod set_voltage(voltage)¶
Set the channel output voltage (in volts).
- abstractmethod voltage()¶
Return the current voltage value (in volts).
- abstractmethod set_current_limit(current)¶
Maximum current limit for the channel in Ampere.
- abstractmethod current_limit()¶
Return the current value in Ampere.
Pulser¶
A pulser is an abstraction for fault injection campaigns. Different type of injector support different parameters.
- class GlitchParams¶
Parameter of a voltage glitch fault injection.
delay_ns
: delay of the pulse from the trigger signal.width_ns
: width of the pulse.
- __init__(*, delay_ns, width_ns)¶
- class EMPulseParams¶
Parameters for electro-magnetic fault injection.
delay_ns
: delay of the pulse from the trigger signal.width_ns
: width of the pulse.rise_time_ns
: delay to reach the maximum voltage.amplitude
: amplitude of the EM pulse.
- __init__(*, delay_ns, width_ns, rise_time_ns, amplitude)¶
- class LaserParams¶
Parameters for laser fault injection.
delay_ns
: delay of the pulse from the trigger signal.width_ns
: width of the pulse.current
: current of the laser source in Ampere.
- __init__(*, delay_ns, width_ns, current)¶
The pulser interface is defined as follows:
- class Pulser¶
Base abstract class for fault injection hardware.
- abstract property description¶
Self-description of this instrument.
- abstractmethod channels()¶
Get the list of channels available on the instrument.
- abstractmethod output_enabled()¶
Is the output enabled?
- abstractmethod set_output_enabled(enabled)¶
Enable or disable the pulser output.
- abstractmethod setup_trigger(source=TriggerSource.external, slope=Slope.rising)¶
Configure the trigger of the pulser.
- disable()¶
Disable the pulser.
This method should ensure that it will be safe to touch the device, typically used in destructors. Default implementation calls
set_output_enabled(False)
.
- channel_names()¶
Get the list of channel names.
- channel_list()¶
Get the list of channels.
- default_channel()¶
Return a conventional default channel for the pulser.
The default implementation returns the first channel.
- clear(pop_errors=True)¶
Clear pending errors on the instrument.
- class PulserChannel¶
- __init__()¶
- abstractmethod classmethod param_type()¶
Type of parameters for this channel.
Must be a dataclass (e.g.,
GlitchParams
).
- abstract property parent¶
Return the parent pulser of this channel.
- abstract property name¶
Name of the channel.
- abstractmethod set_enabled(enabled)¶
Enable or disable the channel.
- abstractmethod enabled()¶
Test if a channel is enabled or not.
- abstractmethod setup(**kwargs)¶
Modify one or more parameters of the channel.
Specific parameters are changed through
kwargs
, the arguments here must be valid fields ofPulserChannel.param_type()
.
- params()¶
Get all parameters of the channel.
- set_params(params)¶
Set all parameters of the channel.
Then, we have some specialized injectors. Those class are just markers to discover specific injectors. For example:
- class EMPulser¶
Inherit this class to mark your class as an EM injector.
Backends¶
Most lab instrument implement some sort of SCPI (Standard Commands for Programmable Instruments) commands. This is commands that look like:
idn = self.query("*IDN?")
self.write("TEMPERATURE:VALUE 100")
temperature = self.query("TEMPERATURE:VALUE?")
Secbench as a set of abstractions that help in writing drivers for
SCPI-like instruments. We rely heavily on the Mixin design pattern. When
possible instruments are written using an abstract
Backend
. We generally inherit the
InstrumentMixin
class to have query
and
write methods in the instrument class. Then, we create subclasses that
instanciate the hardware with specific backend.

Typical class diagram for instrument drivers.¶
The abstract interface of backends is the following:
- class Backend¶
Interface of a communication backend for SCPI instruments.
- set_timeout(secs)¶
Set timeout for blocking operations. (optional)
- flush()¶
Ensure all pending commands were sent to the instrument.
- abstractmethod close()¶
Force closing of the backend.
- abstractmethod query(cmds)¶
Perform a query.
Return a stripped string.
- abstractmethod query_raw(cmds, size)¶
Perform a device query.
Return the data returned by the device “as this”.
We currently provide the following concrete backends.
- class USBTMCBackend¶
The USBTMC backend allows communicating with instruments through USB.
- __init__(path, buffering=None)¶
- set_timeout(secs)¶
Set timeout for blocking operations. (optional)
- close()¶
Force closing of the backend.
- query(cmds)¶
Perform a query.
Return a stripped string.
- query_raw(cmds, size)¶
Perform a device query.
Return the data returned by the device “as this”.
- class VXIBackend¶
- __init__(host)¶
- set_timeout(secs)¶
Set timeout for blocking operations. (optional)
- close()¶
Force closing of the backend.
- query(cmds)¶
Perform a query.
Return a stripped string.
- query_raw(cmds, size)¶
Perform a device query.
Return the data returned by the device “as this”.
- class PyVisaBackend¶
Implementation of the
Backend
interface through PyVisa.This backend allows interacting with any instrument supported by the pyvisa framework.
- __init__(instr)¶
- set_timeout(secs)¶
Set timeout for blocking operations. (optional)
- close()¶
Force closing of the backend.
- query(cmds)¶
Perform a query.
Return a stripped string.
- query_raw(cmds, size)¶
Perform a device query.
Return the data returned by the device “as this”.
- class SerialBackend¶
An instrument backend over serial port.
This backend uses pyserial under the hood for serial communications.
- __init__(path, serial_number=None, **kwargs)¶
- flush()¶
Ensure all pending commands were sent to the instrument.
- close()¶
Force closing of the backend.
- query(cmds)¶
Perform a query.
Return a stripped string.
- query_raw(cmds, size)¶
Perform a device query.
Return the data returned by the device “as this”.
- set_timeout(secs)¶
Set timeout for blocking operations. (optional)
Then, each backend has an associated mixin class that implements the
Discoverable
interface and construct a class with a concrete
backend. When you inherit one of those mixin, you have to implement some
matching function to detect you device. For example, if you inherit the
SerialDiscoverableMixin
, you need to implement
_match_serial()
that should
return if the serial number matches the current hardware.
- class USBTMCDiscoverableMixin¶
Make your hardware an instance of
Discoverable[USBTMCBuildArgs]
You must define
USBTMCDiscoverableMixin._usbtmc_match()
.
- abstractmethod classmethod _usbtmc_match(entry, cfg)¶
A predicate that must be implemented to detect if an USB entry matches the current hardware.
- classmethod _usbtmc_configure(backend)¶
A hook called after a
USBTMCBackend
is constructed.This allows applying hardware-specific configuration.
- classmethod _usbtmc_buffering()¶
Buffering mode to use when opening USB file descriptors.
- class VXIDiscoverableMixin¶
Make your hardware an instance of
Discoverable[VXIBuildArgs]
You must implement
VXIDiscoverableMixin._vxi_match_idn()
.- abstractmethod classmethod _vxi_match_idn(idn)¶
A predicate to detect if an entry matches the current hardware.
- Parameters:
idn – the device description returned by the
*IDN?
SCPI command.
- classmethod _vxi_configure(backend)¶
A hook called after a
VxiBackend
is constructed.This allows applying hardware-specific configuration.
- class PyVisaDiscoverableMixin¶
Make your hardware discoverable through PyVisa.
You must define
PyVisaDiscoverableMixin._pyvisa_match_id()
.- abstractmethod classmethod _pyvisa_match_id(rm, path)¶
A predicate that matches the device.
- Parameters:
rm – a pyvisa ressource manager instance
path – pyvisa device descriptor
- Returns:
True
is the given instance is
- classmethod _pyvisa_configure(backend)¶
An optional hook called after the pyvisa backend is created.
Overriding this method allows customizing the backend properties.
- __init__(*args, **kwargs)¶
- class SerialDiscoverableMixin¶
Make your hardware discoverable through a
SerialBackend
.You must define
SerialDiscoverableMixin._match_serial()
.- abstractmethod classmethod _match_serial(idn)¶
Predicate to detect if the serial identifier of a serial port matches the current hardware.
- classmethod _serial_options()¶
This method can be overwritten to pass additional arguments to the serial port constructor.
The dictionary returned is passed to pyserial’s Serial class constructor.
Mixins and Interfaces For Instruments¶
In this section, we list some of the mixins available implementing instruments.
- class InstrumentMixin¶
Defines convenience methods in instruments.
- __init__(*args, **kwargs)¶
- classmethod from_backend(backend, cfg)¶
Create an instance from a Backend.
- has_error()¶
Return
True
if the instrument has an error pending.
- pop_next_error()¶
Pop the next pending error from the instrument.
- class WriteManyMixin¶
Provide a write_many method for Instruments that support chained commands.
In addition, instruments can implement several additional interfaces depending on their functionalities. You can test if an interface is implemented by a given hardware with:
isinstance(obj, HasSetupStorage)
isinstance(obj, HasWaveformStorage)
- class HasSetupStorage¶
- abstractmethod setup_load(name)¶
Load instrument settings from the specified file preset on the device. Can be used instead of setting up the various channels and parameters manually from Python.
To create such a preset, use the instrument interface or call
setup_save()
.>>> instr.setup_load('my_preset')
- Parameters:
name – file name to load (absolute or relative to default preset location)
- abstractmethod setup_save(name)¶
Save the current instrument settings to the specified file on the device.
To later load this preset, use the instrument interface or call
setup_load()
.>>> instr.setup_save('my_preset')
- Parameters:
name – file name to save (absolute or relative to default preset location)
- class HasWaveformStorage¶
- abstractmethod write_waveform(channel, path, temporary=False)¶
Write waveform of a channel on the scope local storage.
- Parameters:
channel – Channel to be saved
path – Path to the waveform on the device.
temporary – if True, will store the waveform in a temporary directory, thus you can only pass the file name.
- abstractmethod read_waveform(path, temporary=False, reshape=False)¶
Read a binary file at a given location on the scope.
- Parameters:
path – Path to the waveform on the device.
temporary – If True, will look into the scope internal waveform directory, thus you can only pass the file name.
reshape – If true, will reshape the data according to the current segmentation policy and scope acquisition.
- Returns:
the data read.
Helpers¶
Secbench Wrapper and Hooks¶
The hooks.secbench_main()
function allows nice error wrapping and support
for post-experiment notifications.
- secbench_main(on_success=None, on_failure=None, exit_on_failure=True)¶
A Decorator for secbench experiments.
Decorating running your experiment with
secbench_main
, has a few advantages:It will automatically create a
secbench.core.bench.Bench
and pass it as first argument to your function.secbench_main
performs some clean-up and allows to supply post-experiment actions, like sending you an e-mail or copying results (see the example below).
- Parameters:
on_success – An optional hook called if the experiment runs successfully
on_failure – An optional hook called if the experiment failed
exit_on_failure – If true, will invoke sys.exit(1) on error
- Examples:
Here is a simple example of using the
secbench_main
decorator.from secbench.api.hooks import secbench_main, NotifySendHook # A custom hook to copy data into a shared directory. You can do # anything there. def backup_results(results): subprocess.check_call(['cp', 'my-store', '/home/...']) # Apply the secbench_main decorator to your function -> the argument # 'bench' will be passed automatically. @secbench_main(on_success=backup_results, on_failure=NotifySendHook("Something went wrong...")) def my_experiment(bench): # ... return results if __name__ == '__main__': my_experiment()
The hooks are defined in the namespace secbench.api.hooks
, they are
shorhands to be used with the secbench_main()
decorator.
Version¶
You can obtain the current version of secbench.api
with the
version()
function.
- version()¶
Current version of the
secbench.api
package
Other helpers¶
Some general helpers are available in secbench.api.helpers
.
- bytes_to_hex(data)¶
Transform bytes to the corresponding hexadecimal text.
- hex_to_bytes(hex)¶
Transform hexadecimal text to the corresponding bytes.
- find_serial_device(expr)¶
Return the device path of a serial device given its unique vendor id, product id, serial number, etc.
- Parameters:
expr – the expression to look for, eg.
abcd:1234
- Returns:
the device path, eg.
/dev/ttyUSB0
- Raises:
SecbenchError if no such device is found
- find_device_serial_number(expr)¶
Return the device serial number of a serial device given its unique vendor id, product id, path, etc.
- Parameters:
expr – the expression to look for, eg.
ttyUSB3
- Returns:
the device serial number, eg.
DA32XM4Q
- Raises:
SecbenchError if no such device is found
- find_usbtmc_device(expr)¶
Return the device path of an USBTMC device from its vendor ID.
- Parameters:
expr – vendor ID look for (case is ignored), eg.
Tektronix
- Returns:
an iterator on matching devices, eg.
/dev/usbtmc2
- Raises:
SecbenchError if no such device is found
- find_usb_device(expr)¶
Return the device path of an USBTMC device from its vendor ID.
- Parameters:
expr – vendor ID look for (case is ignored), eg.
Tektronix
- Returns:
an iterator on matching devices, eg.
/dev/usbtmc2
- Raises:
SecbenchError if no such device is found
- is_clipping(data, ymin=None, ymax=None, ratio=None)¶
Determine if the data is clipping based on some rules.
- create_cartography_points(tl, br, nx, ny, z=None, shuffle_lines=False)¶
Create grid coordinates.
- Parameters:
tl – top left x and y coordinates.
br – bottom right x and y coordinates.
nx – number of x positions to explore.
ny – number of y positions to explore.
z – z position of the probe.
shuffle_lines – shuffles the lines’ order
- grid_around(p, width, nx, ny)¶
Grid coordinates around a given center
- now_str()¶
Return current date in string format.
This is the conventional datetime representation used across this library.