예제 #1
0
    def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
        self.h5file = h5file  # We'll need this in transition_to_manual
        self.device_name = device_name
        with h5py.File(h5file, 'r') as hdf5_file:
            print("\nUsing "+h5file)
            self.atsparam = atsparam = labscript_utils.properties.get(
                hdf5_file, device_name, 'device_properties')
            #print("atsparam: " + repr(self.atsparam))

        clock_source_id = atsparam['clock_source_id']
        requested_acquisition_rate = atsparam['requested_acquisition_rate']
        clock_edge_id = atsparam['clock_edge_id']
        if clock_source_id == ats.INTERNAL_CLOCK:
            # Actually we should find smallest internal clock faster than the one asked for. Next time.
            actual_acquisition_rate = find_nearest_internal_clock(
                atsSampleRates.keys(), requested_acquisition_rate)
            # This is an ID not a sample per sec. It takes both.
            atsSamplesPerSec_or_id = atsSampleRates[actual_acquisition_rate]
            decimation = 0  # Must be zero for internal clocking
            clock_edge_id = ats.CLOCK_EDGE_RISING
            print('Internal clocking at {:.0f} samples per second ({:.1f} MS/s), from internal reference.'.
                  format(actual_acquisition_rate, actual_acquisition_rate/1e6))
        elif clock_source_id == ats.EXTERNAL_CLOCK_10MHz_REF:
            atsSamplesPerSec_or_id, divisor = ats9462_clock(
                requested_acquisition_rate)
            actual_acquisition_rate = atsSamplesPerSec_or_id // divisor
            decimation = divisor - 1
            clock_edge_id = ats.CLOCK_EDGE_RISING
            print('Internally clock at {:.0f} samples per second ({:.1f} MS/s), from external 10MHz reference ({:d}MHz PLL divided by {:d}).'.
                  format(actual_acquisition_rate, actual_acquisition_rate/1e6, atsSamplesPerSec_or_id//1000000, divisor))
        elif clock_source_id == ats.FAST_EXTERNAL_CLOCK:
            raise LabscriptError(
                "Requested capture clock type FAST_EXTERNAL_CLOCK is not implemented")
        elif clock_source_id == ats.MEDIUM_EXTERNAL_CLOCK:
            raise LabscriptError(
                "Requested capture clock type MEDIUM_EXTERNAL_CLOCK is not implemented")
        elif clock_source_id == ats.SLOW_EXTERNAL_CLOCK:
            raise LabscriptError(
                "Requested capture clock type SLOW_EXTERNAL_CLOCK is not implemented")
        elif clock_source_id == ats.EXTERNAL_CLOCK_AC:
            raise LabscriptError(
                "Requested capture clock type EXTERNAL_CLOCK_AC is not implemented")
        elif clock_source_id == ats.EXTERNAL_CLOCK_DC:
            raise LabscriptError(
                "Requested capture clock type EXTERNAL_CLOCK_DC is not implemented")
        else:
            raise LabscriptError("Requested capture clock type with code {:d} is not recognised".format(
                atsparam['clock_source_id']))
        # The clock_edge_id parameter is not needed for INTERNAL_CLOCK and EXTERNAL_CLOCK_10MHz_REF modes but is here for future extension
        try:
            self.board.setCaptureClock(
                atsparam['clock_source_id'], atsSamplesPerSec_or_id, clock_edge_id, decimation)
        except ats.AlazarException as e:
            errstring, funcname, arguments, retCode, retText = e.args
            if retText == 'ApiPllNotLocked':
                print("Error: PLL not locked! ")
                try:
                    print("Error: For this {:s} board, the ext reference should be {:s}".format(self.board_name,
                                                                                                atsExternalClockAdvice[self.board_name]))
                except KeyError:
                    print("Error: I don't have any advice for you on clocking the {:s} board".format(
                        self.board_name))
            raise ats.AlazarException(e)

        # Store the actual acquisition rate back as an attribute.
        # Again, this should be done as an ACQUISITIONS table entry, but not today
        with h5py.File(h5file, 'r+') as hdf5_file:
            hdf5_file['devices'][device_name].attrs.create(
                'acquisition_rate', actual_acquisition_rate, dtype='int32')

        # ETR_5V means +/-5V, and is 8bit
        # So code 150 means (150-128)/128 * 5V = 860mV.
        self.board.setExternalTrigger(
            atsparam['exttrig_coupling_id'], atsparam['exttrig_range_id'])
        print("Trigger coupling_id: {:d}, range_id: {:d}.".format(
            atsparam['exttrig_coupling_id'], atsparam['exttrig_range_id']))

        self.board.setTriggerOperation(atsparam['trig_operation'],
                                       atsparam['trig_engine_id1'], atsparam['trig_source_id1'], atsparam[
                                           'trig_slope_id1'], atsparam['trig_level_id1'],
                                       atsparam['trig_engine_id2'], atsparam['trig_source_id2'], atsparam['trig_slope_id2'], atsparam['trig_level_id2'])
        print("Trigger operation set to operation: {:d}".format(
            atsparam['trig_operation']))
        print("Trigger engine 1 set to {:d}, source: {:d}, slope: {:d}, level: {:d}.".format(
            atsparam['trig_engine_id1'], atsparam['trig_source_id1'], atsparam['trig_slope_id1'], atsparam['trig_level_id1']))
        print("Trigger engine 2 set to {:d}, source: {:d}, slope: {:d}, level: {:d}.".format(
            atsparam['trig_engine_id2'], atsparam['trig_source_id2'], atsparam['trig_slope_id2'], atsparam['trig_level_id2']))

        # We will deal with trigger delays in labscript!
        triggerDelay_sec = 0
        triggerDelay_samples = int(
            triggerDelay_sec * actual_acquisition_rate + 0.5)
        self.board.setTriggerDelay(0)

        # NOTE: The board will wait for a for this amount of time for a trigger event.  If a trigger event does not arrive, then the
        # board will automatically trigger. Set the trigger timeout value to 0 to force the board to wait forever for a trigger event.
        # LDT: We'll leave this set to zero for now. We timeout on the readout, not on the trigger.
        # But we should probably check if we ever got a trigger!
        self.board.setTriggerTimeOut(0)
        #print("Trigger timeout set to infinity")

        # Configure AUX I/O connector.
        # By default this emits the sample clock; not sure if this is before or after decimation
        # Second param is a dummy value when AUX_OUT_TRIGGER
        self.board.configureAuxIO(ats.AUX_OUT_TRIGGER, 0)
        #print("Aux output set to sample clock.")

        try:
            chA_range_id = atsRanges[atsparam['chA_input_range']]
        except KeyError:
            print("Voltage setting {:d}mV for Channel A is not recognised in atsapi. Make sure you use millivolts.".format(
                atsparam['chA_input_range']))
        self.board.inputControl(
            ats.CHANNEL_A, atsparam['chA_coupling_id'], chA_range_id, atsparam['chA_impedance_id'])
        self.board.setBWLimit(ats.CHANNEL_A, atsparam['chA_bw_limit'])
        print("Channel A input full scale: {:d}, coupling: {:d}, impedance: {:d}, bandwidth limit: {:d}.".format(
            atsparam['chA_input_range'], atsparam['chA_coupling_id'], atsparam['chA_impedance_id'], atsparam['chA_bw_limit']))

        try:
            chB_range_id = atsRanges[atsparam['chB_input_range']]
        except KeyError:
            print("Voltage setting {:d}mV for Channel B is not recognised in atsapi. Make sure you use millivolts.".format(
                atsparam['chB_input_range']))
        self.board.inputControl(
            ats.CHANNEL_B, atsparam['chB_coupling_id'], chB_range_id, atsparam['chB_impedance_id'])
        self.board.setBWLimit(ats.CHANNEL_B, atsparam['chB_bw_limit'])
        print("Channel B input full scale: {:d}, coupling: {:d}, impedance: {:d}, bandwidth limit: {:d}.".format(
            atsparam['chB_input_range'], atsparam['chB_coupling_id'], atsparam['chB_impedance_id'], atsparam['chB_bw_limit']))

        # ====== Acquisition code starts here =====
        # This is a magic number and should at the very least move up
        self.samplesPerBuffer = 204800
        self.oneM = 2**20
        # This should be determined by experiment run time.
        self.timeout = 60000

        # Check which channels we are acquiring
        #channels = ats.CHANNEL_A | ats.CHANNEL_B
        self.channels = atsparam['channels']
        if not (self.channels & ats.CHANNEL_A or self.channels & ats.CHANNEL_B):
            raise LabscriptError(
                "You must select either Channel-A or Channel-B, or both. Zero or >2 channels not supported.")
        self.channelCount = 0
        for c in ats.channels:
            self.channelCount += (c & self.channels == c)

        # Compute the number of bytes per record and per buffer
        memorySize_samples, self.bitsPerSample = self.board.getChannelInfo()
        self.bytesPerDatum = (self.bitsPerSample + 7) // 8

        # One 'sample' is one datum from each channel
        print("bytesPerDatum = {:d}. channelcount = {:d}.".format(
            self.bytesPerDatum, self.channelCount))
        self.bytesPerBuffer = self.bytesPerDatum * \
            self.channelCount * self.samplesPerBuffer

        # Calculate the number of buffers in the acquisition
        self.samplesPerAcquisition = int(
            actual_acquisition_rate * atsparam['acquisition_duration'] + 0.5)
        memoryPerAcquisition = self.bytesPerDatum * \
            self.samplesPerAcquisition * self.channelCount
        self.buffersPerAcquisition = ((self.samplesPerAcquisition + self.samplesPerBuffer - 1) //
                                      self.samplesPerBuffer)
        print('Acquiring for {:5.3f}s generates {:5.3f} MS ({:5.3f} MB total)'.format(
            atsparam['acquisition_duration'], self.samplesPerAcquisition/1e6, memoryPerAcquisition/self.oneM))
        print('Buffers are {:5.3f} MS and {:d} bytes. Allocating {:d} buffers... '.format(
            self.samplesPerBuffer/1e6, self.bytesPerBuffer, self.buffersPerAcquisition), end='')
        self.board.setRecordSize(0, self.samplesPerBuffer)

        # Allocate buffers
        # We know that disk can't keep up, so we preallocate all buffers
        sample_type = ctypes.c_uint16  # It's 16bit, let's not stuff around
        self.buffers = []
        for i in range(self.buffersPerAcquisition):
            self.buffers.append(ats.DMABuffer(
                sample_type, self.bytesPerBuffer))
            #print('{:d} '.format(i),end="")
        print('done.')

        # This works but ADMA_ALLOC_BUFFERS is questionable because we have allocated the buffers (well atsapi.py buffer class has)
        acqflags = ats.ADMA_TRIGGERED_STREAMING | ats.ADMA_ALLOC_BUFFERS | ats.ADMA_FIFO_ONLY_STREAMING
        #print("Acqflags in decimal: {:d}".format(acqflags))

        # This does not actually start the capture, it just sets it up
        self.board.beforeAsyncRead(self.channels,
                                   0,                 # Trig offset, must be 0
                                   self.samplesPerBuffer,
                                   1,                 # Must be 1
                                   0x7FFFFFFF,        # Ignored
                                   acqflags)

        self.acquisition_queue.put('start')
        return {}  # ? Check this
예제 #2
0
    def generate_code(self, hdf5_file):
        '''Automatically called by compiler to write acquisition instructions
        to h5 file. Configures counters, analog and digital acquisitions.'''
        Device.generate_code(self, hdf5_file)
        trans = {'pulse': 'PUL', 'edge': 'EDG', 'pos': 'P', 'neg': 'N'}

        acqs = {'ANALOG': [], 'POD1': [], 'POD2': []}
        for channel in self.child_devices:
            if channel.acquisitions:
                # make sure channel is allowed
                if channel.connection in self.allowed_analog_chan:
                    acqs['ANALOG'].append(
                        (channel.connection, channel.acquisitions[0]['label']))
                elif channel.connection in self.allowed_pod1_chan:
                    acqs['POD1'].append(
                        (channel.connection, channel.acquisitions[0]['label']))
                elif channel.connection in self.allowed_pod2_chan:
                    acqs['POD2'].append(
                        (channel.connection, channel.acquisitions[0]['label']))
                else:
                    raise LabscriptError(
                        '{0:s} is not a valid channel.'.format(
                            channel.connection))

        acquisition_table_dtypes = np.dtype({
            'names': ['connection', 'label'],
            'formats': ['a256', 'a256']
        })

        grp = self.init_device_group(hdf5_file)
        # write tables if non-empty to h5_file
        for acq_group, acq_chan in acqs.items():
            if len(acq_chan):
                table = np.empty(len(acq_chan), dtype=acquisition_table_dtypes)
                for i, acq in enumerate(acq_chan):
                    table[i] = acq
                grp.create_dataset(acq_group + '_ACQUISITIONS',
                                   compression=config.compression,
                                   data=table)
                grp[acq_group +
                    '_ACQUISITIONS'].attrs['trigger_time'] = self.trigger_time

        # now do the counters
        counts = []
        for channel in self.child_devices:
            if hasattr(channel, 'counts'):
                for counter in channel.counts:
                    counts.append((channel.connection, trans[counter['type']],
                                   trans[counter['polarity']]))
        counts_table_dtypes = np.dtype({
            'names': ['connection', 'type', 'polarity'],
            'formats': ['a256', 'a256', 'a256']
        })
        counts_table = np.empty(len(counts), dtype=counts_table_dtypes)
        for i, count in enumerate(counts):
            counts_table[i] = count
        if len(counts_table):
            grp.create_dataset('COUNTERS',
                               compression=config.compression,
                               data=counts_table)
            grp['COUNTERS'].attrs['trigger_time'] = self.trigger_time
 def convert_to_pb_inst(self, dig_outputs, dds_outputs, freqs, amps, phases):
     pb_inst = []
     # An array for storing the line numbers of the instructions at
     # which the slow clock ticks:
     slow_clock_indices = []
     # index to keep track of where in output.raw_output the
     # pulseblaster flags are coming from
     i = 0
     # 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
     
     if self.fast_clock_flag is not None:
         for fast_flag in self.fast_clock_flag:
             flags[fast_flag] = 0
     if self.slow_clock_flag is not None:
         for slow_flag in self.slow_clock_flag:
             flags[slow_flag] = 0
         
     pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                     '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,
                     '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.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
         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 self.fast_clock_flag is not None:
             for fast_flag in self.fast_clock_flag:
                 if (type(instruction['fast_clock']) == list and 'flag %d'%fast_flag in instruction['fast_clock']) or instruction['fast_clock'] == 'all':
                     flags[fast_flag] = 1
                 else:
                     flags[fast_flag] = 1 if instruction['slow_clock_tick'] else 0
         if self.slow_clock_flag is not None:
             for slow_flag in self.slow_clock_flag:
                 flags[slow_flag] = 1 if instruction['slow_clock_tick'] else 0
         if instruction['slow_clock_tick']:
             slow_clock_indices.append(j)
         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.')
             
         # Instruction delays > 55 secs will require a LONG_DELAY
         # to be inserted. How many times does the delay of the
         # loop/endloop instructions go into 55 secs?
         if self.has_clocks:
             quotient, remainder = divmod(instruction['step']/2.0,55.0)
         else:
             quotient, remainder = divmod(instruction['step'],55.0)
         if quotient and remainder < 100e-9:
             # The remainder will be used for the total duration of the LOOP and END_LOOP instructions. 
             # It must not be too short for this, if it is, take one LONG_DELAY iteration and give 
             # its duration to the loop instructions:
             quotient, remainder = quotient - 1, remainder + 55.0
         if self.has_clocks:
             # The loop and endloop instructions will only use the remainder:
             pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                             'flags': flagstring, 'instruction': 'LOOP',
                             'data': instruction['reps'], 'delay': remainder*1e9})
             if self.fast_clock_flag is not None:
                 for fast_flag in self.fast_clock_flag:
                     flags[fast_flag] = 0
             if self.slow_clock_flag is not None:
                 for slow_flag in self.slow_clock_flag:
                     flags[slow_flag] = 0
             flagstring = ''.join([str(flag) for flag in flags])
         
             # If there was a nonzero quotient, let's wait twice that
             # many multiples of 55 seconds (one multiple of 55 seconds
             # for each of the other two loop and endloop instructions):
             if quotient:
                 pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                             'flags': flagstring, 'instruction': 'LONG_DELAY',
                             'data': int(2*quotient), 'delay': 55*1e9})
                             
             pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                             'flags': flagstring, 'instruction': 'END_LOOP',
                             'data': j, 'delay': remainder*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 quotient else 2
         else:
             # The loop and endloop instructions will only use the remainder:
             pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                             'flags': flagstring, 'instruction': 'CONTINUE',
                             'data': 0, 'delay': remainder*1e9})
             # If there was a nonzero quotient, let's wait that many multiples of 55 seconds:
             if quotient:
                 pb_inst.append({'freqs': freqregs, 'amps': ampregs, 'phases': phaseregs, 'enables':dds_enables,
                             'flags': flagstring, 'instruction': 'LONG_DELAY',
                             'data': int(quotient), 'delay': 55*1e9})
             j += 2 if quotient else 1
             
         try:
             if self.clock[k+1] == 'WAIT' or self.clock[k+1]['slow_clock_tick']:
                 i += 1
         except IndexError:
             pass
     # 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,
                     'flags': flagstring, 'instruction': 'BRANCH',
                     'data': 0, 'delay': 10.0/self.clock_limit*1e9})  
                     
     return pb_inst, slow_clock_indices
