Skip to content

Commit b831752

Browse files
committed
Updated PrawnBlaster device classes to support wait monitor and also to have all trigger pins share GPIO0 by default (since PseudoclockDevices can only have a single trigger in labscript regardless of the number of pseudoclocks)
1 parent 74ce6ad commit b831752

File tree

2 files changed

+130
-64
lines changed

2 files changed

+130
-64
lines changed

labscript_devices/PrawnBlaster/blacs_workers.py

Lines changed: 128 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import labscript_utils.h5_lock
1515
import h5py
1616
from blacs.tab_base_classes import Worker
17+
from labscript_utils.connections import _ensure_str
1718
import labscript_utils.properties as properties
1819

1920

@@ -24,10 +25,24 @@ def init(self):
2425
global serial; import serial
2526
global time; import time
2627
global re; import re
28+
global numpy; import numpy
29+
global zprocess; import zprocess
2730
self.smart_cache = {}
2831
self.cached_pll_params = {}
2932
# fmt: on
3033

34+
self.all_waits_finished = zprocess.Event("all_waits_finished", type="post")
35+
self.wait_durations_analysed = zprocess.Event(
36+
"wait_durations_analysed", type="post"
37+
)
38+
self.wait_completed = zprocess.Event("wait_completed", type="post")
39+
self.current_wait = 0
40+
self.wait_table = None
41+
self.measured_waits = None
42+
self.wait_timeout = None
43+
self.h5_file = None
44+
self.started = False
45+
3146
self.prawnblaster = serial.Serial(self.com_port, 115200, timeout=1)
3247
self.check_status()
3348

@@ -43,11 +58,64 @@ def init(self):
4358
assert self.prawnblaster.readline().decode() == "ok\r\n"
4459

