1414import labscript_utils .h5_lock
1515import h5py
1616from blacs .tab_base_classes import Worker
17+ from labscript_utils .connections import _ensure_str
1718import 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 ):
0 commit comments