예제 #4
0
 def setphase(self, value, units=None):
     raise LabscriptError('QuickSyn does not support phase control')
예제 #5
0
    def generate_code(self, hdf5_file):
        DDSs = {}
        for output in self.child_devices:
            # Check that the instructions will fit into RAM:
            if isinstance(output, DDS) and len(
                    output.frequency.raw_output
            ) > 16384 - 2:  # -2 to include space for dummy instructions
                raise LabscriptError(
                    '%s can only support 16383 instructions. ' % self.name +
                    'Please decrease the sample rates of devices on the same clock, '
                    + 'or connect %s to a different pseudoclock.' % self.name)
            try:
                prefix, channel = output.connection.split()
                channel = int(channel)
            except:
                raise LabscriptError(
                    '%s %s has invalid connection string: \'%s\'. ' %
                    (output.description, output.name, str(output.connection)) +
                    'Format must be \'channel n\' with n from 0 to 4.')
            DDSs[channel] = output
        for connection in DDSs:
            if connection in range(4):
                # Dynamic DDS
                dds = DDSs[connection]
                dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(
                    dds.frequency.raw_output, dds)
                dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(
                    dds.phase.raw_output, dds)
                dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(
                    dds.amplitude.raw_output, dds)
            # elif connection in range(2,4):
            # # StaticDDS:
            # dds = DDSs[connection]
            # dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(dds.frequency.static_value, dds)
            # dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(dds.phase.static_value, dds)
            # dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(dds.amplitude.static_value, dds)
            else:
                raise LabscriptError(
                    '%s %s has invalid connection string: \'%s\'. ' %
                    (dds.description, dds.name, str(dds.connection)) +
                    'Format must be \'channel n\' with n from 0 to 4.')

        dtypes = [('freq%d'%i,np.uint32) for i in range(2)] + \
                 [('phase%d'%i,np.uint16) for i in range(2)] + \
                 [('amp%d'%i,np.uint16) for i in range(2)]

        static_dtypes = [('freq%d'%i,np.uint32) for i in range(2,4)] + \
                        [('phase%d'%i,np.uint16) for i in range(2,4)] + \
                        [('amp%d'%i,np.uint16) for i in range(2,4)]

        clockline = self.parent_clock_line
        pseudoclock = clockline.parent_device
        times = pseudoclock.times[clockline]

        out_table = np.zeros(len(times), dtype=dtypes)
        out_table['freq0'].fill(1)
        out_table['freq1'].fill(1)

        static_table = np.zeros(1, dtype=static_dtypes)
        static_table['freq2'].fill(1)
        static_table['freq3'].fill(1)

        for connection in range(2):
            if not connection in DDSs:
                continue
            dds = DDSs[connection]
            # The last two instructions are left blank, for BLACS
            # to fill in at program time.
            out_table['freq%d' % connection][:] = dds.frequency.raw_output
            out_table['amp%d' % connection][:] = dds.amplitude.raw_output
            out_table['phase%d' % connection][:] = dds.phase.raw_output
        for connection in range(2, 4):
            if not connection in DDSs:
                continue
            dds = DDSs[connection]
            static_table['freq%d' % connection] = dds.frequency.raw_output[0]
            static_table['amp%d' % connection] = dds.amplitude.raw_output[0]
            static_table['phase%d' % connection] = dds.phase.raw_output[0]

        if self.update_mode == 'asynchronous' or self.synchronous_first_line_repeat:
            # Duplicate the first line of the table. Otherwise, we are one step
            # ahead in the table from the start of a run. In asynchronous
            # updating mode, this is necessary since the first line of the
            # table is already being output before the first trigger from
            # the master clock. When using a simple delay line for synchronous
            # output, this also seems to be required, in which case
            # synchronous_first_line_repeat should be set to True.
            # However, when a tristate driver is used as described at
            # http://labscriptsuite.org/blog/implementation-of-the-novatech-dds9m/
            # then is is not neccesary to duplicate the first line. Use of a
            # tristate driver in this way is the correct way to use
            # the novatech DDS, as per its instruction manual, and so is likely
            # to be the most reliable. However, through trial and error we've
            # determined that duplicating the first line like this gives correct
            # output in asynchronous mode and in synchronous mode when using a
            # simple delay line, at least for the specific device we tested.
            # Your milage may vary.
            out_table = np.concatenate([out_table[0:1], out_table])

        grp = self.init_device_group(hdf5_file)
        grp.create_dataset('TABLE_DATA',
                           compression=config.compression,
                           data=out_table)
        grp.create_dataset('STATIC_DATA',
                           compression=config.compression,
                           data=static_table)
        self.set_property('frequency_scale_factor',
                          10,
                          location='device_properties')
        self.set_property('amplitude_scale_factor',
                          1023,
                          location='device_properties')
        self.set_property('phase_scale_factor',
                          45.511111111111113,
                          location='device_properties')