4560
def check_status(self):
61+
if self.started and self.wait_table is not None and self.current_wait < len(self.wait_table):
62+
# Try to read out wait. For now, we're only reading out waits from
63+
# pseudoclock 0 since they should all be the same (requirement imposed by labscript)
64+
self.prawnblaster.write(b"getwait %d %d\r\n" % (0, self.current_wait))
65+
response = self.prawnblaster.readline().decode()
66+
if response != "wait not yet available\r\n":
67+
# Parse the response from the PrawnBlaster
68+
wait_remaining = int(response)
69+
clock_resolution = self.device_properties["clock_resolution"]
70+
timeout_length = round(
71+
self.wait_table[self.current_wait]["timeout"] / clock_resolution
72+
)
73+
74+
if wait_remaining == (2 ** 32 - 1):
75+
# The wait hit the timeout - save the timeout duration as wait length
76+
# and flag that this wait timedout
77+
self.measured_waits[self.current_wait] = (
78+
timeout_length * clock_resolution
79+
)
80+
self.wait_timeout[self.current_wait] = True
81+
else:
82+
# Calculate wait length
83+
self.measured_waits[self.current_wait] = (
84+
timeout_length - wait_remaining
85+
) * clock_resolution
86+
self.wait_timeout[self.current_wait] = False
87+
88+
self.logger.info(
89+
f"Wait {self.current_wait} finished. Length={self.measured_waits[self.current_wait]:.9f}s. Timed-out={self.wait_timeout[self.current_wait]}"
90+
)
91+
92+
# Inform any interested parties that a wait has completed:
93+
self.wait_completed.post(
94+
self.h5_file,
95+
data=_ensure_str(self.wait_table[self.current_wait]["label"]),
96+
)
97+
98+
# increment the wait we are looking for!
99+
self.current_wait += 1
100+
101+
# post message if all waits are done
102+
if len(self.wait_table) == self.current_wait:
103+
self.logger.info("All waits finished")
104+
self.all_waits_finished.post(self.h5_file)
105+
106+
# Determine if we are still waiting for wait information
107+
waits_pending = False
108+
if self.wait_table is not None:
109+
if self.current_wait == len(self.wait_table):
110+
waits_pending = False
111+
else:
112+
waits_pending = True
113+
46114
self.prawnblaster.write(b"status\r\n")
47115
response = self.prawnblaster.readline().decode()
48116
match = re.match(r"run-status:(\d) clock-status:(\d)(\r\n)?", response)
49117
if match:
50-
return int(match.group(1)), int(match.group(2)), False
118+
return int(match.group(1)), int(match.group(2)), waits_pending
51119
elif response:
52120
raise Exception(
53121
f"PrawnBlaster is confused: saying '{response}' instead of 'run-status:<int> clock-status:<int>'"
@@ -74,87 +142,61 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
74142
if fresh:
75143
self.smart_cache = {}
76144

145+
self.h5_file = h5file # store reference to h5 file for wait monitor
146+
self.current_wait = 0 # reset wait analysis
147+
self.started = False # Prevent status check from detecting previous wait values
148+
# betwen now and when we actually send the start signal
149+
77150
# Get data from HDF5 file
78151
pulse_programs = []
79152
with h5py.File(h5file, "r") as hdf5_file:
80153
group = hdf5_file[f"devices/{device_name}"]
81154
for i in range(self.num_pseudoclocks):
82155
pulse_programs.append(group[f"PULSE_PROGRAM_{i}"][:])
83156
self.smart_cache.setdefault(i, [])
84-
device_properties = labscript_utils.properties.get(
157+
self.device_properties = labscript_utils.properties.get(
85158
hdf5_file, device_name, "device_properties"
86159
)
87-
self.is_master_pseudoclock = device_properties["is_master_pseudoclock"]
160+
self.is_master_pseudoclock = self.device_properties["is_master_pseudoclock"]
88161

89-
# TODO: Configure clock from device properties
162+
# waits
163+
dataset = hdf5_file["waits"]
164+
acquisition_device = dataset.attrs["wait_monitor_acquisition_device"]
165+
timeout_device = dataset.attrs["wait_monitor_timeout_device"]
166+
if (
167+
len(dataset) > 0
168+
and acquisition_device
169+
== "%s_internal_wait_monitor_outputs" % device_name
170+
and timeout_device == "%s_internal_wait_monitor_outputs" % device_name
171+
):
172+
self.wait_table = dataset[:]
173+
self.measured_waits = numpy.zeros(len(self.wait_table))
174+
self.wait_timeout = numpy.zeros(len(self.wait_table), dtype=bool)
175+
else:
176+
self.wait_table = (
177+
None # This device doesn't need to worry about looking at waits
178+
)
179+
self.measured_waits = None
180+
self.wait_timeout = None
181+
182+
# Configure clock from device properties
90183
clock_mode = 0
91-
clock_vcofreq = 0
92-
clock_plldiv1 = 0
93-
clock_plldiv2 = 0
94-
if device_properties["external_clock_pin"] is not None:
95-
if device_properties["external_clock_pin"] == 20:
184+
if self.device_properties["external_clock_pin"] is not None:
185+
if self.device_properties["external_clock_pin"] == 20:
96186
clock_mode = 1
97-
elif device_properties["external_clock_pin"] == 22:
187+
elif self.device_properties["external_clock_pin"] == 22:
98188
clock_mode = 2
99189
else:
100190
raise RuntimeError(
101-
f"Invalid external clock pin {device_properties['external_clock_pin']}. Pin must be 20, 22 or None."
191+
f"Invalid external clock pin {self.device_properties['external_clock_pin']}. Pin must be 20, 22 or None."
102192
)
103-
clock_frequency = device_properties["clock_frequency"]
104-
105-
if clock_mode == 0:
106-
if clock_frequency == 100e6:
107-
clock_vcofreq = 1200e6
108-
clock_plldiv1 = 6
109-
clock_plldiv2 = 2
110-
elif clock_frequency in self.cached_pll_params:
111-
pll_params = self.cached_pll_params[clock_frequency]
112-
clock_vcofreq = pll_params["vcofreq"]
113-
clock_plldiv1 = pll_params["plldiv1"]
114-
clock_plldiv2 = pll_params["plldiv2"]
115-
else:
116-
self.logger.info("Calculating PLL parameters...")
117-
osc_freq = 12e6
118-
# Techniclally FBDIV can be 16-320 (see 2.18.2 in
119-
# https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf )
120-
# however for a 12MHz reference clock, the range is smaller to ensure
121-
# vcofreq is between 400 and 1600 MHz.
122-
found = False
123-
for fbdiv in range(134, 33, -1):
124-
vcofreq = osc_freq * fbdiv
125-
# PLL1 div should be greater than pll2 div if possible so we start high
126-
for pll1 in range(7, 0, -1):
127-
for pll2 in range(1, 8):
128-
if vco_freq / (pll1 * pll2) == clock_frequency:
129-
found = True
130-
clock_vcofreq = vcofreq
131-
clock_plldiv1 = pll1
132-
clock_plldiv2 = pll2
133-
pll_params = {}
134-
pll_params["vcofreq"] = clock_vcofreq
135-
pll_params["plldiv1"] = clock_plldiv1
136-
pll_params["plldiv2"] = clock_plldiv2
137-
self.cached_pll_params[clock_frequency] = pll_params
138-
break
139-
if found:
140-
break
141-
if found:
142-
break
143-
if not found:
144-
raise RuntimeError(
145-
"Could not determine appropriate clock paramaters"
146-
)
193+
clock_frequency = self.device_properties["clock_frequency"]
147194

148195
# Now set the clock details
149-
self.prawnblaster.write(
150-
b"setclock %d %d %d %d %d\r\n"
151-
% (clock_mode, clock_frequency, clock_vcofreq, clock_plldiv1, clock_plldiv2)
152-
)
196+
self.prawnblaster.write(b"setclock %d %d\r\n" % (clock_mode, clock_frequency))
153197
response = self.prawnblaster.readline().decode()
154198
assert response == "ok\r\n", f"PrawnBlaster said '{response}', expected 'ok'"
155199

156-
# TODO: Save any information we need for wait monitor
157-
158200
# Program instructions
159201
for pseudoclock, pulse_program in enumerate(pulse_programs):
160202
for i, instruction in enumerate(pulse_program):
@@ -187,8 +229,32 @@ def start_run(self):
187229
response = self.prawnblaster.readline().decode()
188230
assert response == "ok\r\n", f"PrawnBlaster said '{response}', expected 'ok'"
189231

232+
# set started = True
233+
self.started = True
234+
190235
def transition_to_manual(self):
191-
# TODO: write this
236+
if self.wait_table is not None:
237+
with h5py.File(self.h5_file, "a") as hdf5_file:
238+
# Work out how long the waits were, save em, post an event saying so
239+
dtypes = [
240+
("label", "a256"),
241+
("time", float),
242+
("timeout", float),
243+
("duration", float),
244+
("timed_out", bool),
245+
]
246+
data = numpy.empty(len(self.wait_table), dtype=dtypes)
247+
data["label"] = self.wait_table["label"]
248+
data["time"] = self.wait_table["time"]
249+
data["timeout"] = self.wait_table["timeout"]
250+
data["duration"] = self.measured_waits
251+
data["timed_out"] = self.wait_timeout
252+
253+
self.logger.info(str(data))
254+
255+
hdf5_file.create_dataset("/data/waits", data=data)
256+
257+
self.wait_durations_analysed.post(self.h5_file)
192258
return True
193259

194260
def shutdown(self):

labscript_devices/PrawnBlaster/labscript_devices.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def __init__(
171171
if out_pins is None:
172172
out_pins = [9, 11, 13, 15]
173173
if in_pins is None:
174-
in_pins = [0, 2, 4, 6]
174+
in_pins = [0, 0, 0, 0]
175175
if len(out_pins) < num_pseudoclocks:
176176
raise LabscriptError(
177177
f"The PrawnBlaster {self.name} is configured with {num_pseudoclocks} but only has pin numbers specified for {len(out_pins)}."
@@ -350,7 +350,7 @@ def generate_code(self, hdf5_file):
350350
f"PULSE_PROGRAM_{i}", compression=config.compression, data=pulse_program
351351
)
352352

353-
# TODO: is this needed, the PulseBlasters don't save it...
353+
# This is needed so the BLACS worker knows whether or not to be a wait monitor
354354
self.set_property(
355355
"is_master_pseudoclock",
356356
self.is_master_pseudoclock,

0 commit comments

Comments
 (0)