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()
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
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}"