예제 #6
0
    def _generate_code(self, hdf5_file):
        IntermediateDevice.generate_code(self, hdf5_file)
        analogs = {}
        digitals = {}
        inputs = {}
        for device in self.child_devices:
            if isinstance(device, AnalogOut):
                analogs[device.connection] = device
            elif isinstance(device, DigitalOut):
                digitals[device.connection] = device
            elif isinstance(device, AnalogIn):
                inputs[device.connection] = device
            else:
                raise Exception('Got unexpected device.')

        clockline = self.parent_device
        pseudoclock = clockline.parent_device
        times = pseudoclock.times[clockline]

        analog_connections = analogs.keys()
        analog_connections.sort()
        analog_out_attrs = []
        #KLUGGEEEE
        #get lenth of one output connection
        output_length = len(analogs[analog_connections[0]].raw_output)
        analog_out_table = np.empty((output_length, len(analogs)),
                                    dtype=np.float32)
        #analog_out_table = np.empty((len(times),len(analogs)), dtype=np.float32)
        for i, connection in enumerate(analog_connections):
            output = analogs[connection]
            if any(output.raw_output > 10) or any(output.raw_output < -10):
                # Bounds checking:
                raise LabscriptError(
                    '%s %s ' % (output.description, output.name) +
                    'can only have values between -10 and 10 Volts, ' +
                    'the limit imposed by %s.' % self.name)
            analog_out_table[:, i] = output.raw_output
            analog_out_attrs.append(self.MAX_name + '/' + connection)
        """
        if self.name == 'ni_card_B':
            #kluge to double every cell
            new_out_table = np.empty((output_length*2, len(analogs)), dtype=np.float32)
            for i, row in enumerate(analog_out_table):
                new_out_table[2*i] = row
                new_out_table[2*i+1] = row
            analog_out_table = new_out_table
        """
        #now we know that the last column should be randomized for one of the
        #CHOOSE CHANNEL HERE
        if self.name == 'ni_card_B':
            random_column = 0  #randint(0,6)
        else:
            # CHANGE THIS FOR CARD A
            random_column = 1

        #analog_out_table[:,-1] = analog_out_table[:,random_column]
        input_connections = inputs.keys()
        input_connections.sort()
        input_attrs = []
        acquisitions = []
        for connection in input_connections:
            input_attrs.append(self.MAX_name + '/' + connection)
            for acq in inputs[connection].acquisitions:
                acquisitions.append(
                    (connection, acq['label'], acq['start_time'],
                     acq['end_time'], acq['wait_label'], acq['scale_factor'],
                     acq['units']))
        # The 'a256' dtype below limits the string fields to 256
        # characters. Can't imagine this would be an issue, but to not
        # specify the string length (using dtype=str) causes the strings
        # to all come out empty.
        acquisitions_table_dtypes = [('connection', 'a256'), ('label', 'a256'),
                                     ('start', float), ('stop', float),
                                     ('wait label', 'a256'),
                                     ('scale factor', float),
                                     ('units', 'a256')]
        acquisition_table = np.empty(len(acquisitions),
                                     dtype=acquisitions_table_dtypes)
        for i, acq in enumerate(acquisitions):
            acquisition_table[i] = acq
        digital_out_table = []
        if digitals:
            digital_out_table = self.convert_bools_to_bytes(digitals.values())
        grp = self.init_device_group(hdf5_file)
        if all(analog_out_table.shape):  # Both dimensions must be nonzero
            grp.create_dataset('ANALOG_OUTS',
                               compression=config.compression,
                               data=analog_out_table)
            self.set_property('analog_out_channels',
                              ', '.join(analog_out_attrs),
                              location='device_properties')
        if len(digital_out_table):  # Table must be non empty
            grp.create_dataset('DIGITAL_OUTS',
                               compression=config.compression,
                               data=digital_out_table)
            self.set_property('digital_lines',
                              '/'.join((self.MAX_name, 'port0',
                                        'line0:%d' % (self.n_digitals - 1))),
                              location='device_properties')
        if len(acquisition_table):  # Table must be non empty
            grp.create_dataset('ACQUISITIONS',
                               compression=config.compression,
                               data=acquisition_table)
            self.set_property('analog_in_channels',
                              ', '.join(input_attrs),
                              location='device_properties')
        # TODO: move this to decorator (requires ability to set positional args with @set_passed_properties)
        self.set_property('clock_terminal',
                          self.clock_terminal,
                          location='connection_table_properties')
    def generate_code(self, hdf5_file):
        DDSs = {}
        for output in self.child_devices:
            # Check that the instructions will fit into RAM:
            if isinstance(output, DDS) and len(
                    output.frequency.raw_output
            ) > 16384 - 2:  # -2 to include space for dummy instructions
                raise LabscriptError(
                    '%s can only support 16383 instructions. ' % self.name +
                    'Please decrease the sample rates of devices on the same clock, '
                    + 'or connect %s to a different pseudoclock.' % self.name)
            try:
                prefix, channel = output.connection.split()
                channel = int(channel)
            except:
                raise LabscriptError(
                    '%s %s has invalid connection string: \'%s\'. ' %
                    (output.description, output.name, str(output.connection)) +
                    'Format must be \'channel n\' with n from 0 to 4.')
            DDSs[channel] = output
        for connection in DDSs:
            if connection in range(4):
                # Dynamic DDS
                dds = DDSs[connection]
                dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(
                    dds.frequency.raw_output, dds)
                dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(
                    dds.phase.raw_output, dds)
                dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(
                    dds.amplitude.raw_output, dds)
            # elif connection in range(2,4):
            # # StaticDDS:
            # dds = DDSs[connection]
            # dds.frequency.raw_output, dds.frequency.scale_factor = self.quantise_freq(dds.frequency.static_value, dds)
            # dds.phase.raw_output, dds.phase.scale_factor = self.quantise_phase(dds.phase.static_value, dds)
            # dds.amplitude.raw_output, dds.amplitude.scale_factor = self.quantise_amp(dds.amplitude.static_value, dds)
            else:
                raise LabscriptError(
                    '%s %s has invalid connection string: \'%s\'. ' %
                    (dds.description, dds.name, str(dds.connection)) +
                    'Format must be \'channel n\' with n from 0 to 4.')

        dtypes = [('freq%d'%i,np.uint32) for i in range(2)] + \
                 [('phase%d'%i,np.uint16) for i in range(2)] + \
                 [('amp%d'%i,np.uint16) for i in range(2)]

        static_dtypes = [('freq%d'%i,np.uint32) for i in range(2,4)] + \
                        [('phase%d'%i,np.uint16) for i in range(2,4)] + \
                        [('amp%d'%i,np.uint16) for i in range(2,4)]

        clockline = self.parent_clock_line
        pseudoclock = clockline.parent_device
        times = pseudoclock.times[clockline]

        out_table = np.zeros(len(times), dtype=dtypes)
        out_table['freq0'].fill(1)
        out_table['freq1'].fill(1)

        static_table = np.zeros(1, dtype=static_dtypes)
        static_table['freq2'].fill(1)
        static_table['freq3'].fill(1)

        for connection in range(2):
            if not connection in DDSs:
                continue
            dds = DDSs[connection]
            # The last two instructions are left blank, for BLACS
            # to fill in at program time.
            out_table['freq%d' % connection][:] = dds.frequency.raw_output
            out_table['amp%d' % connection][:] = dds.amplitude.raw_output
            out_table['phase%d' % connection][:] = dds.phase.raw_output
        for connection in range(2, 4):
            if not connection in DDSs:
                continue
            dds = DDSs[connection]
            static_table['freq%d' % connection] = dds.frequency.raw_output[0]
            static_table['amp%d' % connection] = dds.amplitude.raw_output[0]
            static_table['phase%d' % connection] = dds.phase.raw_output[0]

        if self.update_mode == 'asynchronous':
            # Duplicate the first line. Otherwise, we are one step ahead in the table
            # from the start of a run. This problem is not completely understood, but this
            # fixes it:
            out_table = np.concatenate([out_table[0:1], out_table])

        grp = self.init_device_group(hdf5_file)
        grp.create_dataset('TABLE_DATA',
                           compression=config.compression,
                           data=out_table)
        grp.create_dataset('STATIC_DATA',
                           compression=config.compression,
                           data=static_table)
        self.set_property('frequency_scale_factor',
                          10,
                          location='device_properties')
        self.set_property('amplitude_scale_factor',
                          1023,
                          location='device_properties')
        self.set_property('phase_scale_factor',
                          45.511111111111113,
                          location='device_properties')
