def upload_waveforms(self, waveforms, append=False, allow_existing=False):
        """Uploade a list of waveforms


        TODO:
            allow_existing can be improved by first checking which new waveforms
            already exist, and then subsequently checking if the maximum size
            is exceeded
        """
        total_new_waveform_points = sum(map(len, waveforms))
        if total_new_waveform_points > self._parent.waveform_max_length:
            raise RuntimeError(
                f'Total waveform points {total_new_waveform_points} exceeds '
                f'limit of 81180A ({self._parent.waveform_max_length})')

        total_existing_waveform_points = sum(
            map(len, self.uploaded_waveforms()))
        total_waveform_points = total_existing_waveform_points + total_new_waveform_points

        waveform_idx_mapping = {}

        if append:  # Append all new waveforms to existing waveforms
            if total_waveform_points > self._parent.waveform_max_length:
                raise RuntimeError(
                    f'Total existing and new waveform points combined '
                    f'{total_new_waveform_points} exceeds limit of 81180A '
                    f'({self._parent.waveform_max_length}). Please set force=False'
                )
            else:
                for k, waveform in enumerate(waveforms, start=1):
                    idx = len(
                        self.uploaded_waveforms()) + 1  # 1-based indexing
                    waveform_idx_mapping[k] = idx
                    self.upload_waveform(waveform, idx)
        elif allow_existing:  # Only append new waveforms if not already existing
            if total_waveform_points > self._parent.waveform_max_length:
                self.clear_waveforms()
            for k, waveform in enumerate(waveforms,
                                         start=1):  # 1-based indexing
                idx = arreqclose_in_list(waveform,
                                         self.uploaded_waveforms(),
                                         atol=1e-3)
                if idx is None:
                    idx = self.upload_waveform(waveform)
                else:
                    idx += 1  # 1-based indexing
                waveform_idx_mapping[k] = idx
        else:
            for k, waveform in enumerate(waveforms,
                                         start=1):  # 1-based indexing
                self.upload_waveform(waveform, k)
                waveform_idx_mapping[k] = k

        return waveform_idx_mapping
    def load_waveforms_sequences(self):
        """Load waveforms and sequences into the arbstudio

        If self.force_upload_waveform() is False, a check is performed on each
        waveform if they have already previously been uploaded. If all waveforms
        have already been uploaded, the waveforms are not uploaded.

        If any waveforms need to be uploaded, or if self.force_upload_waveform()
        is True, all waveforms are cleared and the waveforms are then uploaded.

        Todo:
            Only upload waveforms that have not previously been uploaded instead
            of uploading all waveforms if not all waveforms have been uploaded
        """
        for ch in self._output_channels:
            # Get corresponding instrument channel object
            channel = self.instrument.channels[ch]

            if not self.force_upload_waveform():
                # Check if all waveforms already exist on the arbstudio channel.
                # If so, do not upload waveforms
                channel_idxs = [
                    arreqclose_in_list(wf,
                                       channel.waveforms,
                                       rtol=1e-4,
                                       atol=1e-5) for wf in self.waveforms[ch]
                ]

                if None not in channel_idxs:
                    # All waveforms are already uploaded, skip uploading waveforms
                    self.waveforms[ch] = channel.waveforms
                    # Remap sequences to existing waveforms
                    self.sequences[ch] = [
                        channel_idxs[sequence_idx]
                        for sequence_idx in self.sequences[ch]
                    ]
                    upload_waveforms = False
                    logger.debug('waveforms already uploaded, skipping upload')
                else:
                    upload_waveforms = True
            else:
                upload_waveforms = True

            if upload_waveforms:
                channel.clear_waveforms()
                for waveform in self.waveforms[ch]:
                    channel.add_waveform(waveform)
                channel.load_waveforms()

            # Add sequence to channel
            channel.sequence = self.sequences[ch]
            channel.load_sequence()
