-
Notifications
You must be signed in to change notification settings - Fork 320
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DEM-564] Create a new HDAWG8 driver from ZI example (#1)
* [DEM-564] Create a new HDAWG8 driver from ZI example
- Loading branch information
Showing
2 changed files
with
284 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
import json | ||
import time | ||
from functools import partial | ||
|
||
import zhinst | ||
import zhinst.utils | ||
|
||
from qcodes import Instrument | ||
from qcodes.utils import validators as validators | ||
|
||
|
||
class ZIHDAWG8(Instrument): | ||
""" | ||
QCoDeS driver for ZI HDAWG8. | ||
|
||
Requires ZI LabOne software to be installed on the computer running QCoDeS (tested using LabOne (18.05.54618) | ||
and firmware (53866). | ||
Furthermore, the Data Server and Web Server must be running and a connection | ||
between the two must be made. | ||
""" | ||
|
||
def __init__(self, name: str, device_id: str, **kwargs) -> None: | ||
""" | ||
Create an instance of the instrument. | ||
|
||
Args: | ||
name (str): The internal QCoDeS name of the instrument | ||
device_ID (str): The device name as listed in the web server. | ||
""" | ||
super().__init__(name, **kwargs) | ||
self.api_level = 6 | ||
(self.daq, self.device, self.props) = zhinst.utils.create_api_session(device_id, self.api_level, | ||
required_devtype='HDAWG') | ||
self.awg_module = self.daq.awgModule() | ||
self.awg_module.set('awgModule/device', self.device) | ||
self.awg_module.execute() | ||
node_tree = self.download_device_node_tree() | ||
self.create_parameters_from_node_tree(node_tree) | ||
|
||
def enable_channel(self, channel_number): | ||
""" | ||
Enable a signal output, turns on a blue LED on the device. | ||
Args: | ||
channel_number (int): Output channel that should be enabled. | ||
|
||
Returns: None | ||
""" | ||
self.set('sigouts_{}_on'.format(channel_number), 1) | ||
|
||
def disable_channel(self, channel_number): | ||
""" | ||
Disable a signal output, turns off a blue LED on the device. | ||
Args: | ||
channel_number (int): Output channel that should be disabled. | ||
|
||
Returns: None | ||
""" | ||
self.set('sigouts_{}_on'.format(channel_number), 0) | ||
|
||
def start_awg(self, awg_number): | ||
""" | ||
Activate an AWG | ||
Args: | ||
awg_number (int): The AWG that should be enabled. | ||
|
||
Returns: None | ||
""" | ||
self.set('awgs_{}_enable'.format(awg_number), 1) | ||
|
||
def stop_awg(self, awg_number): | ||
""" | ||
Deactivate an AWG | ||
Args: | ||
awg_number (int): The AWG that should be disabled. | ||
|
||
Returns: None | ||
""" | ||
self.set('awgs_{}_enable'.format(awg_number), 0) | ||
|
||
def upload_sequence_program(self, awg, sequence_program): | ||
""" | ||
Uploads a sequence program to the device equivalent to using the the sequencer tab in the device's gui. | ||
Args: | ||
awg (int): The AWG that the sequence program will be uploaded to. | ||
sequence_program (str): A sequence program that should be played on the device. | ||
Returns (int): 0: Compilation was successful with no warnings. | ||
1: Compilation was successful but with warnings. | ||
""" | ||
self.awg_module.set('awgModule/index', awg) | ||
self.awg_module.set('awgModule/compiler/sourcestring', sequence_program) | ||
while self.awg_module.getInt('awgModule/compiler/status') == -1: | ||
time.sleep(0.1) | ||
|
||
if self.awg_module.getInt('awgModule/compiler/status') == 1: | ||
raise Exception(self.awg_module.getString('awgModule/compiler/statusstring')) | ||
while self.awg_module.getDouble('awgModule/progress') < 1.0: | ||
time.sleep(0.1) | ||
|
||
return self.awg_module.getInt('awgModule/compiler/status') | ||
|
||
def upload_waveform(self, awg, waveform, index): | ||
""" | ||
Upload a waveform to the device memory at a given index. | ||
Node: There needs to be a place holder on the device as this only replaces a data in the device memory and there | ||
for does not allocate new memory space. | ||
Args: | ||
awg (int): The AWG where waveform should be uploaded to. | ||
waveform: An array of floating point values from -1.0 to 1.0, or integers in the range (-32768...+32768) | ||
index: Index of the waveform that will be replaced. If there are more than 1 waveforms used then the index | ||
corresponds to the position of the waveform in the Waveforms sub-tab of the AWG tab in the GUI. | ||
|
||
Returns: None | ||
|
||
""" | ||
self.set('awgs_{}_waveform_index'.format(awg), index) | ||
self.daq.sync() | ||
self.set('awgs_{}_waveform_data'.format(awg), waveform) | ||
|
||
def set_channel_grouping(self, group): | ||
""" | ||
Set the channel grouping mode of the device. | ||
|
||
Args: | ||
group (int): 0: Use the outputs in groups of 2. One sequencer program controls 2 outputs. | ||
1: Use the outputs in groups of 4. One sequencer program controls 4 outputs. | ||
2: Use the outputs in groups of 8. One sequencer program controls 8 outputs. | ||
Returns: None | ||
|
||
""" | ||
self.set('system_awg_channelgrouping', group) | ||
|
||
def create_parameters_from_node_tree(self, parameters): | ||
""" | ||
Create QuCoDeS parameters from the device node tree. | ||
Args: | ||
parameters (dict): A device node tree. | ||
|
||
Returns: None | ||
|
||
""" | ||
for parameter in parameters.values(): | ||
getter = partial(self._getter, parameter['Node'], parameter['Type']) if 'Read' in parameter[ | ||
'Properties'] else None | ||
setter = partial(self._setter, parameter['Node'], parameter['Type']) if 'Write' in parameter[ | ||
'Properties'] else None | ||
options = validators.Enum(*[int(val) for val in parameter['Options'].keys()]) \ | ||
if parameter['Type'] == 'Integer (enumerated)' else None | ||
parameter_name = self._generate_parameter_name(parameter['Node']) | ||
self.add_parameter(name=parameter_name, | ||
set_cmd=setter, | ||
get_cmd=getter, | ||
vals=options, | ||
docstring=parameter['Description'], | ||
unit=parameter['Unit'] | ||
) | ||
|
||
@staticmethod | ||
def _generate_parameter_name(node): | ||
values = node.split('/') | ||
return '_'.join(values[2:]).lower() | ||
|
||
def download_device_node_tree(self, flags=0): | ||
""" | ||
Args: | ||
flags: ziPython.ziListEnum.settingsonly -> 0x08 | ||
Returns only nodes which are marked as setting | ||
ziPython.ziListEnum.streamingonly -> 0x10 | ||
Returns only streaming nodes | ||
ziPython.ziListEnum.subscribedonly -> 0x20 | ||
Returns only subscribed nodes | ||
ziPython.ziListEnum.basechannel -> 0x40 | ||
Return only one instance of a node in case of multiple channels | ||
Or any combination of flags can be used. | ||
|
||
Returns: A dictionary of the device node tree. | ||
""" | ||
node_tree = self.daq.listNodesJSON('/{}/'.format(self.device), flags) | ||
return json.loads(node_tree) | ||
|
||
def _setter(self, name, param_type, value): | ||
if param_type == "Integer (64 bit)" or param_type == 'Integer (enumerated)': | ||
self.daq.setInt(name, value) | ||
elif param_type == "Double": | ||
self.daq.setDouble(name, value) | ||
elif param_type == "String": | ||
self.daq.setString(name, value) | ||
elif param_type == "ZIVectorData": | ||
self.daq.vectorWrite(name, value) | ||
|
||
def _getter(self, name, param_type): | ||
if param_type == "Integer (64 bit)" or param_type == 'Integer (enumerated)': | ||
self.daq.getInt(name) | ||
elif param_type == "Double": | ||
self.daq.getDouble(name) | ||
elif param_type == "String": | ||
self.daq.getString(name) | ||
elif param_type == "ZIVectorData": | ||
self.daq.vectorRead(name) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import unittest | ||
from unittest.mock import patch, MagicMock | ||
|
||
import zhinst | ||
|
||
import qcodes | ||
from qcodes.instrument_drivers.ZI.ZIHDAWG8 import ZIHDAWG8 | ||
from qcodes.utils import validators | ||
|
||
|
||
class TestZIHDAWG8(unittest.TestCase): | ||
def setUp(self): | ||
self.node_tree = {"/DEV8049/SYSTEM/AWG/CHANNELGROUPING": { | ||
"Node": "/DEV8049/SYSTEM/AWG/CHANNELGROUPING", | ||
"Description": "Sets the channel grouping mode of the device.", | ||
"Properties": "Read, Write, Setting", | ||
"Type": "Integer (enumerated)", | ||
"Unit": "None", | ||
"Options": { | ||
"0": "Use the outputs in groups of 2. One sequencer program controls 2 outputs ", | ||
"1": "Use the outputs in groups of 4. One sequencer program controls 4 outputs ", | ||
"2": "Use the outputs in groups of 8. One sequencer program controls 8 outputs " | ||
} | ||
}, "/DEV8049/SIGOUTS/0/ON": { | ||
"Node": "/DEV8049/SIGOUTS/0/ON", | ||
"Description": "Enabling/Disabling the Signal Output. Corresponds to the blue LED indicator", | ||
"Properties": "Read, Write, Setting", | ||
"Type": "Integer (64 bit)", | ||
"Unit": "None" | ||
}, "/DEV8049/SYSTEM/OWNER": { | ||
"Node": "/DEV8049/SYSTEM/OWNER", | ||
"Description": "Returns the current owner of the device (IP).", | ||
"Properties": "Read", | ||
"Type": "String", | ||
"Unit": "None" | ||
}, "/DEV8049/SINES/0/AMPLITUDES/0": { | ||
"Node": "/DEV8049/SINES/0/AMPLITUDES/0", | ||
"Description": "Sets the peak amplitude that the sine signal contributes to the signal output. Note that\ | ||
the last index is either 0 or 1 and will map to the pair of outputs given by the first index.\ | ||
(e.g. sines/3/amplitudes/0 corresponds to wave output 2)", | ||
"Properties": "Read, Write, Setting", | ||
"Type": "Double", | ||
"Unit": "None" | ||
}, "/DEV8049/AWGS/1/WAVEFORM/MEMORYUSAGE": { | ||
"Node": "/DEV8049/AWGS/1/WAVEFORM/MEMORYUSAGE", | ||
"Description": "Amount of the used waveform data relative to the device cache memory. The cache memory \ | ||
provides space for 32 kSa of waveform data. Memory Usage over 100% means that waveforms must be loaded from\ | ||
the main memory (128 MSa per channel) during playback, which can lead to delays.", | ||
"Properties": "Read", | ||
"Type": "Double", | ||
"Unit": "%" | ||
}} | ||
|
||
def test_create_parameters_from_node_tree(self): | ||
with patch.object(zhinst.utils, 'create_api_session', return_value=3 * (MagicMock(),)), \ | ||
patch.object(qcodes.instrument_drivers.ZI.ZIHDAWG8.ZIHDAWG8, 'download_device_node_tree', | ||
return_value=self.node_tree): | ||
hdawg8 = ZIHDAWG8('Name', 'dev-test') | ||
|
||
self.assertIn('system_awg_channelgrouping', hdawg8.parameters) | ||
with self.assertRaises(ValueError): | ||
hdawg8.system_awg_channelgrouping.set(4) | ||
self.assertEqual('None', hdawg8.system_awg_channelgrouping.unit) | ||
self.assertEqual('system_awg_channelgrouping', hdawg8.system_awg_channelgrouping.name) | ||
self.assertIsNotNone(hdawg8.system_awg_channelgrouping.vals) | ||
self.assertIsInstance(hdawg8.system_awg_channelgrouping.vals, validators.Enum) | ||
|
||
self.assertIn('sigouts_0_on', hdawg8.parameters) | ||
self.assertEqual('None', hdawg8.sigouts_0_on.unit) | ||
self.assertEqual('sigouts_0_on', hdawg8.sigouts_0_on.name) | ||
self.assertIsNone(hdawg8.sigouts_0_on.vals) | ||
|
||
self.assertIn('system_owner', hdawg8.parameters) | ||
self.assertEqual('None', hdawg8.system_owner.unit) | ||
self.assertEqual('system_owner', hdawg8.system_owner.name) | ||
self.assertIsNone(hdawg8.system_owner.vals) | ||
|
||
self.assertIn('sines_0_amplitudes_0', hdawg8.parameters) | ||
self.assertEqual('None', hdawg8.sines_0_amplitudes_0.unit) | ||
self.assertEqual('sines_0_amplitudes_0', hdawg8.sines_0_amplitudes_0.name) | ||
self.assertIsNone(hdawg8.sines_0_amplitudes_0.vals) | ||
|
||
self.assertIn('awgs_1_waveform_memoryusage', hdawg8.parameters) | ||
self.assertEqual('%', hdawg8.awgs_1_waveform_memoryusage.unit) | ||
self.assertEqual('awgs_1_waveform_memoryusage', hdawg8.awgs_1_waveform_memoryusage.name) | ||
self.assertIsNone(hdawg8.awgs_1_waveform_memoryusage.vals) |