예제 #8
0
    def generate_code(self, hdf5_file):

        IntermediateDevice.generate_code(self, hdf5_file)

        DDSProfs = {}

        if len(self.child_devices) > 1:
            raise LabscriptError(
                "Too many child_devices. This device expects exactly 1 AD_DDS child output."
            )

        output = self.child_devices[0]

        if isinstance(output, AD_DDS):
            prefix = output.connection[0]
            channel = int(output.connection[1])
        else:
            raise Exception('Got unexpected device.')

        numDDSProfs = len(output.profiles)

        grp = hdf5_file.create_group('/devices/' + self.name)

        if numDDSProfs:
            profile_dtypes = [('freq%d'%i,np.float) for i in range(numDDSProfs)] + \
                            [('phase%d'%i,np.float) for i in range(numDDSProfs)] + \
                            [('amp%d'%i,np.float) for i in range(numDDSProfs)]

            profile_table = np.zeros(1, dtype=profile_dtypes)

            for profile in output.profiles:
                profile_table['freq' +
                              profile[-1:]] = output.profiles[profile]['freq']
                profile_table['amp' +
                              profile[-1:]] = output.profiles[profile]['amp']
                profile_table['phase' +
                              profile[-1:]] = output.profiles[profile]['phase']

            grp.create_dataset('PROFILE_DATA',
                               compression=config.compression,
                               data=profile_table)

        if output.sweep:
            sweep_dtypes = [('sweep_type', np.int), ('sweep_low', np.float),
                            ('sweep_high', np.float),
                            ('sweep_risetime', np.float),
                            ('sweep_falltime', np.float),
                            ('sweep_dt', np.float)]

            sweep_table = np.empty(1, dtype=sweep_dtypes)

            sweep_types = {'freq': 0, 'phase': 1, 'amp': 2}

            sweep_table['sweep_type'] = sweep_types[
                output.sweep_params['type']]
            sweep_table['sweep_low'] = output.sweep_params['low']
            sweep_table['sweep_high'] = output.sweep_params['high']
            sweep_table['sweep_risetime'] = output.sweep_params['risetime']
            sweep_table['sweep_falltime'] = output.sweep_params['falltime']
            sweep_table['sweep_dt'] = output.sweep_params['sweep_dt']

            grp.create_dataset('SWEEP_DATA',
                               compression=config.compression,
                               data=sweep_table)
 def set_cam_param(self, param, value):
     if self.other_params.has_key(param):
         self.other_params[param] = value
     else:
         raise LabscriptError(
             'Camera parameter %s does not exist in dictionary' % param)
