Parameterized IO Sync Frames

Most experimental sequences include a scan of one or multiple parameters such as hold times, RF frequencies… which are dynamically reconfigured between different experimental runs. To this end, Parameterized IO Sync Frames are introduced, which are built upon a frame function and frame parameter and which are only recompiled in case a change in any of both is detected.

In this example we scan the RF in/out delay to find the best overlap value, i.e. to establish the RF loopback latency. We use a set of 3 Parameterized IO Sync Frames that initialize the RF outputs, set the LEDs and perform the RF generation and acquisition; together with a nested dictionary defining the corresponding parameters.

Required hardware connection (see README for IO Names and Pin Mapping):

  • SMA cable between rf_out_0 <-> rf_in_0

Imports

import time 
import numpy as np
from matplotlib import pyplot as plt
from openlabctrl.device import Rp_125_14_Z7010
from openlabctrl.sequence import IoSequence
from openlabctrl.frame import IoSyncFrame, ParamIoSyncFrame
from openlabctrl.io.scope import ScopeSource

Device instances

rp_0 = Rp_125_14_Z7010(ip="192.168.1.143", label="rp_0")

IO Sequences & IO Frames instances

seq = IoSequence(device_list=[rp_0])
fr_0 = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_1 = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)
fr_2 = ParamIoSyncFrame(device_type=Rp_125_14_Z7010, trig=None)

Parameters

param_dict = {
    "rf_init" : {
        "frequency" : 0,
        "phase" : 0,
        "amplitude" : 0
        },
    "led" : {
        "led_on_idx" : 0
    },
    "rf_in_out" :{
        "samples": 100,
        "acq_delay": 0
    }
}

data_out_list = 0.5 * np.exp(-np.linspace(-5, 5, 100)**2) 

plt.plot(data_out_list)
plt.show()
../_images/8789e3ff0b643245c14b30ea631caf5ddb1961efcc3de94e61ea39b743f1d4c6.png

Frame definitions

def rf_init(fr : IoSyncFrame, fr_param: dict):
    print("--> Building RF init frame")
    fr.reset()
    fr.led.output(val=0, mask = 0xff)
    fr.rf_out_0.frequency(fr_param["frequency"])
    fr.rf_out_0.phase(fr_param["phase"])
    fr.rf_out_0.amplitude(fr_param["amplitude"])
    fr.rf_out_0.phase_reset()
    fr.delay(1000)

def led_update(fr: IoSyncFrame, fr_param: dict):
    print("--> Building LED update frame")
    fr.reset()
    fr.led.output(val=(1 << fr_param["led_on_idx"]), mask=(1 << fr_param["led_on_idx"]))
    
def rf_in_out(fr: IoSyncFrame, fr_param: dict):
    print("--> Building RF in/out frame")
    fr.reset()
    fr.scope_0.source(ScopeSource.RF_IN_0)
    fr.scope_0.decimation(1) 
    fr.rsync()
    for data_out in data_out_list:
        fr.rf_out_0.amplitude(data_out)
    fr.scope_0.delay(fr_param["acq_delay"])
    fr.scope_0.acquire(samples=fr_param["samples"], label="acq_rf")
fr_0.set_frame_function(rf_init)
fr_0.set_frame_parameter(param_dict["rf_init"]) 
fr_1.set_frame_function(led_update)
fr_1.set_frame_parameter(param_dict["led"])
fr_2.set_frame_function(rf_in_out)
fr_2.set_frame_parameter(param_dict["rf_in_out"]) 

Sequence definition

seq.reset()
seq.add_frame(frame=fr_0, device=rp_0, label="rf init")
seq.add_frame(frame=fr_1, device=rp_0, label="led update")
seq.add_frame(frame=fr_2, device=rp_0, label="rf in out")

print(seq.sequence_description())
+--------------------+
| rp_0@192.168.1.143 |
+--------------------+
| rf init            |
| led update         |
| rf in out          |
+--------------------+
NOTE: Frames with (*) are triggered by external trigger source.

Run scan

N=32
delay_list = np.arange(N) #generate a list of delays from 0 to N-1
led_val_list = np.linspace(0, 7, N, dtype=int) #progressively turn on more LEDs
overlap_list = [] #overlap between RF out and scope acquisition


