Source code for labscript_devices.PulseBlaster

#####################################################################
#                                                                   #
# /PulseBlaster.py                                                  #
#                                                                   #
# Copyright 2013, Monash University                                 #
#                                                                   #
# This file is part of the module labscript_devices, in the         #
# labscript suite (see http://labscriptsuite.org), and is           #
# licensed under the Simplified BSD License. See the license.txt    #
# file in the root of the project for the full license.             #
#                                                                   #
#####################################################################

from labscript_devices import BLACS_tab, runviewer_parser
from labscript_utils import dedent

from labscript import (
    Device,
    PseudoclockDevice,
    Pseudoclock,
    ClockLine,
    IntermediateDevice,
    DigitalQuantity,
    DigitalOut,
    DDS,
    DDSQuantity,
    config,
    LabscriptError,
    set_passed_properties,
    compiler,
)

import numpy as np

import labscript_utils.h5_lock, h5py

import time

[docs] class PulseBlasterDDS(DDSQuantity): description = 'PulseBlasterDDS' def __init__(self, *args, **kwargs): if 'call_parents_add_device' in kwargs: call_parents_add_device = kwargs['call_parents_add_device'] else: call_parents_add_device = True kwargs['call_parents_add_device'] = False DDSQuantity.__init__(self, *args, **kwargs) self.gate = DigitalQuantity(self.name + '_gate', self, 'gate') self.phase_reset = DigitalQuantity(self.name + '_phase_reset', self, 'phase_reset') if call_parents_add_device: self.parent_device.add_device(self)
[docs] def hold_phase(self, t): self.phase_reset.go_high(t)
[docs] def release_phase(self, t): self.phase_reset.go_low(t)
profiles = {}
[docs] def profile(funct): func = funct.__name__ if func not in profiles: profiles[func] = {'total_time':0, 'min':None, 'max':0, 'num_calls':0, 'average_time_per_call':0} def new_func(*args,**kwargs): start_time = time.time() ret = funct(*args,**kwargs) runtime = time.time()-start_time profiles[func]['total_time'] += runtime profiles[func]['num_calls'] += 1 profiles[func]['min'] = profiles[func]['min'] if profiles[func]['min'] is not None and profiles[func]['min'] < runtime else runtime profiles[func]['max'] = profiles[func]['max'] if profiles[func]['max'] > runtime else runtime profiles[func]['average_time_per_call'] = profiles[func]['total_time']/profiles[func]['num_calls'] return ret # return new_func return funct
[docs] def start_profile(name): if name not in profiles: profiles[name] = {'total_time':0, 'min':None, 'max':0, 'num_calls':0, 'average_time_per_call':0} if 'start_time' in profiles[name]: raise Exception('You cannot call start_profile for %s without first calling stop_profile'%name) profiles[name]['start_time'] = time.time()
[docs] def stop_profile(name): if name not in profiles or 'start_time' not in profiles[name]: raise Exception('You must first call start_profile for %s before calling stop_profile') runtime = time.time()-profiles[name]['start_time'] del profiles[name]['start_time'] profiles[name]['total_time'] += runtime profiles[name]['num_calls'] += 1 profiles[name]['min'] = profiles[name]['min'] if profiles[name]['min'] is not None and profiles[name]['min'] < runtime else runtime profiles[name]['max'] = profiles[name]['max'] if profiles[name]['max'] > runtime else runtime profiles[name]['average_time_per_call'] = profiles[name]['total_time']/profiles[name]['num_calls']
[docs] class PulseBlaster(PseudoclockDevice): pb_instructions = {'CONTINUE': 0, 'STOP': 1, 'LOOP': 2, 'END_LOOP': 3, 'BRANCH': 6, 'LONG_DELAY': 7, 'WAIT': 8} description = 'PB-DDSII-300' clock_limit = 8.3e6 # Slight underestimate I think. clock_resolution = 26.6666666666666666e-9 # TODO: Add n_dds and generalise code n_flags = 12 core_clock_freq = 75 # MHz # This value is coupled to a value in the PulseBlaster worker process of BLACS # This number was found experimentally but is determined theoretically by the # instruction lengths in BLACS, and a finite delay in the PulseBlaster # # IF YOU CHANGE ONE, YOU MUST CHANGE THE OTHER! trigger_delay = 250e-9 wait_delay = 100e-9 trigger_edge_type = 'falling' # This device can only have Pseudoclock children (digital outs and DDS outputs should be connected to a child device) allowed_children = [Pseudoclock] @set_passed_properties( property_names = {"connection_table_properties": ["firmware", "programming_scheme"], "device_properties": ["pulse_width", "max_instructions", "time_based_stop_workaround", "time_based_stop_workaround_extra_time"]} ) def __init__(self, name, trigger_device=None, trigger_connection=None, board_number=0, firmware = '', programming_scheme='pb_start/BRANCH', pulse_width='symmetric', max_instructions=4000, time_based_stop_workaround=False, time_based_stop_workaround_extra_time=0.5, **kwargs): PseudoclockDevice.__init__(self, name, trigger_device, trigger_connection, **kwargs) self.BLACS_connection = board_number # TODO: Implement capability checks based on firmware revision of PulseBlaster self.firmware_version = firmware # time_based_stop_workaround is for old pulseblaster models which do # not respond correctly to status checks. These models provide no way # to know when the shot has completed. So if # time_based_stop_workaround=True, we fall back to simply waiting # until stop_time (plus the timeout of all waits) and assuming in the # BLACS worker that the end of the shot occurs at this time. # time_based_stop_workaround_extra_time is a configurable duration for # how much longer than stop_time we should wait, to allow for software # timing variation. Note that since the maximum duration of all waits # is included in the calculation of the time at which the experiemnt # should be stopped, attention should be paid to the timeout argument # of all waits, since if it is larger than necessary, this will # increase the duration of your shots even if the waits are actually # short in duration. # If we are the master pseudoclock, there are two ways we can start and stop the PulseBlaster. # # 'pb_start/BRANCH': # Call pb_start(), to start us in software time. At the end of the program BRANCH to # a WAIT instruction at the beginning, ready to start again. # # 'pb_stop_programming/STOP' # Defer calling pb_stop_programming() until everything is ready to start. # Then, the next hardware trigger to the PulseBlaster will start it. # It is important not to call pb_stop_programming() too soon, because if the PulseBlaster is receiving # repeated triggers (such as from a 50/60-Hz line trigger), then we do not want it to start running # before everything is ready. Not calling pb_stop_programming() until we are ready ensures triggers are # ignored until then. In this case, we end with a STOP instruction, ensuring further triggers do not cause # the PulseBlaster to run repeatedly until start_programming()/stop_programming() are called once more. # The programming scheme is saved as a property in the connection table and read out by BLACS. possible_programming_schemes = ['pb_start/BRANCH', 'pb_stop_programming/STOP'] if programming_scheme not in possible_programming_schemes: raise LabscriptError('programming_scheme must be one of %s'%str(possible_programming_schemes)) if trigger_device is not None and programming_scheme != 'pb_start/BRANCH': raise LabscriptError('only the master pseudoclock can use a programming scheme other than \'pb_start/BRANCH\'') self.programming_scheme = programming_scheme # This is the minimum duration of a pulseblaster instruction. We save this now # because clock_limit will be modified to reflect child device limitations and # other things, but this remains the minimum instruction delay regardless of all # that. self.min_delay = 0.5 / self.clock_limit # For pulseblaster instructions lasting longer than the below duration, we will # instead use some multiple of the below, and then a regular instruction for the # remainder. The max instruction length of a pulseblaster is actually 2**32 # clock cycles, but we subtract the minimum delay so that if the remainder is # less than the minimum instruction length, we can add self.long_delay to it (and # reduce the number of repetitions of the long delay by one), to keep it above # the minimum delay without exceeding the true maximum delay. self.long_delay = 2**32 / (self.core_clock_freq * 1e6) - self.min_delay if pulse_width == 'minimum': pulse_width = 0.5/self.clock_limit # the shortest possible elif pulse_width != 'symmetric': if not isinstance(pulse_width, (float, int, np.integer)): msg = ("pulse_width must be 'symmetric', 'minimum', or a number " + "specifying a fixed pulse width to be used for clocking signals") raise ValueError(msg) if pulse_width < 0.5/self.clock_limit: message = ('pulse_width cannot be less than 0.5/%s.clock_limit '%self.__class__.__name__ + '( = %s seconds)'%str(0.5/self.clock_limit)) raise LabscriptError(message) # Round pulse width up to the nearest multiple of clock resolution: quantised_pulse_width = 2*pulse_width/self.clock_resolution quantised_pulse_width = int(quantised_pulse_width) + 1 # ceil(quantised_pulse_width) # This will be used as the high time of clock ticks: pulse_width = quantised_pulse_width*self.clock_resolution/2 # This pulse width, if larger than the minimum, may limit how fast we can tick. # Update self.clock_limit accordingly. minimum_low_time = 0.5/self.clock_limit if pulse_width > minimum_low_time: self.clock_limit = 1/(pulse_width + minimum_low_time) self.pulse_width = pulse_width self.max_instructions = max_instructions # Create the internal pseudoclock self._pseudoclock = Pseudoclock('%s_pseudoclock'%name, self, 'clock') # possibly a better connection name than 'clock'? # Create the internal direct output clock_line self._direct_output_clock_line = ClockLine('%s_direct_output_clock_line'%name, self.pseudoclock, 'internal', ramping_allowed = False) # Create the internal intermediate device connected to the above clock line # This will have the direct DigitalOuts of DDSs of the PulseBlaster connected to it self._direct_output_device = PulseBlasterDirectOutputs('%s_direct_output_device'%name, self._direct_output_clock_line) @property def pseudoclock(self): return self._pseudoclock @property def direct_outputs(self): return self._direct_output_device
[docs] 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) or isinstance(device, PulseBlasterDDS) or isinstance(device, DigitalOut): #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))
[docs] def flag_valid(self, flag): if -1 < flag < self.n_flags: return True return False
[docs] def flag_is_clock(self, flag): for clock_line in self.pseudoclock.child_devices: if clock_line.connection == 'internal': #ignore internal clockline continue if flag == self.get_flag_number(clock_line.connection): return True return False
[docs] def get_flag_number(self, connection): # TODO: Error checking prefix, connection = connection.split() return int(connection)
[docs] def get_direct_outputs(self): """Finds out which outputs are directly attached to the PulseBlaster""" dig_outputs = [] dds_outputs = [] for output in self.direct_outputs.get_all_outputs(): # If we are a child of a DDS if isinstance(output.parent_device, DDS) or isinstance(output.parent_device, PulseBlasterDDS): # and that DDS has not been processed yet if output.parent_device not in dds_outputs: # process the DDS instead of the child output = output.parent_device else: # ignore the child continue # only check DDS and DigitalOuts (so ignore the children of the DDS) if isinstance(output,DDS) or isinstance(output,PulseBlasterDDS) or isinstance(output, DigitalOut): # get connection number and prefix try: prefix, connection = output.connection.split() assert prefix == 'flag' or prefix == 'dds' connection = int(connection) except: raise LabscriptError('%s %s has invalid connection string: \'%s\'. '%(output.description,output.name,str(output.connection)) + 'Format must be \'flag n\' with n an integer less than %d, or \'dds n\' with n less than 2.'%self.n_flags) # run checks on the connection string to make sure it is valid # TODO: Most of this should be done in add_device() No? if prefix == 'flag' and not self.flag_valid(connection): raise LabscriptError('%s is set as connected to flag %d of %s. '%(output.name, connection, self.name) + 'Output flag number must be a integer from 0 to %d.'%(self.n_flags-1)) if prefix == 'flag' and self.flag_is_clock(connection): raise LabscriptError('%s is set as connected to flag %d of %s.'%(output.name, connection, self.name) + ' This flag is already in use as one of the PulseBlaster\'s clock flags.') if prefix == 'dds' and not connection < 2: raise LabscriptError('%s is set as connected to output connection %d of %s. '%(output.name, connection, self.name) + 'DDS output connection number must be a integer less than 2.') # Check that the connection string doesn't conflict with another output for other_output in dig_outputs + dds_outputs: if output.connection == other_output.connection: raise LabscriptError('%s and %s are both set as connected to %s of %s.'%(output.name, other_output.name, output.connection, self.name)) # store a reference to the output if isinstance(output, DigitalOut): dig_outputs.append(output) elif isinstance(output, DDS) or isinstance(output, PulseBlasterDDS): dds_outputs.append(output) return dig_outputs, dds_outputs
[docs] def generate_registers(self, hdf5_file, dds_outputs): ampdicts = {} phasedicts = {} freqdicts = {} group = hdf5_file['/devices/'+self.name] dds_dict = {} for output in dds_outputs: num = int(output.connection.split()[1]) dds_dict[num] = output for num in [0,1]: if num in dds_dict: output = dds_dict[num] # Ensure that amplitudes are within bounds: if any(output.amplitude.raw_output > 1) or any(output.amplitude.raw_output < 0): raise LabscriptError('%s %s '%(output.amplitude.description, output.amplitude.name) + 'can only have values between 0 and 1, ' + 'the limit imposed by %s.'%output.name) # Ensure that frequencies are within bounds: if any(output.frequency.raw_output > 150e6 ) or any(output.frequency.raw_output < 0): raise LabscriptError('%s %s '%(output.frequency.description, output.frequency.name) + 'can only have values between 0Hz and and 150MHz, ' + 'the limit imposed by %s.'%output.name) # Ensure that phase wraps around: output.phase.raw_output %= 360 amps = set(output.amplitude.raw_output) phases = set(output.phase.raw_output) freqs = set(output.frequency.raw_output) else: # If the DDS is unused, it will use the following values # for the whole experimental run: amps = set([0]) phases = set([0]) freqs = set([0]) if len(amps) > 1024: raise LabscriptError('%s dds%d can only support 1024 amplitude registers, and %s have been requested.'%(self.name, num, str(len(amps)))) if len(phases) > 128: raise LabscriptError('%s dds%d can only support 128 phase registers, and %s have been requested.'%(self.name, num, str(len(phases)))) if len(freqs) > 1024: raise LabscriptError('%s dds%d can only support 1024 frequency registers, and %s have been requested.'%(self.name, num, str(len(freqs)))) # start counting at 1 to leave room for the dummy instruction, # which BLACS will fill in with the state of the front # panel: ampregs = range(1,len(amps)+1) freqregs = range(1,len(freqs)+1) phaseregs = range(1,len(phases)+1) ampdicts[num] = dict(zip(amps,ampregs)) freqdicts[num] = dict(zip(freqs,freqregs)) phasedicts[num] = dict(zip(phases,phaseregs)) # The zeros are the dummy instructions: freq_table = np.array([0] + list(freqs), dtype = np.float64) / 1e6 # convert to MHz amp_table = np.array([0] + list(amps), dtype = np.float32) phase_table = np.array([0] + list(phases), dtype = np.float64) subgroup = group.create_group('DDS%d'%num) subgroup.create_dataset('FREQ_REGS', compression=config.compression, data = freq_table) subgroup.create_dataset('AMP_REGS', compression=config.compression, data = amp_table) subgroup.create_dataset('PHASE_REGS', compression=config.compression, data = phase_table) return freqdicts, ampdicts, phasedicts
[docs] def convert_to_pb_inst(self, dig_outputs, dds_outputs, freqs, amps, phases): pb_inst = [] # index to keep track of where in output.raw_output the # pulseblaster flags are coming from # starts at -1 because the internal flag should always tick on the first instruction and be # incremented (to 0) before it is used to index any arrays i = -1 # index to record what line number of the pulseblaster hardware # instructions we're up to: j = 0 # We've delegated the initial two instructions off to BLACS, which # can ensure continuity with the state of the front panel. Thus # these two instructions don't actually do anything: flags = [0]*self.n_flags freqregs = [0]*2 ampregs = [0]*2 phaseregs = [0]*2 dds_enables = [0]*2 phase_resets = [0]*2 pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets': phase_resets, 'flags': ''.join([str(flag) for flag in flags]), 'instruction': 'STOP', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets': phase_resets, 'flags': ''.join([str(flag) for flag in flags]), 'instruction': 'STOP', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) j += 2 flagstring = '0'*self.n_flags # So that this variable is still defined if the for loop has no iterations for k, instruction in enumerate(self.pseudoclock.clock): if instruction == 'WAIT': # This is a wait instruction. Repeat the last instruction but with a 100ns delay and a WAIT op code: wait_instruction = pb_inst[-1].copy() wait_instruction['delay'] = 100 wait_instruction['instruction'] = 'WAIT' wait_instruction['data'] = 0 pb_inst.append(wait_instruction) j += 1 continue flags = [0]*self.n_flags # The registers below are ones, not zeros, so that we don't # use the BLACS-inserted initial instructions. Instead # unused DDSs have a 'zero' in register one for freq, amp # and phase. freqregs = [1]*2 ampregs = [1]*2 phaseregs = [1]*2 dds_enables = [0]*2 phase_resets = [0]*2 # This flag indicates whether we need a full clock tick, or are just updating an internal output only_internal = True # find out which clock flags are ticking during this instruction for clock_line in instruction['enabled_clocks']: if clock_line == self._direct_output_clock_line: # advance i (the index keeping track of internal clockline output) i += 1 else: flag_index = int(clock_line.connection.split()[1]) flags[flag_index] = 1 # We are not just using the internal clock line only_internal = False for output in dig_outputs: flagindex = int(output.connection.split()[1]) flags[flagindex] = int(output.raw_output[i]) for output in dds_outputs: ddsnumber = int(output.connection.split()[1]) freqregs[ddsnumber] = freqs[ddsnumber][output.frequency.raw_output[i]] ampregs[ddsnumber] = amps[ddsnumber][output.amplitude.raw_output[i]] phaseregs[ddsnumber] = phases[ddsnumber][output.phase.raw_output[i]] dds_enables[ddsnumber] = output.gate.raw_output[i] if isinstance(output, PulseBlasterDDS): phase_resets[ddsnumber] = output.phase_reset.raw_output[i] flagstring = ''.join([str(flag) for flag in flags]) if instruction['reps'] > 1048576: raise LabscriptError('Pulseblaster cannot support more than 1048576 loop iterations. ' + str(instruction['reps']) +' were requested at t = ' + str(instruction['start']) + '. '+ 'This can be fixed easily enough by using nested loops. If it is needed, ' + 'please file a feature request at' + 'http://redmine.physics.monash.edu.au/projects/labscript.') if not only_internal: if self.pulse_width == 'symmetric': high_time = instruction['step']/2 else: high_time = self.pulse_width # High time cannot be longer than self.long_delay (~57 seconds for a # 75MHz core clock freq). If it is, clip it to self.long_delay. In this # case we are not honouring the requested symmetric or fixed pulse # width. To do so would be possible, but would consume more pulseblaster # instructions, so we err on the side of fewer instructions: high_time = min(high_time, self.long_delay) # Low time is whatever is left: low_time = instruction['step'] - high_time # Do we need to insert a LONG_DELAY instruction to create a delay this # long? n_long_delays, remaining_low_time = divmod(low_time, self.long_delay) # If the remainder is too short to be output, add self.long_delay to it. # self.long_delay was constructed such that adding self.min_delay to it # is still not too long for a single instruction: if n_long_delays and remaining_low_time < self.min_delay: n_long_delays -= 1 remaining_low_time += self.long_delay # The start loop instruction, Clock edges are high: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'LOOP', 'data': instruction['reps'], 'delay': high_time*1e9}) for clock_line in instruction['enabled_clocks']: if clock_line != self._direct_output_clock_line: flag_index = int(clock_line.connection.split()[1]) flags[flag_index] = 0 flagstring = ''.join([str(flag) for flag in flags]) # The long delay instruction, if any. Clock edges are low: if n_long_delays: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'LONG_DELAY', 'data': int(n_long_delays), 'delay': self.long_delay*1e9}) # Remaining low time. Clock edges are low: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'END_LOOP', 'data': j, 'delay': remaining_low_time*1e9}) # Two instructions were used in the case of there being no LONG_DELAY, # otherwise three. This increment is done here so that the j referred # to in the previous line still refers to the LOOP instruction. j += 3 if n_long_delays else 2 else: # We only need to update a direct output, so no need to tick the clocks. # Do we need to insert a LONG_DELAY instruction to create a delay this # long? n_long_delays, remaining_delay = divmod(instruction['step'], self.long_delay) # If the remainder is too short to be output, add self.long_delay to it. # self.long_delay was constructed such that adding self.min_delay to it # is still not too long for a single instruction: if n_long_delays and remaining_delay < self.min_delay: n_long_delays -= 1 remaining_delay += self.long_delay if n_long_delays: pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'LONG_DELAY', 'data': int(n_long_delays), 'delay': self.long_delay*1e9}) pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'CONTINUE', 'data': 0, 'delay': remaining_delay*1e9}) j += 2 if n_long_delays else 1 if self.programming_scheme == 'pb_start/BRANCH': # This is how we stop the pulse program. We branch from the last # instruction to the zeroth, which BLACS has programmed in with # the same values and a WAIT instruction. The PulseBlaster then # waits on instuction zero, which is a state ready for either # further static updates or buffered mode. pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'BRANCH', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) elif self.programming_scheme == 'pb_stop_programming/STOP': # An ordinary stop instruction. This has the downside that the PulseBlaster might # (on some models) reset its output to zero momentarily until BLACS calls program_manual, which # it will for this programming scheme. However it is necessary when the PulseBlaster has # repeated triggers coming to it, such as a 50Hz/60Hz line trigger. We can't have it sit # on a WAIT instruction as above, or it will trigger and run repeatedly when that's not what # we wanted. pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables, 'phase_resets':phase_resets, 'flags': flagstring, 'instruction': 'STOP', 'data': 0, 'delay': 10.0/self.clock_limit*1e9}) else: raise AssertionError('Invalid programming scheme %s'%str(self.programming_scheme)) if len(pb_inst) > self.max_instructions: raise LabscriptError("The Pulseblaster memory cannot store more than {:d} instuctions, but the PulseProgram contains {:d} instructions.".format(self.max_instructions, len(pb_inst))) return pb_inst
[docs] def write_pb_inst_to_h5(self, pb_inst, hdf5_file): # OK now we squeeze the instructions into a numpy array ready for writing to hdf5: pb_dtype = [('freq0', np.int32), ('phase0', np.int32), ('amp0', np.int32), ('dds_en0', np.int32), ('phase_reset0', np.int32), ('freq1', np.int32), ('phase1', np.int32), ('amp1', np.int32), ('dds_en1', np.int32), ('phase_reset1', np.int32), ('flags', np.int32), ('inst', np.int32), ('inst_data', np.int32), ('length', np.float64)] pb_inst_table = np.empty(len(pb_inst),dtype = pb_dtype) for i,inst in enumerate(pb_inst): flagint = int(inst['flags'][::-1],2) instructionint = self.pb_instructions[inst['instruction']] dataint = inst['data'] delaydouble = inst['delay'] freq0 = inst['freqs'][0] freq1 = inst['freqs'][1] phase0 = inst['phases'][0] phase1 = inst['phases'][1] amp0 = inst['amps'][0] amp1 = inst['amps'][1] en0 = inst['enables'][0] en1 = inst['enables'][1] phase_reset0 = inst['phase_resets'][0] phase_reset1 = inst['phase_resets'][1] pb_inst_table[i] = (freq0,phase0,amp0,en0,phase_reset0,freq1,phase1,amp1,en1,phase_reset1, flagint, instructionint, dataint, delaydouble) # Okay now write it to the file: group = hdf5_file['/devices/'+self.name] group.create_dataset('PULSE_PROGRAM', compression=config.compression,data = pb_inst_table) self.set_property('stop_time', self.stop_time, location='device_properties')
[docs] def _check_wait_monitor_ok(self): if ( compiler.master_pseudoclock is self and compiler.wait_table and compiler.wait_monitor is None and self.programming_scheme != 'pb_stop_programming/STOP' ): msg = """If using waits without a wait monitor, the PulseBlaster used as a master pseudoclock must have programming_scheme='pb_stop_programming/STOP'. Otherwise there is no way for BLACS to distinguish between a wait, and the end of a shot. Either use a wait monitor (see labscript.WaitMonitor for details) or set programming_scheme='pb_stop_programming/STOP for %s.""" raise LabscriptError(dedent(msg) % self.name)
[docs] 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, dds_outputs = self.get_direct_outputs() freqs, amps, phases = self.generate_registers(hdf5_file, dds_outputs) pb_inst = self.convert_to_pb_inst(dig_outputs, dds_outputs, freqs, amps, phases) self._check_wait_monitor_ok() self.write_pb_inst_to_h5(pb_inst, hdf5_file)
[docs] class PulseBlasterDirectOutputs(IntermediateDevice): allowed_children = [DDS, PulseBlasterDDS, DigitalOut] clock_limit = PulseBlaster.clock_limit description = 'PB-DDSII-300 Direct Outputs'
[docs] def add_device(self, device): IntermediateDevice.add_device(self, device) if isinstance(device, DDS): # Check that the user has not specified another digital line as the gate for this DDS, that doesn't make sense. # Then instantiate a DigitalQuantity to keep track of gating. if device.gate is None: device.gate = DigitalQuantity(device.name + '_gate', device, 'gate') else: raise LabscriptError('You cannot specify a digital gate ' + 'for a DDS connected to %s. '% (self.name) + 'The digital gate is always internal to the Pulseblaster.')
from blacs.tab_base_classes import Worker, define_state from blacs.tab_base_classes import MODE_MANUAL, MODE_TRANSITION_TO_BUFFERED, MODE_TRANSITION_TO_MANUAL, MODE_BUFFERED from blacs.device_base_class import DeviceTab from qtutils import UiLoader import qtutils.icons import os # We can't import * from QtCore & QtGui, as one of them has a function called bin() which overrides the builtin, which is used in the pulseblaster worker from qtutils.qt import QtCore from qtutils.qt import QtGui
[docs] @BLACS_tab class PulseBlasterTab(DeviceTab):
[docs] def initialise_GUI(self): # Capabilities self.base_units = {'freq':'Hz', 'amp':'Vpp', 'phase':'Degrees'} self.base_min = {'freq':0.3, 'amp':0.0, 'phase':0} self.base_max = {'freq':150000000.0, 'amp':1.0, 'phase':360} self.base_step = {'freq':1000000, 'amp':0.01, 'phase':1} self.base_decimals = {'freq':1, 'amp':3, 'phase':3} self.num_DDS = 2 self.num_DO = 12 dds_prop = {} for i in range(self.num_DDS): # 2 is the number of DDS outputs on this device dds_prop['dds %d'%i] = {} for subchnl in ['freq', 'amp', 'phase']: dds_prop['dds %d'%i][subchnl] = {'base_unit':self.base_units[subchnl], 'min':self.base_min[subchnl], 'max':self.base_max[subchnl], 'step':self.base_step[subchnl], 'decimals':self.base_decimals[subchnl] } dds_prop['dds %d'%i]['gate'] = {} do_prop = {} for i in range(self.num_DO): # 12 is the maximum number of flags on this device (some only have 4 though) do_prop['flag %d'%i] = {} # Create the output objects self.create_dds_outputs(dds_prop) self.create_digital_outputs(do_prop) # Create widgets for output objects dds_widgets,ao_widgets,do_widgets = self.auto_create_widgets() # Define the sort function for the digital outputs def sort(channel): flag = channel.replace('flag ','') flag = int(flag) return '%02d'%(flag) # and auto place the widgets in the UI self.auto_place_widgets(("DDS Outputs",dds_widgets),("Flags",do_widgets,sort)) # Store the board number to be used connection_object = self.settings['connection_table'].find_by_name(self.device_name) self.board_number = int(connection_object.BLACS_connection) # And which scheme we're using for buffered output programming and triggering: # (default values for backward compat with old connection tables) self.programming_scheme = connection_object.properties.get('programming_scheme', 'pb_start/BRANCH') # Create and set the primary worker self.create_worker("main_worker",PulseblasterWorker,{'board_number':self.board_number, 'programming_scheme': self.programming_scheme}) self.primary_worker = "main_worker" # Set the capabilities of this device self.supports_smart_programming(True) # Load status monitor (and start/stop/reset buttons) UI ui = UiLoader().load(os.path.join(os.path.dirname(os.path.realpath(__file__)),'pulseblaster.ui')) self.get_tab_layout().addWidget(ui) # Connect signals for buttons ui.start_button.clicked.connect(self.start) ui.stop_button.clicked.connect(self.stop) ui.reset_button.clicked.connect(self.reset) # Add icons ui.start_button.setIcon(QtGui.QIcon(':/qtutils/fugue/control')) ui.start_button.setToolTip('Start') ui.stop_button.setIcon(QtGui.QIcon(':/qtutils/fugue/control-stop-square')) ui.stop_button.setToolTip('Stop') ui.reset_button.setIcon(QtGui.QIcon(':/qtutils/fugue/arrow-circle')) ui.reset_button.setToolTip('Reset') # initialise dictionaries of data to display and get references to the QLabels self.status_states = ['stopped', 'reset', 'running', 'waiting'] self.status = {} self.status_widgets = {} for state in self.status_states: self.status[state] = False self.status_widgets[state] = getattr(ui,'%s_label'%state) # Create status monitor timout self.statemachine_timeout_add(2000, self.status_monitor)
[docs] def get_child_from_connection_table(self, parent_device_name, port): # This is a direct output, let's search for it on the internal intermediate device called # PulseBlasterDirectOutputs if parent_device_name == self.device_name: device = self.connection_table.find_by_name(self.device_name) pseudoclock = device.child_list[list(device.child_list.keys())[0]] # there should always be one (and only one) child, the Pseudoclock clockline = None for child_name, child in pseudoclock.child_list.items(): # store a reference to the internal clockline if child.parent_port == 'internal': clockline = child # if the port is in use by a clockline, return the clockline elif child.parent_port == port: return child if clockline is not None: # There should only be one child of this clock line, the direct outputs direct_outputs = clockline.child_list[list(clockline.child_list.keys())[0]] # look to see if the port is used by a child of the direct outputs return DeviceTab.get_child_from_connection_table(self, direct_outputs.name, port) else: return '' else: # else it's a child of a DDS, so we can use the default behaviour to find the device return DeviceTab.get_child_from_connection_table(self, parent_device_name, port)
# This function gets the status of the Pulseblaster from the spinapi, # and updates the front panel widgets! @define_state(MODE_MANUAL|MODE_BUFFERED|MODE_TRANSITION_TO_BUFFERED|MODE_TRANSITION_TO_MANUAL,True) def status_monitor(self,notify_queue=None): # When called with a queue, this function writes to the queue # when the pulseblaster is waiting. This indicates the end of # an experimental run. self.status, waits_pending, time_based_shot_over = yield(self.queue_work(self._primary_worker,'check_status')) if self.programming_scheme == 'pb_start/BRANCH': done_condition = self.status['waiting'] elif self.programming_scheme == 'pb_stop_programming/STOP': done_condition = self.status['stopped'] if time_based_shot_over is not None: done_condition = time_based_shot_over if notify_queue is not None and done_condition and not waits_pending: # Experiment is over. Tell the queue manager about it, then # set the status checking timeout back to every 2 seconds # with no queue. notify_queue.put('done') self.statemachine_timeout_remove(self.status_monitor) self.statemachine_timeout_add(2000,self.status_monitor) if self.programming_scheme == 'pb_stop_programming/STOP': # Not clear that on all models the outputs will be correct after being # stopped this way, so we do program_manual with current values to be sure: self.program_device() # Update widgets with new status for state in self.status_states: if self.status[state]: icon = QtGui.QIcon(':/qtutils/fugue/tick') else: icon = QtGui.QIcon(':/qtutils/fugue/cross') pixmap = icon.pixmap(QtCore.QSize(16, 16)) self.status_widgets[state].setPixmap(pixmap) @define_state(MODE_MANUAL|MODE_BUFFERED|MODE_TRANSITION_TO_BUFFERED|MODE_TRANSITION_TO_MANUAL,True) def start(self,widget=None): yield(self.queue_work(self._primary_worker,'start_run')) self.status_monitor() @define_state(MODE_MANUAL|MODE_BUFFERED|MODE_TRANSITION_TO_BUFFERED|MODE_TRANSITION_TO_MANUAL,True) def stop(self,widget=None): yield(self.queue_work(self._primary_worker,'pb_stop')) self.status_monitor() @define_state(MODE_MANUAL|MODE_BUFFERED|MODE_TRANSITION_TO_BUFFERED|MODE_TRANSITION_TO_MANUAL,True) def reset(self,widget=None): yield(self.queue_work(self._primary_worker,'pb_reset')) self.status_monitor() @define_state(MODE_BUFFERED,True) def start_run(self, notify_queue): """Starts the Pulseblaster, notifying the queue manager when the run is over""" self.statemachine_timeout_remove(self.status_monitor) self.start() self.statemachine_timeout_add(100,self.status_monitor,notify_queue)
[docs] class PulseblasterWorker(Worker):
[docs] def init(self): exec('from spinapi import *', globals()) global h5py; import labscript_utils.h5_lock, h5py global zprocess; import zprocess self.pb_start = pb_start self.pb_stop = pb_stop self.pb_reset = pb_reset self.pb_close = pb_close self.pb_read_status = pb_read_status self.smart_cache = {'amps0':None,'freqs0':None,'phases0':None, 'amps1':None,'freqs1':None,'phases1':None, 'pulse_program':None,'ready_to_go':False, 'initial_values':None} # An event for checking when all waits (if any) have completed, so that # we can tell the difference between a wait and the end of an experiment. # The wait monitor device is expected to post such events, which we'll wait on: self.all_waits_finished = zprocess.Event('all_waits_finished') self.waits_pending = False pb_select_board(self.board_number) pb_init() pb_core_clock(75) # This is only set to True on a per-shot basis, so set it to False # for manual mode. Set associated attributes to None: self.time_based_stop_workaround = False self.time_based_shot_duration = None self.time_based_shot_end_time = None
[docs] def program_manual(self,values): if self.programming_scheme == 'pb_stop_programming/STOP': # Need to ensure device is stopped before programming - or we won't know what line it's on. pb_stop() # Program the DDS registers: for i in range(2): pb_select_dds(i) # Program the frequency, amplitude and phase into their # zeroth registers: program_amp_regs(values['dds %d'%i]['amp']) # Does not call pb_stop_programming anyway, so no kwarg needed program_freq_regs(values['dds %d'%i]['freq']/10.0**6, call_stop_programming=False) # method expects MHz program_phase_regs(values['dds %d'%i]['phase'], call_stop_programming=False) # create flags string # NOTE: The spinapi can take a string or integer for flags. # If it is a string: # flag: 0 12 # '101100011111' # # If it is a binary number: # flag:12 0 # 0b111110001101 # # Be warned! flags = '' for i in range(12): if values['flag %d'%i]: flags += '1' else: flags += '0' # Write the first two lines of the pulse program: pb_start_programming(PULSE_PROGRAM) # Line zero is a wait: pb_inst_dds2(0,0,0,values['dds 0']['gate'],0,0,0,0,values['dds 1']['gate'],0,flags, WAIT, 0, 100) # Line one is a brach to line 0: pb_inst_dds2(0,0,0,values['dds 0']['gate'],0,0,0,0,values['dds 1']['gate'],0,flags, BRANCH, 0, 100) pb_stop_programming() # Now we're waiting on line zero, so when we start() we'll go to # line one, then brach back to zero, completing the static update: pb_start() # The pulse program now has a branch in line one, and so can't proceed to the pulse program # without a reprogramming of the first two lines: self.smart_cache['ready_to_go'] = False # TODO: return coerced/quantised values return {}
[docs] def start_run(self): if self.programming_scheme == 'pb_start/BRANCH': pb_start() elif self.programming_scheme == 'pb_stop_programming/STOP': pb_stop_programming() pb_start() else: raise ValueError('invalid programming_scheme: %s'%str(self.programming_scheme)) if self.time_based_stop_workaround: import time self.time_based_shot_end_time = time.time() + self.time_based_shot_duration
[docs] def transition_to_buffered(self,device_name,h5file,initial_values,fresh): self.h5file = h5file if self.programming_scheme == 'pb_stop_programming/STOP': # Need to ensure device is stopped before programming - or we wont know what line it's on. pb_stop() with h5py.File(h5file,'r') as hdf5_file: group = hdf5_file['devices/%s'%device_name] # Is this shot using the fixed-duration workaround instead of checking the PulseBlaster's status? self.time_based_stop_workaround = group.attrs.get('time_based_stop_workaround', False) if self.time_based_stop_workaround: self.time_based_shot_duration = (group.attrs['stop_time'] + hdf5_file['waits'][:]['timeout'].sum() + group.attrs['time_based_stop_workaround_extra_time']) # Program the DDS registers: ampregs = [] freqregs = [] phaseregs = [] for i in range(2): amps = group['DDS%d/AMP_REGS'%i][:] freqs = group['DDS%d/FREQ_REGS'%i][:] phases = group['DDS%d/PHASE_REGS'%i][:] amps[0] = initial_values['dds %d'%i]['amp'] freqs[0] = initial_values['dds %d'%i]['freq']/10.0**6 # had better be in MHz! phases[0] = initial_values['dds %d'%i]['phase'] pb_select_dds(i) # Only reprogram each thing if there's been a change: if fresh or len(amps) != len(self.smart_cache['amps%d'%i]) or (amps != self.smart_cache['amps%d'%i]).any(): self.smart_cache['amps%d'%i] = amps program_amp_regs(*amps) if fresh or len(freqs) != len(self.smart_cache['freqs%d'%i]) or (freqs != self.smart_cache['freqs%d'%i]).any(): self.smart_cache['freqs%d'%i] = freqs # We must be careful not to call stop_programming() until the end, # lest the pulseblaster become responsive to triggers before we are done programming. # This is not an issue for program_amp_regs above, only for freq and phase regs. program_freq_regs(*freqs, call_stop_programming=False) if fresh or len(phases) != len(self.smart_cache['phases%d'%i]) or (phases != self.smart_cache['phases%d'%i]).any(): self.smart_cache['phases%d'%i] = phases # See above comment - we must not call pb_stop_programming here: program_phase_regs(*phases, call_stop_programming=False) ampregs.append(amps) freqregs.append(freqs) phaseregs.append(phases) # Now for the pulse program: pulse_program = group['PULSE_PROGRAM'][2:] #Let's get the final state of the pulseblaster. z's are the args we don't need: freqreg0,phasereg0,ampreg0,en0,z,freqreg1,phasereg1,ampreg1,en1,z,flags,z,z,z = pulse_program[-1] finalfreq0 = freqregs[0][freqreg0]*10.0**6 # Front panel expects frequency in Hz finalfreq1 = freqregs[1][freqreg1]*10.0**6 # Front panel expects frequency in Hz finalamp0 = ampregs[0][ampreg0] finalamp1 = ampregs[1][ampreg1] finalphase0 = phaseregs[0][phasereg0] finalphase1 = phaseregs[1][phasereg1] # Always call start_programming regardless of whether we are going to do any # programming or not. This is so that is the programming_scheme is 'pb_stop_programming/STOP' # we are ready to be triggered by a call to pb_stop_programming() even if no programming # occurred due to smart programming: pb_start_programming(PULSE_PROGRAM) if fresh or (self.smart_cache['initial_values'] != initial_values) or \ (len(self.smart_cache['pulse_program']) != len(pulse_program)) or \ (self.smart_cache['pulse_program'] != pulse_program).any() or \ not self.smart_cache['ready_to_go']: self.smart_cache['ready_to_go'] = True self.smart_cache['initial_values'] = initial_values # create initial flags string # NOTE: The spinapi can take a string or integer for flags. # If it is a string: # flag: 0 12 # '101100011111' # # If it is a binary number: # flag:12 0 # 0b111110001101 # # Be warned! initial_flags = '' for i in range(12): if initial_values['flag %d'%i]: initial_flags += '1' else: initial_flags += '0' if self.programming_scheme == 'pb_start/BRANCH': # Line zero is a wait on the final state of the program in 'pb_start/BRANCH' mode pb_inst_dds2(freqreg0,phasereg0,ampreg0,en0,0,freqreg1,phasereg1,ampreg1,en1,0,flags,WAIT,0,100) else: # Line zero otherwise just contains the initial state pb_inst_dds2(0,0,0,initial_values['dds 0']['gate'],0,0,0,0,initial_values['dds 1']['gate'],0,initial_flags, CONTINUE, 0, 100) # Line one is a continue with the current front panel values: pb_inst_dds2(0,0,0,initial_values['dds 0']['gate'],0,0,0,0,initial_values['dds 1']['gate'],0,initial_flags, CONTINUE, 0, 100) # Now the rest of the program: if fresh or len(self.smart_cache['pulse_program']) != len(pulse_program) or \ (self.smart_cache['pulse_program'] != pulse_program).any(): self.smart_cache['pulse_program'] = pulse_program for args in pulse_program: pb_inst_dds2(*args) if self.programming_scheme == 'pb_start/BRANCH': # We will be triggered by pb_start() if we are are the master pseudoclock or a single hardware trigger # from the master if we are not: pb_stop_programming() elif self.programming_scheme == 'pb_stop_programming/STOP': # Don't call pb_stop_programming(). We don't want to pulseblaster to respond to hardware # triggers (such as 50/60Hz line triggers) until we are ready to run. # Our start_method will call pb_stop_programming() when we are ready pass else: raise ValueError('invalid programming_scheme %s'%str(self.programming_scheme)) # Are there waits in use in this experiment? The monitor waiting for the end # of the experiment will need to know: wait_monitor_exists = bool(hdf5_file['waits'].attrs['wait_monitor_acquisition_device']) waits_in_use = bool(len(hdf5_file['waits'])) self.waits_pending = wait_monitor_exists and waits_in_use if waits_in_use and not wait_monitor_exists: # This should be caught during labscript compilation, but just in case. # Having waits but not a wait monitor means we can't tell when the shot # is over unless the shot ends in a STOP instruction: assert self.programming_scheme == 'pb_stop_programming/STOP' # Now we build a dictionary of the final state to send back to the GUI: return_values = {'dds 0':{'freq':finalfreq0, 'amp':finalamp0, 'phase':finalphase0, 'gate':en0}, 'dds 1':{'freq':finalfreq1, 'amp':finalamp1, 'phase':finalphase1, 'gate':en1}, } # Since we are converting from an integer to a binary string, we need to reverse the string! (see notes above when we create flags variables) return_flags = str(bin(flags)[2:]).rjust(12,'0')[::-1] for i in range(12): return_values['flag %d'%i] = return_flags[i] return return_values
[docs] def check_status(self): if self.waits_pending: try: self.all_waits_finished.wait(self.h5file, timeout=0) self.waits_pending = False except zprocess.TimeoutError: pass if self.time_based_shot_end_time is not None: import time time_based_shot_over = time.time() > self.time_based_shot_end_time else: time_based_shot_over = None return pb_read_status(), self.waits_pending, time_based_shot_over
[docs] def transition_to_manual(self): status, waits_pending, time_based_shot_over = self.check_status() if self.programming_scheme == 'pb_start/BRANCH': done_condition = status['waiting'] elif self.programming_scheme == 'pb_stop_programming/STOP': done_condition = status['stopped'] if time_based_shot_over is not None: done_condition = time_based_shot_over # This is only set to True on a per-shot basis, so reset it to False # for manual mode. Reset associated attributes to None: self.time_based_stop_workaround = False self.time_based_shot_duration = None self.time_based_shot_end_time = None if done_condition and not waits_pending: return True else: return False
[docs] def abort_buffered(self): # Stop the execution self.pb_stop() # Reset to the beginning of the pulse sequence self.pb_reset() # abort_buffered in the GUI process queues up a program_device state # which will reprogram the device and call pb_start() # This ensures the device isn't accidentally retriggered by another device # while it is running it's abort function return True
[docs] def abort_transition_to_buffered(self): return True
[docs] def shutdown(self): #TODO: implement this pass
[docs] @runviewer_parser class PulseBlasterParser(object): num_dds = 2 num_flags = 12 def __init__(self, path, device): self.path = path self.name = device.name self.device = device # We create a lookup table for strings to be used later as dictionary keys. # This saves having to evaluate '%d'%i many many times, and makes the _add_pulse_program_row_to_traces method # significantly more efficient self.dds_strings = {} for i in range(self.num_dds): self.dds_strings[i] = {} self.dds_strings[i]['ddsfreq'] = 'dds %d_freq'%i self.dds_strings[i]['ddsamp'] = 'dds %d_amp'%i self.dds_strings[i]['ddsphase'] = 'dds %d_phase'%i self.dds_strings[i]['freq'] = 'freq%d'%i self.dds_strings[i]['amp'] = 'amp%d'%i self.dds_strings[i]['phase'] = 'phase%d'%i self.dds_strings[i]['dds_en'] = 'dds_en%d'%i self.flag_strings = {} self.flag_powers = {} for i in range(self.num_flags): self.flag_strings[i] = 'flag %d'%i self.flag_powers[i] = 2**i
[docs] def get_traces(self, add_trace, parent=None): if parent is None: # we're the master pseudoclock, software triggered. So we don't have to worry about trigger delays, etc pass # get the pulse program with h5py.File(self.path, 'r') as f: pulse_program = f['devices/%s/PULSE_PROGRAM'%self.name][:] # slow_clock_flag = eval(f['devices/%s'%self.name].attrs['slow_clock']) dds = {} for i in range(self.num_dds): dds[i] = {} for reg in ['FREQ', 'AMP', 'PHASE']: dds[i][reg] = f['devices/%s/DDS%d/%s_REGS'%(self.name, i, reg)][:] clock = [] traces = {} for i in range(self.num_flags): traces['flag %d'%i] = [] for i in range(self.num_dds): for sub_chnl in ['freq', 'amp', 'phase']: traces['dds %d_%s'%(i,sub_chnl)] = [] # now build the traces t = 0. if parent is None else PulseBlaster.trigger_delay # Offset by initial trigger of parent i = 0 while i < len(pulse_program): # ignore the first 2 instructions, they are dummy instructions for BLACS if i < 2: i += 1 continue row = pulse_program[i] if row['inst'] == 2: # Loop loops = int(row['inst_data']) buffer = {} j = i while loops > 0: looping = True while looping: row = pulse_program[j] # buffer the index of traces used for this instruction # Cuts the runtime down by ~60% # start_profile('loop_contents') if j not in buffer: clock.append(t) self._add_pulse_program_row_to_traces(traces, row, dds) buffer[j] = len(clock)-1 else: clock.append(t) self._add_pulse_program_row_from_buffer(traces, buffer[j]) # stop_profile('loop_contents') # start_profile('end_of_loop') t+= row['length']*1.0e-9 if row['inst'] == 3: # END_LOOP looping = False # print 'end loop. j=%d, t=%.7f'%(j,t) j = int(row['inst_data']) if loops > 1 else j # print 'setting j=%d'%j else: # print 'in loop. j=%d, t=%.7f'%(j,t) j+=1 # stop_profile('end_of_loop') loops -= 1 i = j # print 'i now %d'%i else: # Continue if row['inst'] == 8: #WAIT print('Wait at %.9f'%t) pass clock.append(t) self._add_pulse_program_row_to_traces(traces,row,dds) t+= row['length']*1.0e-9 if row['inst'] == 8 and parent is not None: #WAIT #TODO: Offset next time by trigger delay is not master pseudoclock t+= PulseBlaster.trigger_delay i += 1 print('Stop time: %.9f'%t) # now put together the traces to_return = {} clock = np.array(clock, dtype=np.float64) for name, data in traces.items(): to_return[name] = (clock, np.array(data)) # if slow_clock_flag is not None: # to_return['slow clock'] = to_return['flag %d'%slow_clock_flag[0]] clocklines_and_triggers = {} for pseudoclock_name, pseudoclock in self.device.child_list.items(): for clock_line_name, clock_line in pseudoclock.child_list.items(): if clock_line.parent_port == 'internal': parent_device_name = '%s.direct_outputs'%self.name for internal_device_name, internal_device in clock_line.child_list.items(): for channel_name, channel in internal_device.child_list.items(): if channel.device_class == 'Trigger': clocklines_and_triggers[channel_name] = to_return[channel.parent_port] add_trace(channel_name, to_return[channel.parent_port], parent_device_name, channel.parent_port) else: if channel.device_class == 'DDS': for subchnl_name, subchnl in channel.child_list.items(): connection = '%s_%s'%(channel.parent_port, subchnl.parent_port) if connection in to_return: add_trace(subchnl.name, to_return[connection], parent_device_name, connection) else: add_trace(channel_name, to_return[channel.parent_port], parent_device_name, channel.parent_port) else: clocklines_and_triggers[clock_line_name] = to_return[clock_line.parent_port] add_trace(clock_line_name, to_return[clock_line.parent_port], self.name, clock_line.parent_port) return clocklines_and_triggers
[docs] @profile def _add_pulse_program_row_from_buffer(self, traces, index): for i in range(self.num_flags): traces[self.flag_strings[i]].append(traces[self.flag_strings[i]][index]) for i in range(self.num_dds): current_strings = self.dds_strings[i] traces[current_strings['ddsfreq']].append(traces[current_strings['ddsfreq']][index]) traces[current_strings['ddsphase']].append(traces[current_strings['ddsphase']][index]) traces[current_strings['ddsamp']].append(traces[current_strings['ddsamp']][index])
[docs] @profile def _add_pulse_program_row_to_traces(self, traces, row, dds, flags = None): # add flags if flags is None: flags = np.binary_repr(row['flags'],self.num_flags)[::-1] for i in range(self.num_flags): traces[self.flag_strings[i]].append(int(flags[i])) # Below block saved for history. This is much slower compared to what is below! # for i in range(self.num_dds): # traces['dds %d_freq'%i].append(dds[i]['FREQ'][row['freq%d'%i]]) # traces['dds %d_phase'%i].append(dds[i]['PHASE'][row['phase%d'%i]]) # amp = dds[i]['AMP'][row['amp%d'%i]] if row['dds_en%d'%i] else 0 # traces['dds %d_amp'%i].append(amp) # note that we are looking up keys for the traces dictionary and row array in self.dds_strings # Doing this reduces the runtime (of the below block) by 25% for i in range(self.num_dds): # Note: This is done to reduce runtime (about 10%) current_strings = self.dds_strings[i] current_dds = dds[i] traces[current_strings['ddsfreq']].append(current_dds['FREQ'][row[current_strings['freq']]]) traces[current_strings['ddsphase']].append(current_dds['PHASE'][row[current_strings['phase']]]) # Note: Using the inline if statement reduces the runtime (of this for loop) by 50% amp = current_dds['AMP'][row[current_strings['amp']]] if row[current_strings['dds_en']] else 0 traces[current_strings['ddsamp']].append(amp)