예제 #10
0
    def __init__(self, name, DoSomething = False, **kwargs):
        if DoSomething is not False:
            raise LabscriptError('test_device does nothing, but kwarg DoSomething was not passed False')


        Device.__init(self, name, None, None, **kwargs)
    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')
예제 #12
0
    def __init__(self,
                 name,
                 parent_device=None,
                 clock_terminal=None,
                 MAX_name=None,
                 static_AO=None,
                 static_DO=None,
                 clock_mirror_terminal=None,
                 acquisition_rate=None,
                 AI_range=None,
                 AI_range_Diff=None,
                 AI_start_delay=0,
                 AI_start_delay_ticks=None,
                 AI_term='RSE',
                 AI_term_cfg=None,
                 AO_range=None,
                 max_AI_multi_chan_rate=None,
                 max_AI_single_chan_rate=None,
                 max_AO_sample_rate=None,
                 max_DO_sample_rate=None,
                 min_semiperiod_measurement=None,
                 num_AI=0,
                 num_AO=0,
                 num_CI=0,
                 ports=None,
                 supports_buffered_AO=False,
                 supports_buffered_DO=False,
                 supports_semiperiod_measurement=False,
                 supports_simultaneous_AI_sampling=False,
                 **kwargs):
        """Generic class for NI_DAQmx devices.

        Generally over-ridden by device-specific subclasses that contain
        the introspected default values.

        Args:
            name (str): name to assign to the created labscript device
            parent_device (clockline): Parent clockline device that will
                clock the outputs of this device
            clock_terminal (str): What input on the DAQ is used for the clockline
            MAX_name (str): NI-MAX device name
            static_AO (int, optional): Number of static analog output channels.
            static_DO (int, optional): Number of static digital output channels.
            clock_mirror_terminal (str, optional): Channel string of digital output
                that mirrors the input clock. Useful for daisy-chaning DAQs on the same
                clockline.
            acquisiton_rate (float, optional): Default sample rate of inputs.
            AI_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog
                input voltage range for all analog inputs.
            AI_range_Diff (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog
                input voltage range for all analog inputs when using Differential termination.
            AI_start_delay (float, optional): Time in seconds between start of an
                analog input task starting and the first sample.
            AI_start_delay_ticks (int, optional): Time in sample clock periods between
                start of an analog input task starting and the first sample. To use
                this method, `AI_start_delay` must be set to `None`. This is necessary
                for DAQs that employ delta ADCs.
            AI_term (str, optional): Configures the analog input termination for all
                analog inputs. Must be supported by the device. Supported options are
                `'RSE'`, `'NRSE'` `'Diff'`, and '`PseudoDiff'`.
            AI_term_cfg (dict, optional): Dictionary of analog input channels and their
                supported terminations. Best to use `get_capabilities.py` to introspect
                these.
            AO_range (iterable, optional): A `[Vmin, Vmax]` pair that sets the analog
                output voltage range for all analog outputs.
            max_AI_multi_chan_rate (float, optional): Max supported analog input 
                sampling rate when using multiple channels.
            max_AI_single_chan_rate (float, optional): Max supported analog input
                sampling rate when only using a single channel.
            max_AO_sample_rate (float, optional): Max supported analog output
                sample rate.
            max_DO_sample_rate (float, optional): Max supported digital output
                sample rate.
            min_sermiperiod_measurement (float, optional): Minimum measurable time
                for a semiperiod measurement.
            num_AI (int, optional): Number of analog inputs channels.
            num_AO (int, optional): Number of analog output channels.
            num_CI (int, optional): Number of counter input channels.
            ports (dict, optional): Dictionarly of DIO ports, which number of lines
                and whether port supports buffered output.
            supports_buffered_AO (bool, optional): True if analog outputs support
                buffered output
            supports_buffered_DO (bool, optional): True if digital outputs support
                buffered output
            supports_semiperiod_measurement (bool, optional): True if device supports
                semi-period measurements

        """

        # Default static output setting based on whether the device supports buffered
        # output:
        if static_AO is None:
            static_AO = not supports_buffered_AO
        if static_DO is None:
            static_DO = not supports_buffered_DO

        # Parent is only allowed to be None if output is static:
        if parent_device is None and not (static_DO and static_AO):
            msg = """Must specify a parent clockline, unless both static_AO and
                static_DO are True"""
            raise LabscriptError(dedent(msg))
        # If parent device is not None though, then clock terminal must be specified:
        if parent_device is not None and clock_terminal is None:
            msg = """If parent_device is given, then clock_terminal must be specified as
                well as the terminal to which the parent pseudoclock is connected."""
            raise ValueError(dedent(msg))
        if acquisition_rate is not None and num_AI == 0:
            msg = "Cannot set set acquisition rate on device with no analog inputs"
            raise ValueError(msg)
        # Acquisition rate cannot be larger than the single channel rate:
        if acquisition_rate is not None and acquisition_rate > max_AI_single_chan_rate:
            msg = """acquisition_rate %f is larger than the maximum single-channel rate
                %f for this device"""
            raise ValueError(
                dedent(msg) % (acquisition_rate, max_AI_single_chan_rate))

        self.clock_terminal = clock_terminal
        self.MAX_name = MAX_name if MAX_name is not None else name
        self.static_AO = static_AO
        self.static_DO = static_DO

        self.acquisition_rate = acquisition_rate
        self.AO_range = AO_range
        self.max_AI_multi_chan_rate = max_AI_multi_chan_rate
        self.max_AI_single_chan_rate = max_AI_single_chan_rate
        self.max_AO_sample_rate = max_AO_sample_rate
        self.max_DO_sample_rate = max_DO_sample_rate
        self.min_semiperiod_measurement = min_semiperiod_measurement
        self.num_AI = num_AI
        # special handling for AI termination configurations
        self.AI_term = AI_term
        if AI_term_cfg == None:
            # assume legacy configuration if none provided
            AI_term_cfg = {f'ai{i:d}': ['RSE'] for i in range(num_AI)}
            # warn user to update their local model specs
            msg = """Model specifications for {} needs to be updated.
                  Please run the `get_capabilites.py` and `generate_subclasses.py`
                  scripts or define the `AI_Term_Cfg` kwarg for your device.
                  """
            warnings.warn(dedent(msg.format(self.description)), FutureWarning)
        self.AI_chans = [
            key for key, val in AI_term_cfg.items() if self.AI_term in val
        ]
        if not len(self.AI_chans):
            msg = """AI termination {0} not supported by this device."""
            raise LabscriptError(dedent(msg.format(AI_term)))
        if AI_term == 'Diff':
            self.AI_range = AI_range_Diff
        if AI_start_delay is None:
            if AI_start_delay_ticks is not None:
                # Tell blacs_worker to use AI_start_delay_ticks to define delay
                self.start_delay_ticks = True
            else:
                raise LabscriptError(
                    "You have specified `AI_start_delay = None` but have not provided `AI_start_delay_ticks`."
                )
        else:
            # Tells blacs_worker to use AI_start_delay to define delay
            self.start_delay_ticks = False
        self.num_AO = num_AO
        self.num_CI = num_CI
        self.ports = ports if ports is not None else {}
        self.supports_buffered_AO = supports_buffered_AO
        self.supports_buffered_DO = supports_buffered_DO
        self.supports_semiperiod_measurement = supports_semiperiod_measurement
        self.supports_simultaneous_AI_sampling = supports_simultaneous_AI_sampling

        if self.supports_buffered_DO and self.supports_buffered_AO:
            self.clock_limit = min(self.max_DO_sample_rate,
                                   self.max_AO_sample_rate)
        elif self.supports_buffered_DO:
            self.clock_limit = self.max_DO_sample_rate
        elif self.supports_buffered_AO:
            self.clock_limit = self.max_AO_sample_rate
        else:
            self.clock_limit = None
            if not (static_AO and static_DO):
                msg = """Device does not support buffered output, please instantiate
                it with static_AO=True and static_DO=True"""
                raise LabscriptError(dedent(msg))

        self.wait_monitor_minimum_pulse_width = self.min_semiperiod_measurement

        self.allowed_children = []
        '''Sets the allowed children types based on the capabilites.'''
        if self.num_AI > 0:
            self.allowed_children += [AnalogIn]
        if self.num_AO > 0 and static_AO:
            self.allowed_children += [StaticAnalogOut]
        if self.num_AO > 0 and not static_AO:
            self.allowed_children += [AnalogOut]
        if self.ports and static_DO:
            self.allowed_children += [StaticDigitalOut]
        if self.ports and not static_DO:
            self.allowed_children += [DigitalOut]

        if clock_terminal is None and not (static_AO and static_DO):
            msg = """Clock terminal must be specified unless static_AO and static_DO are
                both True"""
            raise LabscriptError(dedent(msg))

        self.BLACS_connection = self.MAX_name

        # Cannot be set with set_passed_properties because of name mangling with the
        # initial double underscore:
        self.set_property('__version__', __version__,
                          'connection_table_properties')

        # This is called late since it must be called after our clock_limit attribute is
        # set:
        IntermediateDevice.__init__(self, name, parent_device, **kwargs)
