diff --git a/pulse_lib/tests/channel_timing/__init__.py b/pulse_lib/tests/channel_timing/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/channel_timing/test_delays.py b/pulse_lib/tests/channel_timing/test_delays.py
new file mode 100644
index 0000000000000000000000000000000000000000..0095200c298dbb7f3b347a9c13076436ded884ba
--- /dev/null
+++ b/pulse_lib/tests/channel_timing/test_delays.py
@@ -0,0 +1,38 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+
+def test():
+    pulse = context.init_pulselib(n_gates=2, n_qubits=3, n_sensors=2, n_markers=1)
+
+    s = pulse.mk_segment()
+
+    s.P1.add_block(0, 20, 100)
+    s.P2.add_block(0, 20, -100)
+    s.M1.add_marker(0, 20)
+    s.q1.add_MW_pulse(0, 20, 100, 2.450e9)
+    s.q3.add_MW_pulse(0, 20, 100, 2.650e9)
+    s.SD1.acquire(0, 100)
+    s.SD2.acquire(0, 100)
+
+#    context.plot_segments([s])
+
+    sequence = pulse.mk_sequence([s])
+    sequence.n_rep = 1
+    context.add_hw_schedule(sequence)
+    context.plot_awgs(sequence, print_acquisitions=True)
+
+    # now with other delays
+    pulse.add_channel_delay('P1', -20)
+    pulse.add_channel_delay('M1', 40)
+    pulse.add_channel_delay('SD1', 20)
+    pulse.add_channel_delay('SD2', 20)
+
+    sequence = pulse.mk_sequence([s])
+    sequence.n_rep = 1
+    context.add_hw_schedule(sequence)
+    context.plot_awgs(sequence, print_acquisitions=True)
+
+    return None
+
+if __name__ == '__main__':
+    ds = test()
diff --git a/pulse_lib/tests/configurations/__init__.py b/pulse_lib/tests/configurations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/configurations/configurations.yaml b/pulse_lib/tests/configurations/configurations.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..105c7486d98533160f658fa7c0d3732e3017673f
--- /dev/null
+++ b/pulse_lib/tests/configurations/configurations.yaml
@@ -0,0 +1,92 @@
+default: KeysightMocked
+
+KeysightMocked:
+    station: keysight_mocked.yaml
+    backend: Keysight
+    awg_channels:
+        AWG1: [P1,P2,P3,]
+        AWG2: [P4,P5,P6,P7]
+        AWG3: [I1,Q1,I2,Q2]
+    markers:
+        M1: [AWG1,4]
+        M_IQ: [AWG3,0]
+    sensors:
+        SD1: [Dig1,1]
+        SD2: [Dig1,2]
+    rf:
+        SD2:
+            output: M1
+            startup_time: 500
+    runner: qcodes
+
+
+KeysightQSMocked:
+    station: keysight_qs_mocked.yaml
+    backend: Keysight_QS
+    awg_channels:
+        AWG1: [P1,P2,P3,]
+        AWG2: [P4,P5,P6,P7]
+        AWG3: [I1,Q1,I2,Q2]
+    markers:
+        M1: [AWG1,4]
+        M_IQ: [AWG3,0]
+    sensors:
+        SD1: [Dig1,1]
+        SD2: [Dig1,2]
+    runner: core_tools
+
+
+QbloxMocked:
+    station: qblox_mocked.yaml
+    backend: Qblox
+    awg_channels:
+        QCM1: [P1,P2,P3,]
+        QCM2: [P4,P5,P6,P7]
+        QCM3: [I1,Q1,I2,Q2]
+    markers:
+        M1: [QCM1,1]
+        M_IQ: [QCM3,1]
+    sensors:
+        SD1: [QRM1,1]
+        SD2: [QRM1,2]
+    rf:
+        SD2:
+            output: [QRM1,1]
+            frequency: 2e6
+            amplitude: 400
+            startup_time: 0
+    runner: core_tools
+
+
+TektronixMocked:
+    station: tektronix_mocked.yaml
+    backend: Tektronix_5014
+    awg_channels:
+        AWG1: [P1,P2,P3,]
+        AWG2: [P4,P5,P6,P7]
+        AWG3: [I1,Q1,I2,Q2]
+    markers:
+        M1: [AWG1,[1,1]]
+        M_M4i: [AWG1,[4,2]]
+        M_IQ: [AWG3,[1,1]]
+    sensors:
+        SD1: [Dig1,1]
+        SD2: [Dig1,2]
+    runner: qcodes
+
+
+QbloxV1:
+    station: qblox_v1.yaml
+    backend: Qblox
+    awg_channels:
+        Qblox_module2: [P1,P2,P3,]
+        Qblox_module4: [P4,P5,P6,P7]
+        Qblox_module6: [I1,Q1,I2,Q2]
+    markers:
+        M1: [Qblox_module2,1]
+        M_IQ: [Qblox_module6,1]
+    dig_channels:
+        SD1: [Qblox_module8,1]
+        SD2: [Qblox_module8,2]
+    runner: core_tools
+
diff --git a/pulse_lib/tests/configurations/ct_config.yaml b/pulse_lib/tests/configurations/ct_config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..2ff964bbc3ee502fa95d4dd221eb66efa79f17ee
--- /dev/null
+++ b/pulse_lib/tests/configurations/ct_config.yaml
@@ -0,0 +1,16 @@
+# selection used in databrowser. Any is an allowed value here.
+setup: Test
+project: Pulselib
+sample: yaml_configuration
+
+local_database:
+    user: sdesnoo
+    password: FastSp!ns
+    database: sds_test
+
+logging:
+    disabled: true
+
+databrowser:
+    datasource: local # required value
+
diff --git a/pulse_lib/tests/configurations/keysight_mocked.yaml b/pulse_lib/tests/configurations/keysight_mocked.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..6d5b3f552d3d077894bed21395986b46472d4498
--- /dev/null
+++ b/pulse_lib/tests/configurations/keysight_mocked.yaml
@@ -0,0 +1,89 @@
+instruments:
+
+  sig_gen1:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  sig_gen2:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  AWG1:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 2
+
+  AWG2:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 3
+
+  AWG3:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 4
+
+  AWG4:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 5
+
+  AWG5:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 6
+
+  AWG6:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 7
+
+  AWG7:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 8
+
+  AWG8:
+    type: pulse_lib.tests.mock_m3202a.MockM3202A_fpga
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 9
+
+  Dig1:
+    type: pulse_lib.tests.mock_m3102a.MockM3102A
+    enable_forced_reconnect: true
+    init:
+      chassis: 1
+      slot: 11
+
+  Dig2:
+    type: pulse_lib.tests.mock_m3102a.MockM3102A
+    enable_forced_reconnect: true
+    init:
+      chassis: 1
+      slot: 12
diff --git a/pulse_lib/tests/configurations/keysight_qs_mocked.yaml b/pulse_lib/tests/configurations/keysight_qs_mocked.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8143a2dbbd23fd28fe7d3f7e40d86ce4a326cdaa
--- /dev/null
+++ b/pulse_lib/tests/configurations/keysight_qs_mocked.yaml
@@ -0,0 +1,89 @@
+instruments:
+
+  sig_gen1:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  sig_gen2:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  AWG1:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 2
+
+  AWG2:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 3
+
+  AWG3:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 4
+
+  AWG4:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 5
+
+  AWG5:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 6
+
+  AWG6:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 7
+
+  AWG7:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 8
+
+  AWG8:
+    type: pulse_lib.tests.mock_m3202a_qs.MockM3202A_QS
+    enable_forced_reconnect : True
+    init:
+      chassis : 1
+      slot : 9
+
+  Dig1:
+    type: pulse_lib.tests.mock_m3102a_qs.MockM3102A_QS
+    enable_forced_reconnect: true
+    init:
+      chassis: 1
+      slot: 11
+
+  Dig2:
+    type: pulse_lib.tests.mock_m3102a_qs.MockM3102A_QS
+    enable_forced_reconnect: true
+    init:
+      chassis: 1
+      slot: 12
diff --git a/pulse_lib/tests/configurations/qblox_mocked.yaml b/pulse_lib/tests/configurations/qblox_mocked.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..bfc6b7438dc5863018f1dd0959a18244ce94360c
--- /dev/null
+++ b/pulse_lib/tests/configurations/qblox_mocked.yaml
@@ -0,0 +1,49 @@
+instruments:
+
+  sig_gen1:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  sig_gen2:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  QCM1:
+    type: q1simulator.Q1Simulator
+    enable_forced_reconnect : True
+    init:
+      sim_type: QCM
+
+  QCM2:
+    type: q1simulator.Q1Simulator
+    enable_forced_reconnect : True
+    init:
+      sim_type: QCM
+
+  QCM3:
+    type: q1simulator.Q1Simulator
+    enable_forced_reconnect : True
+    init:
+      sim_type: QCM
+
+  QRM1:
+    type: q1simulator.Q1Simulator
+    enable_forced_reconnect : True
+    init:
+      sim_type: QRM
+
+  QRM2:
+    type: q1simulator.Q1Simulator
+    enable_forced_reconnect : True
+    init:
+      sim_type: QRM
diff --git a/pulse_lib/tests/configurations/tektronix_mocked.yaml b/pulse_lib/tests/configurations/tektronix_mocked.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..77221faaf1073e62e2a3e8f66dcde97481a50687
--- /dev/null
+++ b/pulse_lib/tests/configurations/tektronix_mocked.yaml
@@ -0,0 +1,39 @@
+instruments:
+
+  sig_gen1:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  sig_gen2:
+    type: qcodes.tests.instrument_mocks.DummyInstrument
+    enable_forced_reconnect: true
+    init:
+      gates: ['frequency','power']
+    parameters:
+      frequency:
+        limits: [0,20e9]
+
+  AWG1:
+    type: pulse_lib.tests.mock_tektronix5014.MockTektronix5014
+    enable_forced_reconnect : True
+
+  AWG2:
+    type: pulse_lib.tests.mock_tektronix5014.MockTektronix5014
+    enable_forced_reconnect : True
+
+  AWG3:
+    type: pulse_lib.tests.mock_tektronix5014.MockTektronix5014
+    enable_forced_reconnect : True
+
+  AWG4:
+    type: pulse_lib.tests.mock_tektronix5014.MockTektronix5014
+    enable_forced_reconnect : True
+
+  Dig1:
+    type: pulse_lib.tests.mock_m4i.MockM4i
+    enable_forced_reconnect: true
diff --git a/pulse_lib/tests/configurations/test_configuration.py b/pulse_lib/tests/configurations/test_configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd5df81ef566e19f3785875b29acbfe1fbc4ad43
--- /dev/null
+++ b/pulse_lib/tests/configurations/test_configuration.py
@@ -0,0 +1,305 @@
+import logging
+import os
+import math
+from collections.abc import Sequence
+import matplotlib.pyplot as pt
+import numpy as np
+
+import qcodes as qc
+import qcodes.logger as logger
+from qcodes.logger import start_all_logging
+from qcodes.data.data_set import DataSet
+from qcodes.data.io import DiskIO
+
+from ruamel.yaml import YAML
+
+from pulse_lib.tests.utils.qc_run import qc_run
+from pulse_lib.base_pulse import pulselib
+from pulse_lib.virtual_channel_constructors import IQ_channel_constructor
+from pulse_lib.tests.hw_schedule_mock import HardwareScheduleMock
+from pulse_lib.schedule.tektronix_schedule import TektronixSchedule
+
+try:
+    import core_tools as ct
+    from core_tools.sweeps.sweeps import do0D
+    _ct_imported = True
+    _ct_configured = False
+except:
+    _ct_imported = False
+
+
+def init_logging():
+    start_all_logging()
+    logger.get_console_handler().setLevel(logging.WARN)
+    logger.get_file_handler().setLevel(logging.DEBUG)
+
+
+class Context:
+    def __init__(self):
+        self._dir = os.path.dirname(__file__)
+        self._load_configurations()
+        cfg = self.configurations
+        self.load_configuration(cfg['default'])
+
+    def _load_configurations(self):
+        with open(self._dir + '/configurations.yaml') as fp:
+            yaml = YAML()
+            self.configurations = yaml.load(fp)
+
+    def load_configuration(self, config_name):
+        self.configuration_name = config_name
+        self._configuration = self.configurations[config_name]
+        self._load_station(self._configuration['station'])
+
+    def close_station(self):
+        if qc.Station.default is not None:
+            qc.Station.default.close_all_registered_instruments()
+
+    def _load_station(self, config):
+        self.close_station()
+        config_file = os.path.join(self._dir, config)
+        station = qc.Station(default=True, config_file=config_file)
+        self.station = station
+
+        station.load_all_instruments()
+
+        awgs = []
+        digs = []
+        # map Qblox Cluster to AWG1 and digitizer
+        if 'Cluster' in station.components:
+            cluster = station.Cluster
+            for module in cluster.modules:
+                if module.present():
+                    rf = 'RF' if module.is_rf_type else ''
+                    print(f'  Add {module.name}: {module.module_type}{rf}')
+                    setattr(station, module.name, module)
+                    if module.is_qcm_type:
+                        awgs.append(module)
+                    else:
+                        digs.append(module)
+        else:
+            for name,component in station.components.items():
+                if name.startswith('AWG'):
+                    awgs.append(component)
+                if name.startswith('Dig'):
+                    digs.append(component)
+
+        self.awgs = awgs
+        self.digitizers = digs
+
+    def init_pulselib(self, n_gates=0, n_qubits=0, n_markers=0,
+                      n_sensors=0, rf_sources=False,
+                      virtual_gates=False, finish=True):
+        self.n_plots = 0
+        cfg = self._configuration
+        station = self.station
+        backend = cfg['backend']
+        if backend == 'Qblox':
+            from pulse_lib.qblox.pulsar_uploader import UploadAggregator
+            UploadAggregator.verbose = True
+
+        pulse = pulselib(backend=backend)
+        self.pulse = pulse
+
+        gate_map = {}
+        for awg_name,gates in cfg['awg_channels'].items():
+            for i,gate in enumerate(gates):
+                if backend != 'Qblox':
+                    i += 1
+                gate_map[gate] = (awg_name,i)
+
+        gates = []
+        for i in range(n_gates):
+            gate = f'P{i+1}'
+            awg,channel = gate_map[gate]
+            if awg not in pulse.awg_devices:
+                pulse.add_awg(station.components[awg])
+            pulse.define_channel(gate, awg, channel)
+            pulse.add_channel_compensation_limit(gate, (-100, 50))
+#        pulse.add_channel_attenuation(name, 0.2)
+#        pulse.add_channel_delay(name, value)
+
+        if virtual_gates:
+            n_gates = len(gates)
+            matrix = np.diag([0.9]*n_gates) + 0.1
+            pulse.add_virtual_matrix(
+                    name='virtual-gates',
+                    real_gate_names=gates,
+                    virtual_gate_names=['v'+gate for gate in gates],
+                    matrix=matrix
+                    )
+
+        for i in range(n_markers):
+            self._add_marker(f'M{i+1}')
+
+        n_iq = math.ceil(n_qubits/2)
+        for i in range(n_iq):
+            I,Q = f'I{i+1}', f'Q{i+1}',
+            awg,channel_I = gate_map[I]
+            awg,channel_Q = gate_map[Q]
+            if awg not in pulse.awg_devices:
+                pulse.add_awg(station.components[awg])
+            pulse.define_channel(I, awg, channel_I)
+            pulse.define_channel(Q, awg, channel_Q)
+            pulse.add_channel_delay(I, -40)
+            pulse.add_channel_delay(Q, -40)
+            sig_gen = station.components[f'sig_gen{i+1}']
+
+            IQ_pair = IQ_channel_constructor(pulse, f'IQ{i+1}')
+            IQ_pair.add_IQ_chan(I, 'I')
+            IQ_pair.add_IQ_chan(Q, 'Q')
+            IQ_pair.set_LO(sig_gen.frequency)
+            if i == 0:
+                self._add_marker('M_IQ', setup_ns=20, hold_ns=20)
+                pulse.add_channel_delay('M_IQ', -40)
+                IQ_pair.add_marker('M_IQ')
+
+            sig_gen.frequency(2.400e9 + i*0.400e9)
+            # 2.400, 2.800
+            # 2.450, 2.550, 2.650, 2.750
+            # add qubits: q1 and q2
+            for j in range(2):
+                qubit = 2*i+j+1
+                if qubit < n_qubits+1:
+                    idle_frequency = 2.350e9 + qubit*0.100e9
+                    IQ_pair.add_virtual_IQ_channel(f"q{qubit}", idle_frequency)
+
+        if n_sensors > 0 and backend in ['Keysight']:
+            pulse.configure_digitizer = True
+        for i in range(n_sensors):
+            sensor = f'SD{i+1}'
+            digitizer,channel = cfg['sensors'][sensor]
+            if digitizer not in pulse.digitizers:
+                pulse.add_digitizer(station.components[digitizer])
+            pulse.define_digitizer_channel(sensor, digitizer, channel)
+
+        if n_sensors > 0 and backend == 'Tektronix_5014':
+            self._add_marker('M_M4i')
+            pulse.add_digitizer_marker('Dig1', 'M_M4i')
+
+        if rf_sources:
+            for sensor,params in cfg['rf'].items():
+                if sensor not in pulse.digitizer_channels:
+                    continue
+                if backend == 'Qblox':
+                    pulse.set_digitizer_frequency(sensor, params['frequency'])
+                    pulse.set_digitizer_rf_source(sensor,
+                                                  output=params['output'],
+                                                  amplitude=params['amplitude'],
+                                                  mode='pulsed',
+                                                  startup_time_ns=params['startup_time'])
+                else:
+                    pulse.set_digitizer_rf_source(sensor,
+                                                  output=params['output'],
+                                                  mode='pulsed',
+                                                  startup_time_ns=params['startup_time'])
+
+        if backend == 'Tektronix_5014':
+            # pulselib always wants a digitizer for Tektronix
+            if 'Dig1' not in pulse.digitizers:
+                pulse.add_digitizer(station.components['Dig1'])
+
+
+        if finish:
+            pulse.finish_init()
+
+        return pulse
+
+    def _add_marker(self, name, setup_ns=0, hold_ns=0):
+        cfg = self._configuration
+        pulse = self.pulse
+        awg,channel = cfg['markers'][name]
+        if isinstance(channel, Sequence):
+            channel = tuple(channel)
+        if awg not in pulse.awg_devices:
+            pulse.add_awg(self.station.components[awg])
+        pulse.define_marker(name, awg, channel, setup_ns=setup_ns, hold_ns=hold_ns)
+
+    def add_hw_schedule(self, sequence):
+        backend = self._configuration['backend']
+        if backend in ['Keysight','Keysight_QS']:
+            sequence.set_hw_schedule(HardwareScheduleMock())
+        elif backend == 'Tektronix_5014':
+            sequence.set_hw_schedule(TektronixSchedule(self.pulse))
+
+    def run(self, name, sequence, *params):
+        global _ct_configured
+        runner = self._configuration['runner']
+        if runner == 'qcodes':
+            path = 'C:/measurements/test_pulselib'
+            DataSet.default_io = DiskIO(path)
+            return qc_run(name, sequence, *params)
+
+        elif runner == 'core_tools':
+            if not _ct_imported:
+                raise Exception('core_tools import failed')
+            if not _ct_configured:
+                ct.configure(os.path.join(self._dir, 'ct_config.yaml'))
+                _ct_configured = True
+            ct.set_sample_info(sample=self.configuration_name)
+            return do0D(sequence, *params, name=name).run()
+
+        else:
+            print(f'no implementation for {runner}')
+
+    def plot_awgs(self, sequence, index=None, print_acquisitions=False, **kwargs):
+        job = sequence.upload(index)
+        sequence.play(index)
+        pulse = self.pulse
+        pt.ioff()
+        for awg in list(pulse.awg_devices.values()) + list(pulse.digitizers.values()):
+            if hasattr(awg, 'plot'):
+                pt.figure()
+                awg.plot()
+                pt.legend()
+                pt.grid()
+                pt.ylabel('amplitude [V]')
+                pt.xlabel('time [ns]')
+                pt.title(f'AWG upload {awg.name}')
+                for (method, arguments) in kwargs.items():
+                    getattr(pt, method)(*arguments)
+                self._savefig()
+
+        if print_acquisitions:
+            backend = self._configuration['backend']
+            if backend == 'Keysight':
+                print(sequence.hw_schedule.sequence_params)
+            elif backend == 'Keysight_QS':
+                print(sequence.hw_schedule.sequence_params)
+                for dig in pulse.digitizers.values():
+                    dig.describe()
+            elif backend == 'Qblox':
+                print('See .q1asm file for acquisition timing')
+            elif backend == 'Tektronix_5014':
+                print('triggers:', job.digitizer_triggers)
+            else:
+                print('No acquisition info for backend ' + backend)
+
+    def plot_segments(self, segments, index=(0,), channels=None, awg_output=True):
+        pt.ioff()
+        for s in segments:
+            pt.figure()
+            pt.title(f'Segment index:{index}')
+            s.plot(index, channels=channels, render_full=awg_output)
+            self._savefig()
+
+
+    def _savefig(self):
+        backend = self._configuration['backend']
+        self.n_plots += 1
+        pt.savefig(f'figure_{self.n_plots}-{backend}.png')
+        pt.close()
+
+
+init_logging()
+context = Context()
+
+
+#from core_tools.HVI2.hvi2_schedule_loader import Hvi2ScheduleLoader
+#
+#def cleanup_instruments():
+#    try:
+#        oldLoader.close_all()
+#    except: pass
+#    oldLoader = Hvi2ScheduleLoader
+
diff --git a/pulse_lib/tests/looping/__init__.py b/pulse_lib/tests/looping/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/looping/test_1D_divide.py b/pulse_lib/tests/looping/test_1D_divide.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c7cd50e7b3248358ac672008e366dd5158b5eb5
--- /dev/null
+++ b/pulse_lib/tests/looping/test_1D_divide.py
@@ -0,0 +1,28 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+import pulse_lib.segments.utility.looping as lp
+
+def test():
+    pulse = context.init_pulselib(n_gates=1)
+
+    t_pulse = lp.linspace(20, 100, 5, name='t_pulse', axis=0)
+    amplitude = 1000.0 / t_pulse
+
+    s = pulse.mk_segment()
+
+    s.wait(100)
+    s.P1.add_block(0, t_pulse, amplitude)
+
+    context.plot_segments([s])
+
+    sequence = pulse.mk_sequence([s])
+    context.add_hw_schedule(sequence)
+    for t in sequence.t_pulse.values:
+        sequence.t_pulse(t)
+        context.plot_awgs(sequence, ylim=(-0.100,0.100))
+    ds = context.run('test_1D_divide', sequence)
+
+    return ds
+
+if __name__ == '__main__':
+    ds = test()
diff --git a/pulse_lib/tests/looping/test_2D_divide.py b/pulse_lib/tests/looping/test_2D_divide.py
new file mode 100644
index 0000000000000000000000000000000000000000..5278620eaea2e07aa4dbe559442437ea396b7eb4
--- /dev/null
+++ b/pulse_lib/tests/looping/test_2D_divide.py
@@ -0,0 +1,47 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+import pulse_lib.segments.utility.looping as lp
+
+def test():
+    pulse = context.init_pulselib(n_gates=1, n_sensors=1)
+
+    n_pulses = lp.array([1,2,5,10], name='n_pulses', axis=1)
+    t_all = lp.linspace(1000, 3000, 3, name='t_all', axis=0)
+    t_pulse = t_all / n_pulses
+
+    s = pulse.mk_segment()
+    s.update_dim(n_pulses)
+
+    # Add other axis before indexing. It cannot be added on the slice.
+    # TODO: This shouldn't be necessary...
+    s.update_dim(t_all)
+
+    for i in range(len(n_pulses)):
+        s_i = s[i]
+        for j in range(int(n_pulses[i])):
+            s_i.P1.add_block(0, t_pulse[i], 50)
+            s_i.reset_time()
+            s_i.P1.add_block(0, t_pulse[i], -50)
+            s_i.reset_time()
+
+    s.wait(100)
+    s.SD1.acquire(0, 100)
+
+    context.plot_segments([s], index=(0,0))
+
+    sequence = pulse.mk_sequence([s])
+    sequence.n_rep = 2
+    context.add_hw_schedule(sequence)
+    m_param = sequence.get_measurement_param()
+    for t in sequence.t_all.values:
+        sequence.t_all(t)
+        for i in sequence.n_pulses.values:
+            sequence.n_pulses(i)
+            context.plot_awgs(sequence, ylim=(-0.100,0.100))
+
+    ds = context.run('test_2D_divide', sequence, m_param)
+
+    return ds
+
+if __name__ == '__main__':
+    ds = test()
diff --git a/pulse_lib/tests/mock_m4i.py b/pulse_lib/tests/mock_m4i.py
new file mode 100644
index 0000000000000000000000000000000000000000..43903bf3821c8244d42989a1c6226bfb90a28855
--- /dev/null
+++ b/pulse_lib/tests/mock_m4i.py
@@ -0,0 +1,23 @@
+
+from qcodes.instrument.base import Instrument
+
+class MockM4i(Instrument):
+    def __init__(self, name):
+        super().__init__(name)
+        self.timeout = Parameter(10000)
+
+    def get_idn(self):
+        return dict(vendor='Pulselib', model=type(self).__name__, serial='', firmware='')
+
+    def start_triggered(self):
+        pass
+
+class Parameter:
+    def __init__(self, value):
+        self.value = value
+
+    def set(self, value):
+        self.value = value
+
+    def cache(self):
+        return self.value
\ No newline at end of file
diff --git a/pulse_lib/tests/mock_tektronix5014.py b/pulse_lib/tests/mock_tektronix5014.py
new file mode 100644
index 0000000000000000000000000000000000000000..c031e7c479d9e16fd333c706a82abb48fa6bd3e5
--- /dev/null
+++ b/pulse_lib/tests/mock_tektronix5014.py
@@ -0,0 +1,139 @@
+
+import logging
+from dataclasses import dataclass, field
+from typing import Dict
+
+import numpy as np
+import matplotlib.pyplot as pt
+
+from qcodes.instrument.base import Instrument
+
+
+# mock for M3202A / SD_AWG_Async
+class MockTektronix5014(Instrument):
+
+    def __init__(self, name):
+        super().__init__(name)
+        self.visa_handle = VisaHandlerMock(self)
+        self.settings = {}
+        self.channel_settings = {ch:{} for ch in [1,2,3,4]}
+        self.waveforms = {}
+        self.waveforms_lengths = {}
+        self.sequence = []
+        self.state = 'Stopped'
+        self.all_channels_off()
+
+    def get_idn(self):
+        return dict(vendor='Pulselib', model=type(self).__name__, serial='', firmware='')
+
+    def trigger_source(self, value):
+        self.settings['trigger_source'] = value
+
+    def trigger_impedance(self, value):
+        self.settings['trigger_impedance'] = value
+
+    def trigger_level(self, value):
+        self.settings['trigger_level'] = value
+
+    def trigger_slope(self, value):
+        self.settings['trigger_slope'] = value
+
+    def clock_freq(self, value):
+        self.settings['clock_freq'] = value
+
+    def set(self, name, value):
+        if name.startswith('ch'):
+            channel = int(name[2])
+            self.channel_settings[channel][name[4:]] = value
+        else:
+            self.settings[name] = value
+
+    def delete_all_waveforms_from_list(self):
+        self.waveforms = {}
+
+    def write(self, cmd):
+        if cmd.startswith('WLISt:WAVeform:DEL '):
+            name = cmd.split('"')[1]
+            if name in self.waveforms:
+                del self.waveforms[name]
+        elif cmd.startswith('WLISt:WAVeform:NEW '):
+            name = cmd.split('"')[1]
+            length = int(cmd.split(',')[1])
+            self.waveforms_lengths[name] = length
+
+    def sequence_length(self, length):
+        self.sequence = [SequenceElement() for _ in range(length)]
+
+    def set_sqel_waveform(self, wave_name, channel, element_no):
+        self.sequence[element_no-1].wave_names[channel] = wave_name
+
+    def set_sqel_goto_state(self, element_no, dest):
+        self.sequence[element_no-1].goto = dest
+
+    def set_sqel_trigger_wait(self, element_no):
+        self.sequence[element_no-1].wait = True
+
+    def set_sqel_loopcnt(self, n_repetitions, element_no):
+        self.sequence[element_no-1].loop_cnt = n_repetitions
+
+    def set_sqel_loopcnt_to_inf(self, element_no):
+        self.sequence[element_no-1].loop_cnt = 10**6
+
+    def run_mode(self, mode):
+        self.settings['mode'] = mode
+
+    def run(self):
+        self.state = 'Running'
+
+    def stop(self):
+        self.state = 'Stopped'
+
+    def force_trigger(self):
+        self.state = 'Running'
+
+    def all_channels_off(self):
+        for ch in [1,2,3,4]:
+            settings = self.channel_settings[ch]
+            settings['state'] = 0
+
+    def plot(self):
+        for ch in [1,2,3,4]:
+            settings = self.channel_settings[ch]
+            print(self.name, ch, settings)
+
+            if settings['state'] == 0:
+                continue
+            amp = settings.get('amp', 0.0)
+            offset = settings.get('offset', 0.0)
+            wave_raw = self.waveforms[self.sequence[0].wave_names[ch]]
+            wave_data = (wave_raw & 0x3FFF) # ((wave_raw & 0x3FFF) << 2).astype(np.int16)
+            wave = offset + amp * (wave_data/2**13 - 1)
+            pt.plot(wave, label=f'ch{ch}')
+            if settings.get('m1_high',False):
+                marker_1 = (wave_raw & 0x4000) >> 14
+                pt.plot(marker_1, ':', label=f'M{ch}.1')
+            if settings.get('m2_high',False):
+                marker_2 = (wave_raw & 0x8000) >> 15
+                pt.plot(marker_2, ':', label=f'M{ch}.2')
+
+class VisaHandlerMock:
+    def __init__(self, parent):
+        self.parent = parent
+
+    def write_raw(self, msg):
+        cmd_end = msg.index(ord(','))
+        cmd = str(msg[:cmd_end])
+        name = cmd.split('"')[1]
+        data_length_len = int(chr(msg[cmd_end+2]))
+        data_start = cmd_end+3+data_length_len
+        # data_length = int(str(msg[cmd_end+3:data_start], encoding='utf-8'))
+        data = np.frombuffer(msg[data_start:], dtype='<u2')
+        self.parent.waveforms[name] = data
+
+@dataclass
+class SequenceElement:
+    wave_names: Dict[int,str] = field(default_factory=dict)
+    goto: int = 0
+    wait: bool = False
+    loop_cnt: int = 1
+
diff --git a/pulse_lib/tests/rf_source/__init__.py b/pulse_lib/tests/rf_source/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/rf_source/test_rf_qblox_fast.py b/pulse_lib/tests/rf_source/test_rf_qblox_fast.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9c80763637957387c9157b723942ea913dd9054
--- /dev/null
+++ b/pulse_lib/tests/rf_source/test_rf_qblox_fast.py
@@ -0,0 +1,26 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+from pulse_lib.fast_scan.qblox_fast_scans import fast_scan1D_param, fast_scan2D_param
+
+
+def test():
+    pulse = context.init_pulselib(n_gates=2, n_sensors=2, rf_sources=True)
+
+    param1 = fast_scan1D_param(
+            pulse, 'P1', 100.0, 10, 5000,
+            channels=['SD2'])
+
+    context.plot_segments(param1.my_seq.sequence)
+    context.plot_awgs(param1.my_seq)
+
+    param2 = fast_scan2D_param(
+            pulse, 'P1', 100.0, 10, 'P2', 80.0, 10, 2000,
+            channels=['SD2'])
+
+    context.plot_segments(param2.my_seq.sequence)
+    context.plot_awgs(param2.my_seq)
+
+    return None
+
+if __name__ == '__main__':
+    ds = test()
diff --git a/pulse_lib/tests/test_iq/__init__.py b/pulse_lib/tests/test_iq/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/test_iq/test_phase.py b/pulse_lib/tests/test_iq/test_phase.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e79d08334f8088fefc871d557a2796d0ff89eb4
--- /dev/null
+++ b/pulse_lib/tests/test_iq/test_phase.py
@@ -0,0 +1,32 @@
+
+from pulse_lib.tests.configurations.test_configuration import context
+
+from numpy import pi
+
+def test():
+    pulse = context.init_pulselib(n_qubits=1)
+
+    s = pulse.mk_segment()
+
+    s.q1.add_MW_pulse(0, 20, 100, 2.450e9)
+    s.q1.add_phase_shift(20, pi/2)
+    s.q1.add_phase_shift(20, pi/2)
+    s.q1.add_MW_pulse(20, 40, 100, 2.450e9)
+    s.q1.add_phase_shift(40, pi)
+    s.reset_time()
+    s.q1.add_phase_shift(0, -pi/2)
+    s.q1.add_MW_pulse(0, 20, 100, 2.450e9)
+    s.q1.add_phase_shift(20, pi/2)
+    s.q1.add_MW_pulse(20, 40, 100, 2.450e9)
+
+    context.plot_segments([s])
+
+    sequence = pulse.mk_sequence([s])
+    sequence.n_rep = 1
+    context.add_hw_schedule(sequence)
+    context.plot_awgs(sequence)
+
+    return None
+
+if __name__ == '__main__':
+    ds = test()
diff --git a/pulse_lib/tests/utils/__init__.py b/pulse_lib/tests/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/pulse_lib/tests/utils/qc_run.py b/pulse_lib/tests/utils/qc_run.py
new file mode 100644
index 0000000000000000000000000000000000000000..89931658561d327204ddcd6f361aa5db431b605c
--- /dev/null
+++ b/pulse_lib/tests/utils/qc_run.py
@@ -0,0 +1,60 @@
+from collections.abc import Sequence
+import qcodes as qc
+from qcodes.measure import Measure
+from qcodes.loops import Loop
+from qcodes.actions import Task
+
+from pulse_lib.sequencer import sequencer
+
+def upload_play(seq):
+    seq.upload()
+    seq.play()
+
+def qc_run(name, *args, quiet=False):
+
+    seq = None
+    meas_params = []
+    sweeps = []
+    delays = {}
+    for arg in args:
+        if isinstance(arg, sequencer):
+            seq = arg
+        elif isinstance(arg, (qc.Parameter, qc.MultiParameter)):
+            meas_params.append(arg)
+        elif isinstance(arg, Sequence):
+            if len(arg) not in [4,5]:
+                raise ValueError(f'incorrect sweep {arg}')
+            param = arg[0]
+            if not isinstance(param, qc.Parameter):
+                raise TypeError(f'Expected Parameter, got {type(param)}')
+            sweeps.append(param[arg[1]:arg[2]:arg[3]])
+            if len(arg) == 5:
+                delay = arg[4]
+                delays[len(sweeps)-1] = delay
+
+    actions = []
+
+    if seq is not None:
+        for sp in seq.params:
+            sweep = sp[sp.values]
+            # np.random.shuffle(sweep._values)
+            sweeps.append(sweep)
+        play_task = Task(upload_play, seq)
+        actions.append(play_task)
+
+    loop = None
+    for i,sweep in enumerate(sweeps):
+        delay = delays.get(i, 0)
+        if loop is None:
+            loop = Loop(sweep, delay)
+        else:
+            loop = loop.loop(sweep, delay)
+
+    actions += meas_params
+    if loop is not None:
+        m = loop.each(*actions)
+    else:
+        m = Measure(*actions)
+
+    ds = m.run(loc_record={'name':name}, quiet=quiet)
+    return ds