def generate_code(self, hdf5_file): # Generate the hardware instructions self.init_device_group(hdf5_file) PseudoclockDevice.generate_code(self, hdf5_file) dig_outputs, ignore = self.get_direct_outputs() pb_inst = self.convert_to_pb_inst(dig_outputs, [], {}, {}, {}) self.write_pb_inst_to_h5(pb_inst, hdf5_file)
def add_device(self, device): """Adds child devices. This is automatically called by the labscript compiler. Args: device (:class:`_PrawnBlasterPseudoclock` or :class:`_PrawnBlasterDummyPseudoclock`): Instance to attach to the device. Only the allowed children can be attached. """ if len(self.child_devices) < ( self.num_pseudoclocks + self.use_wait_monitor) and isinstance( device, (_PrawnBlasterPseudoclock, _PrawnBlasterDummyPseudoclock)): PseudoclockDevice.add_device(self, device) elif isinstance(device, _PrawnBlasterPseudoclock): raise LabscriptError( f"The {self.description} {self.name} automatically creates the correct number of pseudoclocks." + "Instead of instantiating your own Pseudoclock object, please use the internal" + f" ones stored in {self.name}.pseudoclocks") else: raise LabscriptError( f"You have connected {device.name} (class {device.__class__}) to {self.name}, but {self.name} does not support children with that class." )
def __init__(self, name, trigger_device=None, trigger_connection=None, serial='', reference_clock='internal', clock_frequency=100e6, use_wait_monitor=False, trigger_debounce_clock_ticks=10): # set device properties based on clock frequency self.clock_limit = clock_frequency / 2 self.clock_resolution = 1 / clock_frequency # We'll set this to be 2x the debounce count self.trigger_minimum_duration = 2 * trigger_debounce_clock_ticks / clock_frequency # Todo: confirm this. # It should only be 5 clock cycles + the debounce_clock_ticks # as I think it takes 3 clock cycles to propagate to the state # machine of the FPGA code (due to the three uses of the non-blocking <= verilog operator # in the debounce code) and then another 2 cycles before the output goes high # (one to move out of the wait for retrigger code and then one because the update of the # output state is non-blocking)\ # self.trigger_delay = (5 + trigger_debounce_clock_ticks) / clock_frequency # Todo: confirm this # I believe it is 1 clock cycle self.wait_delay = self.clock_resolution PseudoclockDevice.__init__(self, name, trigger_device, trigger_connection) self.BLACS_connection = serial if trigger_debounce_clock_ticks >= 2**16: raise LabscriptError( 'The %s %s trigger_debounce_clock_ticks parameter must be between 0 and 65535' % (self.description, self.name)) # create Pseudoclock and clockline self._pseudoclock = CiceroOpalKellyXEM3001Pseudoclock( '%s_pseudoclock' % name, self, 'clock') # possibly a better connection name than 'clock'? # Create the internal direct output clock_line self._clock_line = ClockLine('%s_clock_line' % name, self.pseudoclock, 'Clock Out') # Create internal devices for connecting to a wait monitor self.__wait_monitor_dummy_pseudoclock = CiceroOpalKellyXEM3001DummyPseudoclock( '%s__dummy_wait_pseudoclock' % name, self, '_') self.__wait_monitor_dummy_clock_line = CiceroOpalKellyXEM3001DummyClockLine( '%s__dummy_wait_clock_line' % name, self.__wait_monitor_dummy_pseudoclock, '_') self.__wait_monitor_intermediate_device = CiceroOpalKellyXEM3001DummyIntermediateDevice( '%s_internal_wait_monitor_outputs' % name, self.__wait_monitor_dummy_clock_line) if use_wait_monitor: WaitMonitor('%s__wait_monitor' % name, self.internal_wait_monitor_outputs, 'internal', self.internal_wait_monitor_outputs, 'internal', self.internal_wait_monitor_outputs, 'internal')
def generate_code(self, hdf5_file): # Generate the hardware instructions hdf5_file.create_group('/devices/' + self.name) PseudoclockDevice.generate_code(self, hdf5_file) dig_outputs = self.direct_outputs.get_all_outputs() npg_inst = self.convert_to_npg_inst(dig_outputs) self.write_npg_inst_to_h5(npg_inst, hdf5_file)
def generate_code(self, hdf5_file): PseudoclockDevice.generate_code(self, hdf5_file) group = hdf5_file['devices'].create_group(self.name) # compress clock instructions with the same period: This will # halve the number of instructions roughly, since the PineBlaster # does not have a 'slow clock': reduced_instructions = [] for instruction in self.pseudoclock.clock: if instruction == 'WAIT': # The following period and reps indicates a wait instruction reduced_instructions.append({'period': 0, 'reps': 1}) continue reps = instruction['reps'] # period is in quantised units: period = int(round(instruction['step']/self.clock_resolution)) if reduced_instructions and reduced_instructions[-1]['period'] == period: reduced_instructions[-1]['reps'] += reps else: reduced_instructions.append({'period': period, 'reps': reps}) # The following period and reps indicates a stop instruction: reduced_instructions.append({'period': 0, 'reps': 0}) if len(reduced_instructions) > self.max_instructions: raise LabscriptError("%s %s has too many instructions. It has %d and can only support %d"%(self.description, self.name, len(reduced_instructions), self.max_instructions)) # Store these instructions to the h5 file: dtypes = [('period',int),('reps',int)] pulse_program = np.zeros(len(reduced_instructions),dtype=dtypes) for i, instruction in enumerate(reduced_instructions): pulse_program[i]['period'] = instruction['period'] pulse_program[i]['reps'] = instruction['reps'] group.create_dataset('PULSE_PROGRAM', compression = config.compression, data=pulse_program) # TODO: is this needed, the PulseBlasters don't save it... self.set_property('is_master_pseudoclock', self.is_master_pseudoclock, location='device_properties') self.set_property('stop_time', self.stop_time, location='device_properties')
def add_device(self, device): if not self.child_devices and isinstance(device, Pseudoclock): PseudoclockDevice.add_device(self, device) elif isinstance(device, Pseudoclock): raise LabscriptError( f'The {self.name} PseudoclockDevice only supports a single Pseudoclock, so it automatically creates one.' + f'Instead of instantiating your own Pseudoclock object, please use the internal one stored in {self.name}.pseudoclock' ) elif isinstance(device, DigitalOut): raise LabscriptError( f'You have connected {device.name} directly to {self.name}, which is not allowed. You should instead specify ' + f'the parent_device of {device.name} as {self.name}.direct_outputs' ) elif isinstance(device, ClockLine): raise LabscriptError( f'You have connected {device.name} directly to {self.name}, which is not allowed. You should instead specify ' + f'the parent_device of {device.name} as {self.name}.pseudoclock' ) else: raise LabscriptError( f'You have connected {device.name} (class {device.__class__}) to {self.name}, but {self.name} does not support children with that class.' )
def __init__(self, name, trigger_device=None, trigger_connection=None, usbport='COM1'): PseudoclockDevice.__init__(self, name, trigger_device, trigger_connection) self.BLACS_connection = usbport # create Pseudoclock and clockline self._pseudoclock = PineBlasterPseudoclock('%s_pseudoclock'%name, self, 'clock') # possibly a better connection name than 'clock'? # Create the internal direct output clock_line self._clock_line = ClockLine('%s_clock_line'%name, self.pseudoclock, 'internal')
def add_device(self, device): if len(self.child_devices) < 2 and isinstance(device, Pseudoclock): PseudoclockDevice.add_device(self, device) elif isinstance(device, Pseudoclock): raise LabscriptError('The %s %s automatically creates a Pseudoclock because it only supports one. '%(self.description, self.name) + 'Instead of instantiating your own Pseudoclock object, please use the internal' + ' one stored in %s.pseudoclock'%self.name) else: raise LabscriptError('You have connected %s (class %s) to %s, but %s does not support children with that class.'%(device.name, device.__class__, self.name, self.name))
def generate_code(self, hdf5_file): PseudoclockDevice.generate_code(self, hdf5_file) group = hdf5_file['devices'].create_group(self.name) # compress clock instructions with the same period: This will # halve the number of instructions roughly, since the PineBlaster # does not have a 'slow clock': reduced_instructions = [] current_wait_index = 0 wait_table = sorted(compiler.wait_table) if not self.is_master_pseudoclock: reduced_instructions.append({'on': 0, 'off': ((self.trigger_edge_type=='rising') << 1) + 1, 'reps': 0}) for instruction in self.pseudoclock.clock: if instruction == 'WAIT': # The following period and reps indicates a wait instruction wait_timeout = compiler.wait_table[wait_table[current_wait_index]][1] current_wait_index += 1 # The actual wait instruction. # on_counts correspond to teh number of reference clock cycles # to wait for external trigger before auto-resuming. # It overcounts by 1 here because the logic on the FPGA is different for the first reference clock cycle # (you cannot resume until the after second reference clock cycle), so we subtract 1 off the on counts # so that it times-out after the correct number of samples reduced_instructions.append({'on': round(wait_timeout/self.clock_resolution)-1, 'off': ((self.trigger_edge_type=='rising') << 1) + 1, 'reps': 0}) continue reps = instruction['reps'] # period is in quantised units: periods = int(round(instruction['step']/self.clock_resolution)) # Get the "high" half of the clock period on_period = int(periods/2) # Use the remainder to calculate the "off period" (allows slightly assymetric clock signals so to minimise timing errors) off_period = periods-on_period if reduced_instructions and reduced_instructions[-1]['on'] == on_period and reduced_instructions[-1]['off'] == off_period: reduced_instructions[-1]['reps'] += reps else: reduced_instructions.append({'on': on_period, 'off': off_period, 'reps': reps}) if len(reduced_instructions) > self.max_instructions: raise LabscriptError("%s %s has too many instructions. It has %d and can only support %d"%(self.description, self.name, len(reduced_instructions), self.max_instructions)) # Store these instructions to the h5 file: dtypes = [('on_period',np.int64),('off_period',np.int64),('reps',np.int64)] pulse_program = np.zeros(len(reduced_instructions),dtype=dtypes) for i, instruction in enumerate(reduced_instructions): pulse_program[i]['on_period'] = instruction['on'] pulse_program[i]['off_period'] = instruction['off'] pulse_program[i]['reps'] = instruction['reps'] group.create_dataset('PULSE_PROGRAM', compression = config.compression, data=pulse_program) self.set_property('is_master_pseudoclock', self.is_master_pseudoclock, location='device_properties') self.set_property('stop_time', self.stop_time, location='device_properties')
def __init__(self, name='dummy_pseudoclock', BLACS_connection='dummy_connection', **kwargs): self.BLACS_connection = BLACS_connection PseudoclockDevice.__init__(self, name, None, None, **kwargs) self.pseudoclock = Pseudoclock(self.name + '_pseudoclock', self, 'pseudoclock') self.clockline = ClockLine(name='clockline', pseudoclock=self.pseudoclock, connection='dummy')
def __init__(self, name, ip_address, trigger_device=None, trigger_connection=None): PseudoclockDevice.__init__(self, name, trigger_device, trigger_connection) self.BLACS_connection = ip_address # create Pseudoclock and clockline self._pseudoclock = RFBlasterPseudoclock('%s_pseudoclock'%name, self, 'clock') # possibly a better connection name than 'clock'? # Create the internal direct output clock_line self._clock_line = ClockLine('%s_clock_line'%name, self.pseudoclock, 'internal') # Create the internal intermediate device connected to the above clock line # This will have the DDSs of the RFBlaster connected to it self._direct_output_device = RFBlasterDirectOutputs('%s_direct_output_device'%name, self._clock_line)
def add_device(self, device): if not self.child_devices and isinstance(device, Pseudoclock): PseudoclockDevice.add_device(self, device) elif isinstance(device, Pseudoclock): raise LabscriptError('The %s %s automatically creates a Pseudoclock because it only supports one. '%(self.description, self.name) + 'Instead of instantiating your own Pseudoclock object, please use the internal' + ' one stored in %s.pseudoclock'%self.name) elif isinstance(device, DDS): #TODO: Defensive programming: device.name may not exist! raise LabscriptError('You have connected %s directly to %s, which is not allowed. You should instead specify the parent_device of %s as %s.direct_outputs'%(device.name, self.name, device.name, self.name)) else: raise LabscriptError('You have connected %s (class %s) to %s, but %s does not support children with that class.'%(device.name, device.__class__, self.name, self.name))
def __init__(self, name='dummy_pseudoclock', BLACS_connection='dummy_connection', **kwargs): self.BLACS_connection = BLACS_connection PseudoclockDevice.__init__(self, name, None, None, **kwargs) self._pseudoclock = _DummyPseudoclock( name=f'{name}_pseudoclock', pseudoclock_device=self, connection='pseudoclock', ) self._clock_line = ClockLine( name=f'{name}_clock_line', pseudoclock=self.pseudoclock, connection='internal', )
def __init__(self, name='narwhal_pulsegen', usbport='autodetect', **kwargs): self.BLACS_connection = usbport PseudoclockDevice.__init__(self, name, None, None, **kwargs) # Create the internal pseudoclock self._pseudoclock = NarwhalPulseGenPseudoclock( name=f'{name}_pseudoclock', pseudoclock_device=self, connection='pseudoclock', ) # Create the internal direct output clock_line self._direct_output_clock_line = ClockLine( name=f'{name}_direct_output_clock_line', pseudoclock=self.pseudoclock, connection='internal', ramping_allowed=False, ) # Create the internal intermediate device connected to the above clock line # This will have the direct DigitalOuts of the NarwhalPulseGen connected to it self._direct_output_device = NarwhalPulseGenDirectOutputs( name=f'{name}_direct_output_device', parent_device=self._direct_output_clock_line)
def generate_code(self, hdf5_file): PseudoclockDevice.generate_code(self, hdf5_file) group = self.init_device_group(hdf5_file) self.set_property('stop_time', self.stop_time, location='device_properties')
def generate_code(self, hdf5_file): from rfblaster import caspr import rfblaster.rfjuice rfjuice_folder = os.path.dirname(rfblaster.rfjuice.__file__) import rfblaster.rfjuice.const as c from rfblaster.rfjuice.cython.make_diff_table import make_diff_table from rfblaster.rfjuice.cython.compile import compileD # from rfblaster.rfjuice.compile import compileD import tempfile from subprocess import Popen, PIPE # Generate clock and save raw instructions to the h5 file: PseudoclockDevice.generate_code(self, hdf5_file) dtypes = [('time',float),('amp0',float),('freq0',float),('phase0',float),('amp1',float),('freq1',float),('phase1',float)] times = self.pseudoclock.times[self._clock_line] data = np.zeros(len(times),dtype=dtypes) data['time'] = times for dds in self.direct_outputs.child_devices: prefix, connection = dds.connection.split() data['freq%s'%connection] = dds.frequency.raw_output data['amp%s'%connection] = dds.amplitude.raw_output data['phase%s'%connection] = dds.phase.raw_output group = hdf5_file['devices'].create_group(self.name) group.create_dataset('TABLE_DATA',compression=config.compression, data=data) # Quantise the data and save it to the h5 file: quantised_dtypes = [('time',np.int64), ('amp0',np.int32), ('freq0',np.int32), ('phase0',np.int32), ('amp1',np.int32), ('freq1',np.int32), ('phase1',np.int32)] quantised_data = np.zeros(len(times),dtype=quantised_dtypes) quantised_data['time'] = np.array(c.tT*1e6*data['time']+0.5) for dds in range(2): # TODO: bounds checking # Adding 0.5 to each so that casting to integer rounds: quantised_data['freq%d'%dds] = np.array(c.fF*1e-6*data['freq%d'%dds] + 0.5) quantised_data['amp%d'%dds] = np.array((2**c.bitsA - 1)*data['amp%d'%dds] + 0.5) quantised_data['phase%d'%dds] = np.array(c.pP*data['phase%d'%dds] + 0.5) group.create_dataset('QUANTISED_DATA',compression=config.compression, data=quantised_data) # Generate some assembly code and compile it to machine code: assembly_group = group.create_group('ASSEMBLY_CODE') binary_group = group.create_group('BINARY_CODE') diff_group = group.create_group('DIFF_TABLES') # When should the RFBlaster wait for a trigger? quantised_trigger_times = np.array([c.tT*1e6*t + 0.5 for t in self.trigger_times], dtype=np.int64) for dds in range(2): abs_table = np.zeros((len(times), 4),dtype=np.int64) abs_table[:,0] = quantised_data['time'] abs_table[:,1] = quantised_data['amp%d'%dds] abs_table[:,2] = quantised_data['freq%d'%dds] abs_table[:,3] = quantised_data['phase%d'%dds] # split up the table into chunks delimited by trigger times: abs_tables = [] for i, t in enumerate(quantised_trigger_times): subtable = abs_table[abs_table[:,0] >= t] try: next_trigger_time = quantised_trigger_times[i+1] except IndexError: # No next trigger time pass else: subtable = subtable[subtable[:,0] < next_trigger_time] subtable[:,0] -= t abs_tables.append(subtable) # convert to diff tables: diff_tables = [make_diff_table(tab) for tab in abs_tables] # Create temporary files, get their paths, and close them: with tempfile.NamedTemporaryFile(delete=False) as f: temp_assembly_filepath = f.name with tempfile.NamedTemporaryFile(delete=False) as f: temp_binary_filepath = f.name try: # Compile to assembly: with open(temp_assembly_filepath,'w') as assembly_file: for i, dtab in enumerate(diff_tables): compileD(dtab, assembly_file, init=(i == 0), jump_to_start=(i == 0), jump_from_end=False, close_end=(i == len(diff_tables) - 1), local_loop_pre = str(i), set_defaults = (i==0)) # Save the assembly to the h5 file: with open(temp_assembly_filepath,) as assembly_file: assembly_code = assembly_file.read() assembly_group.create_dataset('DDS%d'%dds, data=assembly_code) for i, diff_table in enumerate(diff_tables): diff_group.create_dataset('DDS%d_difftable%d'%(dds,i), compression=config.compression, data=diff_table) # compile to binary: compilation = Popen([caspr,temp_assembly_filepath,temp_binary_filepath], stdout=PIPE, stderr=PIPE, cwd=rfjuice_folder,startupinfo=startupinfo) stdout, stderr = compilation.communicate() if compilation.returncode: print stdout raise LabscriptError('RFBlaster compilation exited with code %d\n\n'%compilation.returncode + 'Stdout was:\n %s\n'%stdout + 'Stderr was:\n%s\n'%stderr) # Save the binary to the h5 file: with open(temp_binary_filepath,'rb') as binary_file: binary_data = binary_file.read() # has to be numpy.string_ (string_ in this namespace, # imported from pylab) as python strings get stored # as h5py as 'variable length' strings, which 'cannot # contain embedded nulls'. Presumably our binary data # must contain nulls sometimes. So this crashes if we # don't convert to a numpy 'fixes length' string: binary_group.create_dataset('DDS%d'%dds, data=np.string_(binary_data)) finally: # Delete the temporary files: os.remove(temp_assembly_filepath) os.remove(temp_binary_filepath)
def generate_code(self, hdf5_file): from rfblaster import caspr import rfblaster.rfjuice rfjuice_folder = os.path.dirname(rfblaster.rfjuice.__file__) import rfblaster.rfjuice.const as c from rfblaster.rfjuice.cython.make_diff_table import make_diff_table from rfblaster.rfjuice.cython.compile import compileD # from rfblaster.rfjuice.compile import compileD import tempfile from subprocess import Popen, PIPE # Generate clock and save raw instructions to the h5 file: PseudoclockDevice.generate_code(self, hdf5_file) dtypes = [('time',float),('amp0',float),('freq0',float),('phase0',float),('amp1',float),('freq1',float),('phase1',float)] times = self.pseudoclock.times[self._clock_line] data = np.zeros(len(times),dtype=dtypes) data['time'] = times for dds in self.direct_outputs.child_devices: prefix, connection = dds.connection.split() data['freq%s'%connection] = dds.frequency.raw_output data['amp%s'%connection] = dds.amplitude.raw_output data['phase%s'%connection] = dds.phase.raw_output group = hdf5_file['devices'].create_group(self.name) group.create_dataset('TABLE_DATA',compression=config.compression, data=data) # Quantise the data and save it to the h5 file: quantised_dtypes = [('time',np.int64), ('amp0',np.int32), ('freq0',np.int32), ('phase0',np.int32), ('amp1',np.int32), ('freq1',np.int32), ('phase1',np.int32)] quantised_data = np.zeros(len(times),dtype=quantised_dtypes) quantised_data['time'] = np.array(c.tT*1e6*data['time']+0.5) for dds in range(2): # TODO: bounds checking # Adding 0.5 to each so that casting to integer rounds: quantised_data['freq%d'%dds] = np.array(c.fF*1e-6*data['freq%d'%dds] + 0.5) quantised_data['amp%d'%dds] = np.array((2**c.bitsA - 1)*data['amp%d'%dds] + 0.5) quantised_data['phase%d'%dds] = np.array(c.pP*data['phase%d'%dds] + 0.5) group.create_dataset('QUANTISED_DATA',compression=config.compression, data=quantised_data) # Generate some assembly code and compile it to machine code: assembly_group = group.create_group('ASSEMBLY_CODE') binary_group = group.create_group('BINARY_CODE') diff_group = group.create_group('DIFF_TABLES') # When should the RFBlaster wait for a trigger? quantised_trigger_times = np.array([c.tT*1e6*t + 0.5 for t in self.trigger_times], dtype=np.int64) for dds in range(2): abs_table = np.zeros((len(times), 4),dtype=np.int64) abs_table[:,0] = quantised_data['time'] abs_table[:,1] = quantised_data['amp%d'%dds] abs_table[:,2] = quantised_data['freq%d'%dds] abs_table[:,3] = quantised_data['phase%d'%dds] # split up the table into chunks delimited by trigger times: abs_tables = [] for i, t in enumerate(quantised_trigger_times): subtable = abs_table[abs_table[:,0] >= t] try: next_trigger_time = quantised_trigger_times[i+1] except IndexError: # No next trigger time pass else: subtable = subtable[subtable[:,0] < next_trigger_time] subtable[:,0] -= t abs_tables.append(subtable) # convert to diff tables: diff_tables = [make_diff_table(tab) for tab in abs_tables] # Create temporary files, get their paths, and close them: with tempfile.NamedTemporaryFile(delete=False) as f: temp_assembly_filepath = f.name with tempfile.NamedTemporaryFile(delete=False) as f: temp_binary_filepath = f.name try: # Compile to assembly: with open(temp_assembly_filepath,'w') as assembly_file: for i, dtab in enumerate(diff_tables): compileD(dtab, assembly_file, init=(i == 0), jump_to_start=(i == 0), jump_from_end=False, close_end=(i == len(diff_tables) - 1), local_loop_pre = str(i), set_defaults = (i==0)) # Save the assembly to the h5 file: with open(temp_assembly_filepath,) as assembly_file: assembly_code = assembly_file.read() assembly_group.create_dataset('DDS%d'%dds, data=assembly_code) for i, diff_table in enumerate(diff_tables): diff_group.create_dataset('DDS%d_difftable%d'%(dds,i), compression=config.compression, data=diff_table) # compile to binary: compilation = Popen([caspr,temp_assembly_filepath,temp_binary_filepath], stdout=PIPE, stderr=PIPE, cwd=rfjuice_folder,startupinfo=startupinfo) stdout, stderr = compilation.communicate() if compilation.returncode: print(stdout) raise LabscriptError('RFBlaster compilation exited with code %d\n\n'%compilation.returncode + 'Stdout was:\n %s\n'%stdout + 'Stderr was:\n%s\n'%stderr) # Save the binary to the h5 file: with open(temp_binary_filepath,'rb') as binary_file: binary_data = binary_file.read() # has to be numpy.string_ (string_ in this namespace, # imported from pylab) as python strings get stored # as h5py as 'variable length' strings, which 'cannot # contain embedded nulls'. Presumably our binary data # must contain nulls sometimes. So this crashes if we # don't convert to a numpy 'fixes length' string: binary_group.create_dataset('DDS%d'%dds, data=np.string_(binary_data)) finally: # Delete the temporary files: os.remove(temp_assembly_filepath) os.remove(temp_binary_filepath)
def generate_code(self, hdf5_file): """Generates the hardware instructions for the pseudoclocks. This is automatically called by the labscript compiler. Args: hdf5_file (:class:`h5py.File`): h5py file object for shot """ PseudoclockDevice.generate_code(self, hdf5_file) group = self.init_device_group(hdf5_file) current_wait_index = 0 wait_table = sorted(compiler.wait_table) # For each pseudoclock for i, pseudoclock in enumerate(self.pseudoclocks): current_wait_index = 0 # Compress clock instructions with the same half_period reduced_instructions = [] for instruction in pseudoclock.clock: if instruction == "WAIT": # If we're using the internal wait monitor, set the timeout if self.use_wait_monitor: # Get the wait timeout value wait_timeout = compiler.wait_table[ wait_table[current_wait_index]][1] current_wait_index += 1 # The following half_period and reps indicates a wait instruction reduced_instructions.append({ "half_period": round(wait_timeout / (self.clock_resolution / 2)), "reps": 0, }) continue # Else, set an indefinite wait and wait for a trigger from something else. else: # Two waits in a row are an indefinite wait reduced_instructions.append({ "half_period": 2**32 - 1, "reps": 0, }) reduced_instructions.append({ "half_period": 2**32 - 1, "reps": 0, }) # Normal instruction reps = instruction["reps"] # half_period is in quantised units: half_period = int( round(instruction["step"] / self.clock_resolution)) if ( # If there is a previous instruction reduced_instructions # And it's not a wait and reduced_instructions[-1]["reps"] != 0 # And the half_periods match and reduced_instructions[-1]["half_period"] == half_period # And the sum of the previous reps and current reps won't push it over the limit and (reduced_instructions[-1]["reps"] + reps) < (2**32 - 1)): # Combine instructions! reduced_instructions[-1]["reps"] += reps else: # New instruction reduced_instructions.append({ "half_period": half_period, "reps": reps }) # Only add this if there is room in the instruction table. The PrawnBlaster # firmware has extre room at the end for an instruction that is always 0 # and cannot be set over serial! if len(reduced_instructions) != self.max_instructions: # The following half_period and reps indicates a stop instruction: reduced_instructions.append({"half_period": 0, "reps": 0}) # Check we have not exceeded the maximum number of supported instructions # for this number of speudoclocks if len(reduced_instructions) > self.max_instructions: raise LabscriptError( f"{self.description} {self.name}.clocklines[{i}] has too many instructions. It has {len(reduced_instructions)} and can only support {self.max_instructions}" ) # Store these instructions to the h5 file: dtypes = [("half_period", int), ("reps", int)] pulse_program = np.zeros(len(reduced_instructions), dtype=dtypes) for j, instruction in enumerate(reduced_instructions): pulse_program[j]["half_period"] = instruction["half_period"] pulse_program[j]["reps"] = instruction["reps"] group.create_dataset(f"PULSE_PROGRAM_{i}", compression=config.compression, data=pulse_program) # This is needed so the BLACS worker knows whether or not to be a wait monitor self.set_property( "is_master_pseudoclock", self.is_master_pseudoclock, location="device_properties", ) self.set_property("stop_time", self.stop_time, location="device_properties")
def __init__( self, name, trigger_device=None, trigger_connection=None, com_port="COM1", num_pseudoclocks=1, out_pins=None, in_pins=None, clock_frequency=100e6, external_clock_pin=None, use_wait_monitor=True, ): """PrawnBlaster Pseudoclock labscript device. This labscript device creates Pseudoclocks based on the PrawnBlaster, a Raspberry Pi Pico with custom firmware. Args: name (str): python variable name to assign to the PrawnBlaster com_port (str): COM port assigned to the PrawnBlaster by the OS. Takes the form of `'COMd'`, where `d` is an integer. num_pseudoclocks (int): Number of pseudoclocks to create. Ranges from 1-4. trigger_device (:class:`~labscript.IntermediateDevice`, optional): Device that will send the hardware start trigger when using the PrawnBlaster as a secondary Pseudoclock. trigger_connection (str, optional): Which output of the `trigger_device` is connected to the PrawnBlaster hardware trigger input. out_pins (list, optional): What outpins to use for the pseudoclock outputs. Must have length of at least `num_pseudoclocks`. Defaults to `[9,11,13,15]` in_pins (list, optional): What inpins to use for the pseudoclock hardware triggering. Must have length of at least `num_pseudoclocks`. Defaults to `[0,0,0,0]` clock_frequency (float, optional): Frequency of clock. Standard range accepts up to 133 MHz. An experimental overclocked firmware is available that allows higher frequencies. external_clock_pin (int, optional): If not `None` (the default), the PrawnBlaster uses an external clock on the provided pin. Valid options are `20` and `22`. The external frequency must be defined using `clock_frequency`. use_wait_monitor (bool, optional): Configure the PrawnBlaster to perform its own wait monitoring. """ # Check number of pseudoclocks is within range if num_pseudoclocks < 1 or num_pseudoclocks > 4: raise LabscriptError( f"The PrawnBlaster {name} only supports between 1 and 4 pseudoclocks" ) # Update the specs based on the number of pseudoclocks self.max_instructions = self.max_instructions // num_pseudoclocks # Update the specs based on the clock frequency if self.clock_resolution != 2 / clock_frequency: factor = (2 / clock_frequency) / self.clock_resolution self.clock_limit *= factor self.clock_resolution *= factor self.input_response_time *= factor self.trigger_delay *= factor self.trigger_minimum_duration *= factor self.wait_delay *= factor # Instantiate the base class PseudoclockDevice.__init__(self, name, trigger_device, trigger_connection) self.num_pseudoclocks = num_pseudoclocks # Wait monitor can only be used if this is the master pseudoclock self.use_wait_monitor = use_wait_monitor and self.is_master_pseudoclock # Set the BLACS connections self.BLACS_connection = com_port # Check in/out pins if out_pins is None: out_pins = [9, 11, 13, 15] if in_pins is None: in_pins = [0, 0, 0, 0] if len(out_pins) < num_pseudoclocks: raise LabscriptError( f"The PrawnBlaster {self.name} is configured with {num_pseudoclocks} but only has pin numbers specified for {len(out_pins)}." ) else: self.out_pins = out_pins[:num_pseudoclocks] if len(in_pins) < num_pseudoclocks: raise LabscriptError( f"The PrawnBlaster {self.name} is configured with {num_pseudoclocks} but only has pin numbers specified for {len(in_pins)}." ) else: self.in_pins = in_pins[:num_pseudoclocks] self._pseudoclocks = [] self._clocklines = [] for i in range(num_pseudoclocks): self._pseudoclocks.append( _PrawnBlasterPseudoclock( i, name=f"{name}_pseudoclock_{i}", pseudoclock_device=self, connection=f"pseudoclock {i}", )) self._clocklines.append( ClockLine( name=f"{name}_clock_line_{i}", pseudoclock=self._pseudoclocks[i], connection=f"GPIO {self.out_pins[i]}", )) if self.use_wait_monitor: # Create internal devices for connecting to a wait monitor self.__wait_monitor_dummy_pseudoclock = _PrawnBlasterDummyPseudoclock( "%s__dummy_wait_pseudoclock" % name, self, "_") self.__wait_monitor_dummy_clock_line = _PrawnBlasterDummyClockLine( "%s__dummy_wait_clock_line" % name, self.__wait_monitor_dummy_pseudoclock, "_", ) self.__wait_monitor_intermediate_device = ( _PrawnBlasterDummyIntermediateDevice( "%s_internal_wait_monitor_outputs" % name, self.__wait_monitor_dummy_clock_line, )) # Create the wait monitor WaitMonitor( "%s__wait_monitor" % name, self.internal_wait_monitor_outputs, "internal", self.internal_wait_monitor_outputs, "internal", self.internal_wait_monitor_outputs, "internal", )
def __init__(self, name='dummy_pseudoclock', BLACS_connection='dummy_connection', **kwargs): self.BLACS_connection = BLACS_connection PseudoclockDevice.__init__(self, name, None, None, **kwargs)