예제 #13
0
    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 = bytes(i) if PY2 else 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)
예제 #14
0
 def setphase(self, value, units=None):
     """Overridden from StaticDDS so as not to provide phase control, which
     is generally not supported by :obj:`SignalGenerator` devices.
     """
     raise LabscriptError('StaticFreqAmp does not support phase control')
    def __init__(self,
                 name,
                 parent_device,
                 com_port='COM1',
                 FMSignal=None,
                 RFOnOff=None,
                 freq_limits=None,
                 freq_conv_class=None,
                 freq_conv_params={},
                 amp_limits=None,
                 amp_conv_class=None,
                 amp_conv_params={},
                 phase_limits=None,
                 phase_conv_class=None,
                 phase_conv_params={}):
        #self.clock_type = parent_device.clock_type # Don't see that this is needed anymore

        IntermediateDevice.__init__(self, name, parent_device)

        self.BLACS_connection = com_port
        self.sweep_dt = 2e-6

        self.RFSettings = {
            'freq': 0,
            'amp': 0,
            'phase': 0,
            'freq_dev': 0  ##EE
        }

        self.sweep_params = {
            #            'type': None,
            'low': 0,
            'high': 1,
            'duration': 0,
            #            'falltime': 0,
            'sample_rate': self.sweep_dt
        }
        self.sweep = False
        self.ext_in = False

        self.frequency = StaticAnalogQuantity(self.name + '_freq', self,
                                              'freq', freq_limits,
                                              freq_conv_class,
                                              freq_conv_params)
        self.amplitude = StaticAnalogQuantity(self.name + '_amp', self, 'amp',
                                              amp_limits, amp_conv_class,
                                              amp_conv_params)
        self.phase = StaticAnalogQuantity(self.name + '_phase', self, 'phase',
                                          phase_limits, phase_conv_class,
                                          phase_conv_params)

        self.FMSignal = {}
        self.RFOnOff = {}

        if FMSignal:
            if 'device' in FMSignal and 'connection' in FMSignal:
                self.FMSignal = AnalogOut(self.name + '_FMSignal',
                                          FMSignal['device'],
                                          FMSignal['connection'])
            else:
                raise LabscriptError(
                    'You must specify the "device" and "connection" for the analog output FMSignal of '
                    + self.name)
        else:
            raise LabscriptError(
                'Expected analog output for "FMSignal" control')

        if RFOnOff:
            if 'device' in RFOnOff and 'connection' in RFOnOff:
                self.RFOnOff = DigitalOut(self.name + '_RFOnOff',
                                          RFOnOff['device'],
                                          RFOnOff['connection'])
            else:
                raise LabscriptError(
                    'You must specify the "device" and "connection" for the digital output RFOnOff of '
                    + self.name)
        else:
            raise LabscriptError(
                'Expected digital output for "RFOnOff" control')
예제 #16
0
 def disable(self):
     """overridden from StaticDDS so as not to provide time resolution -
     output can be enabled or disabled only at the start of the shot"""
     raise LabscriptError(
         'StaticFreqAmp {:s} does not support a digital gate'.format(
             self.name))
