def __init__(self, hdawg_device: HDAWGRepresentation, channels: Tuple[int, int], identifier: str, timeout: float) -> None: super().__init__(identifier) self._device = weakref.proxy(hdawg_device) if channels not in ((1, 2), (3, 4), (5, 6), (7, 8)): raise HDAWGValueError('Invalid channel pair: {}'.format(channels)) self._channels = channels self.timeout = timeout self._awg_module = self.device.api_session.awgModule() self.awg_module.set('awgModule/device', self.device.serial) self.awg_module.set('awgModule/index', self.awg_group_index) self.awg_module.execute() # Seems creating AWG module sets SINGLE (single execution mode of sequence) to 0 per default. self.device.api_session.setInt( '/{}/awgs/{:d}/single'.format(self.device.serial, self.awg_group_index), 1) self._program_manager = HDAWGProgramManager() self._elf_manager = ELFManager(self.awg_module) self._required_seqc_source = self._program_manager.to_seqc_program() self._uploaded_seqc_source = None self._current_program = None # Currently armed program. self._upload_generator = ()
def __init__(self, group_idx: int, group_size: int, identifier: str, timeout: float) -> None: super().__init__(identifier) self._device = None assert group_idx in range(4) assert group_size in (2, 4, 8) self._group_idx = group_idx self._group_size = group_size self.timeout = timeout self._awg_module = None self._program_manager = HDAWGProgramManager() self._elf_manager = None self._required_seqc_source = self._program_manager.to_seqc_program() self._uploaded_seqc_source = None self._current_program = None # Currently armed program. self._upload_generator = ()
class HDAWGChannelGroup(AWG): """Represents a channel pair of the Zurich Instruments HDAWG as an independent AWG entity. It represents a set of channels that have to have(hardware enforced) the same control flow and sample rate. It keeps track of the AWG state and manages waveforms and programs on the hardware. """ MIN_WAVEFORM_LEN = 192 WAVEFORM_LEN_QUANTUM = 16 def __init__(self, group_idx: int, group_size: int, identifier: str, timeout: float) -> None: super().__init__(identifier) self._device = None assert group_idx in range(4) assert group_size in (2, 4, 8) self._group_idx = group_idx self._group_size = group_size self.timeout = timeout self._awg_module = None self._program_manager = HDAWGProgramManager() self._elf_manager = None self._required_seqc_source = self._program_manager.to_seqc_program() self._uploaded_seqc_source = None self._current_program = None # Currently armed program. self._upload_generator = () def _initialize_awg_module(self): """Only run once""" if self._awg_module: self._awg_module.clear() self._awg_module = self.device.api_session.awgModule() self.awg_module.set('awgModule/device', self.device.serial) self.awg_module.set('awgModule/index', self.awg_group_index) self.awg_module.execute() self._elf_manager = ELFManager(self.awg_module) def disconnect_group(self): """Disconnect this group from device so groups of another size can be used""" if self._awg_module: self.awg_module.clear() self._device = None def connect_group(self, hdawg_device: HDAWGRepresentation): """""" self.disconnect_group() self._device = weakref.proxy(hdawg_device) assert self.device.channel_grouping.group_size() == self._group_size, f"{self.device.channel_grouping} != {self._group_size}" self._initialize_awg_module() # Seems creating AWG module sets SINGLE (single execution mode of sequence) to 0 per default. self.device.api_session.setInt('/{}/awgs/{:d}/single'.format(self.device.serial, self.awg_group_index), 1) def is_connected(self) -> bool: return self._device is not None @property def num_channels(self) -> int: """Number of channels""" return self._group_size def _channels(self, index_start=1) -> Tuple[int, ...]: """1 indexed channel""" offset = index_start + self._group_size * self._group_idx return tuple(ch + offset for ch in range(self._group_size)) @property def num_markers(self) -> int: """Number of marker channels""" return 2 * self.num_channels def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Callable, ...], force: bool = False) -> None: """Upload a program to the AWG. Physically uploads all waveforms required by the program - excluding those already present - to the device and sets up playback sequences accordingly. This method should be cheap for program already on the device and can therefore be used for syncing. Programs that are uploaded should be fast(~1 sec) to arm. Args: name: A name for the program on the AWG. program: The program (a sequence of instructions) to upload. channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel voltage_transformation: transformations applied to the waveforms extracted rom the program. Position in the list corresponds to the AWG channel force: If a different sequence is already present with the same name, it is overwritten if force is set to True. (default = False) Known programs are handled in host memory most of the time. Only when uploading the device memory is touched at all. Returning from setting user register in seqc can take from 50ms to 60 ms. Fluctuates heavily. Not a good way to have deterministic behaviour "setUserReg(PROG_SEL, PROG_IDLE);". """ if len(channels) != self.num_channels: raise HDAWGValueError('Channel ID not specified') if len(markers) != self.num_markers: raise HDAWGValueError('Markers not specified') if len(voltage_transformation) != self.num_channels: raise HDAWGValueError('Wrong number of voltage transformations') if name in self.programs and not force: raise HDAWGValueError('{} is already known on {}'.format(name, self.identifier)) # Go to qupulse nanoseconds time base. q_sample_rate = self.sample_rate / 10**9 # Adjust program to fit criteria. make_compatible(program, minimal_waveform_length=self.MIN_WAVEFORM_LEN, waveform_quantum=self.WAVEFORM_LEN_QUANTUM, sample_rate=q_sample_rate) if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets = (0.,) * self.num_channels elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: voltage_offsets = self.offsets() else: raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) amplitudes = self.amplitudes() if name in self._program_manager.programs: self._program_manager.remove(name) self._program_manager.add_program(name, program, channels=channels, markers=markers, voltage_transformations=voltage_transformation, sample_rate=q_sample_rate, amplitudes=amplitudes, offsets=voltage_offsets) self._required_seqc_source = self._program_manager.to_seqc_program() self._program_manager.waveform_memory.sync_to_file_system(self.device.waveform_file_system) # start compiling the source (non-blocking) self._start_compile_and_upload() def _start_compile_and_upload(self): self._upload_generator = self._elf_manager.compile_and_upload(self._required_seqc_source) def _wait_for_compile_and_upload(self): for state in self._upload_generator: logger.debug("wait_for_compile_and_upload: %r", state) time.sleep(.1) self._uploaded_seqc_source = self._required_seqc_source logger.debug("AWG %d: wait_for_compile_and_upload has finished", self.awg_group_index) def was_current_program_finished(self) -> bool: """Return true if the current program has finished at least once""" playback_finished_mask = int(HDAWGProgramManager.Constants.PLAYBACK_FINISHED_MASK, 2) return bool(self.user_register(HDAWGProgramManager.Constants.PROG_SEL_REGISTER) & playback_finished_mask) def set_volatile_parameters(self, program_name: str, parameters: Mapping[str, ConstantParameter]): """Set the values of parameters which were marked as volatile on program creation.""" new_register_values = self._program_manager.get_register_values_to_update_volatile_parameters(program_name, parameters) if self._current_program == program_name: for register, value in new_register_values.items(): self.user_register(register, value) def remove(self, name: str) -> None: """Remove a program from the AWG. Also discards all waveforms referenced only by the program identified by name. Args: name: The name of the program to remove. """ self._program_manager.remove(name) self._required_seqc_source = self._program_manager.to_seqc_program() def clear(self) -> None: """Removes all programs and waveforms from the AWG. Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! """ self._program_manager.clear() self._current_program = None self._required_seqc_source = self._program_manager.to_seqc_program() self._start_compile_and_upload() self.arm(None) def arm(self, name: Optional[str]) -> None: """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current program. Currently hardware triggering is not implemented. The HDAWGProgramManager needs to emit code that calls `waitDigTrigger` to do that. """ if self._required_seqc_source != self._uploaded_seqc_source: self._wait_for_compile_and_upload() self.user_register(self._program_manager.Constants.TRIGGER_REGISTER, 0) if name is None: self.user_register(self._program_manager.Constants.PROG_SEL_REGISTER, self._program_manager.Constants.PROG_SEL_NONE) self._current_program = None else: if name not in self.programs: raise HDAWGValueError('{} is unknown on {}'.format(name, self.identifier)) self._current_program = name # set the registers of initial repetition counts for register, value in self._program_manager.get_register_values(name).items(): assert register not in (self._program_manager.Constants.PROG_SEL_REGISTER, self._program_manager.Constants.TRIGGER_REGISTER) self.user_register(register, value) self.user_register(self._program_manager.Constants.PROG_SEL_REGISTER, self._program_manager.name_to_index(name) | int(self._program_manager.Constants.NO_RESET_MASK, 2)) # this is a workaround for problems in the past and should be re-thought in case of a re-write for ch_pair in self.device.channel_tuples: ch_pair._wait_for_compile_and_upload() self.enable(True) def run_current_program(self) -> None: """Run armed program.""" if self._current_program is not None: if self._current_program not in self.programs: raise HDAWGValueError('{} is unknown on {}'.format(self._current_program, self.identifier)) if not self.enable(): self.enable(True) self.user_register(self._program_manager.Constants.TRIGGER_REGISTER, int(self._program_manager.Constants.TRIGGER_RESET_MASK, 2)) else: raise HDAWGRuntimeError('No program active') @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(self._program_manager.programs.keys()) @property def sample_rate(self) -> TimeType: """The default sample rate of the AWG channel group.""" node_path = '/{}/awgs/{}/time'.format(self.device.serial, self.awg_group_index) sample_rate_num = self.device.api_session.getInt(node_path) node_path = '/{}/system/clocks/sampleclock/freq'.format(self.device.serial) sample_clock = self.device.api_session.getDouble(node_path) """Calculate exact rational number based on (sample_clock Sa/s) / 2^sample_rate_num. Otherwise numerical imprecision will give rise to errors for very long pulses. fractions.Fraction does not accept floating point numerator, which sample_clock could potentially be.""" return time_from_float(sample_clock) / 2 ** sample_rate_num @property def awg_group_index(self) -> int: """AWG node group index assuming 4x2 channel grouping. Then 0...3 will give appropriate index of group.""" return self._group_idx @property def device(self) -> HDAWGRepresentation: """Reference to HDAWG representation.""" if self._device is None: raise HDAWGValueError('Channel group is currently not connected') return self._device @property def awg_module(self) -> zhinst.ziPython.AwgModule: """Each AWG channel group has its own awg module to manage program compilation and upload.""" if self._awg_module is None: raise HDAWGValueError('Channel group is not connected and was never initialized') return self._awg_module @property def user_directory(self) -> str: """LabOne user directory with subdirectories: "awg/src" (seqc sourcefiles), "awg/elf" (compiled AWG binaries), "awag/waves" (user defined csv waveforms).""" return self.awg_module.getString('awgModule/directory') def enable(self, status: bool = None) -> bool: """Start the AWG sequencer.""" # There is also 'awgModule/awg/enable', which seems to have the same functionality. node_path = '/{}/awgs/{:d}/enable'.format(self.device.serial, self.awg_group_index) if status is not None: self.device.api_session.setInt(node_path, int(status)) self.device.api_session.sync() # Global sync: Ensure settings have taken effect on the device. return bool(self.device.api_session.getInt(node_path)) def user_register(self, reg: UserRegister, value: int = None) -> int: """Query user registers (1-16) and optionally set it. Args: reg: User register. If it is an int, a warning is raised and it is interpreted as a one based index value: Value to set Returns: User Register value after setting it """ if isinstance(reg, int): warnings.warn("User register is not a UserRegister instance. It is interpreted as one based index.") reg = UserRegister(one_based_value=reg) if reg.to_web_interface() not in range(1, 17): raise HDAWGValueError('{reg:repr} not a valid (1-16) register.'.format(reg=reg)) node_path = '/{}/awgs/{:d}/userregs/{:labone}'.format(self.device.serial, self.awg_group_index, reg) if value is not None: self.device.api_session.setInt(node_path, value) self.device.api_session.sync() # Global sync: Ensure settings have taken effect on the device. return self.device.api_session.getInt(node_path) def _amplitude_scales(self) -> Tuple[float, ...]: """not affected by grouping""" return tuple(self.device.api_session.getDouble(f'/{self.device.serial}/awgs/{ch // 2:d}/outputs/{ch % 2:d}/amplitude') for ch in self._channels(index_start=0)) def amplitudes(self) -> Tuple[float, ...]: """Query AWG channel amplitude value (not peak to peak). From manual: The final signal amplitude is given by the product of the full scale output range of 1 V[in this example], the dimensionless amplitude scaling factor 1.0, and the actual dimensionless signal amplitude stored in the waveform memory.""" amplitudes = [] for ch, zi_amplitude in zip(self._channels(), self._amplitude_scales()): zi_range = self.device.range(ch) amplitudes.append(zi_amplitude * zi_range / 2) return tuple(amplitudes) def offsets(self) -> Tuple[float, ...]: return tuple(map(self.device.offset, self._channels()))
def test_full_run(self): defined_channels = frozenset(['A', 'B', 'C']) unique_n = 1000 unique_duration = 32 unique_wfs = get_unique_wfs(n=unique_n, duration=unique_duration, defined_channels=defined_channels) same_wf = DummyWaveform(duration=48, sample_output=np.ones(48), defined_channels=defined_channels) channels = ('A', 'B') markers = ('C', None, 'A', None) amplitudes = (1., 1.) offsets = (0., 0.) volatage_transformations = (lambda x: x, lambda x: x) sample_rate = 1 root = complex_program_as_loop(unique_wfs, wf_same=same_wf) seqc_nodes = complex_program_as_seqc(unique_wfs, wf_same=same_wf) manager = HDAWGProgramManager() manager.add_program('test', root, channels, markers, amplitudes, offsets, volatage_transformations, sample_rate) self.assertEqual({UserRegister(zero_based_value=2): 7}, manager.get_register_values('test')) seqc_program = manager.to_seqc_program() expected_program = """const PROG_SEL_REGISTER = 0; const TRIGGER_REGISTER = 1; const TRIGGER_RESET_MASK = 0b1000000000000000; const PROG_SEL_NONE = 0; const NO_RESET_MASK = 0b1000000000000000; const PROG_SEL_MASK = 0b111111111111111; const IDLE_WAIT_CYCLES = 300; wave test_concatenated_waveform = "3e0090e8ffd002d1134ce38827c6a35fede89cf23d126a44057ef43f466ae4cd"; wave test_shared_waveform_121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518 = "121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518"; //function used by manually triggered programs void waitForSoftwareTrigger() { while (true) { var trigger_register = getUserReg(TRIGGER_REGISTER); if (trigger_register & TRIGGER_RESET_MASK) setUserReg(TRIGGER_REGISTER, 0); if (trigger_register) return; } } // program definitions void test_function() { var pos = 0; var user_reg_2 = getUserReg(2); waitForSoftwareTrigger(); var init_pos_1 = pos; repeat(12) { pos = init_pos_1; repeat(1000) { // stepping repeat repeat(10) { repeat(42) { playWaveIndexed(test_concatenated_waveform, pos, 32); // advance disabled do to parent repetition } repeat(98) { playWave(test_shared_waveform_121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518); } } pos = pos + 32; } repeat(21) { playWaveIndexed(test_concatenated_waveform, pos, 32); // advance disabled do to parent repetition } pos = pos + 32; repeat(23) { playWaveIndexed(test_concatenated_waveform, pos, 48); // advance disabled do to parent repetition } pos = pos + 48; var idx_2; for(idx_2 = 0; idx_2 < user_reg_2; idx_2 = idx_2 + 1) { playWaveIndexed(test_concatenated_waveform, pos, 48); // advance disabled do to parent repetition } pos = pos + 48; } } // INIT program switch. var prog_sel = 0; //runtime block while (true) { // read program selection value prog_sel = getUserReg(PROG_SEL_REGISTER); if (!(prog_sel & NO_RESET_MASK)) setUserReg(PROG_SEL_REGISTER, 0); prog_sel = prog_sel & PROG_SEL_MASK; switch (prog_sel) { case 1: test_function(); waitWave(); default: wait(IDLE_WAIT_CYCLES); } }""" self.assertEqual(expected_program, seqc_program)
def test_full_run(self): defined_channels = frozenset(['A', 'B', 'C']) unique_n = 1000 unique_duration = 32 unique_wfs = get_unique_wfs(n=unique_n, duration=unique_duration, defined_channels=defined_channels) same_wf = DummyWaveform(duration=48, sample_output=np.ones(48), defined_channels=defined_channels) channels = ('A', 'B') markers = ('C', None, 'A', None) amplitudes = (1., 1.) offsets = (0., 0.) volatage_transformations = (lambda x: x, lambda x: x) sample_rate = 1 root = complex_program_as_loop(unique_wfs, wf_same=same_wf) seqc_nodes = complex_program_as_seqc(unique_wfs, wf_same=same_wf) manager = HDAWGProgramManager() manager.add_program('test', root, channels, markers, amplitudes, offsets, volatage_transformations, sample_rate) # 0: Program selection # 1: Trigger self.assertEqual({UserRegister(zero_based_value=2): 7}, manager.get_register_values('test')) seqc_program = manager.to_seqc_program() expected_program = """const PROG_SEL_REGISTER = 0; const TRIGGER_REGISTER = 1; const TRIGGER_RESET_MASK = 0b10000000000000000000000000000000; const PROG_SEL_NONE = 0; const NO_RESET_MASK = 0b10000000000000000000000000000000; const PLAYBACK_FINISHED_MASK = 0b1000000000000000000000000000000; const PROG_SEL_MASK = 0b111111111111111111111111111111; const INVERTED_PROG_SEL_MASK = 0b11000000000000000000000000000000; const IDLE_WAIT_CYCLES = 300; wave test_concatenated_waveform_0 = "c45d955d9dc472d46bf74f7eb9ae2ed4d159adea7d6fe9ce3f48c95423535333"; wave test_shared_waveform_121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518 = "121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518"; // function used by manually triggered programs void waitForSoftwareTrigger() { while (true) { var trigger_register = getUserReg(TRIGGER_REGISTER); if (trigger_register & TRIGGER_RESET_MASK) setUserReg(TRIGGER_REGISTER, 0); if (trigger_register) return; } } // program definitions void test_function() { var pos = 0; var user_reg_2 = getUserReg(2); waitForSoftwareTrigger(); var init_pos_1 = pos; repeat(12) { pos = init_pos_1; repeat(1000) { // stepping repeat repeat(10) { repeat(42) { playWaveIndexed(test_concatenated_waveform_0, pos, 32); // advance disabled do to parent repetition } repeat(98) { playWave(test_shared_waveform_121f5c6e8822793b3836fb3098fa4591b91d4c205cc2d8afd01ee1bf6956e518); } } pos = pos + 32; } repeat(21) { playWaveIndexed(test_concatenated_waveform_0, pos, 32); // advance disabled do to parent repetition } pos = pos + 32; repeat(23) { playWaveIndexed(test_concatenated_waveform_0, pos, 48); // advance disabled do to parent repetition } pos = pos + 48; var idx_2; for(idx_2 = 0; idx_2 < user_reg_2; idx_2 = idx_2 + 1) { playWaveIndexed(test_concatenated_waveform_0, pos, 48); // advance disabled do to parent repetition } pos = pos + 48; } } // Declare and initialize global variables // Selected program index (0 -> None) var prog_sel = 0; // Value that gets written back to program selection register. // Used to signal that at least one program was played completely. var new_prog_sel = 0; // Is OR'ed to new_prog_sel. // Set to PLAYBACK_FINISHED_MASK if a program was played completely. var playback_finished = 0; // runtime block while (true) { // read program selection value prog_sel = getUserReg(PROG_SEL_REGISTER); // calculate value to write back to PROG_SEL_REGISTER new_prog_sel = prog_sel | playback_finished; if (!(prog_sel & NO_RESET_MASK)) new_prog_sel &= INVERTED_PROG_SEL_MASK; setUserReg(PROG_SEL_REGISTER, new_prog_sel); // reset playback flag playback_finished = 0; // only use part of prog sel that does not mean other things to select the program. prog_sel &= PROG_SEL_MASK; switch (prog_sel) { case 1: test_function(); waitWave(); playback_finished = PLAYBACK_FINISHED_MASK; default: wait(IDLE_WAIT_CYCLES); } }""" self.assertEqual(expected_program, seqc_program)
class HDAWGChannelPair(AWG): """Represents a channel pair of the Zurich Instruments HDAWG as an independent AWG entity. It represents a set of channels that have to have(hardware enforced) the same: -control flow -sample rate It keeps track of the AWG state and manages waveforms and programs on the hardware. """ MIN_WAVEFORM_LEN = 192 def __init__(self, hdawg_device: HDAWGRepresentation, channels: Tuple[int, int], identifier: str, timeout: float) -> None: super().__init__(identifier) self._device = weakref.proxy(hdawg_device) if channels not in ((1, 2), (3, 4), (5, 6), (7, 8)): raise HDAWGValueError('Invalid channel pair: {}'.format(channels)) self._channels = channels self.timeout = timeout self._awg_module = self.device.api_session.awgModule() self.awg_module.set('awgModule/device', self.device.serial) self.awg_module.set('awgModule/index', self.awg_group_index) self.awg_module.execute() # Seems creating AWG module sets SINGLE (single execution mode of sequence) to 0 per default. self.device.api_session.setInt( '/{}/awgs/{:d}/single'.format(self.device.serial, self.awg_group_index), 1) self._program_manager = HDAWGProgramManager() self._required_seqc_source = '' self._uploaded_seqc_source = None self._current_program = None # Currently armed program. @property def num_channels(self) -> int: """Number of channels""" return 2 @property def num_markers(self) -> int: """Number of marker channels""" return 4 def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], Optional[ChannelID]], markers: Tuple[Optional[ChannelID], Optional[ChannelID], Optional[ChannelID], Optional[ChannelID]], voltage_transformation: Tuple[Callable, Callable], force: bool = False) -> None: """Upload a program to the AWG. Physically uploads all waveforms required by the program - excluding those already present - to the device and sets up playback sequences accordingly. This method should be cheap for program already on the device and can therefore be used for syncing. Programs that are uploaded should be fast(~1 sec) to arm. Args: name: A name for the program on the AWG. program: The program (a sequence of instructions) to upload. channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel voltage_transformation: transformations applied to the waveforms extracted rom the program. Position in the list corresponds to the AWG channel force: If a different sequence is already present with the same name, it is overwritten if force is set to True. (default = False) Known programs are handled in host memory most of the time. Only when uploading the device memory is touched at all. Returning from setting user register in seqc can take from 50ms to 60 ms. Fluctuates heavily. Not a good way to have deterministic behaviour "setUserReg(PROG_SEL, PROG_IDLE);". """ if len(channels) != self.num_channels: raise HDAWGValueError('Channel ID not specified') if len(markers) != self.num_markers: raise HDAWGValueError('Markers not specified') if len(voltage_transformation) != self.num_channels: raise HDAWGValueError('Wrong number of voltage transformations') if name in self.programs and not force: raise HDAWGValueError('{} is already known on {}'.format( name, self.identifier)) # Go to qupulse nanoseconds time base. q_sample_rate = self.sample_rate / 10**9 # Adjust program to fit criteria. make_compatible( program, minimal_waveform_length=self.MIN_WAVEFORM_LEN, waveform_quantum= 16, # 8 samples for single, 4 for dual channel waveforms. sample_rate=q_sample_rate) if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets = (0., 0.) elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: voltage_offsets = (self._device.offset(self._channels[0]), self._device.offset(self._channels[1])) else: raise ValueError( '{} is invalid as AWGAmplitudeOffsetHandling'.format( self._amplitude_offset_handling)) amplitudes = self._device.range(self._channels[0]), self._device.range( self._channels[1]) if name in self._program_manager.programs: self._program_manager.remove(name) self._program_manager.add_program( name, program, channels=channels, markers=markers, voltage_transformations=voltage_transformation, sample_rate=q_sample_rate, amplitudes=amplitudes, offsets=voltage_offsets) self._required_seqc_source = self._program_manager.to_seqc_program() self._program_manager.waveform_memory.sync_to_file_system( Path(self.user_directory).joinpath('awg', 'waves')) def _upload_sourcestring(self, sourcestring: str) -> None: """Transfer AWG sequencer program as string to HDAWG and block till compilation and upload finish. Allows upload without access to data server file system.""" if not sourcestring: raise HDAWGTypeError( 'sourcestring must not be empty or compilation will not start.' ) logger = logging.getLogger('ziHDAWG') # Transfer the AWG sequence program. Compilation starts automatically if sourcestring is set. self.awg_module.set('awgModule/compiler/sourcestring', sourcestring) self._poll_compile_and_upload_finished(logger) self._uploaded_seqc_source = sourcestring def _poll_compile_and_upload_finished(self, logger: logging.Logger) -> None: """Blocks till compilation on data server and upload to HDAWG succeed, if process takes less time than timeout.""" time_start = time.time() logger.info('Compilation started') while self.awg_module.getInt('awgModule/compiler/status') == -1: time.sleep(0.1) if time.time() - time_start > self._timeout: raise HDAWGTimeoutError("Compilation timeout out") if self.awg_module.getInt('awgModule/compiler/status') == 1: msg = self.awg_module.getString('awgModule/compiler/statusstring') logger.error(msg) raise HDAWGCompilationException(msg) if self.awg_module.getInt('awgModule/compiler/status') == 0: logger.info('Compilation successful') if self.awg_module.getInt('awgModule/compiler/status') == 2: msg = self.awg_module.getString('awgModule/compiler/statusstring') logger.warning(msg) i = 0 while ((self.awg_module.getDouble('awgModule/progress') < 1.0) and (self.awg_module.getInt('awgModule/elf/status') != 1)): time.sleep(0.2) logger.info("{} awgModule/progress: {:.2f}".format( i, self.awg_module.getDouble('awgModule/progress'))) i = i + 1 if time.time() - time_start > self._timeout: raise HDAWGTimeoutError("Upload timeout out") logger.info("{} awgModule/progress: {:.2f}".format( i, self.awg_module.getDouble('awgModule/progress'))) if self.awg_module.getInt('awgModule/elf/status') == 0: logger.info('Upload to the instrument successful') logger.info('Process took {:.3f} seconds'.format(time.time() - time_start)) if self.awg_module.getInt('awgModule/elf/status') == 1: raise HDAWGUploadException() def remove(self, name: str) -> None: """Remove a program from the AWG. Also discards all waveforms referenced only by the program identified by name. Args: name: The name of the program to remove. """ self._program_manager.remove(name) self._required_seqc_source = self._program_manager.to_seqc_program() def clear(self) -> None: """Removes all programs and waveforms from the AWG. Caution: This affects all programs and waveforms on the AWG, not only those uploaded using qupulse! """ self._program_manager.clear() self._current_program = None self._required_seqc_source = '' self.arm(None) def arm(self, name: Optional[str]) -> None: """Load the program 'name' and arm the device for running it. If name is None the awg will "dearm" its current program. Currently hardware triggering is not implemented. The HDAWGProgramManager needs to emit code that calls `waitDigTrigger` to do that. """ if self._required_seqc_source != self._uploaded_seqc_source: self._upload_sourcestring(self._required_seqc_source) self.user_register( self._program_manager.GLOBAL_CONSTS['TRIGGER_REGISTER'], 0) if not name: self.user_register( self._program_manager.GLOBAL_CONSTS['PROG_SEL_REGISTER'] + 1, self._program_manager.GLOBAL_CONSTS['PROG_SEL_NONE']) self._current_program = None else: if name not in self.programs: raise HDAWGValueError('{} is unknown on {}'.format( name, self.identifier)) self._current_program = name self.user_register( self._program_manager.GLOBAL_CONSTS['PROG_SEL_REGISTER'] + 1, self._program_manager.name_to_index(name) | int(self._program_manager.GLOBAL_CONSTS['NO_RESET_MASK'], 2)) self.enable(True) def run_current_program(self) -> None: """Run armed program.""" if self._current_program is not None: if self._current_program not in self.programs: raise HDAWGValueError('{} is unknown on {}'.format( self._current_program, self.identifier)) if not self.enable(): self.enable(True) self.user_register( self._program_manager.GLOBAL_CONSTS['TRIGGER_REGISTER'] + 1, int(self._program_manager.GLOBAL_CONSTS['TRIGGER_RESET_MASK'], 2)) else: raise HDAWGRuntimeError('No program active') @property def programs(self) -> Set[str]: """The set of program names that can currently be executed on the hardware AWG.""" return set(self._program_manager.programs.keys()) @property def sample_rate(self) -> TimeType: """The default sample rate of the AWG channel group.""" node_path = '/{}/awgs/{}/time'.format(self.device.serial, self.awg_group_index) sample_rate_num = self.device.api_session.getInt(node_path) node_path = '/{}/system/clocks/sampleclock/freq'.format( self.device.serial) sample_clock = self.device.api_session.getDouble(node_path) """Calculate exact rational number based on (sample_clock Sa/s) / 2^sample_rate_num. Otherwise numerical imprecision will give rise to errors for very long pulses. fractions.Fraction does not accept floating point numerator, which sample_clock could potentially be.""" return time_from_float(sample_clock) / 2**sample_rate_num @property def awg_group_index(self) -> int: """AWG node group index assuming 4x2 channel grouping. Then 0...3 will give appropriate index of group.""" return self._channels[0] // 2 @property def device(self) -> HDAWGRepresentation: """Reference to HDAWG representation.""" return self._device @property def awg_module(self) -> zhinst.ziPython.AwgModule: """Each AWG channel group has its own awg module to manage program compilation and upload.""" return self._awg_module @property def user_directory(self) -> str: """LabOne user directory with subdirectories: "awg/src" (seqc sourcefiles), "awg/elf" (compiled AWG binaries), "awag/waves" (user defined csv waveforms).""" return self.awg_module.getString('awgModule/directory') def enable(self, status: bool = None) -> bool: """Start the AWG sequencer.""" # There is also 'awgModule/awg/enable', which seems to have the same functionality. node_path = '/{}/awgs/{:d}/enable'.format(self.device.serial, self.awg_group_index) if status is not None: self.device.api_session.setInt(node_path, int(status)) self.device.api_session.sync( ) # Global sync: Ensure settings have taken effect on the device. return bool(self.device.api_session.getInt(node_path)) def user_register(self, reg: int, value: int = None) -> int: """Query user registers (1-16) and optionally set it.""" if reg not in range(1, 17): raise HDAWGValueError( '{} not a valid (1-16) register.'.format(reg)) node_path = '/{}/awgs/{:d}/userregs/{:d}'.format( self.device.serial, self.awg_group_index, reg - 1) if value is not None: self.device.api_session.setInt(node_path, value) self.device.api_session.sync( ) # Global sync: Ensure settings have taken effect on the device. return self.device.api_session.getInt(node_path) def amplitude(self, channel: int, value: float = None) -> float: """Query AWG channel amplitude value and optionally set it. Amplitude in units of full scale of the given AWG Output. The full scale corresponds to the Range voltage setting of the Signal Outputs.""" if channel not in (1, 2): raise HDAWGValueError( '{} not a valid (1-2) channel.'.format(channel)) node_path = '/{}/awgs/{:d}/outputs/{:d}/amplitude'.format( self.device.serial, self.awg_group_index, channel - 1) if value is not None: self.device.api_session.setDouble(node_path, value) self.device.api_session.sync( ) # Global sync: Ensure settings have taken effect on the device. return self.device.api_session.getDouble(node_path)