for i in range(N):
    print(f"Iteration {i}: led_on_idx={led_val_list[i]}, acq_delay={delay_list[i]}")
    
    param_dict["led"]["led_on_idx"] = led_val_list[i]
    param_dict["rf_in_out"]["acq_delay"] = delay_list[i]
    
    seq.upload()
    seq.start()
    seq.wait()
    
    scope_dict = seq.get_scope()
    data_in_list = scope_dict[rp_0.get_uid()]["rf in out"]["scope_0"]["acq_rf"]["data"]
    norm_in = np.sqrt(np.sum(data_in_list.astype(float)**2))
    norm_out = np.sqrt(np.sum(data_out_list.astype(float)**2))
    overlap = np.sum(data_in_list.astype(float)*data_out_list.astype(float)) / (norm_in*norm_out)
    overlap_list.append(overlap)
Iteration 0: led_on_idx=0, acq_delay=0
--> Building RF init frame
--> Building LED update frame
--> Building RF in/out frame
Iteration 1: led_on_idx=0, acq_delay=1
--> Building RF in/out frame
Iteration 2: led_on_idx=0, acq_delay=2
--> Building RF in/out frame
Iteration 3: led_on_idx=0, acq_delay=3
--> Building RF in/out frame
Iteration 4: led_on_idx=0, acq_delay=4
--> Building RF in/out frame
Iteration 5: led_on_idx=1, acq_delay=5
--> Building LED update frame
--> Building RF in/out frame
Iteration 6: led_on_idx=1, acq_delay=6
--> Building RF in/out frame
Iteration 7: led_on_idx=1, acq_delay=7
--> Building RF in/out frame
Iteration 8: led_on_idx=1, acq_delay=8
--> Building RF in/out frame
Iteration 9: led_on_idx=2, acq_delay=9
--> Building LED update frame
--> Building RF in/out frame
Iteration 10: led_on_idx=2, acq_delay=10
--> Building RF in/out frame
Iteration 11: led_on_idx=2, acq_delay=11
--> Building RF in/out frame
Iteration 12: led_on_idx=2, acq_delay=12
--> Building RF in/out frame
Iteration 13: led_on_idx=2, acq_delay=13
--> Building RF in/out frame
Iteration 14: led_on_idx=3, acq_delay=14
--> Building LED update frame
--> Building RF in/out frame
Iteration 15: led_on_idx=3, acq_delay=15
--> Building RF in/out frame
Iteration 16: led_on_idx=3, acq_delay=16
--> Building RF in/out frame
Iteration 17: led_on_idx=3, acq_delay=17
--> Building RF in/out frame
Iteration 18: led_on_idx=4, acq_delay=18
--> Building LED update frame
--> Building RF in/out frame
Iteration 19: led_on_idx=4, acq_delay=19
--> Building RF in/out frame
Iteration 20: led_on_idx=4, acq_delay=20
--> Building RF in/out frame
Iteration 21: led_on_idx=4, acq_delay=21
--> Building RF in/out frame
Iteration 22: led_on_idx=4, acq_delay=22
--> Building RF in/out frame
Iteration 23: led_on_idx=5, acq_delay=23
--> Building LED update frame
--> Building RF in/out frame
Iteration 24: led_on_idx=5, acq_delay=24
--> Building RF in/out frame
Iteration 25: led_on_idx=5, acq_delay=25
--> Building RF in/out frame
Iteration 26: led_on_idx=5, acq_delay=26
--> Building RF in/out frame
Iteration 27: led_on_idx=6, acq_delay=27
--> Building LED update frame
--> Building RF in/out frame
Iteration 28: led_on_idx=6, acq_delay=28
--> Building RF in/out frame
Iteration 29: led_on_idx=6, acq_delay=29
--> Building RF in/out frame
Iteration 30: led_on_idx=6, acq_delay=30
--> Building RF in/out frame
Iteration 31: led_on_idx=7, acq_delay=31
--> Building LED update frame
--> Building RF in/out frame
seq.stop()

Plot overlap

rf_out_in_latency = delay_list[np.argmax(overlap_list)]
print(f"RF loopback latency: {rf_out_in_latency} clk cycles")
plt.plot(delay_list, overlap_list)
plt.xlabel("Acquisition delay (clk cycles)")
plt.ylabel("Overlap")
plt.axvline(rf_out_in_latency, color='r', linestyle='--')
plt.show()
RF loopback latency: 14 clk cycles
../_images/a7d60223970caca9dc5160bc66d59b872dd7c5903ccc2c7d25403e1fbe9a74c9.png