예제 #17
0
 def init(self):
     """Initialization command run automatically by the BLACS tab on 
     startup. It establishes communication and sends initial default 
     configuration commands"""
     self.smart_cache = {'STATIC_DATA': None, 'TABLE_DATA': '',
                             'CURRENT_DATA':None}
     self.baud_dict = {9600:b'78', 19200:b'3c', 38400:b'1e',57600:b'14',115200:b'0a'}
     
     self.err_codes = {b'?0':'Unrecognized Command',
                       b'?1':'Bad Frequency',
                       b'?2':'Bad AM Command',
                       b'?3':'Input Line Too Long',
                       b'?4':'Bad Phase',
                       b'?5':'Bad Time',
                       b'?6':'Bad Mode',
                       b'?7':'Bad Amp',
                       b'?8':'Bad Constant',
                       b'?f':'Bad Byte'}
     
     # total number of DDS channels on device & channel properties
     self.N_chan = 4
     self.subchnls = ['freq','amp','phase']
     
     # conversion dictionaries for program_static from 
     # program_manual                      
     self.conv = {'freq':self.clk_scale*10**(-6),'amp':1023.0,'phase':16384.0/360.0}## check if things break 2019-02-22
     # and from transition_to_buffered
     self.conv_buffered = {'freq':10**(-7),'amp':1,'phase':1}
     # read from device conversion, basically conv_buffered/conv
     self.read_conv = {'freq':1/(self.clk_scale*10.0),'amp':1/1023.0,'phase':360.0/16384.0} ## check if things break 2019-02-22
     
     # set phase mode method
     phase_mode_commands = {
         'aligned': b'm a',
         'continuous': b'm n'}
     self.phase_mode_command = phase_mode_commands[self.phase_mode]
     
     self.connection = serial.Serial(self.com_port, baudrate = self.baud_rate, timeout=0.1)
     self.connection.readlines()
     
     # to configure baud rate, must determine current device baud rate
     # first check desired, since it's most likely
     connected, response = self.check_connection()
     if not connected:
         # not already set
         bauds = list(self.baud_dict)
         if self.baud_rate in bauds:
             bauds.remove(self.baud_rate)
         else:
             raise LabscriptError('%d baud rate not supported by Novatech 409B' % self.baud_rate)
             
         # iterate through other baud-rates to find current
         for rate in bauds:
             self.connection.baudrate = rate
             connected, response = self.check_connection()
             if connected:
                 # found it!
                 break
         else:
             raise LabscriptError('Error: Baud rate not found! Is Novatech DDS connected?')
         
         # now we can set the desired baud rate
         baud_string = b'Kb %s\r\n' % (self.baud_dict[self.baud_rate])
         self.connection.write(baud_string)
         # ensure command finishes before switching rates in pyserial
         time.sleep(0.1)
         self.connection.baudrate = self.baud_rate
         connected, response = self.check_connection()
         if not connected:
             raise LabscriptError('Error: Failed to execute command "%s"' % baud_string.decode('utf8'))           
     
     self.connection.write(b'e d\r\n')
     response = self.connection.readline()
     if response == b'e d\r\n':
         # if echo was enabled, then the command to disable it echos back at us!
         response = self.connection.readline()
     if response != b'OK\r\n':
         raise Exception('Error: Failed to execute command: "e d". Cannot connect to the device.')
     
     # set automatic updates and phase mode
     self.write_check(b'M 0\r\n')
     self.write_check(b'I a\r\n')
     self.write_check(b'%s\r\n'%self.phase_mode_command)
     
     # Set clock parameters
     if self.R_option:
         # Using R option. Do not send C or Kp serial commands
         pass
     else:
         # Pass kp value
         self.write_check(b'Kp %02x\r\n'%self.kp)
         # Pass clock setting            
         if self.ext_clk:
             # Enable external clock
             clk_command = b'C E\r\n'              
         else:
             # Or enable internal clock
             clk_command = b'C I\r\n'
         self.write_check(clk_command)   
     
     # populate the 'CURRENT_DATA' dictionary    
     self.check_remote_values()