Exemple #3
0
    def add_single_waveform(self,
                            channel_name: str,
                            waveform_array: np.ndarray,
                            allow_existing: bool = True) -> int:
        """Add waveform to instrument, uploading if necessary

        If the waveform already exists on the instrument and allow_existing=True,
        the existing waveform is used and no new waveform is uploaded.

        Args:
            channel_name: Name of channel for which to upload waveform
            waveform_array: Waveform array
            allow_existing:

        Returns:
            Waveform index, used for sequencing

        Raises:
            SyntaxError if waveform contains less than 320 points
        """
        if len(waveform_array) < 320:
            raise SyntaxError(f"Waveform length {len(waveform_array)} < 320")

        self.waveforms.setdefault(channel_name, [])

        # Check if waveform already exists in waveform array
        if allow_existing:
            waveform_idx = arreqclose_in_list(waveform_array,
                                              self.waveforms[channel_name],
                                              atol=1e-3)
        else:
            waveform_idx = None

        # Check if new waveform needs to be created and uploaded
        if waveform_idx is not None:
            waveform_idx += 1  # Waveform index is 1-based
        else:
            # Add waveform to current list of waveforms
            self.waveforms[channel_name].append(waveform_array)

            # waveform index should be the position of added waveform (1-based)
            waveform_idx = len(self.waveforms[channel_name])

        return waveform_idx