예제 #18
0
    def SetUpSweep(self, type, low, high, risetime, falltime, dt):

        # Set profiles to default values
        # !!!!! Currently assumes that:
        #           1. Only frequencies are swept (NOT ampl or phase)
        #           2. Frequency sweeps occur with amplitude = 1 and phase = 0
        for profile in range(8):
            self.program_static('profile %i' % profile, 'freq',
                                13)  # Arbitrary frequency
            self.program_static('profile %i' % profile, 'amp', 1)
            self.program_static('profile %i' % profile, 'phase', 0)

        reg0 = self.ReadRegister(0)
        reg0[1] |= 0x10  # Clear accumulator
        self.WriteRegister(0, reg0, True)
        time.sleep(0.1)
        reg0[1] &= ~0x10
        self.WriteRegister(0, reg0, True)

        reg1 = self.ReadRegister(1)
        reg1[2] |= 0x08  # Enable digital ramp
        reg1[2] &= ~0x30  # Clear ramp destination bits
        reg1[2] |= type << 4  # Set ramp destination bits
        self.WriteRegister(1, reg1)

        if low >= high:
            raise LabscriptError(
                "High sweep point must be higher than low sweep point.")

        stepSize_rise = (high - low) * dt / risetime
        stepSize_fall = (high - low) * dt / falltime

        #        print "Step sizes!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
        #        print stepSize_rise
        #        print stepSize_fall

        reg4 = bytearray(4)
        reg5 = bytearray(4)
        reg6 = bytearray(4)
        reg7 = bytearray(4)
        if type == 0:  # freq
            reg4 = self.CalcFTW(low)
            reg5 = self.CalcFTW(high)
            reg6 = self.CalcFTW(stepSize_rise)
            reg7 = self.CalcFTW(stepSize_fall)
        elif type == 1:  # phase
            # Must shift over by 18 bits to fill only the 14 MSBs
            reg4[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcPOW(low))[0]) *
                    (2**2)))[:2]
            reg5[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcPOW(high))[0]) *
                    (2**2)))[:2]
            reg6[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcPOW(stepSize_rise))[0]) *
                    (2**2)))[:2]
            reg7[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcPOW(stepSize_fall))[0]) *
                    (2**2)))[:2]
        elif type == 2:  # amp
            # Must shift over by 20 bits to fill only the 12 MSBs
            reg4[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcASF(low))[0]) *
                    (2**4)))[:2]
            reg5[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcASF(high))[0]) *
                    (2**4)))[:2]
            reg6[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcASF(stepSize_rise))[0]) *
                    (2**4)))[:2]
            reg7[2:] = bytearray(
                struct.pack(
                    '@H',
                    int(struct.unpack('@H', self.CalcASF(stepSize_fall))[0]) *
                    (2**4)))[:2]

        if int(struct.unpack('@I', reg6)[0]) == 0:
            raise LabscriptError(
                "Rising step size is too small - got rounded to 0")
        if int(struct.unpack('@I', reg7)[0]) == 0:
            raise LabscriptError(
                "Falling step size is too small - got rounded to 0")

        self.WriteRegister(4, reg4)
        self.WriteRegister(5, reg5)
        self.WriteRegister(6, reg6)
        self.WriteRegister(7, reg7)

        # Set the slope time interval (constant: 2e-6 s)
        dt_reg = bytearray(4)
        dt_word = int(round(dt * self.clk / 24))
        if dt_word >= 2**16:
            raise LabscriptError("Sweep time interval dt is too long.")

        dt_word = bytearray(struct.pack('@H', dt_word))
        dt_reg[:2] = dt_word
        dt_reg[2:] = dt_word
        self.WriteRegister(8, dt_reg)

        self.IOUpdate()
    def generate_code(self, hdf5_file):
        IntermediateDevice.generate_code(self, hdf5_file)
        analogs = {}
        digitals = {}
        inputs = {}
        for device in self.child_devices:
            if isinstance(device, AnalogOut):
                analogs[device.connection] = device
            elif isinstance(device, DigitalOut):
                digitals[device.connection] = device
            elif isinstance(device, AnalogIn):
                inputs[device.connection] = device
            else:
                raise Exception('Got unexpected device.')

        clockline = self.parent_device
        pseudoclock = clockline.parent_device
        times = pseudoclock.times[clockline]

        analog_out_table = np.empty((len(times), len(analogs)),
                                    dtype=np.float32)
        analog_connections = analogs.keys()
        analog_connections.sort()
        analog_out_attrs = []
        for i, connection in enumerate(analog_connections):
            output = analogs[connection]
            if any(output.raw_output > 10) or any(output.raw_output < -10):
                # Bounds checking:
                raise LabscriptError(
                    '%s %s ' % (output.description, output.name) +
                    'can only have values between -10 and 10 Volts, ' +
                    'the limit imposed by %s.' % self.name)
            analog_out_table[:, i] = output.raw_output
            analog_out_attrs.append(self.MAX_name + '/' + connection)
        input_connections = inputs.keys()
        input_connections.sort()
        input_attrs = []
        acquisitions = []
        for connection in input_connections:
            input_attrs.append(self.MAX_name + '/' + connection)
            for acq in inputs[connection].acquisitions:
                acquisitions.append(
                    (connection, acq['label'], acq['start_time'],
                     acq['end_time'], acq['wait_label'], acq['scale_factor'],
                     acq['units']))
        # The 'a256' dtype below limits the string fields to 256
        # characters. Can't imagine this would be an issue, but to not
        # specify the string length (using dtype=str) causes the strings
        # to all come out empty.
        acquisitions_table_dtypes = [('connection', 'a256'), ('label', 'a256'),
                                     ('start', float), ('stop', float),
                                     ('wait label', 'a256'),
                                     ('scale factor', float),
                                     ('units', 'a256')]
        acquisition_table = np.empty(len(acquisitions),
                                     dtype=acquisitions_table_dtypes)
        for i, acq in enumerate(acquisitions):
            acquisition_table[i] = acq
        digital_out_table = []
        if digitals:
            digital_out_table = self.convert_bools_to_bytes(digitals.values())
        grp = self.init_device_group(hdf5_file)
        if all(analog_out_table.shape):  # Both dimensions must be nonzero
            grp.create_dataset('ANALOG_OUTS',
                               compression=config.compression,
                               data=analog_out_table)
            self.set_property('analog_out_channels',
                              ', '.join(analog_out_attrs),
                              location='device_properties')
        if len(digital_out_table):  # Table must be non empty
            grp.create_dataset('DIGITAL_OUTS',
                               compression=config.compression,
                               data=digital_out_table)
            # construct a single string that has each port and line distribution separated by commas
            # this should coincide with the convention used by the create/write functions in the DAQmx library
            ports_str = ""
            for i in range(self.n_ports):
                ports_str = ports_str + self.MAX_name + '/port%d' % (
                    i) + '/line0:%d' % (self.n_lines - 1) + ','
            ports_str = ports_str[:-1]  # delete final comma in string
            self.set_property('digital_lines', (ports_str),
                              location='device_properties')
        if len(acquisition_table):  # Table must be non empty
            grp.create_dataset('ACQUISITIONS',
                               compression=config.compression,
                               data=acquisition_table)
            self.set_property('analog_in_channels',
                              ', '.join(input_attrs),
                              location='device_properties')
        # TODO: move this to decorator (requires ability to set positional args with @set_passed_properties)
        self.set_property('clock_terminal',
                          self.clock_terminal,
                          location='connection_table_properties')
예제 #20
0
 def add_device(self, device):
     if isinstance(device, WaitMonitor):
         IntermediateDevice.add_device(self, device)
     else:
         raise LabscriptError('You can only connect an instance of WaitMonitor to the device %s.internal_wait_monitor_outputs'%(self.pseudoclock_device.name))
예제 #21
0
 def setamp(self, value, units=None):
     raise LabscriptError('QuickSyn does not support amplitude control')
예제 #22
0
    def add_device(self, device):
        """Error checking for adding a child device"""
        # Verify static/dynamic outputs compatible with configuration:
        if isinstance(device, StaticAnalogOut) and not self.static_AO:
            msg = """Cannot add StaticAnalogOut to NI_DAQmx device configured for
                dynamic analog output. Pass static_AO=True for static analog output"""
            raise LabscriptError(dedent(msg))
        if isinstance(device, StaticDigitalOut) and not self.static_DO:
            msg = """Cannot add StaticDigitalOut to NI_DAQmx device configured for
                dynamic digital output. Pass static_DO=True for static digital output"""
            raise LabscriptError(dedent(msg))
        if isinstance(device, AnalogOut) and self.static_AO:
            msg = """Cannot add AnalogOut to NI_DAQmx device configured for
                static analog output. Pass static_AO=False for dynamic analog output"""
            raise LabscriptError(dedent(msg))
        if isinstance(device, DigitalOut) and self.static_DO:
            msg = """Cannot add DigitalOut to NI_DAQmx device configured for static
                digital output. Pass static_DO=False for dynamic digital output"""
            raise LabscriptError(dedent(msg))
        # Verify connection string is OK:
        if isinstance(device, (AnalogOut, StaticAnalogOut)):
            ao_num = split_conn_AO(device.connection)
            if ao_num >= self.num_AO:
                msg = """Cannot add output with connection string '%s' to device with
                num_AO=%d"""
                raise ValueError(
                    dedent(msg) % (device.connection, self.num_AO))
        elif isinstance(device, (DigitalOut, StaticDigitalOut)):
            port, line = split_conn_DO(device.connection)
            port_str = 'port%d' % port
            if port_str not in self.ports:
                msg = "Parent device has no such DO port '%s'" % port_str
                raise ValueError(msg)
            nlines = self.ports[port_str]['num_lines']
            if line >= nlines:
                msg = """Canot add output with connection string '%s' to port '%s'
                with only %d lines"""
                raise ValueError(
                    dedent(msg) % (device.connection, port_str, nlines))
            supports_buffered = self.ports[port_str]['supports_buffered']
            if isinstance(device, DigitalOut) and not supports_buffered:
                msg = """Cannot add DigitalOut port '%s', which does not support
                    buffered output"""
                raise ValueError(dedent(msg) % port_str)
        elif isinstance(device, AnalogIn):
            ai_num = split_conn_AI(device.connection)
            if ai_num >= self.num_AI:
                msg = """Cannot add analog input with connection string '%s' to device
                with num_AI=%d"""
                raise ValueError(
                    dedent(msg) % (device.connection, self.num_AI))
            if self.acquisition_rate is None:
                msg = """Cannot add analog input to NI_DAQmx device with
                    acquisition_rate=None. Please set acquisition_rate as an
                    instantiation argument to the parent NI_DAQmx device."""
                raise ValueError(dedent(msg))
            if self.parent_device is None:
                msg = """Cannot add analog input to device with no parent pseudoclock.
                    Even if there is no buffered output, a pseudoclock is still required
                    to trigger the start of acquisition. Please specify a parent_device
                    and clock_terminal for device %s"""
                raise ValueError(dedent(msg) % self.name)

        IntermediateDevice.add_device(self, device)
 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