Exemple #4
0
    def setup(self, **kwargs):
        assert not self.is_primary(
        ), 'AWG520 not programmed as primary instrument'

        self.active_channels(
            list({
                pulse.connection.output['channel'].name
                for pulse in self.pulse_sequence
            }))

        # Get waveforms for all channels
        t = 0
        pulses = {
            ch: self.pulse_sequence.get_pulses(output_channel=ch)
            for ch in self.active_channels()
        }
        if len(self.active_channels()) > 1:
            assert len(pulses['ch1']) == len(pulses['ch2']), \
                "Channel1 and channel2 do not have equal number of pulses"

        N_instructions = len(pulses[self.active_channels()[0]])

        waveforms = {ch: [0] * N_instructions for ch in self.active_channels()}
        repetitions = [0] * N_instructions
        for k in range(N_instructions):
            pulse = {ch: pulses[ch][k] for ch in self.active_channels()}
            # Get pulse of first channel
            pulse1 = pulse[self.active_channels()[0]]

            if len(self.active_channels()) > 1:
                assert t == pulse["ch1"].t_start == pulse["ch2"].t_start, \
                    f't={t} does not match pulse.t_start={pulse["ch1"].t_start}'
                assert pulse["ch1"].t_stop == pulse["ch2"].t_stop, \
                    f'pulse["ch1"].t_stop != pulse["ch2"].t_stop: ' \
                    f'{pulse["ch1"].t_stop} != {pulse["ch2"].t_stop}'
            else:
                assert t == pulse1.t_start, "t != pulse.t_start: {t} != {pulse1.t_start}"

            # Waveform points of both channels need to match
            # Here we find out the number of points needed
            pulse_pts = [pulse.implementation.pts for pulse in pulse.values()]
            if None in pulse_pts:
                waveform_pts = int(
                    round((pulse1.t_stop - self.pulse_final_delay() -
                           pulse1.t_start) * self.sampling_rate()))
            else:
                waveform_pts = int(round(max(pulse_pts)))

            # subtract 0.5 from waveform points to ensure correct length
            t_list = np.arange(
                pulse1.t_start,
                pulse1.t_start + (waveform_pts - 0.5) / self.sampling_rate(),
                1 / self.sampling_rate())

            if len(t_list) % 4:
                # Points need to be increment of 4
                t_list = t_list[:len(t_list) - (len(t_list) % 4)]
            assert len(t_list) >= 256, \
                f"Waveform has too few points at pulse.t_start={pulse1.t_start}"

            single_repetitions = [0 for _ in self.active_channels()]
            for ch_idx, ch in enumerate(self.active_channels()):
                waveforms[ch][k], single_repetitions[ch_idx] = pulse[
                    ch].implementation.implement(
                        t_list=t_list, sampling_rate=self.sampling_rate())

            if all(repetition == None for repetition in single_repetitions):
                # None of the channel pulses cares about repetitions, set to 1
                repetitions[k] = 1
            else:
                specified_repetitions = {
                    elem
                    for elem in single_repetitions if elem is not None
                }
                if len(set(specified_repetitions)) > 1:
                    raise RuntimeError(
                        f'Pulses {pulse} require different number'
                        f'of repetitions: {single_repetitions}')
                repetitions[k] = next(iter(specified_repetitions))
            t = pulse1.t_stop

        # Set first point of each waveform to endpoint of previous waveform
        endpoint_voltages = {
            ch: [waveform[-1] for waveform in ch_waveforms]
            for ch, ch_waveforms in waveforms.items()
        }
        for ch in self.active_channels():
            for k, waveform in enumerate(waveforms[ch]):
                waveform[0] = endpoint_voltages[ch][(k - 1) %
                                                    len(waveforms[ch])]

        # Create list of unique waveforms and corresponding sequence
        waveforms_list = []
        sequence = {ch: [] for ch in self.active_channels()}
        for ch in self.active_channels():
            for waveform in waveforms[ch]:
                waveform_idx = arreqclose_in_list(waveform,
                                                  waveforms_list,
                                                  rtol=-1e-4,
                                                  atol=1e-4)
                if waveform_idx is None:
                    waveforms_list.append(waveform)
                    waveform_idx = len(waveforms_list) - 1
                sequence[ch].append(waveform_idx)

        self.instrument.stop()
        self.instrument.trigger_mode('ENH')
        self.instrument.trigger_level(1)
        self.instrument.clock_freq(self.sampling_rate())

        for ch in ['ch1', 'ch2']:
            self.instrument[f'{ch}_offset'](0)
            self.instrument[f'{ch}_amp'](2)
            self.instrument[f'{ch}_status']('OFF')
        # for ch in [self.instrument.ch1, self.instrument.ch2]:
        #     ch.offset(0)
        #     ch.amplitude(1)
        #     ch.status('OFF')

        # Create silq folder to place waveforms and sequences in
        self.instrument.change_folder('/silq', create_if_necessary=True)

        total_waveform_points = sum(
            len(waveform) for waveform in waveforms_list)

        assert total_waveform_points < 3.99e6, \
            f'Too many total waveform points: {total_waveform_points}'

        total_existing_waveform_points = sum(
            len(waveform) for waveform in self.waveform_filenames.values())
        if (total_waveform_points + total_existing_waveform_points > 3.99e6) or \
                len(self.waveform_filenames) > self.max_waveforms:
            logger.info('Deleting existing waveforms from hard disk')
            self.instrument.delete_all_files(root=False)
            self.waveform_filenames.clear()

        waveform_filename_mapping = []
        # Copy waveform filenames because we only want to update the attribute
        # If the setup is complete
        waveform_filenames = copy(self.waveform_filenames)
        for waveform in waveforms_list:
            waveform_idx = arreqclose_in_list(waveform,
                                              waveform_filenames.values(),
                                              rtol=1e-4,
                                              atol=1e-4)
            if waveform_idx is None:
                # Waveform has not yet been uploaded
                waveform_idx = len(waveform_filenames)

                # Upload waveform
                filename = f'waveform_{waveform_idx}.wfm'
                marker1 = marker2 = np.zeros(len(waveform))
                self.instrument.send_waveform(waveform,
                                              marker1,
                                              marker2,
                                              filename,
                                              clock=self.sampling_rate())
                waveform_filenames[filename] = waveform
            else:
                filename = list(waveform_filenames)[waveform_idx]
            # Add waveform mapping
            waveform_filename_mapping.append(filename)

        # Upload sequence
        sequence_waveform_names = [[
            waveform_filename_mapping[waveform_idx]
            for waveform_idx in sequence[ch]
        ] for ch in self.active_channels()]

        wait_trigger = np.ones(N_instructions)
        goto_one = np.zeros(N_instructions)
        logic_jump = np.zeros(N_instructions)
        if len(self.active_channels()) == 1:
            sequence_waveform_names = sequence_waveform_names[0]
        self.instrument.send_sequence('sequence.seq', sequence_waveform_names,
                                      repetitions, wait_trigger, goto_one,
                                      logic_jump)

        # Check for errors here because else it adds a 50% overhead because it's
        # still busy setting the sequence
        for error in self.instrument.get_errors():
            logger.warning(error)

        self.instrument.set_sequence('sequence.seq')

        self.waveforms = waveforms
        self.waveform_filenames = waveform_filenames
        self.sequence = sequence
    def create_waveforms(self, error_threshold):
        waveform_queue = {ch: [] for ch in self.channel_selection()}

        # Sort the list of waveforms for each channel and calculate delays or
        # throw error on overlapping waveforms.
        for channel in self.active_instrument_channels:
            default_sampling_rate = self.default_sampling_rates()[channel.id]
            prescaler = 0 if default_sampling_rate == 500e6 else int(
                100e6 / default_sampling_rate)

            # Handle delays between waveforms
            t = 0
            clock_rate = 100e6
            total_samples = 0  # counted at clock rate
            for pulse in self.pulse_sequence.get_pulses(
                    output_channel=channel.name):
                # TODO: pulse implementation should return single channel only
                assert pulse.t_start >= t, \
                    f"Pulse {pulse} starts {pulse.t_start} < t={t}." \
                    f"This likely means that pulses are overlapping"

                pulse_samples_start = max(
                    int(round(pulse.t_start * clock_rate)),
                    total_samples)

                if pulse.t_start > t:  # Add waveform at 0V
                    logger.info(
                        f'Ch{channel.id}: No pulse defined between t={t} s and next '
                        f'{pulse} (pulse.t_start={pulse.t_start} s), '
                        f'Adding DC pulse at 0V')
                    # Use maximum value because potentially total samples could
                    # be higher than t (rounding errors etc.)
                    samples_start_0V = max(int(round(t * clock_rate)),
                                           total_samples)
                    samples_0V = (pulse_samples_start - samples_start_0V) * default_sampling_rate / clock_rate
                    waveform_0V = self.create_DC_waveform(voltage=0,
                                                          samples=samples_0V,
                                                          prescaler=prescaler,
                                                          t_start=t)
                    if waveform_0V is not None:
                        # Add any potential delay samples after previous pulse
                        waveform_0V['delay'] = max(0, (
                                    samples_start_0V - total_samples) * default_sampling_rate / clock_rate)
                        waveform_queue[channel.name].append(waveform_0V)

                        # Increase total samples to include 0V pulse points
                        total_samples += waveform_0V['delay']
                        total_samples += waveform_0V['points_100MHz'] * \
                                         waveform_0V['cycles']

                pulse_waveforms = pulse.implementation.implement(
                    interface=self,
                    instrument=self.instrument,
                    default_sampling_rate=default_sampling_rate,
                    threshold=error_threshold)

                for waveform in pulse_waveforms:
                    sampling_rate = 500e6 if waveform['prescaler'] == 0 else \
                        100e6 / waveform['prescaler']
                    start_samples = int(round(waveform['t_start'] * clock_rate))

                    waveform['delay'] = max((start_samples - total_samples) * sampling_rate / clock_rate, 0)
                    waveform_queue[channel.name].append(waveform)

                    total_samples += waveform['delay']
                    total_samples += waveform['points_100MHz'] * waveform['cycles']

                t = pulse.t_stop

            if t <= self.pulse_sequence.duration:
                final_samples = int(
                    round(self.pulse_sequence.duration * clock_rate))
                remaining_samples = (final_samples - total_samples) * default_sampling_rate / clock_rate
                waveform_0V = self.create_DC_waveform(voltage=0,
                                                      samples=remaining_samples,
                                                      prescaler=prescaler,
                                                      t_start=t)
                if waveform_0V:
                    waveform_queue[channel.name].append(waveform_0V)

        waveforms = []
        for channel, channel_waveform_queue in waveform_queue.items():
            for waveform_info in channel_waveform_queue:
                waveform = waveform_info.pop('waveform')
                waveform_idx = arreqclose_in_list(waveform,
                                                  waveforms,
                                                  rtol=1e-4, atol=1e-4)
                if waveform_idx is None:  # Add waveform to list
                    waveforms.append(waveform)
                    waveform_idx = len(waveforms) - 1
                waveform_info['idx'] = waveform_idx

        return waveforms, waveform_queue
    def generate_waveforms_sequences(self):
        """Generate waveforms and sequence from pulse sequence

        Updates self.waveforms and self.sequence.
        The waveforms aren't actually uploaded yet
        """
        # Determine sampling rates
        sampling_rates = {
            ch: 250e6 / self.instrument.channels[ch].sampling_rate_prescaler()
            for ch in self.active_channels()
        }

        # Set time t_pulse to zero for each channel
        # This will increase as we iterate over pulses, and is used to ensure
        # that there are no times between pulses
        t_pulse = {ch: 0 for ch in self.active_channels()}

        # Unused channels load a single zero volt DC pulse sequence to ensure
        # that no signal plays from these channels, this has minimal overhead
        self.waveforms = {
            ch: ([np.zeros(4)] if ch not in self.active_channels() else [])
            for ch in self._output_channels
        }
        self.sequences = {
            ch: ([0] if ch not in self.active_channels() else [])
            for ch in self._output_channels
        }

        for pulse in self.pulse_sequence:
            # For each channel, obtain list of waveforms, and the sequence
            # in which to perform the waveforms
            channels_waveforms, channels_sequence = pulse.implementation.implement(
                sampling_rates=sampling_rates,
                input_pulse_sequence=self.input_pulse_sequence)

            for ch in channels_waveforms:
                # Ensure that the start of this pulse corresponds to the end of
                # the previous pulse for each channel
                if abs(pulse.t_start - t_pulse[ch]) > 1e-11:
                    raise ValueError(
                        f"Pulse {pulse}: pulses.t_start = {pulse.t_start} "
                        f"does not match {t_pulse[ch]}")

                channel_waveforms = channels_waveforms[ch]
                channel_sequence = channels_sequence[ch]

                # Check if each waveform already exists in list so that it's
                # only uploaded once
                for waveform in channel_waveforms:
                    # waveform_idx either gives the index in the list
                    # (approximately) corresponding to the waveform, or else None
                    waveform_idx = arreqclose_in_list(waveform,
                                                      self.waveforms[ch],
                                                      rtol=1e-4,
                                                      atol=1e-5)
                    # Only add waveforms that don't already exist
                    if waveform_idx is None:
                        self.waveforms[ch].append(waveform)

                # Finding the correct sequence idx for each item in
                # channel_sequence. By first adding waveforms and then
                # finding the correct sequence indices, we ensure that there
                # are no duplicate waveforms and each sequence idx is correct.
                for k, sequence_idx in enumerate(channel_sequence):
                    waveform = channel_waveforms[sequence_idx]
                    waveform_idx = arreqclose_in_list(waveform,
                                                      self.waveforms[ch],
                                                      rtol=1e-4,
                                                      atol=1e-4)
                    # Update channel_sequence item to correct index
                    channel_sequence[k] = waveform_idx
                self.sequences[ch].extend(channel_sequence)

                # Increase t_pulse to match start of next pulses
                t_pulse[ch] += pulse.duration

        # Ensure that this channel ends at the end of the pulse sequence
        for ch in self.active_channels():
            assert abs(t_pulse[ch] - self.pulse_sequence.duration) < 1e-11, \
                f"Final pulse of channel {ch} ends at {t_pulse[ch]} " \
                f"instead of {self.pulse_sequence.duration}"