From 568a8381f96885bd0f96aa434c968954531b89ff Mon Sep 17 00:00:00 2001 From: "Guilhem J. A. Ribeill" Date: Mon, 17 Jun 2019 23:29:02 -0400 Subject: [PATCH 001/109] Initial commit with AMC599 skeleton. --- src/auspex/instruments/aps3.py | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/auspex/instruments/aps3.py diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py new file mode 100644 index 00000000..ba3c6b17 --- /dev/null +++ b/src/auspex/instruments/aps3.py @@ -0,0 +1,119 @@ +# Copyright 2016 Raytheon BBN Technologies +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +from .instrument import Instrument, MetaInstrument +from auspex.log import logger +import auspex.config as config +from time import sleep +import numpy as np +import socket +import collections +from struct import pack, iter_unpack + +class AMC599(object): + """Base class for simple register manipulations of AMC599 board. + """ + + PORT = 0xbb4e # TCPIP port (BBN!) + + def __init__(self): + self.connected = False + + def __del__(self): + self.disconnect() + + def _check_connected(self): + if not self.connected: + raise IOError("AMC599 Board not connected!") + + def connect(self, ip_addr="192.168.2.200"): + self.ip_addr = ip_addr + self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + self.socket.connect((self.ip_addr, self.PORT)) + self.connected = True + + def disconnect(self): + if self.connected: + self.connected = False + self.socket.close() + + def send_bytes(self, data): + if isinstance(data, collections.Iterable): + return self.socket.sendall(b''.join([pack("!I", _) for _ in data])) + else: + return self.socket.sendall(pack("!I", data)) + + def recv_bytes(self, size): + resp = self.socket.recv(size) + while True: + if len(ans) >= size: + break + ans += self.socket.recv(8) + data = [x[0] for x in iter_unpack("!I", ans)] + return data + + def write_memory(self, addr, data): + self._check_connected() + max_ct = 0xfffc #max writeable block length (TODO: check if still true) + cmd = 0x80000000 #write to RAM command + datagrams_written = 0 + init_addr = addr + idx = 0 + + if isinstance(data, int): + data = [data] + elif isinstance(data, list): + if not all([isinstance(v, int) for v in data]): + raise ValueError("Data must be a list of integers.") + else: + raise ValueError("Data must be an integer or a list of integers.") + + while (len(data) - idx > 0): + ct_left = len(data) - idx + ct = ct_left if (ct_left < max_ct) else max_ct + datagram = [cmd + ct, addr] + datagram.extend(data[idx:idx+ct]) + self.send_bytes(datagram) + datagrams_written += 1 + idx += ct + addr += ct*4 + #read back data and check amount of bytes written makes sense + #the ethernet core echoes back what we wrote + resp = self.recv_bytes(2 * 4 * datagrams_written) + addr = init_addr + for ct in range(datagrams_written): + if ct+1 == datagrams_written: + words_written = len(data) - ((datagrams_written-1) * max_ct) + else: + words_written = max_ct + logger.debug("Wrote {} words in {} datagrams: {}", words_written, + datagrams_written, + [hex(x) for x in resp]) + assert (results[2*ct] == 0x80800000 + words_written) + assert (results[2*ct+1] == addr) + addr += 4 * words_written + + def read_memory(self, addr, num_words): + self._check_connected() + datagram = [0x10000000 + num_words, addr] + self.send_bytes(datagram) + resp_header = self.recv_bytes(2 * 4) #4 bytes per word... + return self.recv_bytes(4 * num_words) + + def read_memory_hex(self, addr, num_words): + data = self.read_memory(addr, num_words) + for d in data: + print(hex(d)) + +#TODO: UPDATE! +CSR_AXI_ADDR = 0x44b400000 +CSR_CONTROL_OFFSET = 0x00 +GPIO1_OFFSET = 0x01C +GPIO2_OFFSET = 0x020 +CSR_TRIGGER_INTERVAL_OFFSET = 0x14 +SDRAM_AXI_ADDR = 0x80000000 From 2d4a592fe21eec250634e498df7e1e4f1353fa46 Mon Sep 17 00:00:00 2001 From: CoherenceController Date: Thu, 20 Jun 2019 14:59:20 -0400 Subject: [PATCH 002/109] Updated Keithley 2400 driver with generic get setter commands --- src/auspex/instruments/keithley.py | 82 +++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/src/auspex/instruments/keithley.py b/src/auspex/instruments/keithley.py index e21ac673..6a8e4008 100644 --- a/src/auspex/instruments/keithley.py +++ b/src/auspex/instruments/keithley.py @@ -15,9 +15,15 @@ class Keithley2400(SCPIInstrument): """Keithley2400 Sourcemeter""" - current = FloatCommand(get_string=":sour:curr?", set_string="sour:curr:lev {:g};") - voltage = FloatCommand(get_string=":sour:volt?", set_string="sour:volt:lev {:g};") - resistance = FloatCommand(get_string=":read?") + SOUR_VALS = ['VOLT','CURR'] + SENSE_VALS = ['VOLT','CURR','RES'] + + source = StringCommand(scpi_string=":SOUR:FUNC",allowed_values=SOUR_VALS) + sense = StringCommand(scpi_string=":SENS:FUNC",allowed_values=SENSE_VALS) + current = FloatCommand(get_string=":MEAS:CURR?") + voltage = FloatCommand(get_string=":MEAS:VOLT?") + resistance = FloatCommand(get_string=":MEAS:RES?") + def __init__(self, resource_name, *args, **kwargs): super(Keithley2400, self).__init__(resource_name, *args, **kwargs) @@ -44,6 +50,62 @@ def triad(self, freq=440, duration=0.2, minor=False, down=False): def beep(self, freq, dur): self.interface.write(":SYST:BEEP {:g}, {:g}".format(freq, dur)) +#Level of Source + + @property + def level(self): + return self.interface.query(":SOUR:{}:LEV?".format(self.source)) + + @level.setter + def level(self, level): + self.interface.write(":SOUR:{}:LEV {:g}".format(self.source,level)) + +#Range of Source + + @property + def source_range(self): + auto = self.interface.query(":SOUR:{}:RANG:AUTO?".format(self.source)) + if auto == 1: + return "AUTO" + else: + return self.interface.query(":SOUR:{}:RANG?".format(self.source)) + + @source_range.setter + def source_range(self, range): + source = self.source + if range != "AUTO": + self.interface.write(":SOUR:{}:RANG:AUTO 0;:SOUR:{}:RANG {:g}".format(source,source,range)) + else: + self.interface.write(":SOUR:{}:RANG:AUTO 1".format(source)) + +#Compliance of Sense + + @property + def compliance(self): + return self.interface.query(":SENS:{}:PROT?".format(self.sense)) + + @compliance.setter + def compliance(self, comp): + self.interface.write(":SENS:{}:PROT {:g}".format(self.sense,comp)) + +#Range of Sense + + @property + def sense_range(self): + auto = self.interface.query(":SENS:{}:RANG:AUTO?".format(self.source)) + if auto == 1: + return "AUTO" + else: + return self.interface.query(":SENS:{}:RANG?".format(self.source)) + + @sense_range.setter + def sense_range(self, range): + source = self.source + if range != "AUTO": + self.interface.write(":SOUR:{}:RANG:AUTO 0;:SOUR:{}:RANG {:g}".format(source,source,range)) + else: + self.interface.write(":SOUR:{}:RANG:AUTO 1".format(source)) + # One must configure the measurement before the source to avoid potential range issues def conf_meas_res(self, NPLC=1, res_range=1000.0, auto_range=True): self.interface.write(":sens:func \"res\";:sens:res:mode man;:sens:res:nplc {:f};:form:elem res;".format(NPLC)) @@ -51,17 +113,3 @@ def conf_meas_res(self, NPLC=1, res_range=1000.0, auto_range=True): self.interface.write(":sens:res:rang:auto 1;") else: self.interface.write(":sens:res:rang:auto 0;:sens:res:rang {:g}".format(res_range)) - - def conf_src_curr(self, comp_voltage=0.1, curr_range=1.0e-3, auto_range=True): - if auto_range: - self.interface.write(":sour:func curr;:sour:curr:rang:auto 1;") - else: - self.interface.write(":sour:func curr;:sour:curr:rang:auto 0;:sour:curr:rang {:g};".format(curr_range)) - self.interface.write(":sens:volt:prot {:g};".format(comp_voltage)) - - def conf_src_volt(self, comp_current=10e-6, volt_range=1.0, auto_range=True): - if auto_range: - self.interface.write(":sour:func volt;:sour:volt:rang:auto 1;") - else: - self.interface.write(":sour:func volt;:sour:volt:rang:auto 0;:sour:volt:rang {:g};".format(volt_range)) - self.interface.write(":sens:curr:prot {:g};".format(comp_current)) From 49a173a143773dddd781b22d991c7dcb4d5a626f Mon Sep 17 00:00:00 2001 From: CoherenceController Date: Thu, 20 Jun 2019 16:18:40 -0400 Subject: [PATCH 003/109] Addd sweep functionality to Keithley --- src/auspex/instruments/keithley.py | 68 +++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/src/auspex/instruments/keithley.py b/src/auspex/instruments/keithley.py index 6a8e4008..ecef70de 100644 --- a/src/auspex/instruments/keithley.py +++ b/src/auspex/instruments/keithley.py @@ -16,13 +16,25 @@ class Keithley2400(SCPIInstrument): """Keithley2400 Sourcemeter""" SOUR_VALS = ['VOLT','CURR'] - SENSE_VALS = ['VOLT','CURR','RES'] - - source = StringCommand(scpi_string=":SOUR:FUNC",allowed_values=SOUR_VALS) - sense = StringCommand(scpi_string=":SENS:FUNC",allowed_values=SENSE_VALS) - current = FloatCommand(get_string=":MEAS:CURR?") - voltage = FloatCommand(get_string=":MEAS:VOLT?") - resistance = FloatCommand(get_string=":MEAS:RES?") + SENSE_VALS = ['VOLT:DC','CURR:DC','RES'] + MODE_VALS = ['SWE','LIST','FIX'] + SWEEP_RANG = ['BEST','AUTO','FIX'] + SWEEP_SPACE = ['LIN','LOG'] + SWEEP_DIR = ['UP','DOWN'] + SWEEP_ABOR = ['NEV','EARL','LATE'] + + source = StringCommand(scpi_string=":SOUR:FUNC",allowed_values=SOUR_VALS) + sweep_range = StringCommand(scpi_string=":SOUR:SWE:RANG",allowed_values=SWEEP_RANG) + sweep_space = StringCommand(scpi_string=":SOUR:SWE:SPAC",allowed_values=SWEEP_SPACE) + sweep_direction = StringCommand(scpi_string=":SOUR:SWE:DIRE",allowed_values=SWEEP_DIR) + sweep_abort = StringCommand(scpi_string=":SOUR:SWE:CAB",allowed_values=SWEEP_ABOR) + output = StringCommand(scpi_string=":OUTP",value_map={'ON': '1', 'OFF': '0'}) + + sense = StringCommand(scpi_string=":SENS:FUNC",allowed_values=SENSE_VALS) + read = FloatCommand(get_string=":READ?") + current = FloatCommand(get_string=":MEAS:CURR?") + voltage = FloatCommand(get_string=":MEAS:VOLT?") + resistance = FloatCommand(get_string=":MEAS:RES?") def __init__(self, resource_name, *args, **kwargs): @@ -60,6 +72,18 @@ def level(self): def level(self, level): self.interface.write(":SOUR:{}:LEV {:g}".format(self.source,level)) +#Mode of Source + + @property + def mode(self): + return self.interface.query(":SOUR:{}:MODE?".format(self.source)) + + @mode.setter + def mode(self, mode): + if mode not in self.MODE_VALS: + raise ValueError(("Mode must be "+'|'.join(['{}']*len(self.MODE_VALS))).format(*self.MODE_VALS)) + self.interface.write(":SOUR:{}:MODE {:s}".format(self.source,mode)) + #Range of Source @property @@ -78,6 +102,36 @@ def source_range(self, range): else: self.interface.write(":SOUR:{}:RANG:AUTO 1".format(source)) +#Sweep Start + + @property + def sweep_start(self): + return self.interface.query(":SOUR:{}:STAR?".format(self.source)) + + @sweep_start.setter + def sweep_start(self, level): + self.interface.write(":SOUR:{}:STAR {:g}".format(self.source,level)) + +#Sweep Stop + + @property + def sweep_stop(self): + return self.interface.query(":SOUR:{}:STOP?".format(self.source)) + + @sweep_stop.setter + def sweep_start(self, level): + self.interface.write(":SOUR:{}:STOP {:g}".format(self.source,level)) + +#Sweep Step + + @property + def sweep_step(self): + return self.interface.query(":SOUR:{}:STEP?".format(self.source)) + + @sweep_step.setter + def sweep_start(self, level): + self.interface.write(":SOUR:{}:STEP {:g}".format(self.source,level)) + #Compliance of Sense @property From a63bb0c59069e636370f4d7fb3f2638170208b2e Mon Sep 17 00:00:00 2001 From: gribeill Date: Thu, 20 Jun 2019 18:32:45 -0400 Subject: [PATCH 004/109] Add CSR control register locations from spec doc. --- src/auspex/instruments/aps3.py | 63 +++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index ba3c6b17..663f770f 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -57,7 +57,7 @@ def recv_bytes(self, size): data = [x[0] for x in iter_unpack("!I", ans)] return data - def write_memory(self, addr, data): + def write_memory(self, addr, data, offset = 0x0): self._check_connected() max_ct = 0xfffc #max writeable block length (TODO: check if still true) cmd = 0x80000000 #write to RAM command @@ -110,10 +110,57 @@ def read_memory_hex(self, addr, num_words): for d in data: print(hex(d)) -#TODO: UPDATE! -CSR_AXI_ADDR = 0x44b400000 -CSR_CONTROL_OFFSET = 0x00 -GPIO1_OFFSET = 0x01C -GPIO2_OFFSET = 0x020 -CSR_TRIGGER_INTERVAL_OFFSET = 0x14 -SDRAM_AXI_ADDR = 0x80000000 +##################################################################### + +#APS3 AMC599 Control and Status Register offsets +#Add to CSR_AXI_ADDR_BASE to get to correct memory location +#Registers are read/write unless otherwise noted +#Current as of 6/20/19 + +CSR_AXI_ADDR_BASE = 0x44b4000 + +CSR_CACHE_CONTROL = 0x0010 #Cache control register +CSR_SEQ_CONTROL = 0x0024 #Sequencer control register + +CSR_WFA_OFFSET = 0x0014 #Waveform A Offset +CSR_WFB_OFFSET = 0x0018 #Waveform B offset +CSR_SEQ_OFFSET = 0x001C #Sequence data offset + +CSR_TRIG_WORD = 0x002C #Trigger word register, Read Only +CSR_TRIG_INTERVAL = 0x0030 #trigger interval register + +CSR_UPTIME_SEC = 0x0050 #uptime in seconds, read only +CSR_UPTIME_NS = 0x0054 #uptime in nanoseconds, read only +CSR_FPGA_REV = 0x0058 #FPGA revision, read only +CSR_GIT_SHA1 = 0x0060 #git SHA1 hash, read only +CSR_BUILD_TSTAMP = 0x0064 #build timestamp, read only + +CSR_CMAT_R0 = 0x0068 #correction matrix row 0 +CSR_CMAT_R1 = 0x006C #correction matrix row 1 + +#### NOT CONNECTED TO ANY LOGIC -- USE FOR VALUE STORAGE ############ +CSR_A_AMPLITUDE = 0x0070 #Channel A amplitude +CSR_B_AMPLITUDE = 0x0074 #Channel B amplitude +CSR_MIX_AMP = 0x0078 #Mixer amplitude correction +CSR_MIX_PHASE = 0x007C #Mixer phase skew correction +CSR_WFA_LEN = 0x0080 #channel A waveform length +CSR_WFB_LEN = 0x0084 #channel B waveform length +CSR_WF_MOD_FREQ = 0x0088 #waveform modulation frequency +###################################################################### + +CSR_WFA_DELAY = 0x008C #Channel A delay +CSR_WFB_DELAY = 0x0080 #channel B delay + +CSR_BD_CONTROL = 0x00A0 #board control register +CSR_FPGA_ID = 0x00B4 #FPGA ID (read-only) + +CSR_DATA1_IO = 0x00B8 #Data 1 IO register +CSR_DATA2_IO = 0x00BC #Data 2 IO register + +CSR_MARKER_DELAY = 0x00C0 #Marker delay + +CSR_IPV4 = 0x00C4 #IPv4 address register + +##################################################################### + +DRAM_AXI_BASE = 0x80000000 From 418c783aa15885efe28ac8ec203e7196ca351382 Mon Sep 17 00:00:00 2001 From: CoherenceController Date: Fri, 21 Jun 2019 11:04:54 -0400 Subject: [PATCH 005/109] Bug Fixes to driver --- src/auspex/instruments/keithley.py | 51 +++++++++++++++--------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/auspex/instruments/keithley.py b/src/auspex/instruments/keithley.py index ecef70de..1b11a0cc 100644 --- a/src/auspex/instruments/keithley.py +++ b/src/auspex/instruments/keithley.py @@ -16,7 +16,7 @@ class Keithley2400(SCPIInstrument): """Keithley2400 Sourcemeter""" SOUR_VALS = ['VOLT','CURR'] - SENSE_VALS = ['VOLT:DC','CURR:DC','RES'] + SENSE_VALS = ['VOLT','CURR','RES'] MODE_VALS = ['SWE','LIST','FIX'] SWEEP_RANG = ['BEST','AUTO','FIX'] SWEEP_SPACE = ['LIN','LOG'] @@ -30,7 +30,8 @@ class Keithley2400(SCPIInstrument): sweep_abort = StringCommand(scpi_string=":SOUR:SWE:CAB",allowed_values=SWEEP_ABOR) output = StringCommand(scpi_string=":OUTP",value_map={'ON': '1', 'OFF': '0'}) - sense = StringCommand(scpi_string=":SENS:FUNC",allowed_values=SENSE_VALS) + sense = StringCommand(get_string=":SENS:FUNC?",set_string=":SENS:FUNC '{:s}'",allowed_values=SENSE_VALS) + concurrent = StringCommand(scpi_string=":SENS:FUNC:CONC",value_map={'ON': '1', 'OFF': '0'}) read = FloatCommand(get_string=":READ?") current = FloatCommand(get_string=":MEAS:CURR?") voltage = FloatCommand(get_string=":MEAS:VOLT?") @@ -66,99 +67,99 @@ def beep(self, freq, dur): @property def level(self): - return self.interface.query(":SOUR:{}:LEV?".format(self.source)) + return self.interface.query(":SOUR:{:s}:LEV?".format(self.source)) @level.setter def level(self, level): - self.interface.write(":SOUR:{}:LEV {:g}".format(self.source,level)) + self.interface.write(":SOUR:{:s}:LEV {:g}".format(self.source,level)) #Mode of Source @property def mode(self): - return self.interface.query(":SOUR:{}:MODE?".format(self.source)) + return self.interface.query(":SOUR:{:s}:MODE?".format(self.source)) @mode.setter def mode(self, mode): if mode not in self.MODE_VALS: raise ValueError(("Mode must be "+'|'.join(['{}']*len(self.MODE_VALS))).format(*self.MODE_VALS)) - self.interface.write(":SOUR:{}:MODE {:s}".format(self.source,mode)) + self.interface.write(":SOUR:{:s}:MODE {:s}".format(self.source,mode)) #Range of Source @property def source_range(self): - auto = self.interface.query(":SOUR:{}:RANG:AUTO?".format(self.source)) + auto = int(self.interface.query(":SOUR:{:s}:RANG:AUTO?".format(self.source))) if auto == 1: return "AUTO" else: - return self.interface.query(":SOUR:{}:RANG?".format(self.source)) + return self.interface.query(":SOUR:{:s}:RANG?".format(self.source)) @source_range.setter def source_range(self, range): source = self.source if range != "AUTO": - self.interface.write(":SOUR:{}:RANG:AUTO 0;:SOUR:{}:RANG {:g}".format(source,source,range)) + self.interface.write(":SOUR:{:s}:RANG:AUTO 0;:SOUR:{:s}:RANG {:g}".format(source,source,range)) else: - self.interface.write(":SOUR:{}:RANG:AUTO 1".format(source)) + self.interface.write(":SOUR:{:s}:RANG:AUTO 1".format(source)) #Sweep Start @property def sweep_start(self): - return self.interface.query(":SOUR:{}:STAR?".format(self.source)) + return self.interface.query(":SOUR:{:s}:STAR?".format(self.source)) @sweep_start.setter - def sweep_start(self, level): - self.interface.write(":SOUR:{}:STAR {:g}".format(self.source,level)) + def sweep_start(self, start): + self.interface.write(":SOUR:{:s}:STAR {:g}".format(self.source,start)) #Sweep Stop @property def sweep_stop(self): - return self.interface.query(":SOUR:{}:STOP?".format(self.source)) + return self.interface.query(":SOUR:{:s}:STOP?".format(self.source)) @sweep_stop.setter - def sweep_start(self, level): - self.interface.write(":SOUR:{}:STOP {:g}".format(self.source,level)) + def sweep_stop(self, stop): + self.interface.write(":SOUR:{:s}:STOP {:g}".format(self.source,stop)) #Sweep Step @property def sweep_step(self): - return self.interface.query(":SOUR:{}:STEP?".format(self.source)) + return self.interface.query(":SOUR:{:s}:STEP?".format(self.source)) @sweep_step.setter - def sweep_start(self, level): - self.interface.write(":SOUR:{}:STEP {:g}".format(self.source,level)) + def sweep_step(self, step): + self.interface.write(":SOUR:{:s}:STEP {:g}".format(self.source,step)) #Compliance of Sense @property def compliance(self): - return self.interface.query(":SENS:{}:PROT?".format(self.sense)) + return self.interface.query(":SENS:{:s}:PROT?".format(self.sense)) @compliance.setter def compliance(self, comp): - self.interface.write(":SENS:{}:PROT {:g}".format(self.sense,comp)) + self.interface.write(":SENS:{:s}:PROT {:g}".format(self.sense,comp)) #Range of Sense @property def sense_range(self): - auto = self.interface.query(":SENS:{}:RANG:AUTO?".format(self.source)) + auto = int(self.interface.query(":SENS:{:s}:RANG:AUTO?".format(self.source))) if auto == 1: return "AUTO" else: - return self.interface.query(":SENS:{}:RANG?".format(self.source)) + return self.interface.query(":SENS:{:s}:RANG?".format(self.source)) @sense_range.setter def sense_range(self, range): source = self.source if range != "AUTO": - self.interface.write(":SOUR:{}:RANG:AUTO 0;:SOUR:{}:RANG {:g}".format(source,source,range)) + self.interface.write(":SOUR:{:s}:RANG:AUTO 0;:SOUR:{:s}:RANG {:g}".format(source,source,range)) else: - self.interface.write(":SOUR:{}:RANG:AUTO 1".format(source)) + self.interface.write(":SOUR:{:s}:RANG:AUTO 1".format(source)) # One must configure the measurement before the source to avoid potential range issues def conf_meas_res(self, NPLC=1, res_range=1000.0, auto_range=True): From 34269e8bc40b162c33f12871b888ea153887e78d Mon Sep 17 00:00:00 2001 From: CoherenceController Date: Fri, 21 Jun 2019 15:38:11 -0400 Subject: [PATCH 006/109] Added Sweep functionality --- src/auspex/instruments/keithley.py | 37 +++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/auspex/instruments/keithley.py b/src/auspex/instruments/keithley.py index 1b11a0cc..e8983c9d 100644 --- a/src/auspex/instruments/keithley.py +++ b/src/auspex/instruments/keithley.py @@ -10,6 +10,7 @@ import time from auspex.log import logger +import numpy as np from .instrument import SCPIInstrument, StringCommand, FloatCommand, IntCommand class Keithley2400(SCPIInstrument): @@ -28,11 +29,11 @@ class Keithley2400(SCPIInstrument): sweep_space = StringCommand(scpi_string=":SOUR:SWE:SPAC",allowed_values=SWEEP_SPACE) sweep_direction = StringCommand(scpi_string=":SOUR:SWE:DIRE",allowed_values=SWEEP_DIR) sweep_abort = StringCommand(scpi_string=":SOUR:SWE:CAB",allowed_values=SWEEP_ABOR) + sweep_points = IntCommand(scpi_string=":SOUR:SWE:POIN") output = StringCommand(scpi_string=":OUTP",value_map={'ON': '1', 'OFF': '0'}) sense = StringCommand(get_string=":SENS:FUNC?",set_string=":SENS:FUNC '{:s}'",allowed_values=SENSE_VALS) concurrent = StringCommand(scpi_string=":SENS:FUNC:CONC",value_map={'ON': '1', 'OFF': '0'}) - read = FloatCommand(get_string=":READ?") current = FloatCommand(get_string=":MEAS:CURR?") voltage = FloatCommand(get_string=":MEAS:VOLT?") resistance = FloatCommand(get_string=":MEAS:RES?") @@ -63,6 +64,40 @@ def triad(self, freq=440, duration=0.2, minor=False, down=False): def beep(self, freq, dur): self.interface.write(":SYST:BEEP {:g}, {:g}".format(freq, dur)) +# Run Measurement Sweep + def sweep(self): + + #Construct list of variables to report from sense and source lists + var_list = self.sense.split(",") + var_list = list(map(lambda x: x.replace(':DC','').replace('"',''),var_list)) + var_list.append(self.source) + + #Format Sweep Output + self.interface.write((":FORM:ELEM "+','.join(['{:s}']*len(var_list))).format(*var_list)) + nvar = len(self.interface.query(":FORM:ELEM?").split(",")) + + #Setup Trigger + sweep_points = self.sweep_points + self.interface.write(":TRIG:COUN {:d}".format(sweep_points)) + + #Set long timeout for read + tmout = self.interface._resource.timeout + self.interface._resource.timeout = 12. * 60. * 60. * 1000. #milliseconds + + #Run Sweep + if self.output == 'OFF': + self.output = 'ON' + + sweep = np.array(self.interface.query(":READ?").split(","), dtype=np.float32).reshape(sweep_points,nvar) + self.output = 'OFF' + + self.interface._resource.timeout = tmout + self.beep(261.6,0.25) + + return sweep + + + #Level of Source @property From 81d89d8f0e2a4b9e8c733541f54dc979bb1397a3 Mon Sep 17 00:00:00 2001 From: gribeill Date: Fri, 21 Jun 2019 16:18:07 -0400 Subject: [PATCH 007/109] Continue adding functionality to APS3 class. --- src/auspex/instruments/aps3.py | 209 ++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 3 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 663f770f..145ef7ad 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,7 +6,8 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -from .instrument import Instrument, MetaInstrument +from .instrument import Instrument, MetaInstrument, is_valid_ipv4 +from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config from time import sleep @@ -15,6 +16,9 @@ import collections from struct import pack, iter_unpack +U32 = 0xFFFFFFFF #mask for 32-bit unsigned int +U16 = 0xFFFF + class AMC599(object): """Base class for simple register manipulations of AMC599 board. """ @@ -55,9 +59,9 @@ def recv_bytes(self, size): break ans += self.socket.recv(8) data = [x[0] for x in iter_unpack("!I", ans)] - return data + return data if len(data)>1 else data[0] - def write_memory(self, addr, data, offset = 0x0): + def write_memory(self, addr, data): self._check_connected() max_ct = 0xfffc #max writeable block length (TODO: check if still true) cmd = 0x80000000 #write to RAM command @@ -73,6 +77,8 @@ def write_memory(self, addr, data, offset = 0x0): else: raise ValueError("Data must be an integer or a list of integers.") + data = [x & U32 for x in data] #make sure everything is 32 bits + while (len(data) - idx > 0): ct_left = len(data) - idx ct = ct_left if (ct_left < max_ct) else max_ct @@ -135,6 +141,7 @@ def read_memory_hex(self, addr, num_words): CSR_GIT_SHA1 = 0x0060 #git SHA1 hash, read only CSR_BUILD_TSTAMP = 0x0064 #build timestamp, read only +CSR_CORR_OFFSET = 0x0024 CSR_CMAT_R0 = 0x0068 #correction matrix row 0 CSR_CMAT_R1 = 0x006C #correction matrix row 1 @@ -164,3 +171,199 @@ def read_memory_hex(self, addr, num_words): ##################################################################### DRAM_AXI_BASE = 0x80000000 + +##################################################################### + +def check_bits(value, shift, mask=0b1): + return (((value & U32) >> shift) & mask) + +def set_bits(value, shift, x, mask=0b1): + return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) + +class APS3(Instrument, metaclass=MakeSettersGetters): + + def __init__(self, resource_name=None, name="Unlabled APS3"): + self.name = name + self.resource_name = resource_name + + self.board = AMC599() + + def connect(self, resource_name=None): + if resource_name is None and self.resource_name is None: + raise ValueError("Must supply a resource name!") + elif resource_name is not None: + self.resource_name = resource_name + if is_valid_ipv4(self.resource_name): + self.board.connect(ip_addr = self.resource_name) + + def disconnect(self): + if self.board.connected: + self.board.disconnect() + + def write_csr(self, offset, data): + self.board.write_memory(CSR_AXI_ADDR_BASE + offset, data) + + def read_csr(self, offset, num_words = 1): + return self.board.read_memory(CSR_AXI_ADDR_BASE + offset, num_words) + + def write_dram(self, offset, data): + self.board.write_memory(DRAM_AXI_BASE + offset, data) + + def read_dram(self, offset, num_words = 1): + return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) + + ####### CACHE CONTROL REGSITER ############################################# + + @property + def cache_controller(self): + """Get value of cache controller + False: Cache controller in reset. + True: Cache controller taken out of reset. + """ + return bool(self.read_csr(CSR_CACHE_CONTROL) & 0x1) + @cache_controller.setter + def cache_controller(self, value): + self.write_csr(CSR_CACHE_CONTROL, int(value)) + + ####### WAVEFORM OFFSET REGISTER ########################################### + + @property + def waveform_offsets(self): + """Get waveform A and B offset register values. These are used as + the DMA source address. + """ + return [self.read_csr(CSR_WFA_OFFSET), + self.read_csr(CSR_WFB_OFFSET)] + @property + def waveform_offsets(self, offsets): + """Set waveform A and B offsets, passed as list [A offset, B offset]. + Set one offset to None to not change its value. + """ + if offsets[0] is not None: + self.write_csr(CSR_WFA_OFFSET, offsets[0]) + if offsets[1] is not None: + self.write_csr(CSR_WFB_OFFSET, offsets[1]) + + ####### SEQUENCER CONTROL REGISTER ######################################### + + @property + def sequencer_reset(self): + """Sequencer reset: + False: Sequencer, trigger input, modulator, SATA, VRAMs, Debug Streams + disabled. + True: Sequencer logic taken out of reset. + """ + reg = self.read_csr(CSR_SEQ_CONTROL) + return bool(check_bits(csr, 0)) + @sequencer_reset.setter + def sequencer_reset(self, reset): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 0, int(reset))) + + @property + def trigger_source(self): + trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) + trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", + 0b10: "SOFTWARE", 0b11 "MESSAGE"} + return trigger_map[trig_val] + @trigger_source.setter + def trigger_source(self, value): + trig_map = {"EXTERNAL": 0b00, "INTERNAL": 0b01, + "SOFTWARE": 0b10, "MESSAGE": 0b11} + if value.upper() not in trig_map.keys(): + raise ValueError(f"Unknown trigger mode. Must be one of {trig_map.keys()}") + if value.upper == "MESSAGE": + raise NotImplementedError("APS3 does not yet support message triggers.") + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 1, trig_map[value.upper()], 0b11)) + + def soft_trigger(self): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 3, 0b1)) + + @property + def trigger_enable(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 4)) + @trigger_enable.setter + def trigger_enable(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, 0b1)) + + @property + def bypass_modulator(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 5)) + @bypass_modulator.setter + def bypass_modulator(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, 0b1)) + + @property + def bypass_nco(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 6)) + @bypass_nco.setter + def bypass_nco(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, 0b1)) + + ####### CORRECTION CONTROL REGISTER ######################################## + @property + def correction_control(self): + reg = self.read_csr(CSR_CORR_OFFSET) + return ((reg >> 16) & U16, reg & U16) #returns (I, Q) + @correction_control.setter + def correction_control(self, value): + packed_value = ((value[0] & U16) << 16) | (value[1] & U16) + self.write_csr(CSR_CORR_OFFSET, packed_value) + + ####### TRIGGER INTERVAL ################################################### + @property + def trigger_interval(self): + return self.read_csr(CSR_TRIG_INTERVAL) + @trigger_interval.setter + def trigger_interval(self, value) + return self.write_csr(CSR_TRIG_INTERVAL, value & U32) + + ####### UPTIME REGISTERS ################################################### + @property + def uptime_seconds(self): + return self.read_csr(CSR_UPTIME_SEC) + + @property + def uptime_nanoseconds(self): + return self.read_csr(CSR_UPTIME_NS) + + ####### BUILD INFO REGISTERS ############################################### + def get_firmware_info(self): + fpga_rev = self.read_csr(CSR_FPGA_REV) + fpga_id = self.read_csr(CSR_FPGA_ID) + git_sha1 = self.read_csr(CSR_GIT_SHA1) + build_tstamp = self.read_csr(CSR_BUILD_TSTAMP) + + fpga_build_minor = check_bits(fpga_rev, 0, 0xFF) + fpga_build_major = check_bits(fpga_rev, 8, 0xFF) + commit_history = check_bits(fpga_rev, 16, 0x7FF) + build_clean = "dirty" if check_bits(fpga_rev, 27, 0xF) else "clean" + + return {"FPGA ID": fpga_id, + "FPGA REV": f"{fpga_build_major}.{fpga_build_minor} - {commit_history} - {build_clean}", + "GIT SHA1": hex(git_sha1), + "DATE": hex(build_tstamp)[2:]} + + ####### CORRECTION MATRIX ################################################## + def get_correction_matrix(self): + row0 = self.read_csr(CSR_CMAT_R0) + row1 = self.read_csr(CSR_CMAT_R1) + + r00 = (row0 >> 16) & U16 + r01 = row0 & U16 + r10 = (row1 >> 16) & U16 + r11 = row0 & U16 + return np.array([[r00, r01], [r10, r11]], dtype=np.uint16) + + def set_correction_matrix(self, matrix): + row0 = ((matix[0, 0] & U16) << 16) | (matrix[0, 1] & U16) + row1 = ((matix[1, 0] & U16) << 16) | (matrix[1, 1] & U16) + self.write_csr(CSR_CMAT_R0, row0) + self.write_csr(CSR_CMAT_R1, row1) + + ####### BOARD_CONTROL ################################################## From 6787e6a750024d47b244a8da7634dcc7d3b7bf49 Mon Sep 17 00:00:00 2001 From: gribeill Date: Fri, 21 Jun 2019 16:18:56 -0400 Subject: [PATCH 008/109] Continue adding functionality to APS3 class. --- src/auspex/instruments/aps3.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 145ef7ad..420e7475 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -366,4 +366,10 @@ def set_correction_matrix(self, matrix): self.write_csr(CSR_CMAT_R0, row0) self.write_csr(CSR_CMAT_R1, row1) - ####### BOARD_CONTROL ################################################## + ####### BOARD_CONTROL ###################################################### + @property + def microblaze(self): + return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) + @microbalze.setter(self, value) + reg = self.read_csr(CSR_BD_CONTROL) + self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) From 5e9fb27cca3ab33d38a25579bfc92de740cf1222 Mon Sep 17 00:00:00 2001 From: CoherenceController Date: Fri, 21 Jun 2019 17:25:29 -0400 Subject: [PATCH 009/109] Bug fix to compliance command --- src/auspex/instruments/keithley.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/auspex/instruments/keithley.py b/src/auspex/instruments/keithley.py index e8983c9d..cd8bf5c5 100644 --- a/src/auspex/instruments/keithley.py +++ b/src/auspex/instruments/keithley.py @@ -172,29 +172,29 @@ def sweep_step(self, step): @property def compliance(self): - return self.interface.query(":SENS:{:s}:PROT?".format(self.sense)) + return self.interface.query(":SENS:{:s}:PROT?".format(self.sense.replace('"',''))) @compliance.setter def compliance(self, comp): - self.interface.write(":SENS:{:s}:PROT {:g}".format(self.sense,comp)) + self.interface.write(":SENS:{:s}:PROT {:g}".format(self.sense.replace('"',''),comp)) #Range of Sense @property def sense_range(self): - auto = int(self.interface.query(":SENS:{:s}:RANG:AUTO?".format(self.source))) + auto = int(self.interface.query(":SENS:{:s}:RANG:AUTO?".format(self.sense.replace('"','')))) if auto == 1: return "AUTO" else: - return self.interface.query(":SENS:{:s}:RANG?".format(self.source)) + return self.interface.query(":SENS:{:s}:RANG?".format(self.sense.replace('"',''))) @sense_range.setter def sense_range(self, range): - source = self.source + sense = self.sense.replace('"','') if range != "AUTO": - self.interface.write(":SOUR:{:s}:RANG:AUTO 0;:SOUR:{:s}:RANG {:g}".format(source,source,range)) + self.interface.write(":SENS:{:s}:RANG:AUTO 0;:SENS:{:s}:RANG {:g}".format(sense,sense,range)) else: - self.interface.write(":SOUR:{:s}:RANG:AUTO 1".format(source)) + self.interface.write(":SENS:{:s}:RANG:AUTO 1".format(sense)) # One must configure the measurement before the source to avoid potential range issues def conf_meas_res(self, NPLC=1, res_range=1000.0, auto_range=True): From 30aa6702ce28b0cda8f3662b2d47c650775ce35a Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 25 Jun 2019 11:45:49 -0400 Subject: [PATCH 010/109] Update APS3 instrument with all CSR setters/getters. --- src/auspex/instruments/aps3.py | 39 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 420e7475..fd3ec377 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,6 +6,8 @@ # # http://www.apache.org/licenses/LICENSE-2.0 +__all__ = ['AMC599', 'APS3'] + from .instrument import Instrument, MetaInstrument, is_valid_ipv4 from .bbn import MakeSettersGetters from auspex.log import logger @@ -180,13 +182,13 @@ def check_bits(value, shift, mask=0b1): def set_bits(value, shift, x, mask=0b1): return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) -class APS3(Instrument, metaclass=MakeSettersGetters): +class APS3(Instrument): def __init__(self, resource_name=None, name="Unlabled APS3"): self.name = name self.resource_name = resource_name - self.board = AMC599() + super().__init__() def connect(self, resource_name=None): if resource_name is None and self.resource_name is None: @@ -264,7 +266,7 @@ def sequencer_reset(self, reset): def trigger_source(self): trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", - 0b10: "SOFTWARE", 0b11 "MESSAGE"} + 0b10: "SOFTWARE", 0b11: "MESSAGE"} return trigger_map[trig_val] @trigger_source.setter def trigger_source(self, value): @@ -320,7 +322,7 @@ def correction_control(self, value): def trigger_interval(self): return self.read_csr(CSR_TRIG_INTERVAL) @trigger_interval.setter - def trigger_interval(self, value) + def trigger_interval(self, value): return self.write_csr(CSR_TRIG_INTERVAL, value & U32) ####### UPTIME REGISTERS ################################################### @@ -366,10 +368,37 @@ def set_correction_matrix(self, matrix): self.write_csr(CSR_CMAT_R0, row0) self.write_csr(CSR_CMAT_R1, row1) + def correction_bypass(self): + row0 = 0x20000000 + row1 = 0x00002000 + self.write_csr(CSR_CMAT_R0, row0) + self.write_csr(CSR_CMAT_R0, row1) + self.write_csr(CSR_CORR_OFFSET, 0x0) + + ####### BOARD_CONTROL ###################################################### @property def microblaze(self): return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) - @microbalze.setter(self, value) + @microblaze.setter + def microblaze(self, value): reg = self.read_csr(CSR_BD_CONTROL) self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) + + @property + def dac_output_mux(self): + return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 4)) + @dac_output_mux.setter + def dac_output_mux(self, value): + reg = self.read_csr(CSR_BD_CONTROL) + self.write_csr(CSR_BD_CONTROL, set_bits(reg, 4, int(value))) + + ####### MARKER_DELAY ####################################################### + @property + def marker_delay(self): + return self.read_csr(CSR_MARKER_DELAY) + @marker_delay.setter + def marker_delay(self, value): + if value > U16: + logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") + self.write_csr(CSR_MARKER_DELAY, value & U16) From 299f05d869008fff12e2f3463035e06474963ae4 Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 2 Jul 2019 23:09:40 -0400 Subject: [PATCH 011/109] Add metaprogramming for bit-field commands --- src/auspex/instruments/aps3.py | 228 ++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 35 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index fd3ec377..b1732f5a 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,29 +6,134 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -__all__ = ['AMC599', 'APS3'] +#__all__ = ['AMC599', 'APS3'] -from .instrument import Instrument, MetaInstrument, is_valid_ipv4 +from .instrument import Instrument, is_valid_ipv4, Command from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config from time import sleep import numpy as np import socket +from unittest.mock import Mock import collections from struct import pack, iter_unpack U32 = 0xFFFFFFFF #mask for 32-bit unsigned int U16 = 0xFFFF +def check_bits(value, shift, mask=0b1): + """Helper function to get a bit-slice of a 32-bit value. + Returns (value >> shift) & mask.""" + return (((value & U32) >> shift) & mask) + +def set_bits(value, shift, x, mask=0b1): + """Set bits in a 32-bit value given an offset and a mask. """ + return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) + +class BitFieldCommand(Command): + """An instrument command that sets/gets a value from a register. + See also the Command object in .instrument.py + + Args: + None. + Kwargs: + register: Control register address. (required) + shift: 0-indexed bit position. (required) + mask: Size of bit field -- i.e. use 0b111 if setting a 3-bit value + defaults to 0b1 or is inferred from a value map. + """ + + def parse(self): + super().parse() + + for a in ('register', 'shift', 'mask'): + if a in self.kwargs: + setattr(self, a, self.kwargs.pop(a)) + else: + setattr(self, a, None) + + if self.register is None: + raise ValueError("Must specify a destination or source register.") + if self.shift is None: + raise ValueError("Must specify a bit shift for register command.") + + if self.mask is None: + if self.value_map is not None: + max_bits = max((v.bit_length() for v in self.value_map.items())) + self.mask = 2**max_bits - 1 + else: + self.mask = 0b1 + + def convert_set(self, set_value_python): + if self.python_to_instr is None: + return int(set_value_python) + else: + return self.python_to_instr[set_value_python] + + def convert_get(self, get_value_instrument): + if self.python_to_instr is None: + return bool(set_value_python) + else: + return self.instr_to_python[get_value_instrument] + +def add_command_bitfield(instr, name, cmd): + """Helper function to create a new BitFieldCommand when parsing an instrument.""" + + new_cmd = BitFieldCommand(*cmd.args, **cmd.kwargs) + new_cmd.parse() + + def fget(self, **kwargs): + val = check_bits(self.read_register(new_cmd.register), new_cmd.shift, new_cmd.mask) + if new_cmd.get_delay is not None: + sleep(new_cmd.get_delay) + return new_cmd.convert_get(val) + + def fset(self, val, **kwargs): + if new_cmd.value_range is not None: + if (val < new_cmd.value_range[0]) or (val > new_cmd.value_range[1]): + err_msg = "The value {} is outside of the allowable range {} specified for instrument '{}'.".format(val, new_cmd.value_range, self.name) + raise ValueError(err_msg) + + if new_cmd.allowed_values is not None: + if not val in new_cmd.allowed_values: + err_msg = "The value {} is not in the allowable set of values specified for instrument '{}': {}".format(val, self.name, new_cmd.allowed_values) + raise ValueError(err_msg) + + start_val = self.read_register(new_cmd.register) + new_val = set_bits(start_val, self.shift, new_cmd.convert_set(val), self.mask) + self.write_register(new_cmd.register, new_val) + if new_cmd.set_delay is not None: + sleep(new_cmd.set_delay) + + setattr(instr, name, property(fget, fset, None, new_cmd.doc)) + + return new_cmd + +class MakeBitFieldParams(MakeSettersGetters): + def __init__(self, name, bases, dct): + super().__init__(self, name, bases, dct) + + if 'write_register' not in dct or 'read_register' not in dct: + raise TypeError("An instrument using BitFieldParams must implement" + + " `read_register` and `write_register` functions.") + + for k, v in dct.items(): + if isinstance(v, BitFieldCommand): + logger.debug("Adding %s command", k) + nv = add_command_SCPI(self, k, v) + class AMC599(object): """Base class for simple register manipulations of AMC599 board. """ PORT = 0xbb4e # TCPIP port (BBN!) - def __init__(self): + def __init__(self, debug=False): self.connected = False + self.debug = debug + if self.debug: + self.debug_memory = {} def __del__(self): self.disconnect() @@ -39,9 +144,10 @@ def _check_connected(self): def connect(self, ip_addr="192.168.2.200"): self.ip_addr = ip_addr - self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) - self.socket.connect((self.ip_addr, self.PORT)) - self.connected = True + if not self.debug: + self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + self.socket.connect((self.ip_addr, self.PORT)) + self.connected = True def disconnect(self): if self.connected: @@ -57,20 +163,13 @@ def send_bytes(self, data): def recv_bytes(self, size): resp = self.socket.recv(size) while True: - if len(ans) >= size: + if len(resp) >= size: break - ans += self.socket.recv(8) - data = [x[0] for x in iter_unpack("!I", ans)] + resp += self.socket.recv(8) + data = [x[0] for x in iter_unpack("!I", resp)] return data if len(data)>1 else data[0] def write_memory(self, addr, data): - self._check_connected() - max_ct = 0xfffc #max writeable block length (TODO: check if still true) - cmd = 0x80000000 #write to RAM command - datagrams_written = 0 - init_addr = addr - idx = 0 - if isinstance(data, int): data = [data] elif isinstance(data, list): @@ -79,7 +178,20 @@ def write_memory(self, addr, data): else: raise ValueError("Data must be an integer or a list of integers.") - data = [x & U32 for x in data] #make sure everything is 32 bits + if self.debug: + print("Wrote starting at memory address 0x%x:" % addr) + for d in data: + print(hex(d)) + for off, d in enumerate(data): + self.debug_memory[addr + off*0x4] = d + return + + self._check_connected() + max_ct = 0xfffc #max writeable block length (TODO: check if still true) + cmd = 0x80000000 #write to RAM command + datagrams_written = 0 + init_addr = addr + idx = 0 while (len(data) - idx > 0): ct_left = len(data) - idx @@ -102,22 +214,24 @@ def write_memory(self, addr, data): logger.debug("Wrote {} words in {} datagrams: {}", words_written, datagrams_written, [hex(x) for x in resp]) - assert (results[2*ct] == 0x80800000 + words_written) - assert (results[2*ct+1] == addr) + assert (resp[2*ct] == 0x80800000 + words_written) + assert (resp[2*ct+1] == addr) addr += 4 * words_written def read_memory(self, addr, num_words): + + if self.debug: + response = [] + for x in range(num_words): + response.append(self.debug_memory.get(addr+0x4*x, 0x0)) + return response[0] if num_words == 1 else response + self._check_connected() datagram = [0x10000000 + num_words, addr] self.send_bytes(datagram) resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) - def read_memory_hex(self, addr, num_words): - data = self.read_memory(addr, num_words) - for d in data: - print(hex(d)) - ##################################################################### #APS3 AMC599 Control and Status Register offsets @@ -125,7 +239,7 @@ def read_memory_hex(self, addr, num_words): #Registers are read/write unless otherwise noted #Current as of 6/20/19 -CSR_AXI_ADDR_BASE = 0x44b4000 +CSR_AXI_ADDR_BASE = 0x44b40000 CSR_CACHE_CONTROL = 0x0010 #Cache control register CSR_SEQ_CONTROL = 0x0024 #Sequencer control register @@ -176,14 +290,12 @@ def read_memory_hex(self, addr, num_words): ##################################################################### -def check_bits(value, shift, mask=0b1): - return (((value & U32) >> shift) & mask) -def set_bits(value, shift, x, mask=0b1): - return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) class APS3(Instrument): + instrument_type = "AWG" + def __init__(self, resource_name=None, name="Unlabled APS3"): self.name = name self.resource_name = resource_name @@ -202,10 +314,11 @@ def disconnect(self): if self.board.connected: self.board.disconnect() - def write_csr(self, offset, data): + def write_register(self, offset, data): + logger.info(f"Setting CSR: {hex(offset)} to: {hex(data)}") self.board.write_memory(CSR_AXI_ADDR_BASE + offset, data) - def read_csr(self, offset, num_words = 1): + def read_register(self, offset, num_words = 1): return self.board.read_memory(CSR_AXI_ADDR_BASE + offset, num_words) def write_dram(self, offset, data): @@ -289,7 +402,7 @@ def trigger_enable(self): @trigger_enable.setter def trigger_enable(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, int(value))) @property def bypass_modulator(self): @@ -297,7 +410,7 @@ def bypass_modulator(self): @bypass_modulator.setter def bypass_modulator(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, int(value))) @property def bypass_nco(self): @@ -305,7 +418,7 @@ def bypass_nco(self): @bypass_nco.setter def bypass_nco(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, int(value))) ####### CORRECTION CONTROL REGISTER ######################################## @property @@ -346,7 +459,7 @@ def get_firmware_info(self): commit_history = check_bits(fpga_rev, 16, 0x7FF) build_clean = "dirty" if check_bits(fpga_rev, 27, 0xF) else "clean" - return {"FPGA ID": fpga_id, + return {"FPGA ID": hex(fpga_id), "FPGA REV": f"{fpga_build_major}.{fpga_build_minor} - {commit_history} - {build_clean}", "GIT SHA1": hex(git_sha1), "DATE": hex(build_tstamp)[2:]} @@ -402,3 +515,48 @@ def marker_delay(self, value): if value > U16: logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") self.write_csr(CSR_MARKER_DELAY, value & U16) + + ###### DRAM OFFSET REGISTERS ############################################### + @property + def SEQ_OFFSET(self): + return (self.read_csr(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) + + @property + def WFA_OFFSET(self): + return (self.read_csr(CSR_WFA_OFFSET) - DRAM_AXI_BASE) + + @property + def WFB_OFFSET(self): + return (self.read_csr(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + + ###### UTILITIES ########################################################### + def run(self): + logger.info("Taking cache controller out of reset...") + self.cache_controller = True + sleep(0.01) + logger.info("Taking sequencer out of reset...") + self.sequencer_reset = True + sleep(0.01) + logger.info("Enabling trigger...") + self.trigger_enable = True + sleep(0.01) + + def stop(self): + logger.info("Resetting cache controller...") + self.cache_controller = False + sleep(0.01) + logger.info("Resetting sequencer...") + self.sequencer_reset = False + + def write_sequence(self, sequence): + packed_seq = [] + for instr in sequence: + packed_seq.append(instr & U32) + packed_seq.append(instr >> 32) + + self.cache_controller = False + sleep(0.01) + self.write_dram(self.SEQ_OFFSET, packed_seq) + logger.info(f"Wrote {len(packed_seq)} words to sequence memory.") + sleep(0.01) + self.cache_controller = True From a0c62b93f19bb238c3d01247cbd6e3cf90d4bd43 Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 2 Jul 2019 23:48:02 -0400 Subject: [PATCH 012/109] Use metaprogramming to set bit fields in APS3 driver. --- src/auspex/instruments/aps3.py | 217 ++++++++------------------- src/auspex/instruments/instrument.py | 2 +- 2 files changed, 63 insertions(+), 156 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index b1732f5a..7e08dcd7 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -47,7 +47,7 @@ class BitFieldCommand(Command): def parse(self): super().parse() - for a in ('register', 'shift', 'mask'): + for a in ('register', 'shift', 'mask', 'readonly'): if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: @@ -58,9 +58,12 @@ def parse(self): if self.shift is None: raise ValueError("Must specify a bit shift for register command.") + if self.readonly is None: + self.readonly = False + if self.mask is None: if self.value_map is not None: - max_bits = max((v.bit_length() for v in self.value_map.items())) + max_bits = max((v.bit_length() for v in self.value_map.values())) self.mask = 2**max_bits - 1 else: self.mask = 0b1 @@ -73,7 +76,10 @@ def convert_set(self, set_value_python): def convert_get(self, get_value_instrument): if self.python_to_instr is None: - return bool(set_value_python) + if self.mask == 0b1: + return bool(get_value_instrument) + else: + return get_value_instrument else: return self.instr_to_python[get_value_instrument] @@ -101,18 +107,20 @@ def fset(self, val, **kwargs): raise ValueError(err_msg) start_val = self.read_register(new_cmd.register) - new_val = set_bits(start_val, self.shift, new_cmd.convert_set(val), self.mask) + new_val = set_bits(start_val, new_cmd.shift, new_cmd.convert_set(val), new_cmd.mask) self.write_register(new_cmd.register, new_val) if new_cmd.set_delay is not None: sleep(new_cmd.set_delay) - setattr(instr, name, property(fget, fset, None, new_cmd.doc)) + setattr(instr, name, property(fget, None if new_cmd.readonly else fset, None, new_cmd.doc)) + setattr(instr, "set_"+name, fset) + setattr(instr, "get_"+name, fget) return new_cmd class MakeBitFieldParams(MakeSettersGetters): def __init__(self, name, bases, dct): - super().__init__(self, name, bases, dct) + super().__init__(name, bases, dct) if 'write_register' not in dct or 'read_register' not in dct: raise TypeError("An instrument using BitFieldParams must implement" + @@ -121,7 +129,7 @@ def __init__(self, name, bases, dct): for k, v in dct.items(): if isinstance(v, BitFieldCommand): logger.debug("Adding %s command", k) - nv = add_command_SCPI(self, k, v) + nv = add_command_bitfield(self, k, v) class AMC599(object): """Base class for simple register manipulations of AMC599 board. @@ -179,9 +187,6 @@ def write_memory(self, addr, data): raise ValueError("Data must be an integer or a list of integers.") if self.debug: - print("Wrote starting at memory address 0x%x:" % addr) - for d in data: - print(hex(d)) for off, d in enumerate(data): self.debug_memory[addr + off*0x4] = d return @@ -290,16 +295,14 @@ def read_memory(self, addr, num_words): ##################################################################### - - -class APS3(Instrument): +class APS3(Instrument, metaclass=MakeBitFieldParams): instrument_type = "AWG" - def __init__(self, resource_name=None, name="Unlabled APS3"): + def __init__(self, resource_name=None, name="Unlabled APS3", debug=False): self.name = name self.resource_name = resource_name - self.board = AMC599() + self.board = AMC599(debug=debug) super().__init__() def connect(self, resource_name=None): @@ -329,130 +332,54 @@ def read_dram(self, offset, num_words = 1): ####### CACHE CONTROL REGSITER ############################################# - @property - def cache_controller(self): - """Get value of cache controller - False: Cache controller in reset. - True: Cache controller taken out of reset. - """ - return bool(self.read_csr(CSR_CACHE_CONTROL) & 0x1) - @cache_controller.setter - def cache_controller(self, value): - self.write_csr(CSR_CACHE_CONTROL, int(value)) - - ####### WAVEFORM OFFSET REGISTER ########################################### + cache_controller = BitFieldCommand(register=CSR_CACHE_CONTROL, shift=0, + doc="""Cache controller enable bit.""") - @property - def waveform_offsets(self): - """Get waveform A and B offset register values. These are used as - the DMA source address. - """ - return [self.read_csr(CSR_WFA_OFFSET), - self.read_csr(CSR_WFB_OFFSET)] - @property - def waveform_offsets(self, offsets): - """Set waveform A and B offsets, passed as list [A offset, B offset]. - Set one offset to None to not change its value. - """ - if offsets[0] is not None: - self.write_csr(CSR_WFA_OFFSET, offsets[0]) - if offsets[1] is not None: - self.write_csr(CSR_WFB_OFFSET, offsets[1]) + ####### WAVEFORM OFFSET REGISTERS ########################################## + + wf_offset_A = BitFieldCommand(register=CSR_WFA_OFFSET, shift=0, mask=U32) + wf_offset_B = BitFieldCommand(register=CSR_WFB_OFFSET, shift=0, mask=U32) ####### SEQUENCER CONTROL REGISTER ######################################### - @property - def sequencer_reset(self): - """Sequencer reset: - False: Sequencer, trigger input, modulator, SATA, VRAMs, Debug Streams - disabled. - True: Sequencer logic taken out of reset. - """ - reg = self.read_csr(CSR_SEQ_CONTROL) - return bool(check_bits(csr, 0)) - @sequencer_reset.setter - def sequencer_reset(self, reset): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 0, int(reset))) + sequencer_enable = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=0, + doc="""Sequencer, trigger input, modulator, SATA, VRAM, debug stream enable bit.""") - @property - def trigger_source(self): - trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) - trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", - 0b10: "SOFTWARE", 0b11: "MESSAGE"} - return trigger_map[trig_val] - @trigger_source.setter - def trigger_source(self, value): - trig_map = {"EXTERNAL": 0b00, "INTERNAL": 0b01, - "SOFTWARE": 0b10, "MESSAGE": 0b11} - if value.upper() not in trig_map.keys(): - raise ValueError(f"Unknown trigger mode. Must be one of {trig_map.keys()}") - if value.upper == "MESSAGE": - raise NotImplementedError("APS3 does not yet support message triggers.") - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 1, trig_map[value.upper()], 0b11)) - - def soft_trigger(self): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 3, 0b1)) + trigger_source = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=1, + value_map={"EXTERNAL": 0b00, "INTERNAL": 0b01, "SOFTWARE": 0b10, "MESSAGE": 0b11}) - @property - def trigger_enable(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 4)) - @trigger_enable.setter - def trigger_enable(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, int(value))) - - @property - def bypass_modulator(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 5)) - @bypass_modulator.setter - def bypass_modulator(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, int(value))) - - @property - def bypass_nco(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 6)) - @bypass_nco.setter - def bypass_nco(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, int(value))) + soft_trigger = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=3) + trigger_enable = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=4) + bypass_modulator = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=5) + bypass_nco = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=6) ####### CORRECTION CONTROL REGISTER ######################################## @property def correction_control(self): - reg = self.read_csr(CSR_CORR_OFFSET) + reg = self.read_register(CSR_CORR_OFFSET) return ((reg >> 16) & U16, reg & U16) #returns (I, Q) @correction_control.setter def correction_control(self, value): packed_value = ((value[0] & U16) << 16) | (value[1] & U16) - self.write_csr(CSR_CORR_OFFSET, packed_value) + self.write_register(CSR_CORR_OFFSET, packed_value) ####### TRIGGER INTERVAL ################################################### - @property - def trigger_interval(self): - return self.read_csr(CSR_TRIG_INTERVAL) - @trigger_interval.setter - def trigger_interval(self, value): - return self.write_csr(CSR_TRIG_INTERVAL, value & U32) + + trigger_interval = BitFieldCommand(register=CSR_TRIG_INTERVAL, shift=0, mask=U32) ####### UPTIME REGISTERS ################################################### - @property - def uptime_seconds(self): - return self.read_csr(CSR_UPTIME_SEC) - @property - def uptime_nanoseconds(self): - return self.read_csr(CSR_UPTIME_NS) + uptime_seconds = BitFieldCommand(register=CSR_UPTIME_SEC, shift=0, + mask=U32, readonly=True) + uptime_nanoseconds = BitFieldCommand(register=CSR_UPTIME_NS, shift=0, + mask=U32, readonly=True) ####### BUILD INFO REGISTERS ############################################### def get_firmware_info(self): - fpga_rev = self.read_csr(CSR_FPGA_REV) - fpga_id = self.read_csr(CSR_FPGA_ID) - git_sha1 = self.read_csr(CSR_GIT_SHA1) - build_tstamp = self.read_csr(CSR_BUILD_TSTAMP) + fpga_rev = self.read_register(CSR_FPGA_REV) + fpga_id = self.read_register(CSR_FPGA_ID) + git_sha1 = self.read_register(CSR_GIT_SHA1) + build_tstamp = self.read_register(CSR_BUILD_TSTAMP) fpga_build_minor = check_bits(fpga_rev, 0, 0xFF) fpga_build_major = check_bits(fpga_rev, 8, 0xFF) @@ -466,9 +393,8 @@ def get_firmware_info(self): ####### CORRECTION MATRIX ################################################## def get_correction_matrix(self): - row0 = self.read_csr(CSR_CMAT_R0) - row1 = self.read_csr(CSR_CMAT_R1) - + row0 = self.read_register(CSR_CMAT_R0) + row1 = self.read_register(CSR_CMAT_R1) r00 = (row0 >> 16) & U16 r01 = row0 & U16 r10 = (row1 >> 16) & U16 @@ -478,56 +404,37 @@ def get_correction_matrix(self): def set_correction_matrix(self, matrix): row0 = ((matix[0, 0] & U16) << 16) | (matrix[0, 1] & U16) row1 = ((matix[1, 0] & U16) << 16) | (matrix[1, 1] & U16) - self.write_csr(CSR_CMAT_R0, row0) - self.write_csr(CSR_CMAT_R1, row1) + self.write_register(CSR_CMAT_R0, row0) + self.write_register(CSR_CMAT_R1, row1) def correction_bypass(self): row0 = 0x20000000 row1 = 0x00002000 - self.write_csr(CSR_CMAT_R0, row0) - self.write_csr(CSR_CMAT_R0, row1) - self.write_csr(CSR_CORR_OFFSET, 0x0) + self.write_register(CSR_CMAT_R0, row0) + self.write_register(CSR_CMAT_R0, row1) + self.write_register(CSR_CORR_OFFSET, 0x0) ####### BOARD_CONTROL ###################################################### - @property - def microblaze(self): - return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) - @microblaze.setter - def microblaze(self, value): - reg = self.read_csr(CSR_BD_CONTROL) - self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) - @property - def dac_output_mux(self): - return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 4)) - @dac_output_mux.setter - def dac_output_mux(self, value): - reg = self.read_csr(CSR_BD_CONTROL) - self.write_csr(CSR_BD_CONTROL, set_bits(reg, 4, int(value))) + microblaze_enable = BitFieldCommand(register=CSR_BD_CONTROL, shift=1) + dac_output_mux = BitFieldCommand(register=CSR_BD_CONTROL, shift=4, + value_map={"SOF200": 0x0, "APS": 0x1}) ####### MARKER_DELAY ####################################################### - @property - def marker_delay(self): - return self.read_csr(CSR_MARKER_DELAY) - @marker_delay.setter - def marker_delay(self, value): - if value > U16: - logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") - self.write_csr(CSR_MARKER_DELAY, value & U16) + + marker_delay = BitFieldCommand(register=CSR_MARKER_DELAY, shift=0, + mask=U16, allowed_values=[0,U16]) ###### DRAM OFFSET REGISTERS ############################################### - @property def SEQ_OFFSET(self): - return (self.read_csr(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) - @property def WFA_OFFSET(self): - return (self.read_csr(CSR_WFA_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_WFA_OFFSET) - DRAM_AXI_BASE) - @property def WFB_OFFSET(self): - return (self.read_csr(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_WFB_OFFSET) - DRAM_AXI_BASE) ###### UTILITIES ########################################################### def run(self): @@ -535,7 +442,7 @@ def run(self): self.cache_controller = True sleep(0.01) logger.info("Taking sequencer out of reset...") - self.sequencer_reset = True + self.sequencer_enable = True sleep(0.01) logger.info("Enabling trigger...") self.trigger_enable = True @@ -546,7 +453,7 @@ def stop(self): self.cache_controller = False sleep(0.01) logger.info("Resetting sequencer...") - self.sequencer_reset = False + self.sequencer_enable = False def write_sequence(self, sequence): packed_seq = [] diff --git a/src/auspex/instruments/instrument.py b/src/auspex/instruments/instrument.py index 09f4a4a7..c81b0e2b 100644 --- a/src/auspex/instruments/instrument.py +++ b/src/auspex/instruments/instrument.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): def parse(self): for a in ['aliases', 'set_delay', 'get_delay', 'value_map', 'value_range', - 'allowed_values']: + 'allowed_values', 'doc']: if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: From 29a90a6a89634c7de03055042081de93d2ac9d74 Mon Sep 17 00:00:00 2001 From: gribeill Date: Wed, 3 Jul 2019 09:28:58 -0400 Subject: [PATCH 013/109] Update property docstrings correctly --- src/auspex/instruments/aps3.py | 2 ++ src/auspex/instruments/bbn.py | 5 +++++ src/auspex/instruments/instrument.py | 10 ++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 7e08dcd7..8628cdc7 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -114,7 +114,9 @@ def fset(self, val, **kwargs): setattr(instr, name, property(fget, None if new_cmd.readonly else fset, None, new_cmd.doc)) setattr(instr, "set_"+name, fset) + setattr(getattr(instr, "set_"+name), "__doc__", new_cmd.doc) setattr(instr, "get_"+name, fget) + setattr(getattr(instr, "set_"+name), "__doc__", new_cmd.doc) return new_cmd diff --git a/src/auspex/instruments/bbn.py b/src/auspex/instruments/bbn.py index ea91c5ae..818363e0 100644 --- a/src/auspex/instruments/bbn.py +++ b/src/auspex/instruments/bbn.py @@ -167,6 +167,11 @@ def __init__(self, name, bases, dct): logger.debug("Adding '%s' command to APS", k) setattr(self, 'set_'+k, v.fset) setattr(self, 'get_'+k, v.fget) + try: + setattr(getattr(self, 'set_'+k), "__doc__", v.__doc__) + setattr(getattr(self, 'get_'+k), "__doc__", v.__doc__) + except: + pass class APS(Instrument, metaclass=MakeSettersGetters): """BBN APSI or DACII""" diff --git a/src/auspex/instruments/instrument.py b/src/auspex/instruments/instrument.py index c81b0e2b..64287cb8 100644 --- a/src/auspex/instruments/instrument.py +++ b/src/auspex/instruments/instrument.py @@ -36,18 +36,22 @@ def __init__(self, *args, **kwargs): def parse(self): for a in ['aliases', 'set_delay', 'get_delay', 'value_map', 'value_range', - 'allowed_values', 'doc']: + 'allowed_values']: if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: setattr(self, a, None) # Default to None + if 'doc' in self.kwargs: + self.doc = self.kwargs.pop('doc') + else: + self.doc = "" + if self.value_range is not None: self.value_range = (min(self.value_range), max(self.value_range)) self.python_to_instr = None # Dict StringCommandmapping from python values to instrument values self.instr_to_python = None # Dict mapping from instrument values to python values - self.doc = "" if self.value_map is not None: self.python_to_instr = self.value_map @@ -350,9 +354,11 @@ def fset(self, val, **kwargs): # In this case we can't create a property given additional arguments if new_cmd.get_string: setattr(instr, "get_" + name, fget) + setattr(getattr(instr, "get_" + name), "__doc__", new_cmd.doc) if new_cmd.set_string: setattr(instr, "set_" + name, fset) + setattr(getattr(instr, "set_" + name), "__doc__", new_cmd.doc) return new_cmd From 7aafdfbb8386784fce5b91cc0941a840e50fc9a3 Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 16 Jul 2019 14:00:55 -0400 Subject: [PATCH 014/109] Update APS3 driver to match transmitter syntax, fix a few typos. --- src/auspex/instruments/aps3.py | 53 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 8628cdc7..0be2d750 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,7 +6,7 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -#__all__ = ['AMC599', 'APS3'] +__all__ = ['APS3'] from .instrument import Instrument, is_valid_ipv4, Command from .bbn import MakeSettersGetters @@ -367,7 +367,15 @@ def correction_control(self, value): ####### TRIGGER INTERVAL ################################################### - trigger_interval = BitFieldCommand(register=CSR_TRIG_INTERVAL, shift=0, mask=U32) + @property + def trigger_interval(self): + """Gets/sets the trigger interval in seconds, based on a 300 MHz clock.""" + return int(self.read_register(CSR_TRIG_INTERVAL)) / 300e6 + @trigger_interval.setter + def trigger_interval(self, value): + trig_bits = int(value * 300e6) + assert (trig_bits >= 0 and trig_bits < U32), "Trigger interval out of range!" + self.write_register(CSR_TRIG_INTERVAL, trig_bits) ####### UPTIME REGISTERS ################################################### @@ -419,14 +427,26 @@ def correction_bypass(self): ####### BOARD_CONTROL ###################################################### - microblaze_enable = BitFieldCommand(register=CSR_BD_CONTROL, shift=1) + microblaze_reset = BitFieldCommand(register=CSR_BD_CONTROL, shift=1, + doc="True resets Microblaze softcore. False takes Microblaze out of reset.") dac_output_mux = BitFieldCommand(register=CSR_BD_CONTROL, shift=4, - value_map={"SOF200": 0x0, "APS": 0x1}) + value_map={"SOF200": 0x0, "APS": 0x1}, + doc="Select SOF200 test output or APS sequencer output from DAC.") + trigger_output_select = BitFieldCommand(register=CSR_BD_CONTROL, shift=5, + doc="""True: select trigger to be output on front panel. + False: select marker to be output on front pane.""") ####### MARKER_DELAY ####################################################### - marker_delay = BitFieldCommand(register=CSR_MARKER_DELAY, shift=0, - mask=U16, allowed_values=[0,U16]) + @property + def marker_delay(self): + """Gets/sets the marker delay in seconds, based on a 300 MHz clock.""" + return (1 + int(self.read_register(CSR_TRIG_INTERVAL))) / 300e6 + @marker_delay.setter + def marker_delay(self, value): + trig_bits = int(value * 300e6) + 1 + assert (trig_bits >= 0 and trig_bits < U16), "Marker delay out of range!" + self.write_register(CSR_TRIG_INTERVAL, trig_bits) ###### DRAM OFFSET REGISTERS ############################################### def SEQ_OFFSET(self): @@ -438,6 +458,13 @@ def WFA_OFFSET(self): def WFB_OFFSET(self): return (self.read_register(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + ####### GPIO REGISTERS ##################################################### + + GPIO0_high = BitFieldCommand(register=CSR_BD_CONTROL, shift=16, mask=U16) + GPIO0_low = BitFieldCommand(register=CSR_BD_CONTROL, shift=8, mask=0xFF) + GPIO1 = BitFieldCommand(register=CSR_DATA1_IO, shift=0, mask=U32) + GPIO2 = BitFieldCommand(register=CSR_DATA2_IO, shift=0, mask=U32) + ###### UTILITIES ########################################################### def run(self): logger.info("Taking cache controller out of reset...") @@ -457,7 +484,7 @@ def stop(self): logger.info("Resetting sequencer...") self.sequencer_enable = False - def write_sequence(self, sequence): + def load_sequence(self, sequence): packed_seq = [] for instr in sequence: packed_seq.append(instr & U32) @@ -465,7 +492,17 @@ def write_sequence(self, sequence): self.cache_controller = False sleep(0.01) - self.write_dram(self.SEQ_OFFSET, packed_seq) + self.write_dram(self.SEQ_OFFSET(), packed_seq) logger.info(f"Wrote {len(packed_seq)} words to sequence memory.") sleep(0.01) self.cache_controller = True + + def load_waveforms(self, wfA, wfB): + raise NotImplementedError("Billy implement me too!") + + @property + def load_sequence_file(self, seq_file): + raise NotImplementedError("Billy please implement me!") + @load_sequence_file.setter + def load_sequence_file(self, seq_file): + raise NotImplementedError("Implement me!") From 388d915385dc0189b8f8d5f2c716ffe6f9956685 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 11:23:44 -0400 Subject: [PATCH 015/109] Add text progress bars for console Auspex useage. --- .travis.yml | 2 +- setup.py | 3 ++- src/auspex/experiment.py | 22 +++++++++++++++++----- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index cf2c9b89..935f1821 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ install: - pip install git+https://github.com/BBN-Q/bbndb.git - pip install git+https://github.com/BBN-Q/QGL.git@develop - pip install git+https://github.com/spatialaudio/nbsphinx.git@master - - pip install pyvisa coveralls scikit-learn pyusb future python-usbtmc setproctitle + - pip install pyvisa coveralls scikit-learn pyusb future python-usbtmc setproctitle progress - export GIT_LFS_SKIP_SMUDGE=0 - export PYTHONPATH=$PYTHONPATH:$PWD/src diff --git a/setup.py b/setup.py index 5054d88e..78b8c279 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,8 @@ "ipykernel>=5.0.0", "ipywidgets>=7.0.0", "sqlalchemy >= 1.2.15", - "setproctitle" + "setproctitle", + "progress" ] #Use PyVISA-Py if running on Linux or MacOS diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 9c7d0655..d4638989 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -55,6 +55,8 @@ from auspex.filters import Plotter, MeshPlotter, ManualPlotter, WriteToFile, DataBuffer, Filter from auspex.log import logger import auspex.config +from auspex.config import isnotebook + def auspex_plot_server(): client_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"plot_server.py") @@ -330,10 +332,16 @@ def sweep(self): if hasattr(self, 'progressbars') and self.progressbars: for axis in self.sweeper.axes: - if axis.done: - self.progressbars[axis].value = axis.num_points() + if isnotebook(): + if axis.done: + self.progressbars[axis].value = axis.num_points() + else: + self.progressbars[axis].value = axis.step else: - self.progressbars[axis].value = axis.step + if axis.done: + self.progressbars[axis].finish() + else: + self.progressbars.next() if self.sweeper.is_adaptive(): # Add the new tuples to the stream descriptors @@ -457,17 +465,21 @@ def final_init(self): def init_progress_bars(self): """ initialize the progress bars.""" - from auspex.config import isnotebook + self.progressbars = {} if isnotebook(): from ipywidgets import IntProgress, VBox from IPython.display import display - self.progressbars = {} + for axis in self.sweeper.axes: self.progressbars[axis] = IntProgress(min=0, max=axis.num_points(), description=f'Sweep {axis.name}:', style={'description_width': 'initial'}) display(VBox(list(self.progressbars.values()))) + else: + from progress.bar import Bar + for axis in self.sweeper.axes: + self.progressbars[axis] = Bar(f"Sweep {axis.name}", max=axis.num_points()) def run_sweeps(self): # Propagate the descriptors through the network From f6dc2d6aa225bf9a6b8bf59965e0bcdb2c3abed9 Mon Sep 17 00:00:00 2001 From: William Kalfus Date: Wed, 24 Jul 2019 14:11:21 -0400 Subject: [PATCH 016/109] APS3 features with serial --- src/auspex/instruments/agilent.py | 21 +- src/auspex/instruments/aps3.py | 359 ++++++++++++++++++++++++++++-- 2 files changed, 356 insertions(+), 24 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index fe3460e5..8462337c 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -840,6 +840,10 @@ class AgilentE9010A(SCPIInstrument): num_sweep_points = FloatCommand(scpi_string=":SWEep:POINTs") resolution_bandwidth = FloatCommand(scpi_string=":BANDwidth") sweep_time = FloatCommand(scpi_string=":SWEep:TIME") + averaging_count = IntCommand(scpi_string=':AVER:COUN') + + marker1_amplitude = FloatCommand(scpi_string=':CALC:MARK1:Y') + marker1_position = FloatCommand(scpi_string=':CALC:MARK1:X') mode = StringCommand(scpi_string=":INSTrument", allowed_values=["SA", "BASIC", "PULSE", "PNOISE"]) @@ -855,9 +859,9 @@ def connect(self, resource_name=None, interface_type=None): if resource_name is not None: self.resource_name = resource_name #If we only have an IP address then tack on the raw socket port to the VISA resource string - if is_valid_ipv4(resource_name): - resource_name += "::5025::SOCKET" - super(AgilentE9010A, self).connect(resource_name=resource_name, interface_type=interface_type) + if is_valid_ipv4(self.resource_name): + self.resource_name += "::5025::SOCKET" + super(AgilentE9010A, self).connect(resource_name=self.resource_name, interface_type=interface_type) self.interface._resource.read_termination = u"\n" self.interface._resource.write_termination = u"\n" self.interface._resource.timeout = 3000 #seem to have trouble timing out on first query sometimes @@ -882,3 +886,14 @@ def get_pn_trace(self, num=3): def restart_sweep(self): """ Aborts current sweep and restarts. """ self.interface.write(":INITiate:RESTart") + + def peak_search(self, marker=1): + self.interface.write(':CALC:MARK{:d}:MAX'.format(marker)) + + def marker_to_center(self, marker=1): + self.interface.write(':CALC:MARK{:d}:CENT'.format(marker)) + + def clear_averaging(self): + self.interface.write(':AVER:CLE') + + diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 0be2d750..e5659e1a 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -8,7 +8,7 @@ __all__ = ['APS3'] -from .instrument import Instrument, is_valid_ipv4, Command +from .instrument import Instrument, is_valid_ipv4, Command, MetaInstrument from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config @@ -18,6 +18,7 @@ from unittest.mock import Mock import collections from struct import pack, iter_unpack +import serial U32 = 0xFFFFFFFF #mask for 32-bit unsigned int U16 = 0xFFFF @@ -134,10 +135,11 @@ def __init__(self, name, bases, dct): nv = add_command_bitfield(self, k, v) class AMC599(object): - """Base class for simple register manipulations of AMC599 board. + """Base class for simple register manipulations of AMC599 board and DAC. """ PORT = 0xbb4e # TCPIP port (BBN!) + ser = None def __init__(self, debug=False): self.connected = False @@ -152,17 +154,19 @@ def _check_connected(self): if not self.connected: raise IOError("AMC599 Board not connected!") - def connect(self, ip_addr="192.168.2.200"): - self.ip_addr = ip_addr + def connect(self, resource=("192.168.2.200", "COM1")): + self.ip_addr = resource[0] if not self.debug: self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) self.socket.connect((self.ip_addr, self.PORT)) + self.ser = serial.Serial(resource[1], 115200) self.connected = True - + def disconnect(self): if self.connected: self.connected = False self.socket.close() + self.ser.close() def send_bytes(self, data): if isinstance(data, collections.Iterable): @@ -238,6 +242,234 @@ def read_memory(self, addr, num_words): self.send_bytes(datagram) resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) + + def serial_read_dac0(self, addr): + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rd d0 {0:#x}\n'.format(addr), 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + resp = self.ser.readline().decode() + start_index = len('Read value = ') + end_index = resp.find('@') + return int(resp[start_index:end_index], 16) + + def serial_write_dac0(self, addr, val): + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('wd d0 {0:#x} {1:#x}\n'.format(addr, val), 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + return self.ser.readline() # Echo back the "wrote xx to xx" line + + def serial_configure_JESD(self): + # Configure the JESD interface properly + logger.info(self.serial_write_dac0(0x300, 0x00)) # disable all links + sleep(0.01) + logger.info(self.serial_write_dac0(0x475, 0x09)) # soft reset DAC0 deframer + sleep(0.01) + logger.info(self.serial_write_dac0(0x110, 0x81)) # set interpolation to 2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x456, 0x01)) # set M=2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x459, 0x21)) # set S=2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x477, 0x00)) # disable ILS_MODE for DAC0 + sleep(0.01) + logger.info(self.serial_write_dac0(0x475, 0x01)) # bring DAC0 deframer out of reset + sleep(0.01) + logger.info(self.serial_write_dac0(0x300, 0x01)) # enable all links + sleep(0.01) + + def serial_set_switch_mode(self, mode): + ''' + Sets DAC output switch mode to one of NRZ, Mix-Mode, or RZ. + Parameters: + mode (string): Switch mode, one of "NRZ", "MIX", or "RZ" + ''' + if mode == 'NRZ': + code = 0x00 + elif mode == 'MIX': + code = 0x01 + elif mode == 'RZ': + code = 0x02 + else: + raise Exception('DAC switch mode "' + mode + '" not recognized.') + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x152, code)) + + def serial_get_switch_mode(self): + ''' + Reads DAC output switch mode as one of NRZ, Mix-Mode, or RZ. + Parameters: + mode (string): Switch mode, one of "NRZ", "MIX", or "RZ" + ''' + if self.ser is None: + logger.info('Fake read mix-mode.') + return 'MIX' + + code = self.serial_read_dac0(0x152) & 0x03 + if code == 0x00: + return 'NRZ' + if code == 0x01: + return 'MIX' + if code == 0x02: + return 'RZ' + + raise Exception('Unrecognized DAC switch mode ' + code + '.') + + def serial_set_analog_full_scale_current(self, current): + ''' + Sets DAC full-scale current, rounding to nearest LSB of current register. + Parameters: + current (float): Full-scale current in mA + + Returns: + (float) actual programmed current in mA + ''' + if current < 8 or current > 40: + raise Exception('DAC full-scale current must be between 8 mA and 40 mA.') + + # From AD9164 datasheet: + # IOUTFS = 32 mA × (ANA_FULL_SCALE_CURRENT[9:0]/1023) + 8 mA + reg_value = int(1023 * (current - 8) / 32) + + if self.ser is None: + logger.info('{:#x}'.format(reg_value & 0x3)) + logger.info('{:#x}'.format((reg_value >> 2) & 0xFF)) + else: + logger.info(self.serial_write_dac0(0x041, reg_value & 0x3)) + sleep(0.01) + logger.info(self.serial_write_dac0(0x042, (reg_value >> 2) & 0xFF)) + sleep(0.01) + + return 32 * (reg_value / 1023) + 8 + + def serial_get_analog_full_scale_current(self): + ''' + Reads programmed full-scale current. + Returns: + Full-scale current in mA + ''' + if self.ser is None: + return 0 + + LSbits = self.serial_read_dac0(0x041) & 0x03 + MSbits = self.serial_read_dac0(0x042) & 0xFF + reg_value = (MSbits << 2) & LSbits + return 32 * (reg_value / 1023) + 8 + + def serial_set_nco_enable(self, en): + ''' + Enables the DAC NCO. + Parameters: + en (bool): Enables the NCO if True, disables it if False + FIR85 (bool): Enables the FIR85 NCO filter if True, disables it if False + ''' + # Configure NCO_EN (Bit 6) = 0b1 + # Set the reserved bits (Bit 5 and Bit 3) to 0b0 + if self.ser is None: + logger.info('Fake read 0x00.') + code = 0x00 + else: + code = self.serial_read_dac0(0x111) + + if en: + code |= (1 << 6) + else: + code &= ~(1 << 6) + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x111, code)) + + sleep(0.1) + + def serial_get_nco_enable(self): + ''' + Checks whether the DAC NCO is enabled. + Returns: + True if DAC NCO is enabled, otherwise False + ''' + if self.ser is None: + logger.info('Fake reported DAC NCO enabled.') + return True + + return (self.serial_read_dac0(0x111) & (1 << 6)) != 0 + + def serial_set_FIR85_enable(self, FIR85): + ''' + Enables the DAC NCO FIR85 filter. + Parameters: + FIR85 (bool): Enables the FIR85 NCO filter if True, disables it if False + ''' + if self.ser is None: + logger.info('Fake read 0x00.') + code = 0x00 + else: + code = self.serial_read_dac0(0x111) + + if FIR85: + code |= (1 << 0) + else: + code &= ~(1 << 0) + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x111, code)) + + sleep(0.1) + + def serial_get_FIR85_enable(self): + ''' + Checks whether the DAC NCO FIR85 filter is enabled. + Returns: + True if DAC NCO FIR85 filter is enabled, otherwise False + ''' + if self.ser is None: + logger.info('Fake reported DAC NCO FIR85 enabled.') + return True + + return (self.serial_read_dac0(0x111) & (1 << 0)) != 0 + + def serial_set_nco_frequency(self, f): + ''' + Writes the given frequency, assuming not in NCO-only mode. + Follows procedure in Table 44 of AD9164 datasheet. + ''' + logger.info('Setting frequency to {}...'.format(f)) + + # Configure DC_TEST_EN bit: 0b0 = NCO operation with data interface + logger.info(self.serial_write_dac0(0x150, 0x00)) + sleep(0.01) + + # Ensure the frequency tuning word write request is low. + logger.info(self.serial_write_dac0(0x113, 0x00)) + sleep(0.01) + + # Write FTW. + ftw = [(int((f/5e9)*(1 << 48)) >> x) & 0xFF for x in range(0, 48, 8)] + for index, b in enumerate(ftw): + logger.info(self.serial_write_dac0(0x114 + index, b)) + sleep(0.01) + + # Load the FTW to the NCO. + logger.info(self.serial_write_dac0(0x113, 0x01)) + sleep(0.1) + + def serial_get_nco_frequency(self): + ''' + Reads the current NCO frequency, assuming not in NCO-only mode. + ''' + ftw = 0 + for index, shift in enumerate(range(0, 48, 8)): + ftw |= self.serial_read_dac0(0x114 + index) << shift + sleep(0.01) + + return ftw * 5e9 ##################################################################### @@ -300,11 +532,13 @@ def read_memory(self, addr, num_words): class APS3(Instrument, metaclass=MakeBitFieldParams): instrument_type = "AWG" + serial_port = '' - def __init__(self, resource_name=None, name="Unlabled APS3", debug=False): + def __init__(self, resource_name=None, name="Unlabeled APS3", debug=False, serial_port=''): self.name = name self.resource_name = resource_name self.board = AMC599(debug=debug) + self.serial_port = serial_port super().__init__() def connect(self, resource_name=None): @@ -312,8 +546,8 @@ def connect(self, resource_name=None): raise ValueError("Must supply a resource name!") elif resource_name is not None: self.resource_name = resource_name - if is_valid_ipv4(self.resource_name): - self.board.connect(ip_addr = self.resource_name) + if is_valid_ipv4(self.resource_name[0]): + self.board.connect(resource = self.resource_name) def disconnect(self): if self.board.connected: @@ -333,7 +567,7 @@ def read_dram(self, offset, num_words = 1): return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) ####### CACHE CONTROL REGSITER ############################################# - + cache_controller = BitFieldCommand(register=CSR_CACHE_CONTROL, shift=0, doc="""Cache controller enable bit.""") @@ -369,11 +603,11 @@ def correction_control(self, value): @property def trigger_interval(self): - """Gets/sets the trigger interval in seconds, based on a 300 MHz clock.""" - return int(self.read_register(CSR_TRIG_INTERVAL)) / 300e6 + """Gets/sets the trigger interval in seconds, based on a 312.5 MHz clock.""" + return int(self.read_register(CSR_TRIG_INTERVAL)) / 312.5e6 @trigger_interval.setter def trigger_interval(self, value): - trig_bits = int(value * 300e6) + trig_bits = int(value * 312.5e6) assert (trig_bits >= 0 and trig_bits < U32), "Trigger interval out of range!" self.write_register(CSR_TRIG_INTERVAL, trig_bits) @@ -441,12 +675,12 @@ def correction_bypass(self): @property def marker_delay(self): """Gets/sets the marker delay in seconds, based on a 300 MHz clock.""" - return (1 + int(self.read_register(CSR_TRIG_INTERVAL))) / 300e6 + return (1 + int(self.read_register(CSR_MARKER_DELAY))) / 312.5e6 @marker_delay.setter def marker_delay(self, value): - trig_bits = int(value * 300e6) + 1 + trig_bits = int(value * 312.5e6) + 1 assert (trig_bits >= 0 and trig_bits < U16), "Marker delay out of range!" - self.write_register(CSR_TRIG_INTERVAL, trig_bits) + self.write_register(CSR_MARKER_DELAY, trig_bits) ###### DRAM OFFSET REGISTERS ############################################### def SEQ_OFFSET(self): @@ -467,6 +701,9 @@ def WFB_OFFSET(self): ###### UTILITIES ########################################################### def run(self): + logger.info("Configuring JESD...") + self.board.serial_configure_JESD() + sleep(0.01) logger.info("Taking cache controller out of reset...") self.cache_controller = True sleep(0.01) @@ -484,6 +721,10 @@ def stop(self): logger.info("Resetting sequencer...") self.sequencer_enable = False + ###### SEQUENCE LOADING #################################################### + + sequence_filename = '' + def load_sequence(self, sequence): packed_seq = [] for instr in sequence: @@ -498,11 +739,87 @@ def load_sequence(self, sequence): self.cache_controller = True def load_waveforms(self, wfA, wfB): - raise NotImplementedError("Billy implement me too!") + self.write_dram(self.WFA_OFFSET(), wfA) + self.write_dram(self.WFB_OFFSET(), wfB) - @property def load_sequence_file(self, seq_file): - raise NotImplementedError("Billy please implement me!") - @load_sequence_file.setter - def load_sequence_file(self, seq_file): - raise NotImplementedError("Implement me!") + self.sequence_filename = seq_file + with open(seq_file, 'rb') as file: + instrument_type = file.read(4) + if instrument_type != b'APS3': + raise ValueError('Sequence file not designated for APS3; header for ' + str(instrument_type) + ' found.') + + version = np.frombuffer(file.read(4), dtype=np.float32)[0] + min_firmware_version = np.frombuffer(file.read(4), dtype=np.float32)[0] + num_channels = int(np.frombuffer(file.read(2), dtype=np.uint16)[0]) + + if num_channels != 2: + raise ValueError('Unexpected number of channels, sequence file reports ' + str(num_channels) + ' channels.') + + instructions_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) + instructions = [int(x) for x in np.frombuffer(file.read(instructions_size*8), dtype=np.uint64)] + + load_sequence(instructions) + + data = [] + for chan in range(num_channels): + data_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) + data.append(np.frombuffer(file.read(data_size*8), dtype=np.uint64)) + + load_waveforms(data[0], data[1]) + + def serial_check_alive(self): + return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 + + @property + def sequence_file(self): + return self.sequence_filename + + @property + def sequence_file(self): + return self.sequence_filename + + @sequence_file.setter + def sequence_file(self, value): + self.load_sequence_file(value) + + @property + def dac_switch_mode(self): + return self.board.serial_get_switch_mode() + + @dac_switch_mode.setter + def dac_switch_mode(self, value): + self.board.serial_set_switch_mode(value) + + @property + def dac_full_scale_current(self): + return self.board.serial_get_analog_full_scale_current() + + @dac_full_scale_current.setter + def dac_full_scale_current(self, value): + self.board.serial_set_analog_full_scale_current(value) + + @property + def dac_nco_enable(self): + return self.board.serial_get_nco_enable() + + @dac_nco_enable.setter + def dac_nco_enable(self, value): + self.board.serial_set_nco_enable(value) + + @property + def dac_FIR85_enable(self): + return self.board.serial_get_FIR85_enable() + + @dac_FIR85_enable.setter + def dac_FIR85_enable(self, value): + self.board.serial_set_FIR85_enable(value) + + @property + def dac_nco_frequency(self): + return self.board.serial_get_nco_frequency() + + @dac_nco_frequency.setter + def dac_nco_frequency(self, value): + self.board.serial_set_nco_frequency(value) + From 7d0c1ac1ea326832bc8e0c8426e8713069b998fc Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 14:45:04 -0400 Subject: [PATCH 017/109] Use for console progress bar. --- src/auspex/experiment.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index d4638989..0ebf188c 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -339,9 +339,10 @@ def sweep(self): self.progressbars[axis].value = axis.step else: if axis.done: + self.progressbars.next() self.progressbars[axis].finish() else: - self.progressbars.next() + self.progressbars.goto(axis.step) if self.sweeper.is_adaptive(): # Add the new tuples to the stream descriptors @@ -466,7 +467,7 @@ def final_init(self): def init_progress_bars(self): """ initialize the progress bars.""" self.progressbars = {} - + print('initializing progress bars!') if isnotebook(): from ipywidgets import IntProgress, VBox from IPython.display import display @@ -477,9 +478,9 @@ def init_progress_bars(self): description=f'Sweep {axis.name}:', style={'description_width': 'initial'}) display(VBox(list(self.progressbars.values()))) else: - from progress.bar import Bar + from progress.bar import ShadyBar for axis in self.sweeper.axes: - self.progressbars[axis] = Bar(f"Sweep {axis.name}", max=axis.num_points()) + self.progressbars[axis] = ShadyBar(f"Sweep {axis.name}", max=axis.num_points()) def run_sweeps(self): # Propagate the descriptors through the network From c06b3b1ae71b8e8ab0a7d95e61ea9e042a43769b Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 14:45:27 -0400 Subject: [PATCH 018/109] Add console progress bars to qubit experiment. --- src/auspex/qubit/qubit_exp.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index c0c74692..50dd0ea8 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -1,4 +1,5 @@ from auspex.log import logger +from auspex.config import isnotebook from auspex.experiment import Experiment, FloatParameter from auspex.stream import DataStream, DataAxis, SweepAxis, DataStreamDescriptor, InputConnector, OutputConnector from auspex.instruments import instrument_map @@ -526,15 +527,11 @@ def final_init(self): def init_progress_bars(self): """ initialize the progress bars.""" - - from auspex.config import isnotebook - + self.progressbars = {} + ocs = list(self.output_connectors.values()) if isnotebook(): from ipywidgets import IntProgress, VBox from IPython.display import display - - ocs = list(self.output_connectors.values()) - self.progressbars = {} if len(ocs)>0: for oc in ocs: self.progressbars[oc] = IntProgress(min=0, max=oc.output_streams[0].descriptor.num_points(), bar_style='success', @@ -543,6 +540,14 @@ def init_progress_bars(self): self.progressbars[axis] = IntProgress(min=0, max=axis.num_points(), description=f'{axis.name}:', style={'description_width': 'initial'}) display(VBox(list(self.progressbars.values()))) + else: + from progress.bar import ShadyBar + if len(ocs)>0: + for oc in ocs: + self.progressbars[oc] = ShadyBar(f'Digitizer Data {oc.name}:', + max=oc.output_streams[0].descriptor.num_points()) + for axis in self.sweeper.axes: + self.progressbars[axis] = ShadyBar(f"Sweep {axis.name}", max=axis.num_points()) def run(self): # Begin acquisition before enabling the AWGs From 2d5f23205660f4121d314b84fdd9b573728971d2 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 14:45:56 -0400 Subject: [PATCH 019/109] Update progress bars correctly in digitizer methods. --- src/auspex/instruments/X6.py | 28 ++++++++++++++++++++++++++-- src/auspex/instruments/alazar.py | 27 ++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 52c02c94..6097b11e 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -318,6 +318,14 @@ def get_buffer_for_channel(self, channel): def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None): + progress_updaters = {} + if ocs and progressbars: + for oc in ocs: + if hasattr(progressbars[oc], 'goto'): #it's a command line progress bar + progress_updaters[oc] = lambda x: progressbars[oc].goto(x) + else: + progress_updaters[oc] = lambda x: setattr(progressbars[oc], 'value', x) + if self.gen_fake_data: total_spewed = 0 @@ -352,14 +360,24 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) for oc in ocs: total_taken += oc.points_taken.value - initial_points[oc] if progressbars: - progressbars[oc].value = ocs[0].points_taken.value + progress_updaters[oc](ocs[0].points_taken.value) # logger.info('TOTAL fake data received %d', oc.points_taken.value - initial_points[oc]) if total_taken == total_spewed: break # logger.info('WAITING for acquisition to finish %d < %d', total_taken, total_spewed) time.sleep(0.025) + for oc in ocs: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass else: + print('waiting for acquisition') + for oc in ocs: + print(oc.points_taken) + print(oc.descriptor) while not self.done(): if not dig_run.is_set(): self.last_timestamp.value = datetime.datetime.now().timestamp() @@ -368,8 +386,14 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) break if progressbars: for oc in ocs: - progressbars[oc].value = oc.points_taken.value + progress_updaters[oc](ocs[0].points_taken.value) time.sleep(0.1) + for oc in ocs: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass # pass thru properties @property diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index b7910976..ba952ebf 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -150,7 +150,7 @@ def spew_fake_data(self, counter, ideal_datapoint=0, random_mag=0.1, random_seed the test with fake data """ total = 0 - + for chan, wsock in self._chan_to_wsocket.items(): length = int(self.record_length) signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) @@ -197,6 +197,14 @@ def get_buffer_for_channel(self, channel): return getattr(self._lib, 'ch{}Buffer'.format(self._chan_to_buf[channel])) def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): + progress_updaters = {} + if ocs and progressbar: + for oc in ocs: + if hasattr(progressbars[oc], 'goto'): + progress_updaters[oc] = lambda x: progressbars[oc].goto(x) + else: + progress_updaters[oc] = lambda x: setattr(progressbars[oc], 'value', x) + if self.gen_fake_data: total_spewed = 0 @@ -232,14 +240,18 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): for oc in ocs: total_taken += oc.points_taken.value - initial_points[oc] if progressbars: - progressbars[oc].value = oc.points_taken.value + progress_updaters[oc](oc.points_taken.value) # logger.info('TOTAL fake data received %d', oc.points_taken.value - initial_points[oc]) if total_taken == total_spewed: break - + # logger.info('WAITING for acquisition to finish %d < %d', total_taken, total_spewed) time.sleep(0.025) - + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass else: while not self.done(): if not dig_run.is_set(): @@ -249,8 +261,13 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): raise Exception("Alazar timed out.") if progressbars: for oc in ocs: - progressbars[oc].value = oc.points_taken.value + progress_updaters[oc](oc.points_taken.value) #time.sleep(0.2) Does this need to be here at all? + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass logger.debug("Digitizer %s finished getting data.", self.name) From f6ea2c12ff5b5c5f04a4df8a563835fe7905ba54 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 14:52:40 -0400 Subject: [PATCH 020/109] Remove debug print statement. --- src/auspex/experiment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 0ebf188c..8e6ba517 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -467,7 +467,6 @@ def final_init(self): def init_progress_bars(self): """ initialize the progress bars.""" self.progressbars = {} - print('initializing progress bars!') if isnotebook(): from ipywidgets import IntProgress, VBox from IPython.display import display From 0a7e9c422da855d9994238cb3783ee087e2197ae Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 14:58:54 -0400 Subject: [PATCH 021/109] Fix progressbar attribute error. --- src/auspex/experiment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 8e6ba517..db88d299 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -339,10 +339,10 @@ def sweep(self): self.progressbars[axis].value = axis.step else: if axis.done: - self.progressbars.next() + self.progressbars[axis].next() self.progressbars[axis].finish() else: - self.progressbars.goto(axis.step) + self.progressbars[axis].goto(axis.step) if self.sweeper.is_adaptive(): # Add the new tuples to the stream descriptors From a46add5f8975705d86c9eda3ec06885512ece0ab Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 15:29:25 -0400 Subject: [PATCH 022/109] Fix progressbar updates. --- src/auspex/instruments/X6.py | 22 ++++++++++++---------- src/auspex/instruments/alazar.py | 24 +++++++++++++----------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 6097b11e..92eaa4ab 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -367,11 +367,12 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) # logger.info('WAITING for acquisition to finish %d < %d', total_taken, total_spewed) time.sleep(0.025) for oc in ocs: - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass + if progressbars: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass else: print('waiting for acquisition') @@ -389,11 +390,12 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) progress_updaters[oc](ocs[0].points_taken.value) time.sleep(0.1) for oc in ocs: - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass + if progressbars: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass # pass thru properties @property diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index ba952ebf..3340bf28 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -198,7 +198,7 @@ def get_buffer_for_channel(self, channel): def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): progress_updaters = {} - if ocs and progressbar: + if ocs and progressbars: for oc in ocs: if hasattr(progressbars[oc], 'goto'): progress_updaters[oc] = lambda x: progressbars[oc].goto(x) @@ -247,11 +247,12 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): # logger.info('WAITING for acquisition to finish %d < %d', total_taken, total_spewed) time.sleep(0.025) - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass + if progressbars: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass else: while not self.done(): if not dig_run.is_set(): @@ -263,11 +264,12 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): for oc in ocs: progress_updaters[oc](oc.points_taken.value) #time.sleep(0.2) Does this need to be here at all? - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass + if progressbars: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass logger.debug("Digitizer %s finished getting data.", self.name) From 7927bd488d565f32b51c5e142c76824ca0b69de2 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Wed, 24 Jul 2019 15:40:38 -0400 Subject: [PATCH 023/109] Remove more debug print statements.. --- src/auspex/instruments/X6.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 92eaa4ab..44b6f583 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -375,10 +375,6 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) pass else: - print('waiting for acquisition') - for oc in ocs: - print(oc.points_taken) - print(oc.descriptor) while not self.done(): if not dig_run.is_set(): self.last_timestamp.value = datetime.datetime.now().timestamp() From 52b908cb52a13d6919ab3452dae675f11d91707f Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Thu, 25 Jul 2019 17:22:57 -0400 Subject: [PATCH 024/109] WIP doneness improvemenets --- src/auspex/filters/filter.py | 67 +++++--------------------------- src/auspex/instruments/alazar.py | 59 ++++++++++------------------ src/auspex/stream.py | 13 +++++-- 3 files changed, 39 insertions(+), 100 deletions(-) diff --git a/src/auspex/filters/filter.py b/src/auspex/filters/filter.py index e454e740..cae6f872 100644 --- a/src/auspex/filters/filter.py +++ b/src/auspex/filters/filter.py @@ -176,6 +176,9 @@ def push_to_all(self, message): for oc in self.output_connectors.values(): for ost in oc.output_streams: ost.queue.put(message) + if message['type'] == 'event' and message["event_type"] == "done": + logger.info(f"Closing out queue {ost.queue}") + ost.queue.close() def push_resource_usage(self): if self.perf_queue and (datetime.datetime.now() - self.last_performance_update).seconds > 1.0: @@ -215,27 +218,23 @@ def main(self): break self.push_resource_usage() - + # if len(messages) > 1: + # logger.info('%s "%s" received "%d" messages', self.__class__.__name__, self.filter_name, len(messages)) for message in messages: + message_type = message['type'] message_data = message['data'] if message['type'] == 'event': - logger.debug('%s "%s" received event "%s"', self.__class__.__name__, self.filter_name, message_data) + logger.debug('%s "%s" received event with data "%s"', self.__class__.__name__, self.filter_name, message_data) # Propagate along the graph self.push_to_all(message) # Check to see if we're done if message['event_type'] == 'done': - logger.debug(f"{self} received done message!") + logger.info(f"{self} received done message!") stream_done = True - elif message['event_type'] == 'refined': - self.refine(message_data) - continue - elif message['event_type'] == 'new_tuples': - self.process_new_tuples(input_stream.descriptor, message_data) - # break elif message['type'] == 'data': if not hasattr(message_data, 'size'): @@ -246,11 +245,8 @@ def main(self): self.process_data(message_data.flatten()) self.processed += message_data.nbytes - elif message['type'] == 'data_direct': - self.processed += message_data.nbytes - self.process_direct(message_data) - if stream_done: + logger.info("Dealing with done message!") self.done.set() break @@ -259,48 +255,3 @@ def main(self): except Exception as e: logger.warning(f"Filter {self} raised exception {e}. Bailing.") - - - def process_data(self, data): - """Process data coming through the filter pipeline""" - pass - - def process_direct(self, data): - """Process direct data, ignore things like the data descriptors.""" - pass - - def process_new_tuples(self, descriptor, message_data): - axis_names, sweep_values = message_data - # All the axis names for this connector - ic_axis_names = [ax.name for ax in descriptor.axes] - # The sweep values from sweep axes that are present (axes may have been dropped) - sweep_values = [sv for an, sv in zip(axis_names, sweep_values) if an in ic_axis_names] - vals = [a for a in descriptor.data_axis_values()] - if sweep_values: - vals = [[v] for v in sweep_values] + vals - - # Create the outer product of axes - nested_list = list(itertools.product(*vals)) - flattened_list = [tuple((val for sublist in line for val in sublist)) for line in nested_list] - descriptor.visited_tuples = descriptor.visited_tuples + flattened_list - - for oc in self.output_connectors.values(): - oc.push_event("new_tuples", message_data) - - return len(flattened_list) - - def refine(self, refine_data): - """Try to deal with a refinement along the given axes.""" - axis_name, reset_axis, points = refine_data - - for ic in self.input_connectors.values(): - for desc in [ic.descriptor] + [s.descriptor for s in ic.input_streams]: - for ax in desc.axes: - if ax.name == axis_name: - if reset_axis: - ax.points = points - else: - ax.points = np.append(ax.points, points) - - for oc in self.output_connectors.values(): - oc.push_event("refined", refine_data) diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index 3340bf28..fc6c10ce 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -114,7 +114,7 @@ def data_available(self): return self._lib.data_available() def done(self): - #logger.debug(f"Checking alazar doneness: {self.total_received.value} {self.number_segments * self.number_averages * self.record_length}") + logger.warning(f"Checking alazar doneness: {self.total_received.value} {self.number_segments * self.number_averages * self.record_length}") return self.total_received.value >= (self.number_segments * self.number_averages * self.record_length) def get_socket(self, channel): @@ -168,6 +168,7 @@ def receive_data(self, channel, oc, exit, ready, run): sock = self._chan_to_rsocket[channel] sock.settimeout(2) self.last_timestamp.value = datetime.datetime.now().timestamp() + last_print = datetime.datetime.now().timestamp() ready.value += 1 while not exit.is_set(): @@ -189,6 +190,9 @@ def receive_data(self, channel, oc, exit, ready, run): buf = buf+buf2 data = np.frombuffer(buf, dtype=np.float32) self.total_received.value += len(data) + if datetime.datetime.now().timestamp() - last_print > 0.25: + last_print = datetime.datetime.now().timestamp() + logger.info(f"Alz: {self.total_received.value}") oc.push(data) self.fetch_count.value += 1 @@ -232,44 +236,23 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): time.sleep(0.0001) self.ideal_counter += 1 - # logger.info("Counter: %s", str(counter)) - # logger.info('TOTAL fake data generated %d', total_spewed) - if ocs: - while True: - total_taken = 0 - for oc in ocs: - total_taken += oc.points_taken.value - initial_points[oc] - if progressbars: - progress_updaters[oc](oc.points_taken.value) - # logger.info('TOTAL fake data received %d', oc.points_taken.value - initial_points[oc]) - if total_taken == total_spewed: - break - - # logger.info('WAITING for acquisition to finish %d < %d', total_taken, total_spewed) - time.sleep(0.025) - if progressbars: - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass - else: - while not self.done(): - if not dig_run.is_set(): - self.last_timestamp.value = datetime.datetime.now().timestamp() - if (datetime.datetime.now().timestamp() - self.last_timestamp.value) > timeout: - logger.error("Digitizer %s timed out. Timeout was %f, time was %f", self.name, timeout, (datetime.datetime.now().timestamp() - self.last_timestamp.value)) - raise Exception("Alazar timed out.") - if progressbars: - for oc in ocs: - progress_updaters[oc](oc.points_taken.value) - #time.sleep(0.2) Does this need to be here at all? + + while not self.done(): + if not dig_run.is_set(): + self.last_timestamp.value = datetime.datetime.now().timestamp() + if (datetime.datetime.now().timestamp() - self.last_timestamp.value) > timeout: + logger.error("Digitizer %s timed out. Timeout was %f, time was %f", self.name, timeout, (datetime.datetime.now().timestamp() - self.last_timestamp.value)) + raise Exception("Alazar timed out.") if progressbars: - try: - progressbars[oc].next() - progressbars[oc].finish() - except AttributeError: - pass + for oc in ocs: + progress_updaters[oc](oc.points_taken.value) + #time.sleep(0.2) Does this need to be here at all? + if progressbars: + try: + progressbars[oc].next() + progressbars[oc].finish() + except AttributeError: + pass logger.debug("Digitizer %s finished getting data.", self.name) diff --git a/src/auspex/stream.py b/src/auspex/stream.py index a784c1b2..c4635956 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -485,6 +485,7 @@ def __init__(self, name=None, unit=None): self.descriptor = None self.start_connector = None self.end_connector = None + self.closed = False def set_descriptor(self, descriptor): if isinstance(descriptor,DataStreamDescriptor): @@ -525,6 +526,8 @@ def __repr__(self): self.name, self.percent_complete(), self.descriptor) def push(self, data): + if self.closed: + raise Exception("The queue is closed and should not be receiving any more data") with self.points_taken_lock: if hasattr(data, 'size'): self.points_taken.value += data.size @@ -542,12 +545,14 @@ def push(self, data): self.queue.put(message) def push_event(self, event_type, data=None): + if self.closed: + raise Exception("The queue is closed and should not be receiving any more data") message = {"type": "event", "event_type": event_type, "data": data} self.queue.put(message) - - # def push_direct(self, data): - # message = {"type": "data_direct", "compression": "none", "data": data} - # self.queue.put(message) + if event_type == "done": + logger.info(f"Closing out queue {self}") + self.queue.close() + self.closed = True # These connectors are where we attached the DataStreams class InputConnector(object): From e7eb0f8fd5644946b9b31cf2676516907ef4ac05 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Thu, 25 Jul 2019 17:29:58 -0400 Subject: [PATCH 025/109] WIP doneness improvemenets --- src/auspex/filters/filter.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/auspex/filters/filter.py b/src/auspex/filters/filter.py index cae6f872..35c53236 100644 --- a/src/auspex/filters/filter.py +++ b/src/auspex/filters/filter.py @@ -27,6 +27,7 @@ import time, datetime import queue import copy +import ctypes import numpy as np from auspex.parameter import Parameter @@ -75,6 +76,12 @@ def __init__(self, name=None, **kwargs): # Keep track of data throughput self.processed = 0 + # Shared memory interface + self.w_idx_shared = Value('i', 0) + self.r_idx_shared = Value('i', 0) + self.buff_shared_re = Array(ctypes.c_double, lock=True) + self.buff_shared_im = Array(ctypes.c_double, lock=True) + # For objectively measuring doneness self.finished_processing = Event() self.finished_processing.clear() From 627599f587508e6b72131299d089fea7dff3c642 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Fri, 26 Jul 2019 10:54:18 -0400 Subject: [PATCH 026/109] WIP working shared memory replacement for MP queues --- src/auspex/filters/filter.py | 114 ++++++++++++++++------------------- src/auspex/stream.py | 39 ++++++++++-- 2 files changed, 88 insertions(+), 65 deletions(-) diff --git a/src/auspex/filters/filter.py b/src/auspex/filters/filter.py index 35c53236..23530bc9 100644 --- a/src/auspex/filters/filter.py +++ b/src/auspex/filters/filter.py @@ -20,6 +20,7 @@ from multiprocessing import Process from multiprocessing import Event from multiprocessing import Queue + from multiprocessing import Value, Array from setproctitle import setproctitle import cProfile @@ -76,12 +77,6 @@ def __init__(self, name=None, **kwargs): # Keep track of data throughput self.processed = 0 - # Shared memory interface - self.w_idx_shared = Value('i', 0) - self.r_idx_shared = Value('i', 0) - self.buff_shared_re = Array(ctypes.c_double, lock=True) - self.buff_shared_im = Array(ctypes.c_double, lock=True) - # For objectively measuring doneness self.finished_processing = Event() self.finished_processing.clear() @@ -197,68 +192,65 @@ def main(self): """ Generic run method which waits on a single stream and calls `process_data` on any new_data """ - try: - - logger.debug('Running "%s" run loop', self.filter_name) - setproctitle(f"python auspex filter: {self}") - input_stream = getattr(self, self._input_connectors[0]).input_streams[0] - desc = input_stream.descriptor - - stream_done = False - stream_points = 0 - - while not self.exit.is_set():# and not self.finished_processing.is_set(): - # Try to pull all messages in the queue. queue.empty() is not reliable, so we - # ask for forgiveness rather than permission. - messages = [] - - # Check to see if the parent process still exists: - if not self._parent_process_running(): - logger.warning(f"{self} with pid {os.getpid()} could not find parent with pid {os.getppid()}. Assuming something has gone wrong. Exiting.") + # try: + + logger.debug('Running "%s" run loop', self.filter_name) + setproctitle(f"python auspex filter: {self}") + input_stream = getattr(self, self._input_connectors[0]).input_streams[0] + desc = input_stream.descriptor + + stream_done = False + stream_points = 0 + + while not self.exit.is_set():# and not self.finished_processing.is_set(): + # Try to pull all messages in the queue. queue.empty() is not reliable, so we + # ask for forgiveness rather than permission. + messages = [] + + # Check to see if the parent process still exists: + if not self._parent_process_running(): + logger.warning(f"{self} with pid {os.getpid()} could not find parent with pid {os.getppid()}. Assuming something has gone wrong. Exiting.") + break + + while not self.exit.is_set(): + try: + messages.append(input_stream.queue.get(False)) + except queue.Empty as e: + time.sleep(0.002) break - while not self.exit.is_set(): - try: - messages.append(input_stream.queue.get(False)) - except queue.Empty as e: - time.sleep(0.002) - break - - self.push_resource_usage() - # if len(messages) > 1: - # logger.info('%s "%s" received "%d" messages', self.__class__.__name__, self.filter_name, len(messages)) - for message in messages: - - message_type = message['type'] - message_data = message['data'] - - if message['type'] == 'event': - logger.debug('%s "%s" received event with data "%s"', self.__class__.__name__, self.filter_name, message_data) - - # Propagate along the graph - self.push_to_all(message) - - # Check to see if we're done - if message['event_type'] == 'done': - logger.info(f"{self} received done message!") - stream_done = True - - elif message['type'] == 'data': - if not hasattr(message_data, 'size'): - message_data = np.array([message_data]) + self.push_resource_usage() + for message in messages: + message_type = message['type'] + if message['type'] == 'event': + logger.debug('%s "%s" received event with type "%s"', self.__class__.__name__, message_type) + + # Propagate along the graph + self.push_to_all(message) + + # Check to see if we're done + if message['event_type'] == 'done': + logger.info(f"{self} received done message!") + stream_done = True + + elif message['type'] == 'data': + # if not hasattr(message_data, 'size'): + # message_data = np.array([message_data]) + message_data = input_stream.pop() + if message_data is not None: logger.debug('%s "%s" received %d points.', self.__class__.__name__, self.filter_name, message_data.size) logger.debug("Now has %d of %d points.", input_stream.points_taken.value, input_stream.num_points()) stream_points += len(message_data.flatten()) self.process_data(message_data.flatten()) self.processed += message_data.nbytes - if stream_done: - logger.info("Dealing with done message!") - self.done.set() - break + if stream_done: + logger.info("Dealing with done message!") + self.done.set() + break - # When we've finished, either prematurely or as expected - self.on_done() + # When we've finished, either prematurely or as expected + self.on_done() - except Exception as e: - logger.warning(f"Filter {self} raised exception {e}. Bailing.") + # except Exception as e: + # logger.warning(f"Filter {self} raised exception {e}. Bailing.") diff --git a/src/auspex/stream.py b/src/auspex/stream.py index c4635956..37b2cd1a 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -15,8 +15,8 @@ else: import multiprocessing as mp from multiprocessing import Queue -from multiprocessing import Value - +from multiprocessing import Value, RawValue, RawArray +import ctypes import logging import numbers import itertools @@ -487,6 +487,15 @@ def __init__(self, name=None, unit=None): self.end_connector = None self.closed = False + # Shared memory interface + self.buffer_lock = mp.Lock() + self.buffer_size = 5000000 + self.buff_idx = Value('i', 0) + self.buff_shared_re = RawArray(ctypes.c_double, self.buffer_size) + self.buff_shared_im = RawArray(ctypes.c_double, self.buffer_size) + self.re_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) + self.im_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) + def set_descriptor(self, descriptor): if isinstance(descriptor,DataStreamDescriptor): logger.debug("Setting descriptor on stream '%s' to '%s'", self.name, descriptor) @@ -540,10 +549,32 @@ def push(self, data): self.points_taken.value += 1 except: raise ValueError("Got data {} that is neither an array nor a float".format(data)) - - message = {"type": "data", "data": data} + with self.buffer_lock: + start = self.buff_idx.value + re = np.real(data).flatten() + # logger.info(f"in buff_shared_re:{self.buff_idx.value} {re[0:4]}") + self.re_np[start:start+re.size] = re + if issubclass(self.descriptor.dtype, np.complex): + im = np.real(data).flatten() + # logger.info(f"in buff_shared_im:{self.buff_idx.value} {im[0:4]}") + self.im_np[start:start+im.size] = im + message = {"type": "data", "data": None} + self.buff_idx.value = start + data.size self.queue.put(message) + def pop(self): + with self.buffer_lock: + idx = self.buff_idx.value + if idx == 0: + return None + result = self.re_np[:idx] + # logger.info(f"out buff_shared: {idx} {result[0:4]}") + if issubclass(self.descriptor.dtype, np.complex): + result = result.astype(np.complex128) + 1.0j*self.im_np[:idx] + # logger.info(f"out buff_shared now: {idx} {result[0:4]}") + self.buff_idx.value = 0 + return result + def push_event(self, event_type, data=None): if self.closed: raise Exception("The queue is closed and should not be receiving any more data") From e74ebb8e3a3cab6a52445f2d27c6609b4f982f32 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Fri, 26 Jul 2019 12:45:26 -0400 Subject: [PATCH 027/109] WIP much faster spew_fake_data for X6 and alazar --- src/auspex/instruments/X6.py | 67 +++++++++++++++++--------------- src/auspex/instruments/alazar.py | 57 +++++++++++++-------------- src/auspex/stream.py | 2 +- 3 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 44b6f583..53dbcca2 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -233,36 +233,45 @@ def add_channel(self, channel): # todo: other checking here self._channels.append(channel) - def spew_fake_data(self, counter, ideal_datapoint=0, random_mag=0.1, random_seed=12345): + def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345): """ Generate fake data on the stream. For unittest usage. - ideal_datapoint: mean of the expected signal for stream_type = "Integrated". + ideal_data: array or list giving means of the expected signal for each segment Returns the total number of fake data points, so that we can keep track of how many we expect to receive, when we're doing the test with fake data """ total = 0 - + # import ipdb; ipdb.set_trace(); + segs = self._lib.nbr_segments for chan, wsock in self._chan_to_wsocket.items(): if chan.stream_type == "integrated": length = 1 - data = random_mag*(np.random.random(length).astype(chan.dtype) + 1j*np.random.random(length).astype(chan.dtype)) + ideal_datapoint elif chan.stream_type == "demodulated": length = int(self._lib.record_length/32) - data = np.zeros(length, dtype=chan.dtype) - data[int(length/4):int(3*length/4)] = 1.0 if ideal_datapoint == 0 else ideal_datapoint - data += random_mag*(np.random.random(length) + 1j*np.random.random(length)) else: #Raw length = int(self._lib.record_length/4) - signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) - data = np.zeros(length, dtype=chan.dtype) - data[int(length/4):int(length/4)+len(signal)] = signal * (1.0 if ideal_datapoint == 0 else ideal_datapoint) - data += random_mag*np.random.random(length) - - total += length - wsock.send(struct.pack('n', length*data.dtype.itemsize) + data.tostring()) - counter[chan] += length + buff = np.zeros((segs, length), dtype=np.float32) + # for chan, wsock in self._chan_to_wsocket.items(): + for i in range(segs): + if chan.stream_type == "integrated": + # random_mag*(np.random.random(length).astype(chan.dtype) + 1j*np.random.random(length).astype(chan.dtype)) + + buff[i,:] = ideal_data[i] + elif chan.stream_type == "demodulated": + buff[i, int(length/4):int(3*length/4)] = 1.0 if ideal_data[i] == 0 else ideal_data[i] + else: #Raw + signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) + buff[i, int(length/4):int(length/4)+len(signal)] = signal * (1.0 if ideal_data[i] == 0 else ideal_data[i]) + # import ipdb; ipdb.set_trace(); + if chan.stream_type == "raw": + buff += random_mag*np.random.random((segs, length)) + else: + buff = buff.astype(np.complex128) + random_mag*np.random.random((segs, length))+ 1j*random_mag*np.random.random((segs, length)) + + total += length*segs + wsock.send(struct.pack('n', segs*length*buff.dtype.itemsize) + buff.flatten().tostring()) + counter[chan] += length*segs return total @@ -332,24 +341,20 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) counter = {chan: 0 for chan in self._chan_to_wsocket.keys()} initial_points = {oc: oc.points_taken.value for oc in ocs} for j in range(self._lib.nbr_round_robins): - for i in range(self._lib.nbr_segments): - if self.ideal_data is not None: - #add ideal data for testing - if hasattr(self, 'exp_step') and self.increment_ideal_data: - raise Exception("Cannot use both exp_step and increment_ideal_data") - elif hasattr(self, 'exp_step'): - total_spewed += self.spew_fake_data( - counter, self.ideal_data[self.exp_step][i]) - elif self.increment_ideal_data: - total_spewed += self.spew_fake_data( - counter, self.ideal_data[self.ideal_counter][i]) - else: - total_spewed += self.spew_fake_data( - counter, self.ideal_data[i]) + if self.ideal_data is not None: + #add ideal data for testing + if hasattr(self, 'exp_step') and self.increment_ideal_data: + raise Exception("Cannot use both exp_step and increment_ideal_data") + elif hasattr(self, 'exp_step'): + total_spewed += self.spew_fake_data(counter, self.ideal_data[self.exp_step]) + elif self.increment_ideal_data: + total_spewed += self.spew_fake_data(counter, self.ideal_data[self.ideal_counter]) else: - total_spewed += self.spew_fake_data(counter) + total_spewed += self.spew_fake_data(counter, self.ideal_data) + else: + total_spewed += self.spew_fake_data(counter, [0.0 for i in range(self.number_segments)]) - time.sleep(0.0001) + time.sleep(0.0001) self.ideal_counter += 1 # logger.info("Counter: %s", str(counter)) diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index fc6c10ce..cfd5dafe 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -140,29 +140,28 @@ def add_channel(self, channel): self.channels.append(channel) self._chan_to_buf[channel] = channel.phys_channel - def spew_fake_data(self, counter, ideal_datapoint=0, random_mag=0.1, random_seed=12345): + def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345): """ Generate fake data on the stream. For unittest usage. - ideal_datapoint: mean of the expected signal + ideal_data: array or list giving means of the expected signal for each segment Returns the total number of fake data points, so that we can keep track of how many we expect to receive, when we're doing the test with fake data """ - total = 0 - for chan, wsock in self._chan_to_wsocket.items(): length = int(self.record_length) - signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) - data = np.zeros(length, dtype=np.float32) - data[int(length/4):int(length/4)+len(signal)] = signal * (1.0 if ideal_datapoint == 0 else ideal_datapoint) - data += random_mag*np.random.random(length) - total += length - # logger.info(f"Sending {struct.pack('n', length*np.float32().itemsize)}") - wsock.send(struct.pack('n', length*np.float32().itemsize) + data.tostring()) - counter[chan] += length + buff = np.zeros((self.number_segments, length), dtype=np.float32) + for i in range(self.number_segments): + signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) + buff[i, int(length/4):int(length/4)+len(signal)] = signal * (1.0 if ideal_data[i] == 0 else ideal_data[i]) + + buff += random_mag*np.random.random((self.number_segments, length)) + + wsock.send(struct.pack('n', self.number_segments*length*np.float32().itemsize) + buff.flatten().tostring()) + counter[chan] += length*self.number_segments - return total + return length*self.number_segments*len(self._chan_to_wsocket) def receive_data(self, channel, oc, exit, ready, run): sock = self._chan_to_rsocket[channel] @@ -216,24 +215,24 @@ def wait_for_acquisition(self, dig_run, timeout=5, ocs=None, progressbars=None): initial_points = {oc: oc.points_taken.value for oc in ocs} # print(self.number_averages, self.number_segments) for j in range(self.number_averages): - for i in range(self.number_segments): - if self.ideal_data is not None: - #add ideal data for testing - if hasattr(self, 'exp_step') and self.increment_ideal_data: - raise Exception("Cannot use both exp_step and increment_ideal_data") - elif hasattr(self, 'exp_step'): - total_spewed += self.spew_fake_data( - counter, self.ideal_data[self.exp_step][i]) - elif self.increment_ideal_data: - total_spewed += self.spew_fake_data( - counter, self.ideal_data[self.ideal_counter][i]) - else: - total_spewed += self.spew_fake_data( - counter, self.ideal_data[i]) + # for i in range(self.number_segments): + if self.ideal_data is not None: + #add ideal data for testing + if hasattr(self, 'exp_step') and self.increment_ideal_data: + raise Exception("Cannot use both exp_step and increment_ideal_data") + elif hasattr(self, 'exp_step'): + total_spewed += self.spew_fake_data( + counter, self.ideal_data[self.exp_step]) + elif self.increment_ideal_data: + total_spewed += self.spew_fake_data( + counter, self.ideal_data[self.ideal_counter]) else: - total_spewed += self.spew_fake_data(counter) + total_spewed += self.spew_fake_data( + counter, self.ideal_data) + else: + total_spewed += self.spew_fake_data(counter, [0.0 for i in range(self.number_segments)]) - time.sleep(0.0001) + time.sleep(0.0001) self.ideal_counter += 1 diff --git a/src/auspex/stream.py b/src/auspex/stream.py index 37b2cd1a..bec69d3b 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -489,7 +489,7 @@ def __init__(self, name=None, unit=None): # Shared memory interface self.buffer_lock = mp.Lock() - self.buffer_size = 5000000 + self.buffer_size = 500000 self.buff_idx = Value('i', 0) self.buff_shared_re = RawArray(ctypes.c_double, self.buffer_size) self.buff_shared_im = RawArray(ctypes.c_double, self.buffer_size) From 050b8ba0766f71aae73d0db6359f26a81763d12e Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Fri, 26 Jul 2019 15:59:24 -0400 Subject: [PATCH 028/109] Fixed copy on access of shared buffers. Tests working now. --- src/auspex/filters/elementwise.py | 14 ++++++++------ src/auspex/filters/filter.py | 16 ++++++++-------- src/auspex/instruments/X6.py | 6 ++++-- src/auspex/instruments/alazar.py | 4 ++-- src/auspex/qubit/qubit_exp.py | 2 +- src/auspex/stream.py | 18 +++++++++--------- test/test_alazar.py | 4 ++-- test/test_correlator.py | 18 ++++++++++-------- test/test_pipeline.py | 2 +- 9 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/auspex/filters/elementwise.py b/src/auspex/filters/elementwise.py index bc86fa6f..f21c2867 100644 --- a/src/auspex/filters/elementwise.py +++ b/src/auspex/filters/elementwise.py @@ -92,19 +92,20 @@ def main(self): for stream, messages in msgs_by_stream.items(): for message in messages: message_type = message['type'] - message_data = message['data'] - message_data = message_data if hasattr(message_data, 'size') else np.array([message_data]) + # message_data = message['data'] + # message_data = message_data if hasattr(message_data, 'size') else np.array([message_data]) if message_type == 'event': if message['event_type'] == 'done': streams_done[stream] = True elif message['event_type'] == 'refine': logger.warning("ElementwiseFilter doesn't handle refinement yet!") - self.push_to_all(message) elif message_type == 'data': # Add any old data... - points_per_stream[stream] += len(message_data.flatten()) - stream_data[stream] = np.concatenate((stream_data[stream], message_data.flatten())) - + message_data = stream.pop() + if message_data is not None: + points_per_stream[stream] += len(message_data) + stream_data[stream] = np.concatenate((stream_data[stream], message_data)) + # logger.info(f"{stream.name}: {message_data} now {stream_data[stream]}") # Now process the data with the elementwise operation smallest_length = min([d.size for d in stream_data.values()]) new_data = [d[:smallest_length] for d in stream_data.values()] @@ -123,5 +124,6 @@ def main(self): # If the amount of data processed is equal to the num points in the stream, we are done if np.all([streams_done[stream] for stream in streams]): + self.push_to_all({"type": "event", "event_type": "done", "data": None}) self.done.set() break diff --git a/src/auspex/filters/filter.py b/src/auspex/filters/filter.py index 23530bc9..0437a48e 100644 --- a/src/auspex/filters/filter.py +++ b/src/auspex/filters/filter.py @@ -179,7 +179,7 @@ def push_to_all(self, message): for ost in oc.output_streams: ost.queue.put(message) if message['type'] == 'event' and message["event_type"] == "done": - logger.info(f"Closing out queue {ost.queue}") + logger.debug(f"Closing out queue {ost.queue}") ost.queue.close() def push_resource_usage(self): @@ -225,13 +225,13 @@ def main(self): if message['type'] == 'event': logger.debug('%s "%s" received event with type "%s"', self.__class__.__name__, message_type) - # Propagate along the graph - self.push_to_all(message) - # Check to see if we're done if message['event_type'] == 'done': - logger.info(f"{self} received done message!") + logger.debug(f"{self} received done message!") stream_done = True + else: + # Propagate along the graph + self.push_to_all(message) elif message['type'] == 'data': # if not hasattr(message_data, 'size'): @@ -240,12 +240,12 @@ def main(self): if message_data is not None: logger.debug('%s "%s" received %d points.', self.__class__.__name__, self.filter_name, message_data.size) logger.debug("Now has %d of %d points.", input_stream.points_taken.value, input_stream.num_points()) - stream_points += len(message_data.flatten()) - self.process_data(message_data.flatten()) + stream_points += len(message_data) + self.process_data(message_data) self.processed += message_data.nbytes if stream_done: - logger.info("Dealing with done message!") + self.push_to_all({"type": "event", "event_type": "done", "data": None}) self.done.set() break diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 53dbcca2..cce159f1 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -252,7 +252,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) length = int(self._lib.record_length/32) else: #Raw length = int(self._lib.record_length/4) - buff = np.zeros((segs, length), dtype=np.float32) + buff = np.zeros((segs, length), dtype=chan.dtype) # for chan, wsock in self._chan_to_wsocket.items(): for i in range(segs): if chan.stream_type == "integrated": @@ -270,6 +270,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) buff = buff.astype(np.complex128) + random_mag*np.random.random((segs, length))+ 1j*random_mag*np.random.random((segs, length)) total += length*segs + # logger.info(f"In Spew: {buff.dtype} {chan.dtype} {buff.size}") wsock.send(struct.pack('n', segs*length*buff.dtype.itemsize) + buff.flatten().tostring()) counter[chan] += length*segs @@ -303,6 +304,7 @@ def receive_data(self, channel, oc, exit, ready, run): logger.error("Expected %s bytes, received %s bytes" % (msg_size, len(buf))) return data = np.frombuffer(buf, dtype=channel.dtype) + # logger.info(f"X6 {msg_size} got {len(data)}") total += len(data) oc.push(data) @@ -353,7 +355,7 @@ def wait_for_acquisition(self, dig_run, timeout=15, ocs=None, progressbars=None) total_spewed += self.spew_fake_data(counter, self.ideal_data) else: total_spewed += self.spew_fake_data(counter, [0.0 for i in range(self.number_segments)]) - + # logger.info(f"Spewed {total_spewed}") time.sleep(0.0001) self.ideal_counter += 1 diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index cfd5dafe..a98b6647 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -114,7 +114,7 @@ def data_available(self): return self._lib.data_available() def done(self): - logger.warning(f"Checking alazar doneness: {self.total_received.value} {self.number_segments * self.number_averages * self.record_length}") + # logger.warning(f"Checking alazar doneness: {self.total_received.value} {self.number_segments * self.number_averages * self.record_length}") return self.total_received.value >= (self.number_segments * self.number_averages * self.record_length) def get_socket(self, channel): @@ -191,7 +191,7 @@ def receive_data(self, channel, oc, exit, ready, run): self.total_received.value += len(data) if datetime.datetime.now().timestamp() - last_print > 0.25: last_print = datetime.datetime.now().timestamp() - logger.info(f"Alz: {self.total_received.value}") + # logger.info(f"Alz: {self.total_received.value}") oc.push(data) self.fetch_count.value += 1 diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 50dd0ea8..eb7b5338 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -417,7 +417,7 @@ def init_instruments(self): listener.start() while ready.value < len(self.chan_to_dig): - time.sleep(0.3) + time.sleep(0.1) if self.cw_mode: for awg in self.awgs: diff --git a/src/auspex/stream.py b/src/auspex/stream.py index bec69d3b..b96fcb63 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -563,16 +563,16 @@ def push(self, data): self.queue.put(message) def pop(self): + result = None with self.buffer_lock: idx = self.buff_idx.value - if idx == 0: - return None - result = self.re_np[:idx] - # logger.info(f"out buff_shared: {idx} {result[0:4]}") - if issubclass(self.descriptor.dtype, np.complex): - result = result.astype(np.complex128) + 1.0j*self.im_np[:idx] - # logger.info(f"out buff_shared now: {idx} {result[0:4]}") - self.buff_idx.value = 0 + if idx != 0: + result = self.re_np[:idx] + # logger.info(f"out buff_shared: {idx} {result[0:4]}") + if issubclass(self.descriptor.dtype, np.complex): + result = result.astype(np.complex128) + 1.0j*self.im_np[:idx] + self.buff_idx.value = 0 + result = result.copy() return result def push_event(self, event_type, data=None): @@ -581,7 +581,7 @@ def push_event(self, event_type, data=None): message = {"type": "event", "event_type": event_type, "data": data} self.queue.put(message) if event_type == "done": - logger.info(f"Closing out queue {self}") + logger.debug(f"Closing out queue {self}") self.queue.close() self.closed = True diff --git a/test/test_alazar.py b/test/test_alazar.py index 2aad7582..9f77817b 100644 --- a/test/test_alazar.py +++ b/test/test_alazar.py @@ -84,7 +84,7 @@ def push(self, data): alz.wait_for_acquisition(run, timeout=5, ocs=[oc]) exit.set() - time.sleep(1) + time.sleep(0.1) proc.join(3.0) if proc.is_alive(): proc.terminate() @@ -121,7 +121,7 @@ def test_qubit_experiment(self): exp.set_fake_data(dig_1, np.cos(np.linspace(-np.pi, np.pi, 51))) exp.run_sweeps() data, desc = exp.buffers[0].get_data() - self.assertAlmostEqual(np.abs(data).sum(),459.2,places=0) + self.assertAlmostEqual(np.abs(data).sum(),610.5,places=0) if __name__ == '__main__': unittest.main() diff --git a/test/test_correlator.py b/test/test_correlator.py index 1735d29b..7f68c9d9 100644 --- a/test/test_correlator.py +++ b/test/test_correlator.py @@ -23,8 +23,6 @@ class CorrelatorExperiment(Experiment): # Constants samples = 100 - idx_1 = 0 - idx_2 = 0 # For correlator verification vals = 2.0 + np.linspace(0, 10*np.pi, samples) @@ -35,16 +33,19 @@ def init_streams(self): def run(self): logger.debug("Data taker running (inner loop)") - + np.random.seed(12345) + self.idx_1 = 0 + self.idx_2 = 0 while self.idx_1 < self.samples or self.idx_2 < self.samples: - + # print(self.idx_1, self.idx_2) # Generate random number of samples: - new_1 = np.random.randint(1,5) - new_2 = np.random.randint(1,5) + new_1 = np.random.randint(2,5) + new_2 = np.random.randint(2,5) if self.chan1.points_taken.value < self.chan1.num_points(): if self.chan1.points_taken.value + new_1 > self.chan1.num_points(): new_1 = self.chan1.num_points() - self.chan1.points_taken.value + # logger.info(f"C1 push {self.vals[self.idx_1:self.idx_1+new_1]}") self.chan1.push(self.vals[self.idx_1:self.idx_1+new_1]) self.idx_1 += new_1 if self.chan2.points_taken.value < self.chan2.num_points(): @@ -52,6 +53,7 @@ def run(self): new_2 = self.chan2.num_points() - self.chan2.points_taken.value self.chan2.push(self.vals[self.idx_2:self.idx_2+new_2]) self.idx_2 += new_2 + # logger.info(f"C2 push {self.vals[self.idx_2:self.idx_2+new_2]}") time.sleep(0.002) logger.debug("Idx_1: %d, Idx_2: %d", self.idx_1, self.idx_2) @@ -69,10 +71,10 @@ def test_correlator(self): exp.set_graph(edges) exp.run_sweeps() - time.sleep(0.1) + time.sleep(0.01) corr_data = buff.output_data expected_data = exp.vals*exp.vals - self.assertAlmostEqual(np.sum(corr_data), np.sum(expected_data), places=1) + self.assertAlmostEqual(np.sum(corr_data), np.sum(expected_data), places=0) if __name__ == '__main__': diff --git a/test/test_pipeline.py b/test/test_pipeline.py index cb7bde5e..53a81fa5 100644 --- a/test/test_pipeline.py +++ b/test/test_pipeline.py @@ -163,7 +163,7 @@ def test_multiple_streamselectors_per_qubit(self): self.assertTrue(len(exp.buffers)==2) - def test_run_direct(self): + def test_run_pipeline(self): cl.clear() q1 = cl.new_qubit("q1") aps1 = cl.new_APS2("BBNAPS1", address="192.168.5.102") From 14193cfb92b70eea3102c086170b494f5aa5cbcf Mon Sep 17 00:00:00 2001 From: William Kalfus Date: Mon, 29 Jul 2019 17:25:22 -0400 Subject: [PATCH 029/109] Updated APS3 driver --- src/auspex/instruments/aps3.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index e5659e1a..3f8d4037 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -140,6 +140,7 @@ class AMC599(object): PORT = 0xbb4e # TCPIP port (BBN!) ser = None + ref = '' def __init__(self, debug=False): self.connected = False @@ -470,6 +471,30 @@ def serial_get_nco_frequency(self): sleep(0.01) return ftw * 5e9 + + def serial_set_reference(self, ref): + ''' + Sets the SOF200 PLL reference to either the front panel or the FPGA. + Parameters: + ref (str): Either "REF IN" or "FPGA" + ''' + if ref == 'REF IN': + self.ref = ref + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rs fp\n', 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + elif ref == 'FPGA': + self.ref = ref + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rs fpga\n', 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + else: + logger.info('Error: unrecognized reference input "' + ref + '".') + + def serial_get_reference(self): + return self.ref ##################################################################### @@ -822,4 +847,11 @@ def dac_nco_frequency(self): @dac_nco_frequency.setter def dac_nco_frequency(self, value): self.board.serial_set_nco_frequency(value) - + + @property + def dac_pll_reference(self): + return self.board.serial_get_reference() + + @dac_pll_reference.setter + def dac_pll_reference(self, value): + self.board.serial_set_reference(value) From e8bb7929b2405b3138686664521b65c1d3b82482 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Tue, 30 Jul 2019 14:50:21 -0400 Subject: [PATCH 030/109] Real is not imaginary. --- src/auspex/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/stream.py b/src/auspex/stream.py index b96fcb63..41494c3a 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -555,7 +555,7 @@ def push(self, data): # logger.info(f"in buff_shared_re:{self.buff_idx.value} {re[0:4]}") self.re_np[start:start+re.size] = re if issubclass(self.descriptor.dtype, np.complex): - im = np.real(data).flatten() + im = np.imag(data).flatten() # logger.info(f"in buff_shared_im:{self.buff_idx.value} {im[0:4]}") self.im_np[start:start+im.size] = im message = {"type": "data", "data": None} From cee385779e9570882868e12ecf0ea07ccc091cac Mon Sep 17 00:00:00 2001 From: "Guilhem J. A. Ribeill" Date: Mon, 17 Jun 2019 23:29:02 -0400 Subject: [PATCH 031/109] Initial commit with AMC599 skeleton. --- src/auspex/instruments/aps3.py | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/auspex/instruments/aps3.py diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py new file mode 100644 index 00000000..ba3c6b17 --- /dev/null +++ b/src/auspex/instruments/aps3.py @@ -0,0 +1,119 @@ +# Copyright 2016 Raytheon BBN Technologies +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +from .instrument import Instrument, MetaInstrument +from auspex.log import logger +import auspex.config as config +from time import sleep +import numpy as np +import socket +import collections +from struct import pack, iter_unpack + +class AMC599(object): + """Base class for simple register manipulations of AMC599 board. + """ + + PORT = 0xbb4e # TCPIP port (BBN!) + + def __init__(self): + self.connected = False + + def __del__(self): + self.disconnect() + + def _check_connected(self): + if not self.connected: + raise IOError("AMC599 Board not connected!") + + def connect(self, ip_addr="192.168.2.200"): + self.ip_addr = ip_addr + self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + self.socket.connect((self.ip_addr, self.PORT)) + self.connected = True + + def disconnect(self): + if self.connected: + self.connected = False + self.socket.close() + + def send_bytes(self, data): + if isinstance(data, collections.Iterable): + return self.socket.sendall(b''.join([pack("!I", _) for _ in data])) + else: + return self.socket.sendall(pack("!I", data)) + + def recv_bytes(self, size): + resp = self.socket.recv(size) + while True: + if len(ans) >= size: + break + ans += self.socket.recv(8) + data = [x[0] for x in iter_unpack("!I", ans)] + return data + + def write_memory(self, addr, data): + self._check_connected() + max_ct = 0xfffc #max writeable block length (TODO: check if still true) + cmd = 0x80000000 #write to RAM command + datagrams_written = 0 + init_addr = addr + idx = 0 + + if isinstance(data, int): + data = [data] + elif isinstance(data, list): + if not all([isinstance(v, int) for v in data]): + raise ValueError("Data must be a list of integers.") + else: + raise ValueError("Data must be an integer or a list of integers.") + + while (len(data) - idx > 0): + ct_left = len(data) - idx + ct = ct_left if (ct_left < max_ct) else max_ct + datagram = [cmd + ct, addr] + datagram.extend(data[idx:idx+ct]) + self.send_bytes(datagram) + datagrams_written += 1 + idx += ct + addr += ct*4 + #read back data and check amount of bytes written makes sense + #the ethernet core echoes back what we wrote + resp = self.recv_bytes(2 * 4 * datagrams_written) + addr = init_addr + for ct in range(datagrams_written): + if ct+1 == datagrams_written: + words_written = len(data) - ((datagrams_written-1) * max_ct) + else: + words_written = max_ct + logger.debug("Wrote {} words in {} datagrams: {}", words_written, + datagrams_written, + [hex(x) for x in resp]) + assert (results[2*ct] == 0x80800000 + words_written) + assert (results[2*ct+1] == addr) + addr += 4 * words_written + + def read_memory(self, addr, num_words): + self._check_connected() + datagram = [0x10000000 + num_words, addr] + self.send_bytes(datagram) + resp_header = self.recv_bytes(2 * 4) #4 bytes per word... + return self.recv_bytes(4 * num_words) + + def read_memory_hex(self, addr, num_words): + data = self.read_memory(addr, num_words) + for d in data: + print(hex(d)) + +#TODO: UPDATE! +CSR_AXI_ADDR = 0x44b400000 +CSR_CONTROL_OFFSET = 0x00 +GPIO1_OFFSET = 0x01C +GPIO2_OFFSET = 0x020 +CSR_TRIGGER_INTERVAL_OFFSET = 0x14 +SDRAM_AXI_ADDR = 0x80000000 From bc0c25078e20f7d54e5e6d3d19144f966ead80e2 Mon Sep 17 00:00:00 2001 From: gribeill Date: Thu, 20 Jun 2019 18:32:45 -0400 Subject: [PATCH 032/109] Add CSR control register locations from spec doc. --- src/auspex/instruments/aps3.py | 63 +++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index ba3c6b17..663f770f 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -57,7 +57,7 @@ def recv_bytes(self, size): data = [x[0] for x in iter_unpack("!I", ans)] return data - def write_memory(self, addr, data): + def write_memory(self, addr, data, offset = 0x0): self._check_connected() max_ct = 0xfffc #max writeable block length (TODO: check if still true) cmd = 0x80000000 #write to RAM command @@ -110,10 +110,57 @@ def read_memory_hex(self, addr, num_words): for d in data: print(hex(d)) -#TODO: UPDATE! -CSR_AXI_ADDR = 0x44b400000 -CSR_CONTROL_OFFSET = 0x00 -GPIO1_OFFSET = 0x01C -GPIO2_OFFSET = 0x020 -CSR_TRIGGER_INTERVAL_OFFSET = 0x14 -SDRAM_AXI_ADDR = 0x80000000 +##################################################################### + +#APS3 AMC599 Control and Status Register offsets +#Add to CSR_AXI_ADDR_BASE to get to correct memory location +#Registers are read/write unless otherwise noted +#Current as of 6/20/19 + +CSR_AXI_ADDR_BASE = 0x44b4000 + +CSR_CACHE_CONTROL = 0x0010 #Cache control register +CSR_SEQ_CONTROL = 0x0024 #Sequencer control register + +CSR_WFA_OFFSET = 0x0014 #Waveform A Offset +CSR_WFB_OFFSET = 0x0018 #Waveform B offset +CSR_SEQ_OFFSET = 0x001C #Sequence data offset + +CSR_TRIG_WORD = 0x002C #Trigger word register, Read Only +CSR_TRIG_INTERVAL = 0x0030 #trigger interval register + +CSR_UPTIME_SEC = 0x0050 #uptime in seconds, read only +CSR_UPTIME_NS = 0x0054 #uptime in nanoseconds, read only +CSR_FPGA_REV = 0x0058 #FPGA revision, read only +CSR_GIT_SHA1 = 0x0060 #git SHA1 hash, read only +CSR_BUILD_TSTAMP = 0x0064 #build timestamp, read only + +CSR_CMAT_R0 = 0x0068 #correction matrix row 0 +CSR_CMAT_R1 = 0x006C #correction matrix row 1 + +#### NOT CONNECTED TO ANY LOGIC -- USE FOR VALUE STORAGE ############ +CSR_A_AMPLITUDE = 0x0070 #Channel A amplitude +CSR_B_AMPLITUDE = 0x0074 #Channel B amplitude +CSR_MIX_AMP = 0x0078 #Mixer amplitude correction +CSR_MIX_PHASE = 0x007C #Mixer phase skew correction +CSR_WFA_LEN = 0x0080 #channel A waveform length +CSR_WFB_LEN = 0x0084 #channel B waveform length +CSR_WF_MOD_FREQ = 0x0088 #waveform modulation frequency +###################################################################### + +CSR_WFA_DELAY = 0x008C #Channel A delay +CSR_WFB_DELAY = 0x0080 #channel B delay + +CSR_BD_CONTROL = 0x00A0 #board control register +CSR_FPGA_ID = 0x00B4 #FPGA ID (read-only) + +CSR_DATA1_IO = 0x00B8 #Data 1 IO register +CSR_DATA2_IO = 0x00BC #Data 2 IO register + +CSR_MARKER_DELAY = 0x00C0 #Marker delay + +CSR_IPV4 = 0x00C4 #IPv4 address register + +##################################################################### + +DRAM_AXI_BASE = 0x80000000 From edc410748ec650fc63e2305e6d3b664af1b0092e Mon Sep 17 00:00:00 2001 From: gribeill Date: Fri, 21 Jun 2019 16:18:07 -0400 Subject: [PATCH 033/109] Continue adding functionality to APS3 class. --- src/auspex/instruments/aps3.py | 209 ++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 3 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 663f770f..145ef7ad 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,7 +6,8 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -from .instrument import Instrument, MetaInstrument +from .instrument import Instrument, MetaInstrument, is_valid_ipv4 +from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config from time import sleep @@ -15,6 +16,9 @@ import collections from struct import pack, iter_unpack +U32 = 0xFFFFFFFF #mask for 32-bit unsigned int +U16 = 0xFFFF + class AMC599(object): """Base class for simple register manipulations of AMC599 board. """ @@ -55,9 +59,9 @@ def recv_bytes(self, size): break ans += self.socket.recv(8) data = [x[0] for x in iter_unpack("!I", ans)] - return data + return data if len(data)>1 else data[0] - def write_memory(self, addr, data, offset = 0x0): + def write_memory(self, addr, data): self._check_connected() max_ct = 0xfffc #max writeable block length (TODO: check if still true) cmd = 0x80000000 #write to RAM command @@ -73,6 +77,8 @@ def write_memory(self, addr, data, offset = 0x0): else: raise ValueError("Data must be an integer or a list of integers.") + data = [x & U32 for x in data] #make sure everything is 32 bits + while (len(data) - idx > 0): ct_left = len(data) - idx ct = ct_left if (ct_left < max_ct) else max_ct @@ -135,6 +141,7 @@ def read_memory_hex(self, addr, num_words): CSR_GIT_SHA1 = 0x0060 #git SHA1 hash, read only CSR_BUILD_TSTAMP = 0x0064 #build timestamp, read only +CSR_CORR_OFFSET = 0x0024 CSR_CMAT_R0 = 0x0068 #correction matrix row 0 CSR_CMAT_R1 = 0x006C #correction matrix row 1 @@ -164,3 +171,199 @@ def read_memory_hex(self, addr, num_words): ##################################################################### DRAM_AXI_BASE = 0x80000000 + +##################################################################### + +def check_bits(value, shift, mask=0b1): + return (((value & U32) >> shift) & mask) + +def set_bits(value, shift, x, mask=0b1): + return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) + +class APS3(Instrument, metaclass=MakeSettersGetters): + + def __init__(self, resource_name=None, name="Unlabled APS3"): + self.name = name + self.resource_name = resource_name + + self.board = AMC599() + + def connect(self, resource_name=None): + if resource_name is None and self.resource_name is None: + raise ValueError("Must supply a resource name!") + elif resource_name is not None: + self.resource_name = resource_name + if is_valid_ipv4(self.resource_name): + self.board.connect(ip_addr = self.resource_name) + + def disconnect(self): + if self.board.connected: + self.board.disconnect() + + def write_csr(self, offset, data): + self.board.write_memory(CSR_AXI_ADDR_BASE + offset, data) + + def read_csr(self, offset, num_words = 1): + return self.board.read_memory(CSR_AXI_ADDR_BASE + offset, num_words) + + def write_dram(self, offset, data): + self.board.write_memory(DRAM_AXI_BASE + offset, data) + + def read_dram(self, offset, num_words = 1): + return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) + + ####### CACHE CONTROL REGSITER ############################################# + + @property + def cache_controller(self): + """Get value of cache controller + False: Cache controller in reset. + True: Cache controller taken out of reset. + """ + return bool(self.read_csr(CSR_CACHE_CONTROL) & 0x1) + @cache_controller.setter + def cache_controller(self, value): + self.write_csr(CSR_CACHE_CONTROL, int(value)) + + ####### WAVEFORM OFFSET REGISTER ########################################### + + @property + def waveform_offsets(self): + """Get waveform A and B offset register values. These are used as + the DMA source address. + """ + return [self.read_csr(CSR_WFA_OFFSET), + self.read_csr(CSR_WFB_OFFSET)] + @property + def waveform_offsets(self, offsets): + """Set waveform A and B offsets, passed as list [A offset, B offset]. + Set one offset to None to not change its value. + """ + if offsets[0] is not None: + self.write_csr(CSR_WFA_OFFSET, offsets[0]) + if offsets[1] is not None: + self.write_csr(CSR_WFB_OFFSET, offsets[1]) + + ####### SEQUENCER CONTROL REGISTER ######################################### + + @property + def sequencer_reset(self): + """Sequencer reset: + False: Sequencer, trigger input, modulator, SATA, VRAMs, Debug Streams + disabled. + True: Sequencer logic taken out of reset. + """ + reg = self.read_csr(CSR_SEQ_CONTROL) + return bool(check_bits(csr, 0)) + @sequencer_reset.setter + def sequencer_reset(self, reset): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 0, int(reset))) + + @property + def trigger_source(self): + trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) + trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", + 0b10: "SOFTWARE", 0b11 "MESSAGE"} + return trigger_map[trig_val] + @trigger_source.setter + def trigger_source(self, value): + trig_map = {"EXTERNAL": 0b00, "INTERNAL": 0b01, + "SOFTWARE": 0b10, "MESSAGE": 0b11} + if value.upper() not in trig_map.keys(): + raise ValueError(f"Unknown trigger mode. Must be one of {trig_map.keys()}") + if value.upper == "MESSAGE": + raise NotImplementedError("APS3 does not yet support message triggers.") + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 1, trig_map[value.upper()], 0b11)) + + def soft_trigger(self): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 3, 0b1)) + + @property + def trigger_enable(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 4)) + @trigger_enable.setter + def trigger_enable(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, 0b1)) + + @property + def bypass_modulator(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 5)) + @bypass_modulator.setter + def bypass_modulator(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, 0b1)) + + @property + def bypass_nco(self): + return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 6)) + @bypass_nco.setter + def bypass_nco(self, value): + reg = self.read_csr(CSR_SEQ_CONTROL) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, 0b1)) + + ####### CORRECTION CONTROL REGISTER ######################################## + @property + def correction_control(self): + reg = self.read_csr(CSR_CORR_OFFSET) + return ((reg >> 16) & U16, reg & U16) #returns (I, Q) + @correction_control.setter + def correction_control(self, value): + packed_value = ((value[0] & U16) << 16) | (value[1] & U16) + self.write_csr(CSR_CORR_OFFSET, packed_value) + + ####### TRIGGER INTERVAL ################################################### + @property + def trigger_interval(self): + return self.read_csr(CSR_TRIG_INTERVAL) + @trigger_interval.setter + def trigger_interval(self, value) + return self.write_csr(CSR_TRIG_INTERVAL, value & U32) + + ####### UPTIME REGISTERS ################################################### + @property + def uptime_seconds(self): + return self.read_csr(CSR_UPTIME_SEC) + + @property + def uptime_nanoseconds(self): + return self.read_csr(CSR_UPTIME_NS) + + ####### BUILD INFO REGISTERS ############################################### + def get_firmware_info(self): + fpga_rev = self.read_csr(CSR_FPGA_REV) + fpga_id = self.read_csr(CSR_FPGA_ID) + git_sha1 = self.read_csr(CSR_GIT_SHA1) + build_tstamp = self.read_csr(CSR_BUILD_TSTAMP) + + fpga_build_minor = check_bits(fpga_rev, 0, 0xFF) + fpga_build_major = check_bits(fpga_rev, 8, 0xFF) + commit_history = check_bits(fpga_rev, 16, 0x7FF) + build_clean = "dirty" if check_bits(fpga_rev, 27, 0xF) else "clean" + + return {"FPGA ID": fpga_id, + "FPGA REV": f"{fpga_build_major}.{fpga_build_minor} - {commit_history} - {build_clean}", + "GIT SHA1": hex(git_sha1), + "DATE": hex(build_tstamp)[2:]} + + ####### CORRECTION MATRIX ################################################## + def get_correction_matrix(self): + row0 = self.read_csr(CSR_CMAT_R0) + row1 = self.read_csr(CSR_CMAT_R1) + + r00 = (row0 >> 16) & U16 + r01 = row0 & U16 + r10 = (row1 >> 16) & U16 + r11 = row0 & U16 + return np.array([[r00, r01], [r10, r11]], dtype=np.uint16) + + def set_correction_matrix(self, matrix): + row0 = ((matix[0, 0] & U16) << 16) | (matrix[0, 1] & U16) + row1 = ((matix[1, 0] & U16) << 16) | (matrix[1, 1] & U16) + self.write_csr(CSR_CMAT_R0, row0) + self.write_csr(CSR_CMAT_R1, row1) + + ####### BOARD_CONTROL ################################################## From e4ba4559d7be282954bcf6cd2e64bec7a9500915 Mon Sep 17 00:00:00 2001 From: gribeill Date: Fri, 21 Jun 2019 16:18:56 -0400 Subject: [PATCH 034/109] Continue adding functionality to APS3 class. --- src/auspex/instruments/aps3.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 145ef7ad..420e7475 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -366,4 +366,10 @@ def set_correction_matrix(self, matrix): self.write_csr(CSR_CMAT_R0, row0) self.write_csr(CSR_CMAT_R1, row1) - ####### BOARD_CONTROL ################################################## + ####### BOARD_CONTROL ###################################################### + @property + def microblaze(self): + return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) + @microbalze.setter(self, value) + reg = self.read_csr(CSR_BD_CONTROL) + self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) From a082eb952c7dec67c1def7f57d0d92a94dbe4952 Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 25 Jun 2019 11:45:49 -0400 Subject: [PATCH 035/109] Update APS3 instrument with all CSR setters/getters. --- src/auspex/instruments/aps3.py | 39 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 420e7475..fd3ec377 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,6 +6,8 @@ # # http://www.apache.org/licenses/LICENSE-2.0 +__all__ = ['AMC599', 'APS3'] + from .instrument import Instrument, MetaInstrument, is_valid_ipv4 from .bbn import MakeSettersGetters from auspex.log import logger @@ -180,13 +182,13 @@ def check_bits(value, shift, mask=0b1): def set_bits(value, shift, x, mask=0b1): return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) -class APS3(Instrument, metaclass=MakeSettersGetters): +class APS3(Instrument): def __init__(self, resource_name=None, name="Unlabled APS3"): self.name = name self.resource_name = resource_name - self.board = AMC599() + super().__init__() def connect(self, resource_name=None): if resource_name is None and self.resource_name is None: @@ -264,7 +266,7 @@ def sequencer_reset(self, reset): def trigger_source(self): trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", - 0b10: "SOFTWARE", 0b11 "MESSAGE"} + 0b10: "SOFTWARE", 0b11: "MESSAGE"} return trigger_map[trig_val] @trigger_source.setter def trigger_source(self, value): @@ -320,7 +322,7 @@ def correction_control(self, value): def trigger_interval(self): return self.read_csr(CSR_TRIG_INTERVAL) @trigger_interval.setter - def trigger_interval(self, value) + def trigger_interval(self, value): return self.write_csr(CSR_TRIG_INTERVAL, value & U32) ####### UPTIME REGISTERS ################################################### @@ -366,10 +368,37 @@ def set_correction_matrix(self, matrix): self.write_csr(CSR_CMAT_R0, row0) self.write_csr(CSR_CMAT_R1, row1) + def correction_bypass(self): + row0 = 0x20000000 + row1 = 0x00002000 + self.write_csr(CSR_CMAT_R0, row0) + self.write_csr(CSR_CMAT_R0, row1) + self.write_csr(CSR_CORR_OFFSET, 0x0) + + ####### BOARD_CONTROL ###################################################### @property def microblaze(self): return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) - @microbalze.setter(self, value) + @microblaze.setter + def microblaze(self, value): reg = self.read_csr(CSR_BD_CONTROL) self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) + + @property + def dac_output_mux(self): + return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 4)) + @dac_output_mux.setter + def dac_output_mux(self, value): + reg = self.read_csr(CSR_BD_CONTROL) + self.write_csr(CSR_BD_CONTROL, set_bits(reg, 4, int(value))) + + ####### MARKER_DELAY ####################################################### + @property + def marker_delay(self): + return self.read_csr(CSR_MARKER_DELAY) + @marker_delay.setter + def marker_delay(self, value): + if value > U16: + logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") + self.write_csr(CSR_MARKER_DELAY, value & U16) From 15e7da152966d79d86f48e2ec61c7fc30f613c9c Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 2 Jul 2019 23:09:40 -0400 Subject: [PATCH 036/109] Add metaprogramming for bit-field commands --- src/auspex/instruments/aps3.py | 228 ++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 35 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index fd3ec377..b1732f5a 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,29 +6,134 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -__all__ = ['AMC599', 'APS3'] +#__all__ = ['AMC599', 'APS3'] -from .instrument import Instrument, MetaInstrument, is_valid_ipv4 +from .instrument import Instrument, is_valid_ipv4, Command from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config from time import sleep import numpy as np import socket +from unittest.mock import Mock import collections from struct import pack, iter_unpack U32 = 0xFFFFFFFF #mask for 32-bit unsigned int U16 = 0xFFFF +def check_bits(value, shift, mask=0b1): + """Helper function to get a bit-slice of a 32-bit value. + Returns (value >> shift) & mask.""" + return (((value & U32) >> shift) & mask) + +def set_bits(value, shift, x, mask=0b1): + """Set bits in a 32-bit value given an offset and a mask. """ + return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) + +class BitFieldCommand(Command): + """An instrument command that sets/gets a value from a register. + See also the Command object in .instrument.py + + Args: + None. + Kwargs: + register: Control register address. (required) + shift: 0-indexed bit position. (required) + mask: Size of bit field -- i.e. use 0b111 if setting a 3-bit value + defaults to 0b1 or is inferred from a value map. + """ + + def parse(self): + super().parse() + + for a in ('register', 'shift', 'mask'): + if a in self.kwargs: + setattr(self, a, self.kwargs.pop(a)) + else: + setattr(self, a, None) + + if self.register is None: + raise ValueError("Must specify a destination or source register.") + if self.shift is None: + raise ValueError("Must specify a bit shift for register command.") + + if self.mask is None: + if self.value_map is not None: + max_bits = max((v.bit_length() for v in self.value_map.items())) + self.mask = 2**max_bits - 1 + else: + self.mask = 0b1 + + def convert_set(self, set_value_python): + if self.python_to_instr is None: + return int(set_value_python) + else: + return self.python_to_instr[set_value_python] + + def convert_get(self, get_value_instrument): + if self.python_to_instr is None: + return bool(set_value_python) + else: + return self.instr_to_python[get_value_instrument] + +def add_command_bitfield(instr, name, cmd): + """Helper function to create a new BitFieldCommand when parsing an instrument.""" + + new_cmd = BitFieldCommand(*cmd.args, **cmd.kwargs) + new_cmd.parse() + + def fget(self, **kwargs): + val = check_bits(self.read_register(new_cmd.register), new_cmd.shift, new_cmd.mask) + if new_cmd.get_delay is not None: + sleep(new_cmd.get_delay) + return new_cmd.convert_get(val) + + def fset(self, val, **kwargs): + if new_cmd.value_range is not None: + if (val < new_cmd.value_range[0]) or (val > new_cmd.value_range[1]): + err_msg = "The value {} is outside of the allowable range {} specified for instrument '{}'.".format(val, new_cmd.value_range, self.name) + raise ValueError(err_msg) + + if new_cmd.allowed_values is not None: + if not val in new_cmd.allowed_values: + err_msg = "The value {} is not in the allowable set of values specified for instrument '{}': {}".format(val, self.name, new_cmd.allowed_values) + raise ValueError(err_msg) + + start_val = self.read_register(new_cmd.register) + new_val = set_bits(start_val, self.shift, new_cmd.convert_set(val), self.mask) + self.write_register(new_cmd.register, new_val) + if new_cmd.set_delay is not None: + sleep(new_cmd.set_delay) + + setattr(instr, name, property(fget, fset, None, new_cmd.doc)) + + return new_cmd + +class MakeBitFieldParams(MakeSettersGetters): + def __init__(self, name, bases, dct): + super().__init__(self, name, bases, dct) + + if 'write_register' not in dct or 'read_register' not in dct: + raise TypeError("An instrument using BitFieldParams must implement" + + " `read_register` and `write_register` functions.") + + for k, v in dct.items(): + if isinstance(v, BitFieldCommand): + logger.debug("Adding %s command", k) + nv = add_command_SCPI(self, k, v) + class AMC599(object): """Base class for simple register manipulations of AMC599 board. """ PORT = 0xbb4e # TCPIP port (BBN!) - def __init__(self): + def __init__(self, debug=False): self.connected = False + self.debug = debug + if self.debug: + self.debug_memory = {} def __del__(self): self.disconnect() @@ -39,9 +144,10 @@ def _check_connected(self): def connect(self, ip_addr="192.168.2.200"): self.ip_addr = ip_addr - self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) - self.socket.connect((self.ip_addr, self.PORT)) - self.connected = True + if not self.debug: + self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) + self.socket.connect((self.ip_addr, self.PORT)) + self.connected = True def disconnect(self): if self.connected: @@ -57,20 +163,13 @@ def send_bytes(self, data): def recv_bytes(self, size): resp = self.socket.recv(size) while True: - if len(ans) >= size: + if len(resp) >= size: break - ans += self.socket.recv(8) - data = [x[0] for x in iter_unpack("!I", ans)] + resp += self.socket.recv(8) + data = [x[0] for x in iter_unpack("!I", resp)] return data if len(data)>1 else data[0] def write_memory(self, addr, data): - self._check_connected() - max_ct = 0xfffc #max writeable block length (TODO: check if still true) - cmd = 0x80000000 #write to RAM command - datagrams_written = 0 - init_addr = addr - idx = 0 - if isinstance(data, int): data = [data] elif isinstance(data, list): @@ -79,7 +178,20 @@ def write_memory(self, addr, data): else: raise ValueError("Data must be an integer or a list of integers.") - data = [x & U32 for x in data] #make sure everything is 32 bits + if self.debug: + print("Wrote starting at memory address 0x%x:" % addr) + for d in data: + print(hex(d)) + for off, d in enumerate(data): + self.debug_memory[addr + off*0x4] = d + return + + self._check_connected() + max_ct = 0xfffc #max writeable block length (TODO: check if still true) + cmd = 0x80000000 #write to RAM command + datagrams_written = 0 + init_addr = addr + idx = 0 while (len(data) - idx > 0): ct_left = len(data) - idx @@ -102,22 +214,24 @@ def write_memory(self, addr, data): logger.debug("Wrote {} words in {} datagrams: {}", words_written, datagrams_written, [hex(x) for x in resp]) - assert (results[2*ct] == 0x80800000 + words_written) - assert (results[2*ct+1] == addr) + assert (resp[2*ct] == 0x80800000 + words_written) + assert (resp[2*ct+1] == addr) addr += 4 * words_written def read_memory(self, addr, num_words): + + if self.debug: + response = [] + for x in range(num_words): + response.append(self.debug_memory.get(addr+0x4*x, 0x0)) + return response[0] if num_words == 1 else response + self._check_connected() datagram = [0x10000000 + num_words, addr] self.send_bytes(datagram) resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) - def read_memory_hex(self, addr, num_words): - data = self.read_memory(addr, num_words) - for d in data: - print(hex(d)) - ##################################################################### #APS3 AMC599 Control and Status Register offsets @@ -125,7 +239,7 @@ def read_memory_hex(self, addr, num_words): #Registers are read/write unless otherwise noted #Current as of 6/20/19 -CSR_AXI_ADDR_BASE = 0x44b4000 +CSR_AXI_ADDR_BASE = 0x44b40000 CSR_CACHE_CONTROL = 0x0010 #Cache control register CSR_SEQ_CONTROL = 0x0024 #Sequencer control register @@ -176,14 +290,12 @@ def read_memory_hex(self, addr, num_words): ##################################################################### -def check_bits(value, shift, mask=0b1): - return (((value & U32) >> shift) & mask) -def set_bits(value, shift, x, mask=0b1): - return ((value & U32) & ~(mask << shift)) | ((x & U32) << shift) class APS3(Instrument): + instrument_type = "AWG" + def __init__(self, resource_name=None, name="Unlabled APS3"): self.name = name self.resource_name = resource_name @@ -202,10 +314,11 @@ def disconnect(self): if self.board.connected: self.board.disconnect() - def write_csr(self, offset, data): + def write_register(self, offset, data): + logger.info(f"Setting CSR: {hex(offset)} to: {hex(data)}") self.board.write_memory(CSR_AXI_ADDR_BASE + offset, data) - def read_csr(self, offset, num_words = 1): + def read_register(self, offset, num_words = 1): return self.board.read_memory(CSR_AXI_ADDR_BASE + offset, num_words) def write_dram(self, offset, data): @@ -289,7 +402,7 @@ def trigger_enable(self): @trigger_enable.setter def trigger_enable(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, int(value))) @property def bypass_modulator(self): @@ -297,7 +410,7 @@ def bypass_modulator(self): @bypass_modulator.setter def bypass_modulator(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, int(value))) @property def bypass_nco(self): @@ -305,7 +418,7 @@ def bypass_nco(self): @bypass_nco.setter def bypass_nco(self, value): reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, 0b1)) + self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, int(value))) ####### CORRECTION CONTROL REGISTER ######################################## @property @@ -346,7 +459,7 @@ def get_firmware_info(self): commit_history = check_bits(fpga_rev, 16, 0x7FF) build_clean = "dirty" if check_bits(fpga_rev, 27, 0xF) else "clean" - return {"FPGA ID": fpga_id, + return {"FPGA ID": hex(fpga_id), "FPGA REV": f"{fpga_build_major}.{fpga_build_minor} - {commit_history} - {build_clean}", "GIT SHA1": hex(git_sha1), "DATE": hex(build_tstamp)[2:]} @@ -402,3 +515,48 @@ def marker_delay(self, value): if value > U16: logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") self.write_csr(CSR_MARKER_DELAY, value & U16) + + ###### DRAM OFFSET REGISTERS ############################################### + @property + def SEQ_OFFSET(self): + return (self.read_csr(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) + + @property + def WFA_OFFSET(self): + return (self.read_csr(CSR_WFA_OFFSET) - DRAM_AXI_BASE) + + @property + def WFB_OFFSET(self): + return (self.read_csr(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + + ###### UTILITIES ########################################################### + def run(self): + logger.info("Taking cache controller out of reset...") + self.cache_controller = True + sleep(0.01) + logger.info("Taking sequencer out of reset...") + self.sequencer_reset = True + sleep(0.01) + logger.info("Enabling trigger...") + self.trigger_enable = True + sleep(0.01) + + def stop(self): + logger.info("Resetting cache controller...") + self.cache_controller = False + sleep(0.01) + logger.info("Resetting sequencer...") + self.sequencer_reset = False + + def write_sequence(self, sequence): + packed_seq = [] + for instr in sequence: + packed_seq.append(instr & U32) + packed_seq.append(instr >> 32) + + self.cache_controller = False + sleep(0.01) + self.write_dram(self.SEQ_OFFSET, packed_seq) + logger.info(f"Wrote {len(packed_seq)} words to sequence memory.") + sleep(0.01) + self.cache_controller = True From bb147568530231ed7b21847d98aa064222fbb9fe Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 2 Jul 2019 23:48:02 -0400 Subject: [PATCH 037/109] Use metaprogramming to set bit fields in APS3 driver. --- src/auspex/instruments/aps3.py | 217 ++++++++------------------- src/auspex/instruments/instrument.py | 2 +- 2 files changed, 63 insertions(+), 156 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index b1732f5a..7e08dcd7 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -47,7 +47,7 @@ class BitFieldCommand(Command): def parse(self): super().parse() - for a in ('register', 'shift', 'mask'): + for a in ('register', 'shift', 'mask', 'readonly'): if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: @@ -58,9 +58,12 @@ def parse(self): if self.shift is None: raise ValueError("Must specify a bit shift for register command.") + if self.readonly is None: + self.readonly = False + if self.mask is None: if self.value_map is not None: - max_bits = max((v.bit_length() for v in self.value_map.items())) + max_bits = max((v.bit_length() for v in self.value_map.values())) self.mask = 2**max_bits - 1 else: self.mask = 0b1 @@ -73,7 +76,10 @@ def convert_set(self, set_value_python): def convert_get(self, get_value_instrument): if self.python_to_instr is None: - return bool(set_value_python) + if self.mask == 0b1: + return bool(get_value_instrument) + else: + return get_value_instrument else: return self.instr_to_python[get_value_instrument] @@ -101,18 +107,20 @@ def fset(self, val, **kwargs): raise ValueError(err_msg) start_val = self.read_register(new_cmd.register) - new_val = set_bits(start_val, self.shift, new_cmd.convert_set(val), self.mask) + new_val = set_bits(start_val, new_cmd.shift, new_cmd.convert_set(val), new_cmd.mask) self.write_register(new_cmd.register, new_val) if new_cmd.set_delay is not None: sleep(new_cmd.set_delay) - setattr(instr, name, property(fget, fset, None, new_cmd.doc)) + setattr(instr, name, property(fget, None if new_cmd.readonly else fset, None, new_cmd.doc)) + setattr(instr, "set_"+name, fset) + setattr(instr, "get_"+name, fget) return new_cmd class MakeBitFieldParams(MakeSettersGetters): def __init__(self, name, bases, dct): - super().__init__(self, name, bases, dct) + super().__init__(name, bases, dct) if 'write_register' not in dct or 'read_register' not in dct: raise TypeError("An instrument using BitFieldParams must implement" + @@ -121,7 +129,7 @@ def __init__(self, name, bases, dct): for k, v in dct.items(): if isinstance(v, BitFieldCommand): logger.debug("Adding %s command", k) - nv = add_command_SCPI(self, k, v) + nv = add_command_bitfield(self, k, v) class AMC599(object): """Base class for simple register manipulations of AMC599 board. @@ -179,9 +187,6 @@ def write_memory(self, addr, data): raise ValueError("Data must be an integer or a list of integers.") if self.debug: - print("Wrote starting at memory address 0x%x:" % addr) - for d in data: - print(hex(d)) for off, d in enumerate(data): self.debug_memory[addr + off*0x4] = d return @@ -290,16 +295,14 @@ def read_memory(self, addr, num_words): ##################################################################### - - -class APS3(Instrument): +class APS3(Instrument, metaclass=MakeBitFieldParams): instrument_type = "AWG" - def __init__(self, resource_name=None, name="Unlabled APS3"): + def __init__(self, resource_name=None, name="Unlabled APS3", debug=False): self.name = name self.resource_name = resource_name - self.board = AMC599() + self.board = AMC599(debug=debug) super().__init__() def connect(self, resource_name=None): @@ -329,130 +332,54 @@ def read_dram(self, offset, num_words = 1): ####### CACHE CONTROL REGSITER ############################################# - @property - def cache_controller(self): - """Get value of cache controller - False: Cache controller in reset. - True: Cache controller taken out of reset. - """ - return bool(self.read_csr(CSR_CACHE_CONTROL) & 0x1) - @cache_controller.setter - def cache_controller(self, value): - self.write_csr(CSR_CACHE_CONTROL, int(value)) - - ####### WAVEFORM OFFSET REGISTER ########################################### + cache_controller = BitFieldCommand(register=CSR_CACHE_CONTROL, shift=0, + doc="""Cache controller enable bit.""") - @property - def waveform_offsets(self): - """Get waveform A and B offset register values. These are used as - the DMA source address. - """ - return [self.read_csr(CSR_WFA_OFFSET), - self.read_csr(CSR_WFB_OFFSET)] - @property - def waveform_offsets(self, offsets): - """Set waveform A and B offsets, passed as list [A offset, B offset]. - Set one offset to None to not change its value. - """ - if offsets[0] is not None: - self.write_csr(CSR_WFA_OFFSET, offsets[0]) - if offsets[1] is not None: - self.write_csr(CSR_WFB_OFFSET, offsets[1]) + ####### WAVEFORM OFFSET REGISTERS ########################################## + + wf_offset_A = BitFieldCommand(register=CSR_WFA_OFFSET, shift=0, mask=U32) + wf_offset_B = BitFieldCommand(register=CSR_WFB_OFFSET, shift=0, mask=U32) ####### SEQUENCER CONTROL REGISTER ######################################### - @property - def sequencer_reset(self): - """Sequencer reset: - False: Sequencer, trigger input, modulator, SATA, VRAMs, Debug Streams - disabled. - True: Sequencer logic taken out of reset. - """ - reg = self.read_csr(CSR_SEQ_CONTROL) - return bool(check_bits(csr, 0)) - @sequencer_reset.setter - def sequencer_reset(self, reset): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 0, int(reset))) + sequencer_enable = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=0, + doc="""Sequencer, trigger input, modulator, SATA, VRAM, debug stream enable bit.""") - @property - def trigger_source(self): - trig_val = check_bits(self.read_csr(CSR_SEQ_CONTROL), 1, 0b11) - trigger_map = {0b00: "EXTERNAL", 0b01: "INTERNAL", - 0b10: "SOFTWARE", 0b11: "MESSAGE"} - return trigger_map[trig_val] - @trigger_source.setter - def trigger_source(self, value): - trig_map = {"EXTERNAL": 0b00, "INTERNAL": 0b01, - "SOFTWARE": 0b10, "MESSAGE": 0b11} - if value.upper() not in trig_map.keys(): - raise ValueError(f"Unknown trigger mode. Must be one of {trig_map.keys()}") - if value.upper == "MESSAGE": - raise NotImplementedError("APS3 does not yet support message triggers.") - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 1, trig_map[value.upper()], 0b11)) - - def soft_trigger(self): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 3, 0b1)) + trigger_source = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=1, + value_map={"EXTERNAL": 0b00, "INTERNAL": 0b01, "SOFTWARE": 0b10, "MESSAGE": 0b11}) - @property - def trigger_enable(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 4)) - @trigger_enable.setter - def trigger_enable(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 4, int(value))) - - @property - def bypass_modulator(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 5)) - @bypass_modulator.setter - def bypass_modulator(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 5, int(value))) - - @property - def bypass_nco(self): - return bool(check_bits(self.read_csr(CSR_SEQ_CONTROL), 6)) - @bypass_nco.setter - def bypass_nco(self, value): - reg = self.read_csr(CSR_SEQ_CONTROL) - self.write_csr(CSR_SEQ_CONTROL, set_bits(reg, 6, int(value))) + soft_trigger = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=3) + trigger_enable = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=4) + bypass_modulator = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=5) + bypass_nco = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=6) ####### CORRECTION CONTROL REGISTER ######################################## @property def correction_control(self): - reg = self.read_csr(CSR_CORR_OFFSET) + reg = self.read_register(CSR_CORR_OFFSET) return ((reg >> 16) & U16, reg & U16) #returns (I, Q) @correction_control.setter def correction_control(self, value): packed_value = ((value[0] & U16) << 16) | (value[1] & U16) - self.write_csr(CSR_CORR_OFFSET, packed_value) + self.write_register(CSR_CORR_OFFSET, packed_value) ####### TRIGGER INTERVAL ################################################### - @property - def trigger_interval(self): - return self.read_csr(CSR_TRIG_INTERVAL) - @trigger_interval.setter - def trigger_interval(self, value): - return self.write_csr(CSR_TRIG_INTERVAL, value & U32) + + trigger_interval = BitFieldCommand(register=CSR_TRIG_INTERVAL, shift=0, mask=U32) ####### UPTIME REGISTERS ################################################### - @property - def uptime_seconds(self): - return self.read_csr(CSR_UPTIME_SEC) - @property - def uptime_nanoseconds(self): - return self.read_csr(CSR_UPTIME_NS) + uptime_seconds = BitFieldCommand(register=CSR_UPTIME_SEC, shift=0, + mask=U32, readonly=True) + uptime_nanoseconds = BitFieldCommand(register=CSR_UPTIME_NS, shift=0, + mask=U32, readonly=True) ####### BUILD INFO REGISTERS ############################################### def get_firmware_info(self): - fpga_rev = self.read_csr(CSR_FPGA_REV) - fpga_id = self.read_csr(CSR_FPGA_ID) - git_sha1 = self.read_csr(CSR_GIT_SHA1) - build_tstamp = self.read_csr(CSR_BUILD_TSTAMP) + fpga_rev = self.read_register(CSR_FPGA_REV) + fpga_id = self.read_register(CSR_FPGA_ID) + git_sha1 = self.read_register(CSR_GIT_SHA1) + build_tstamp = self.read_register(CSR_BUILD_TSTAMP) fpga_build_minor = check_bits(fpga_rev, 0, 0xFF) fpga_build_major = check_bits(fpga_rev, 8, 0xFF) @@ -466,9 +393,8 @@ def get_firmware_info(self): ####### CORRECTION MATRIX ################################################## def get_correction_matrix(self): - row0 = self.read_csr(CSR_CMAT_R0) - row1 = self.read_csr(CSR_CMAT_R1) - + row0 = self.read_register(CSR_CMAT_R0) + row1 = self.read_register(CSR_CMAT_R1) r00 = (row0 >> 16) & U16 r01 = row0 & U16 r10 = (row1 >> 16) & U16 @@ -478,56 +404,37 @@ def get_correction_matrix(self): def set_correction_matrix(self, matrix): row0 = ((matix[0, 0] & U16) << 16) | (matrix[0, 1] & U16) row1 = ((matix[1, 0] & U16) << 16) | (matrix[1, 1] & U16) - self.write_csr(CSR_CMAT_R0, row0) - self.write_csr(CSR_CMAT_R1, row1) + self.write_register(CSR_CMAT_R0, row0) + self.write_register(CSR_CMAT_R1, row1) def correction_bypass(self): row0 = 0x20000000 row1 = 0x00002000 - self.write_csr(CSR_CMAT_R0, row0) - self.write_csr(CSR_CMAT_R0, row1) - self.write_csr(CSR_CORR_OFFSET, 0x0) + self.write_register(CSR_CMAT_R0, row0) + self.write_register(CSR_CMAT_R0, row1) + self.write_register(CSR_CORR_OFFSET, 0x0) ####### BOARD_CONTROL ###################################################### - @property - def microblaze(self): - return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 1)) - @microblaze.setter - def microblaze(self, value): - reg = self.read_csr(CSR_BD_CONTROL) - self.write_csr(CSR_BD_CONTROL, set_bits(reg, 1, int(value))) - @property - def dac_output_mux(self): - return bool(check_bits(self.read_csr(CSR_BD_CONTROL), 4)) - @dac_output_mux.setter - def dac_output_mux(self, value): - reg = self.read_csr(CSR_BD_CONTROL) - self.write_csr(CSR_BD_CONTROL, set_bits(reg, 4, int(value))) + microblaze_enable = BitFieldCommand(register=CSR_BD_CONTROL, shift=1) + dac_output_mux = BitFieldCommand(register=CSR_BD_CONTROL, shift=4, + value_map={"SOF200": 0x0, "APS": 0x1}) ####### MARKER_DELAY ####################################################### - @property - def marker_delay(self): - return self.read_csr(CSR_MARKER_DELAY) - @marker_delay.setter - def marker_delay(self, value): - if value > U16: - logger.warning(f"Marker delay {value} is greater than maximum allowed value of {U16} {hex(U16)}!") - self.write_csr(CSR_MARKER_DELAY, value & U16) + + marker_delay = BitFieldCommand(register=CSR_MARKER_DELAY, shift=0, + mask=U16, allowed_values=[0,U16]) ###### DRAM OFFSET REGISTERS ############################################### - @property def SEQ_OFFSET(self): - return (self.read_csr(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_SEQ_OFFSET) - DRAM_AXI_BASE) - @property def WFA_OFFSET(self): - return (self.read_csr(CSR_WFA_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_WFA_OFFSET) - DRAM_AXI_BASE) - @property def WFB_OFFSET(self): - return (self.read_csr(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + return (self.read_register(CSR_WFB_OFFSET) - DRAM_AXI_BASE) ###### UTILITIES ########################################################### def run(self): @@ -535,7 +442,7 @@ def run(self): self.cache_controller = True sleep(0.01) logger.info("Taking sequencer out of reset...") - self.sequencer_reset = True + self.sequencer_enable = True sleep(0.01) logger.info("Enabling trigger...") self.trigger_enable = True @@ -546,7 +453,7 @@ def stop(self): self.cache_controller = False sleep(0.01) logger.info("Resetting sequencer...") - self.sequencer_reset = False + self.sequencer_enable = False def write_sequence(self, sequence): packed_seq = [] diff --git a/src/auspex/instruments/instrument.py b/src/auspex/instruments/instrument.py index 09f4a4a7..c81b0e2b 100644 --- a/src/auspex/instruments/instrument.py +++ b/src/auspex/instruments/instrument.py @@ -36,7 +36,7 @@ def __init__(self, *args, **kwargs): def parse(self): for a in ['aliases', 'set_delay', 'get_delay', 'value_map', 'value_range', - 'allowed_values']: + 'allowed_values', 'doc']: if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: From 95f061bf87b34b74161ca0f957abf0f8d4e8cf24 Mon Sep 17 00:00:00 2001 From: gribeill Date: Wed, 3 Jul 2019 09:28:58 -0400 Subject: [PATCH 038/109] Update property docstrings correctly --- src/auspex/instruments/aps3.py | 2 ++ src/auspex/instruments/bbn.py | 5 +++++ src/auspex/instruments/instrument.py | 10 ++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 7e08dcd7..8628cdc7 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -114,7 +114,9 @@ def fset(self, val, **kwargs): setattr(instr, name, property(fget, None if new_cmd.readonly else fset, None, new_cmd.doc)) setattr(instr, "set_"+name, fset) + setattr(getattr(instr, "set_"+name), "__doc__", new_cmd.doc) setattr(instr, "get_"+name, fget) + setattr(getattr(instr, "set_"+name), "__doc__", new_cmd.doc) return new_cmd diff --git a/src/auspex/instruments/bbn.py b/src/auspex/instruments/bbn.py index ea91c5ae..818363e0 100644 --- a/src/auspex/instruments/bbn.py +++ b/src/auspex/instruments/bbn.py @@ -167,6 +167,11 @@ def __init__(self, name, bases, dct): logger.debug("Adding '%s' command to APS", k) setattr(self, 'set_'+k, v.fset) setattr(self, 'get_'+k, v.fget) + try: + setattr(getattr(self, 'set_'+k), "__doc__", v.__doc__) + setattr(getattr(self, 'get_'+k), "__doc__", v.__doc__) + except: + pass class APS(Instrument, metaclass=MakeSettersGetters): """BBN APSI or DACII""" diff --git a/src/auspex/instruments/instrument.py b/src/auspex/instruments/instrument.py index c81b0e2b..64287cb8 100644 --- a/src/auspex/instruments/instrument.py +++ b/src/auspex/instruments/instrument.py @@ -36,18 +36,22 @@ def __init__(self, *args, **kwargs): def parse(self): for a in ['aliases', 'set_delay', 'get_delay', 'value_map', 'value_range', - 'allowed_values', 'doc']: + 'allowed_values']: if a in self.kwargs: setattr(self, a, self.kwargs.pop(a)) else: setattr(self, a, None) # Default to None + if 'doc' in self.kwargs: + self.doc = self.kwargs.pop('doc') + else: + self.doc = "" + if self.value_range is not None: self.value_range = (min(self.value_range), max(self.value_range)) self.python_to_instr = None # Dict StringCommandmapping from python values to instrument values self.instr_to_python = None # Dict mapping from instrument values to python values - self.doc = "" if self.value_map is not None: self.python_to_instr = self.value_map @@ -350,9 +354,11 @@ def fset(self, val, **kwargs): # In this case we can't create a property given additional arguments if new_cmd.get_string: setattr(instr, "get_" + name, fget) + setattr(getattr(instr, "get_" + name), "__doc__", new_cmd.doc) if new_cmd.set_string: setattr(instr, "set_" + name, fset) + setattr(getattr(instr, "set_" + name), "__doc__", new_cmd.doc) return new_cmd From af1d563483ec39ac39d2c6b95ea806cae7b850f8 Mon Sep 17 00:00:00 2001 From: gribeill Date: Tue, 16 Jul 2019 14:00:55 -0400 Subject: [PATCH 039/109] Update APS3 driver to match transmitter syntax, fix a few typos. --- src/auspex/instruments/aps3.py | 53 +++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 8628cdc7..0be2d750 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -6,7 +6,7 @@ # # http://www.apache.org/licenses/LICENSE-2.0 -#__all__ = ['AMC599', 'APS3'] +__all__ = ['APS3'] from .instrument import Instrument, is_valid_ipv4, Command from .bbn import MakeSettersGetters @@ -367,7 +367,15 @@ def correction_control(self, value): ####### TRIGGER INTERVAL ################################################### - trigger_interval = BitFieldCommand(register=CSR_TRIG_INTERVAL, shift=0, mask=U32) + @property + def trigger_interval(self): + """Gets/sets the trigger interval in seconds, based on a 300 MHz clock.""" + return int(self.read_register(CSR_TRIG_INTERVAL)) / 300e6 + @trigger_interval.setter + def trigger_interval(self, value): + trig_bits = int(value * 300e6) + assert (trig_bits >= 0 and trig_bits < U32), "Trigger interval out of range!" + self.write_register(CSR_TRIG_INTERVAL, trig_bits) ####### UPTIME REGISTERS ################################################### @@ -419,14 +427,26 @@ def correction_bypass(self): ####### BOARD_CONTROL ###################################################### - microblaze_enable = BitFieldCommand(register=CSR_BD_CONTROL, shift=1) + microblaze_reset = BitFieldCommand(register=CSR_BD_CONTROL, shift=1, + doc="True resets Microblaze softcore. False takes Microblaze out of reset.") dac_output_mux = BitFieldCommand(register=CSR_BD_CONTROL, shift=4, - value_map={"SOF200": 0x0, "APS": 0x1}) + value_map={"SOF200": 0x0, "APS": 0x1}, + doc="Select SOF200 test output or APS sequencer output from DAC.") + trigger_output_select = BitFieldCommand(register=CSR_BD_CONTROL, shift=5, + doc="""True: select trigger to be output on front panel. + False: select marker to be output on front pane.""") ####### MARKER_DELAY ####################################################### - marker_delay = BitFieldCommand(register=CSR_MARKER_DELAY, shift=0, - mask=U16, allowed_values=[0,U16]) + @property + def marker_delay(self): + """Gets/sets the marker delay in seconds, based on a 300 MHz clock.""" + return (1 + int(self.read_register(CSR_TRIG_INTERVAL))) / 300e6 + @marker_delay.setter + def marker_delay(self, value): + trig_bits = int(value * 300e6) + 1 + assert (trig_bits >= 0 and trig_bits < U16), "Marker delay out of range!" + self.write_register(CSR_TRIG_INTERVAL, trig_bits) ###### DRAM OFFSET REGISTERS ############################################### def SEQ_OFFSET(self): @@ -438,6 +458,13 @@ def WFA_OFFSET(self): def WFB_OFFSET(self): return (self.read_register(CSR_WFB_OFFSET) - DRAM_AXI_BASE) + ####### GPIO REGISTERS ##################################################### + + GPIO0_high = BitFieldCommand(register=CSR_BD_CONTROL, shift=16, mask=U16) + GPIO0_low = BitFieldCommand(register=CSR_BD_CONTROL, shift=8, mask=0xFF) + GPIO1 = BitFieldCommand(register=CSR_DATA1_IO, shift=0, mask=U32) + GPIO2 = BitFieldCommand(register=CSR_DATA2_IO, shift=0, mask=U32) + ###### UTILITIES ########################################################### def run(self): logger.info("Taking cache controller out of reset...") @@ -457,7 +484,7 @@ def stop(self): logger.info("Resetting sequencer...") self.sequencer_enable = False - def write_sequence(self, sequence): + def load_sequence(self, sequence): packed_seq = [] for instr in sequence: packed_seq.append(instr & U32) @@ -465,7 +492,17 @@ def write_sequence(self, sequence): self.cache_controller = False sleep(0.01) - self.write_dram(self.SEQ_OFFSET, packed_seq) + self.write_dram(self.SEQ_OFFSET(), packed_seq) logger.info(f"Wrote {len(packed_seq)} words to sequence memory.") sleep(0.01) self.cache_controller = True + + def load_waveforms(self, wfA, wfB): + raise NotImplementedError("Billy implement me too!") + + @property + def load_sequence_file(self, seq_file): + raise NotImplementedError("Billy please implement me!") + @load_sequence_file.setter + def load_sequence_file(self, seq_file): + raise NotImplementedError("Implement me!") From 3c3ac3ba9974df43faf5793366fed43db60dab6c Mon Sep 17 00:00:00 2001 From: William Kalfus Date: Wed, 24 Jul 2019 14:11:21 -0400 Subject: [PATCH 040/109] APS3 features with serial --- src/auspex/instruments/agilent.py | 21 +- src/auspex/instruments/aps3.py | 359 ++++++++++++++++++++++++++++-- 2 files changed, 356 insertions(+), 24 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index fe3460e5..8462337c 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -840,6 +840,10 @@ class AgilentE9010A(SCPIInstrument): num_sweep_points = FloatCommand(scpi_string=":SWEep:POINTs") resolution_bandwidth = FloatCommand(scpi_string=":BANDwidth") sweep_time = FloatCommand(scpi_string=":SWEep:TIME") + averaging_count = IntCommand(scpi_string=':AVER:COUN') + + marker1_amplitude = FloatCommand(scpi_string=':CALC:MARK1:Y') + marker1_position = FloatCommand(scpi_string=':CALC:MARK1:X') mode = StringCommand(scpi_string=":INSTrument", allowed_values=["SA", "BASIC", "PULSE", "PNOISE"]) @@ -855,9 +859,9 @@ def connect(self, resource_name=None, interface_type=None): if resource_name is not None: self.resource_name = resource_name #If we only have an IP address then tack on the raw socket port to the VISA resource string - if is_valid_ipv4(resource_name): - resource_name += "::5025::SOCKET" - super(AgilentE9010A, self).connect(resource_name=resource_name, interface_type=interface_type) + if is_valid_ipv4(self.resource_name): + self.resource_name += "::5025::SOCKET" + super(AgilentE9010A, self).connect(resource_name=self.resource_name, interface_type=interface_type) self.interface._resource.read_termination = u"\n" self.interface._resource.write_termination = u"\n" self.interface._resource.timeout = 3000 #seem to have trouble timing out on first query sometimes @@ -882,3 +886,14 @@ def get_pn_trace(self, num=3): def restart_sweep(self): """ Aborts current sweep and restarts. """ self.interface.write(":INITiate:RESTart") + + def peak_search(self, marker=1): + self.interface.write(':CALC:MARK{:d}:MAX'.format(marker)) + + def marker_to_center(self, marker=1): + self.interface.write(':CALC:MARK{:d}:CENT'.format(marker)) + + def clear_averaging(self): + self.interface.write(':AVER:CLE') + + diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 0be2d750..e5659e1a 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -8,7 +8,7 @@ __all__ = ['APS3'] -from .instrument import Instrument, is_valid_ipv4, Command +from .instrument import Instrument, is_valid_ipv4, Command, MetaInstrument from .bbn import MakeSettersGetters from auspex.log import logger import auspex.config as config @@ -18,6 +18,7 @@ from unittest.mock import Mock import collections from struct import pack, iter_unpack +import serial U32 = 0xFFFFFFFF #mask for 32-bit unsigned int U16 = 0xFFFF @@ -134,10 +135,11 @@ def __init__(self, name, bases, dct): nv = add_command_bitfield(self, k, v) class AMC599(object): - """Base class for simple register manipulations of AMC599 board. + """Base class for simple register manipulations of AMC599 board and DAC. """ PORT = 0xbb4e # TCPIP port (BBN!) + ser = None def __init__(self, debug=False): self.connected = False @@ -152,17 +154,19 @@ def _check_connected(self): if not self.connected: raise IOError("AMC599 Board not connected!") - def connect(self, ip_addr="192.168.2.200"): - self.ip_addr = ip_addr + def connect(self, resource=("192.168.2.200", "COM1")): + self.ip_addr = resource[0] if not self.debug: self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) self.socket.connect((self.ip_addr, self.PORT)) + self.ser = serial.Serial(resource[1], 115200) self.connected = True - + def disconnect(self): if self.connected: self.connected = False self.socket.close() + self.ser.close() def send_bytes(self, data): if isinstance(data, collections.Iterable): @@ -238,6 +242,234 @@ def read_memory(self, addr, num_words): self.send_bytes(datagram) resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) + + def serial_read_dac0(self, addr): + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rd d0 {0:#x}\n'.format(addr), 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + resp = self.ser.readline().decode() + start_index = len('Read value = ') + end_index = resp.find('@') + return int(resp[start_index:end_index], 16) + + def serial_write_dac0(self, addr, val): + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('wd d0 {0:#x} {1:#x}\n'.format(addr, val), 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + return self.ser.readline() # Echo back the "wrote xx to xx" line + + def serial_configure_JESD(self): + # Configure the JESD interface properly + logger.info(self.serial_write_dac0(0x300, 0x00)) # disable all links + sleep(0.01) + logger.info(self.serial_write_dac0(0x475, 0x09)) # soft reset DAC0 deframer + sleep(0.01) + logger.info(self.serial_write_dac0(0x110, 0x81)) # set interpolation to 2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x456, 0x01)) # set M=2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x459, 0x21)) # set S=2 + sleep(0.01) + logger.info(self.serial_write_dac0(0x477, 0x00)) # disable ILS_MODE for DAC0 + sleep(0.01) + logger.info(self.serial_write_dac0(0x475, 0x01)) # bring DAC0 deframer out of reset + sleep(0.01) + logger.info(self.serial_write_dac0(0x300, 0x01)) # enable all links + sleep(0.01) + + def serial_set_switch_mode(self, mode): + ''' + Sets DAC output switch mode to one of NRZ, Mix-Mode, or RZ. + Parameters: + mode (string): Switch mode, one of "NRZ", "MIX", or "RZ" + ''' + if mode == 'NRZ': + code = 0x00 + elif mode == 'MIX': + code = 0x01 + elif mode == 'RZ': + code = 0x02 + else: + raise Exception('DAC switch mode "' + mode + '" not recognized.') + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x152, code)) + + def serial_get_switch_mode(self): + ''' + Reads DAC output switch mode as one of NRZ, Mix-Mode, or RZ. + Parameters: + mode (string): Switch mode, one of "NRZ", "MIX", or "RZ" + ''' + if self.ser is None: + logger.info('Fake read mix-mode.') + return 'MIX' + + code = self.serial_read_dac0(0x152) & 0x03 + if code == 0x00: + return 'NRZ' + if code == 0x01: + return 'MIX' + if code == 0x02: + return 'RZ' + + raise Exception('Unrecognized DAC switch mode ' + code + '.') + + def serial_set_analog_full_scale_current(self, current): + ''' + Sets DAC full-scale current, rounding to nearest LSB of current register. + Parameters: + current (float): Full-scale current in mA + + Returns: + (float) actual programmed current in mA + ''' + if current < 8 or current > 40: + raise Exception('DAC full-scale current must be between 8 mA and 40 mA.') + + # From AD9164 datasheet: + # IOUTFS = 32 mA × (ANA_FULL_SCALE_CURRENT[9:0]/1023) + 8 mA + reg_value = int(1023 * (current - 8) / 32) + + if self.ser is None: + logger.info('{:#x}'.format(reg_value & 0x3)) + logger.info('{:#x}'.format((reg_value >> 2) & 0xFF)) + else: + logger.info(self.serial_write_dac0(0x041, reg_value & 0x3)) + sleep(0.01) + logger.info(self.serial_write_dac0(0x042, (reg_value >> 2) & 0xFF)) + sleep(0.01) + + return 32 * (reg_value / 1023) + 8 + + def serial_get_analog_full_scale_current(self): + ''' + Reads programmed full-scale current. + Returns: + Full-scale current in mA + ''' + if self.ser is None: + return 0 + + LSbits = self.serial_read_dac0(0x041) & 0x03 + MSbits = self.serial_read_dac0(0x042) & 0xFF + reg_value = (MSbits << 2) & LSbits + return 32 * (reg_value / 1023) + 8 + + def serial_set_nco_enable(self, en): + ''' + Enables the DAC NCO. + Parameters: + en (bool): Enables the NCO if True, disables it if False + FIR85 (bool): Enables the FIR85 NCO filter if True, disables it if False + ''' + # Configure NCO_EN (Bit 6) = 0b1 + # Set the reserved bits (Bit 5 and Bit 3) to 0b0 + if self.ser is None: + logger.info('Fake read 0x00.') + code = 0x00 + else: + code = self.serial_read_dac0(0x111) + + if en: + code |= (1 << 6) + else: + code &= ~(1 << 6) + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x111, code)) + + sleep(0.1) + + def serial_get_nco_enable(self): + ''' + Checks whether the DAC NCO is enabled. + Returns: + True if DAC NCO is enabled, otherwise False + ''' + if self.ser is None: + logger.info('Fake reported DAC NCO enabled.') + return True + + return (self.serial_read_dac0(0x111) & (1 << 6)) != 0 + + def serial_set_FIR85_enable(self, FIR85): + ''' + Enables the DAC NCO FIR85 filter. + Parameters: + FIR85 (bool): Enables the FIR85 NCO filter if True, disables it if False + ''' + if self.ser is None: + logger.info('Fake read 0x00.') + code = 0x00 + else: + code = self.serial_read_dac0(0x111) + + if FIR85: + code |= (1 << 0) + else: + code &= ~(1 << 0) + + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(code)) + else: + logger.info(self.serial_write_dac0(0x111, code)) + + sleep(0.1) + + def serial_get_FIR85_enable(self): + ''' + Checks whether the DAC NCO FIR85 filter is enabled. + Returns: + True if DAC NCO FIR85 filter is enabled, otherwise False + ''' + if self.ser is None: + logger.info('Fake reported DAC NCO FIR85 enabled.') + return True + + return (self.serial_read_dac0(0x111) & (1 << 0)) != 0 + + def serial_set_nco_frequency(self, f): + ''' + Writes the given frequency, assuming not in NCO-only mode. + Follows procedure in Table 44 of AD9164 datasheet. + ''' + logger.info('Setting frequency to {}...'.format(f)) + + # Configure DC_TEST_EN bit: 0b0 = NCO operation with data interface + logger.info(self.serial_write_dac0(0x150, 0x00)) + sleep(0.01) + + # Ensure the frequency tuning word write request is low. + logger.info(self.serial_write_dac0(0x113, 0x00)) + sleep(0.01) + + # Write FTW. + ftw = [(int((f/5e9)*(1 << 48)) >> x) & 0xFF for x in range(0, 48, 8)] + for index, b in enumerate(ftw): + logger.info(self.serial_write_dac0(0x114 + index, b)) + sleep(0.01) + + # Load the FTW to the NCO. + logger.info(self.serial_write_dac0(0x113, 0x01)) + sleep(0.1) + + def serial_get_nco_frequency(self): + ''' + Reads the current NCO frequency, assuming not in NCO-only mode. + ''' + ftw = 0 + for index, shift in enumerate(range(0, 48, 8)): + ftw |= self.serial_read_dac0(0x114 + index) << shift + sleep(0.01) + + return ftw * 5e9 ##################################################################### @@ -300,11 +532,13 @@ def read_memory(self, addr, num_words): class APS3(Instrument, metaclass=MakeBitFieldParams): instrument_type = "AWG" + serial_port = '' - def __init__(self, resource_name=None, name="Unlabled APS3", debug=False): + def __init__(self, resource_name=None, name="Unlabeled APS3", debug=False, serial_port=''): self.name = name self.resource_name = resource_name self.board = AMC599(debug=debug) + self.serial_port = serial_port super().__init__() def connect(self, resource_name=None): @@ -312,8 +546,8 @@ def connect(self, resource_name=None): raise ValueError("Must supply a resource name!") elif resource_name is not None: self.resource_name = resource_name - if is_valid_ipv4(self.resource_name): - self.board.connect(ip_addr = self.resource_name) + if is_valid_ipv4(self.resource_name[0]): + self.board.connect(resource = self.resource_name) def disconnect(self): if self.board.connected: @@ -333,7 +567,7 @@ def read_dram(self, offset, num_words = 1): return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) ####### CACHE CONTROL REGSITER ############################################# - + cache_controller = BitFieldCommand(register=CSR_CACHE_CONTROL, shift=0, doc="""Cache controller enable bit.""") @@ -369,11 +603,11 @@ def correction_control(self, value): @property def trigger_interval(self): - """Gets/sets the trigger interval in seconds, based on a 300 MHz clock.""" - return int(self.read_register(CSR_TRIG_INTERVAL)) / 300e6 + """Gets/sets the trigger interval in seconds, based on a 312.5 MHz clock.""" + return int(self.read_register(CSR_TRIG_INTERVAL)) / 312.5e6 @trigger_interval.setter def trigger_interval(self, value): - trig_bits = int(value * 300e6) + trig_bits = int(value * 312.5e6) assert (trig_bits >= 0 and trig_bits < U32), "Trigger interval out of range!" self.write_register(CSR_TRIG_INTERVAL, trig_bits) @@ -441,12 +675,12 @@ def correction_bypass(self): @property def marker_delay(self): """Gets/sets the marker delay in seconds, based on a 300 MHz clock.""" - return (1 + int(self.read_register(CSR_TRIG_INTERVAL))) / 300e6 + return (1 + int(self.read_register(CSR_MARKER_DELAY))) / 312.5e6 @marker_delay.setter def marker_delay(self, value): - trig_bits = int(value * 300e6) + 1 + trig_bits = int(value * 312.5e6) + 1 assert (trig_bits >= 0 and trig_bits < U16), "Marker delay out of range!" - self.write_register(CSR_TRIG_INTERVAL, trig_bits) + self.write_register(CSR_MARKER_DELAY, trig_bits) ###### DRAM OFFSET REGISTERS ############################################### def SEQ_OFFSET(self): @@ -467,6 +701,9 @@ def WFB_OFFSET(self): ###### UTILITIES ########################################################### def run(self): + logger.info("Configuring JESD...") + self.board.serial_configure_JESD() + sleep(0.01) logger.info("Taking cache controller out of reset...") self.cache_controller = True sleep(0.01) @@ -484,6 +721,10 @@ def stop(self): logger.info("Resetting sequencer...") self.sequencer_enable = False + ###### SEQUENCE LOADING #################################################### + + sequence_filename = '' + def load_sequence(self, sequence): packed_seq = [] for instr in sequence: @@ -498,11 +739,87 @@ def load_sequence(self, sequence): self.cache_controller = True def load_waveforms(self, wfA, wfB): - raise NotImplementedError("Billy implement me too!") + self.write_dram(self.WFA_OFFSET(), wfA) + self.write_dram(self.WFB_OFFSET(), wfB) - @property def load_sequence_file(self, seq_file): - raise NotImplementedError("Billy please implement me!") - @load_sequence_file.setter - def load_sequence_file(self, seq_file): - raise NotImplementedError("Implement me!") + self.sequence_filename = seq_file + with open(seq_file, 'rb') as file: + instrument_type = file.read(4) + if instrument_type != b'APS3': + raise ValueError('Sequence file not designated for APS3; header for ' + str(instrument_type) + ' found.') + + version = np.frombuffer(file.read(4), dtype=np.float32)[0] + min_firmware_version = np.frombuffer(file.read(4), dtype=np.float32)[0] + num_channels = int(np.frombuffer(file.read(2), dtype=np.uint16)[0]) + + if num_channels != 2: + raise ValueError('Unexpected number of channels, sequence file reports ' + str(num_channels) + ' channels.') + + instructions_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) + instructions = [int(x) for x in np.frombuffer(file.read(instructions_size*8), dtype=np.uint64)] + + load_sequence(instructions) + + data = [] + for chan in range(num_channels): + data_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) + data.append(np.frombuffer(file.read(data_size*8), dtype=np.uint64)) + + load_waveforms(data[0], data[1]) + + def serial_check_alive(self): + return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 + + @property + def sequence_file(self): + return self.sequence_filename + + @property + def sequence_file(self): + return self.sequence_filename + + @sequence_file.setter + def sequence_file(self, value): + self.load_sequence_file(value) + + @property + def dac_switch_mode(self): + return self.board.serial_get_switch_mode() + + @dac_switch_mode.setter + def dac_switch_mode(self, value): + self.board.serial_set_switch_mode(value) + + @property + def dac_full_scale_current(self): + return self.board.serial_get_analog_full_scale_current() + + @dac_full_scale_current.setter + def dac_full_scale_current(self, value): + self.board.serial_set_analog_full_scale_current(value) + + @property + def dac_nco_enable(self): + return self.board.serial_get_nco_enable() + + @dac_nco_enable.setter + def dac_nco_enable(self, value): + self.board.serial_set_nco_enable(value) + + @property + def dac_FIR85_enable(self): + return self.board.serial_get_FIR85_enable() + + @dac_FIR85_enable.setter + def dac_FIR85_enable(self, value): + self.board.serial_set_FIR85_enable(value) + + @property + def dac_nco_frequency(self): + return self.board.serial_get_nco_frequency() + + @dac_nco_frequency.setter + def dac_nco_frequency(self, value): + self.board.serial_set_nco_frequency(value) + From 3373873b78d4bfdc87336045d5c788b9e65184fd Mon Sep 17 00:00:00 2001 From: William Kalfus Date: Mon, 29 Jul 2019 17:25:22 -0400 Subject: [PATCH 041/109] Updated APS3 driver --- src/auspex/instruments/aps3.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index e5659e1a..3f8d4037 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -140,6 +140,7 @@ class AMC599(object): PORT = 0xbb4e # TCPIP port (BBN!) ser = None + ref = '' def __init__(self, debug=False): self.connected = False @@ -470,6 +471,30 @@ def serial_get_nco_frequency(self): sleep(0.01) return ftw * 5e9 + + def serial_set_reference(self, ref): + ''' + Sets the SOF200 PLL reference to either the front panel or the FPGA. + Parameters: + ref (str): Either "REF IN" or "FPGA" + ''' + if ref == 'REF IN': + self.ref = ref + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rs fp\n', 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + elif ref == 'FPGA': + self.ref = ref + self.ser.reset_output_buffer() + self.ser.reset_input_buffer() + self.ser.write(bytearray('rs fpga\n', 'ascii')) + self.ser.readline() # Throw out the echo line from the terminal interface + else: + logger.info('Error: unrecognized reference input "' + ref + '".') + + def serial_get_reference(self): + return self.ref ##################################################################### @@ -822,4 +847,11 @@ def dac_nco_frequency(self): @dac_nco_frequency.setter def dac_nco_frequency(self, value): self.board.serial_set_nco_frequency(value) - + + @property + def dac_pll_reference(self): + return self.board.serial_get_reference() + + @dac_pll_reference.setter + def dac_pll_reference(self, value): + self.board.serial_set_reference(value) From 1a70f075982bdc5449922e74e09ea8305c0d01d3 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Wed, 31 Jul 2019 09:46:25 -0400 Subject: [PATCH 042/109] Add unit test for complex data passing through pipeline --- test/test_buffer.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/test_buffer.py b/test/test_buffer.py index 55d9a268..c9e12228 100644 --- a/test/test_buffer.py +++ b/test/test_buffer.py @@ -37,6 +37,9 @@ class SweptTestExperiment(Experiment): samples = 5 time_val = 0 + # Complex Values? + complex_data = False + def init_instruments(self): self.field.assign_method(lambda x: logger.debug("Field got value " + str(x))) self.freq.assign_method(lambda x: logger.debug("Freq got value " + str(x))) @@ -54,7 +57,10 @@ def run(self): logger.debug("Data taker running (inner loop)") time_step = 0.1 time.sleep(0.002) - data_row = np.sin(2*np.pi*self.time_val)*np.ones(5) + 0.1*np.random.random(5) + if self.complex_data: + data_row = np.exp(2j*np.pi*self.time_val)*np.ones(5) + 0.1j*np.random.random(5) + else: + data_row = np.sin(2*np.pi*self.time_val)*np.ones(5) + 0.1*np.random.random(5) self.time_val += time_step self.voltage.push(data_row) self.current.push(data_row*2.0) @@ -135,5 +141,27 @@ def test_buffer_metadata(self): self.assertTrue(np.all(desc['field'] == np.linspace(0,100.0,4))) self.assertTrue(np.all(desc.axis('samples').metadata == ["data", "data", "data", "0", "1"])) + def test_buffer_complex(self): + exp = SweptTestExperiment() + + db = DataBuffer() + exp.voltage.descriptor.dtype = np.complex128 + exp.current.descriptor.dtype = np.complex128 + exp.complex_data = True + exp.update_descriptors() + + + edges = [(exp.voltage, db.sink)] + exp.set_graph(edges) + + exp.add_sweep(exp.field, np.linspace(0,100.0,4)) + exp.add_sweep(exp.freq, np.linspace(0,10.0,3)) + exp.run_sweeps() + + data, desc = db.get_data() + + self.assertAlmostEqual(np.sum(data.real-data.imag), 0.0, places=3) + # self.assertTrue(np.all(desc['field'] == np.linspace(0,100.0,4))) + if __name__ == '__main__': unittest.main() From 1a2e154652c5de580094bee88aa3a9f3426df001 Mon Sep 17 00:00:00 2001 From: Leonardo Ranzani Date: Mon, 17 Jun 2019 10:45:11 -0400 Subject: [PATCH 043/109] Integrate APS1 with APS2 mixer cal --- src/auspex/qubit/mixer_calibration.py | 44 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index ba864daf..1e92a139 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -41,6 +41,7 @@ def model(x, a, b, c): xpts_fine = np.linspace(xpts[0],xpts[-1],101) fit_pts = np.array([np.real(model(x, *fit[0])) for x in xpts_fine]) if min(fit_pts)<0: fit_pts-=min(fit_pts)-1e-10 #prevent log of a negative number + best_offset = xpts[min_idx] return best_offset, xpts_fine, 10*np.log10(fit_pts) @@ -248,23 +249,36 @@ def connect_instruments(self): """Extend connect_instruments to reset I,Q offsets and amplitude and phase imbalance.""" super(MixerCalibrationExperiment, self).connect_instruments() - self.awg.set_offset(0, 0.0) - self.awg.set_offset(1, 0.0) - self.awg.set_mixer_amplitude_imbalance(1.0) - self.awg.set_mixer_phase_skew(0.0) + if isinstance(self.awg, bbn.APS2): + self.awg.set_offset(1,0.0) + self.awg.set_offset(2,0.0) + self.awg.set_mixer_amplitude_imbalance(1.0) + self.awg.set_mixer_phase_skew(1.0) + else: + self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) + self.awg.set_offset(int(self._phys_chan.label[-1]), 0.0) + self.awg.set_amplitude(int(self._phys_chan.label[-2]), 1) + self.awg.set_amplitude(int(self._phys_chan.label[-1]), 1) + self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) + self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) + self.reset_calibration() def init_instruments(self): for k,v in self.config_dict.items(): if k != "sideband_modulation": getattr(self, k).value = v - self.I_offset.assign_method(lambda x: self.awg.set_offset(0, x)) - self.Q_offset.assign_method(lambda x: self.awg.set_offset(1, x)) - self.amplitude_factor.assign_method(self.awg.set_mixer_amplitude_imbalance) if isinstance(self.awg, bbn.APS2): self.phase_skew.assign_method(self.awg.set_mixer_phase_skew) + self.I_offset.assign_method(lambda x: self.awg.set_offset(1, x)) + self.Q_offset.assign_method(lambda x: self.awg.set_offset(2, x)) + self.amplitude_factor.assign_method(lambda x: self.awg.set_mixer_amplitude_imbalance(x)) + self.phase_skew.assign_method(lambda x: self.awg.set_mixer_phase_skew(x, self.SSB_FREQ)) else: - self.phase_skew.assign_method(lambda x: self.awg.set_mixer_phase_skew('12', x, self.SSB_FREQ)) + self.amplitude_factor.assign_method(lambda x: self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:], x)) + self.I_offset.assign_method(lambda x: self.awg.set_offset(int(self._phys_chan.label[-2]), x)) + self.Q_offset.assign_method(lambda x: self.awg.set_offset(int(self._phys_chan.label[-1]), x)) + self.phase_skew.assign_method(lambda x: self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:], x, self.SSB_FREQ)) self.I_offset.add_post_push_hook(lambda: time.sleep(0.1)) self.Q_offset.add_post_push_hook(lambda: time.sleep(0.1)) self.amplitude_factor.add_post_push_hook(lambda: time.sleep(0.1)) @@ -286,10 +300,16 @@ def init_instruments(self): def reset_calibration(self): try: - self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) - self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) - self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) - self.awg.set_offset(int(self._phys_chan.label[-1]), 0.0) + if isinstance(self.awg, bbn.APS2): + self.awg.set_mixer_amplitude_imbalance(1.0) + self.awg.set_mixer_phase_skew(0.0) + self.awg.set_offset(1, 0.0) + self.awg.set_offset(2, 0.0) + else: + self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) + self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) + self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) + self.awg.set_offset(int(self._phys_chan.label[-1]), 0.0) except Exception as ex: raise Exception("Could not reset mixer calibration. Is the AWG connected?") from ex From 7852502eb6c5a72467370d1d561eff1218e534a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 1 Aug 2019 12:23:04 -0400 Subject: [PATCH 044/109] Fix mixer typos, optional fit --- src/auspex/qubit/mixer_calibration.py | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index 1e92a139..a12b2fac 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -24,7 +24,7 @@ from auspex.filters.plot import ManualPlotter from auspex.instruments import instrument_map, bbn -def find_null_offset(xpts, powers, default=0.0): +def find_null_offset(xpts, powers, default=0.0, use_fit=True): """Finds the offset corresponding to the minimum power using a fit to the measured data""" def model(x, a, b, c): return a*(x - b)**2 + c @@ -35,13 +35,15 @@ def model(x, a, b, c): except RuntimeError: logger.warning("Mixer null offset fit failed.") return default, np.zeros(len(powers)) - best_offset = np.real(fit[0][1]) - best_offset = np.minimum(best_offset, xpts[-1]) - best_offset = np.maximum(best_offset, xpts[0]) + if use_fit: + best_offset = np.real(fit[0][1]) + best_offset = np.minimum(best_offset, xpts[-1]) + best_offset = np.maximum(best_offset, xpts[0]) + else: + best_offset = xpts[min_idx] xpts_fine = np.linspace(xpts[0],xpts[-1],101) fit_pts = np.array([np.real(model(x, *fit[0])) for x in xpts_fine]) if min(fit_pts)<0: fit_pts-=min(fit_pts)-1e-10 #prevent log of a negative number - best_offset = xpts[min_idx] return best_offset, xpts_fine, 10*np.log10(fit_pts) @@ -54,8 +56,7 @@ class MixerCalibration(Calibration): MIN_PHASE = -0.3 MAX_PHASE = 0.3 - def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase", - offset_range = (-0.2,0.2), amp_range = (0.4,0.8), phase_range = (-np.pi/2,np.pi/2), nsteps = 101, plot=True): + def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase", offset_range = (-0.2,0.2), amp_range = (0.4,0.8), phase_range = (-np.pi/2,np.pi/2), nsteps = 101, use_fit=True, plot=True): self.channel = channel self.spectrum_analyzer = spectrum_analyzer self.mixer = mixer @@ -65,6 +66,7 @@ def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase self.amp_range = amp_range self.phase_range = phase_range self.nsteps = nsteps + self.use_fit = use_fit super(MixerCalibration, self).__init__() def init_plots(self): @@ -97,7 +99,7 @@ def _calibrate(self): I1_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:]) + I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I1_amps) @@ -107,7 +109,7 @@ def _calibrate(self): Q1_amps = self.run_sweeps("Q_offset", offset_pts, config_dict) try: - Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:]) + Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit) except: raise ValueError("Could not find null offset") self.plt1["Q-offset"] = (offset_pts, Q1_amps) @@ -117,7 +119,7 @@ def _calibrate(self): I2_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:]) + I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I2_amps) @@ -138,7 +140,7 @@ def _calibrate(self): amps1 = self.run_sweeps(cals[first_cal], cal_pts[first_cal], config_dict) try: - offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal]) + offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit) except: raise ValueError("Could not find null offset") correct_plotter[first_cal][cals[first_cal]] = (cal_pts[first_cal], amps1) @@ -148,7 +150,7 @@ def _calibrate(self): amps2 = self.run_sweeps(cals[second_cal], cal_pts[second_cal], config_dict) try: - offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal]) + offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit) except: raise ValueError("Could not find null offset") correct_plotter[second_cal][cals[second_cal]] = (cal_pts[second_cal], amps2) @@ -270,10 +272,9 @@ def init_instruments(self): if isinstance(self.awg, bbn.APS2): self.phase_skew.assign_method(self.awg.set_mixer_phase_skew) - self.I_offset.assign_method(lambda x: self.awg.set_offset(1, x)) - self.Q_offset.assign_method(lambda x: self.awg.set_offset(2, x)) - self.amplitude_factor.assign_method(lambda x: self.awg.set_mixer_amplitude_imbalance(x)) - self.phase_skew.assign_method(lambda x: self.awg.set_mixer_phase_skew(x, self.SSB_FREQ)) + self.I_offset.assign_method(lambda x: self.awg.set_offset(0, x)) + self.Q_offset.assign_method(lambda x: self.awg.set_offset(1, x)) + self.amplitude_factor.assign_method(self.awg.set_mixer_amplitude_imbalance) else: self.amplitude_factor.assign_method(lambda x: self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:], x)) self.I_offset.assign_method(lambda x: self.awg.set_offset(int(self._phys_chan.label[-2]), x)) @@ -303,8 +304,8 @@ def reset_calibration(self): if isinstance(self.awg, bbn.APS2): self.awg.set_mixer_amplitude_imbalance(1.0) self.awg.set_mixer_phase_skew(0.0) + self.awg.set_offset(0, 0.0) self.awg.set_offset(1, 0.0) - self.awg.set_offset(2, 0.0) else: self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) From ffeb618111697ba19406dd50d0c07bd9d43b50da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 1 Aug 2019 13:09:53 -0400 Subject: [PATCH 045/109] Use_fit --- src/auspex/qubit/mixer_calibration.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index a12b2fac..5b815cdd 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -99,7 +99,7 @@ def _calibrate(self): I1_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit) + I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit = use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I1_amps) @@ -109,7 +109,7 @@ def _calibrate(self): Q1_amps = self.run_sweeps("Q_offset", offset_pts, config_dict) try: - Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit) + Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit = use_fit) except: raise ValueError("Could not find null offset") self.plt1["Q-offset"] = (offset_pts, Q1_amps) @@ -119,7 +119,7 @@ def _calibrate(self): I2_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit) + I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit = use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I2_amps) @@ -140,7 +140,7 @@ def _calibrate(self): amps1 = self.run_sweeps(cals[first_cal], cal_pts[first_cal], config_dict) try: - offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit) + offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit = use_fit) except: raise ValueError("Could not find null offset") correct_plotter[first_cal][cals[first_cal]] = (cal_pts[first_cal], amps1) @@ -150,7 +150,7 @@ def _calibrate(self): amps2 = self.run_sweeps(cals[second_cal], cal_pts[second_cal], config_dict) try: - offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit) + offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit = use_fit) except: raise ValueError("Could not find null offset") correct_plotter[second_cal][cals[second_cal]] = (cal_pts[second_cal], amps2) From 9c0050c48d91b38b4b7c8267711d2a967dd4fd4a Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 1 Aug 2019 15:13:32 -0400 Subject: [PATCH 046/109] A better way to test for complex-ness of stream dtypes. --- src/auspex/stream.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/auspex/stream.py b/src/auspex/stream.py index 41494c3a..b45663ee 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -494,7 +494,7 @@ def __init__(self, name=None, unit=None): self.buff_shared_re = RawArray(ctypes.c_double, self.buffer_size) self.buff_shared_im = RawArray(ctypes.c_double, self.buffer_size) self.re_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) - self.im_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) + self.im_np = np.frombuffer(self.buff_shared_im, dtype=np.float64) def set_descriptor(self, descriptor): if isinstance(descriptor,DataStreamDescriptor): @@ -552,11 +552,9 @@ def push(self, data): with self.buffer_lock: start = self.buff_idx.value re = np.real(data).flatten() - # logger.info(f"in buff_shared_re:{self.buff_idx.value} {re[0:4]}") self.re_np[start:start+re.size] = re - if issubclass(self.descriptor.dtype, np.complex): + if np.issubdtype(self.descriptor.dtype, np.complexfloating): im = np.imag(data).flatten() - # logger.info(f"in buff_shared_im:{self.buff_idx.value} {im[0:4]}") self.im_np[start:start+im.size] = im message = {"type": "data", "data": None} self.buff_idx.value = start + data.size @@ -568,8 +566,8 @@ def pop(self): idx = self.buff_idx.value if idx != 0: result = self.re_np[:idx] - # logger.info(f"out buff_shared: {idx} {result[0:4]}") - if issubclass(self.descriptor.dtype, np.complex): + + if np.issubdtype(self.descriptor.dtype, np.complexfloating): result = result.astype(np.complex128) + 1.0j*self.im_np[:idx] self.buff_idx.value = 0 result = result.copy() From 742c9b1520ea22ed201b07347fc40fdeb482036d Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 1 Aug 2019 15:21:37 -0400 Subject: [PATCH 047/109] Don't complain about channel libraries without edges defined. --- src/auspex/qubit/qubit_exp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index eb7b5338..7d4001cc 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -120,7 +120,10 @@ def create_from_meta(self, meta_file, averages): self.controlled_qubits = [c for c in self.chan_db.channels if c.label in meta_info["qubits"]] self.measurements = [c for c in self.chan_db.channels if c.label in meta_info["measurements"]] self.measured_qubits = [c for c in self.chan_db.channels if "M-"+c.label in meta_info["measurements"]] - self.edges = [c for c in self.chan_db.channels if c.label in meta_info["edges"]] + if 'edges' in meta_info: + self.edges = [c for c in self.chan_db.channels if c.label in meta_info["edges"]] + else: + self.edges = [] self.phys_chans = list(set([e.phys_chan for e in self.controlled_qubits + self.measurements + self.edges])) self.transmitters = list(set([e.phys_chan.transmitter for e in self.controlled_qubits + self.measurements + self.edges])) self.receiver_chans = list(set([e.receiver_chan for e in self.measurements])) From 43b70d7de31af857e5faf8c7604cb497c5f21923 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 1 Aug 2019 15:22:13 -0400 Subject: [PATCH 048/109] Simply channelizer output connector descriptor setup. --- src/auspex/filters/channelizer.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/auspex/filters/channelizer.py b/src/auspex/filters/channelizer.py index ce7d9b6f..76da77cd 100644 --- a/src/auspex/filters/channelizer.py +++ b/src/auspex/filters/channelizer.py @@ -85,7 +85,7 @@ def final_init(self): self.idx = 0 # For storing carryover if getting uneven buffers - self.carry = np.zeros(0, dtype=self.output_descriptor.dtype) + self.carry = np.zeros(0, dtype=self.source.descriptor.dtype) def update_references(self, frequency): @@ -185,11 +185,8 @@ def update_descriptors(self): decimated_descriptor.axes[-1].original_points = decimated_descriptor.axes[-1].points decimated_descriptor._exp_src = self.sink.descriptor._exp_src decimated_descriptor.dtype = np.complex64 - self.output_descriptor = decimated_descriptor - for os in self.source.output_streams: - os.set_descriptor(decimated_descriptor) - if os.end_connector is not None: - os.end_connector.update_descriptors() + self.source.descriptor = decimated_descriptor + self.source.update_descriptors() def process_data(self, data): @@ -210,7 +207,7 @@ def process_data(self, data): else: self.carry = data else: - self.carry = np.zeros(0, dtype=self.output_descriptor.dtype) + self.carry = np.zeros(0, dtype=self.source.descriptor.dtype) if num_records > 0: # The records are processed in parallel after being reshaped here From ddb70088656de9f2c8a656ae1902abb687c7b1e8 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 1 Aug 2019 15:42:29 -0400 Subject: [PATCH 049/109] Fix a few bugs for APS2 functionality --- src/auspex/qubit/mixer_calibration.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index 5b815cdd..ea7df726 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -99,7 +99,7 @@ def _calibrate(self): I1_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit = use_fit) + I1_offset, xpts, ypts = find_null_offset(offset_pts[1:], I1_amps[1:], use_fit = self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I1_amps) @@ -109,7 +109,7 @@ def _calibrate(self): Q1_amps = self.run_sweeps("Q_offset", offset_pts, config_dict) try: - Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit = use_fit) + Q1_offset, xpts, ypts = find_null_offset(offset_pts[1:], Q1_amps[1:], use_fit = self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["Q-offset"] = (offset_pts, Q1_amps) @@ -119,7 +119,7 @@ def _calibrate(self): I2_amps = self.run_sweeps("I_offset", offset_pts, config_dict) try: - I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit = use_fit) + I2_offset, xpts, ypts = find_null_offset(offset_pts[1:], I2_amps[1:], use_fit = self.use_fit) except: raise ValueError("Could not find null offset") self.plt1["I-offset"] = (offset_pts, I2_amps) @@ -140,7 +140,7 @@ def _calibrate(self): amps1 = self.run_sweeps(cals[first_cal], cal_pts[first_cal], config_dict) try: - offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit = use_fit) + offset1, xpts, ypts = find_null_offset(cal_pts[first_cal][1:], amps1[1:], default=cal_defaults[first_cal], use_fit = self.use_fit) except: raise ValueError("Could not find null offset") correct_plotter[first_cal][cals[first_cal]] = (cal_pts[first_cal], amps1) @@ -150,7 +150,7 @@ def _calibrate(self): amps2 = self.run_sweeps(cals[second_cal], cal_pts[second_cal], config_dict) try: - offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit = use_fit) + offset2, xpts, ypts = find_null_offset(cal_pts[second_cal][1:], amps2[1:], default=cal_defaults[second_cal], use_fit = self.use_fit) except: raise ValueError("Could not find null offset") correct_plotter[second_cal][cals[second_cal]] = (cal_pts[second_cal], amps2) @@ -252,10 +252,10 @@ def connect_instruments(self): imbalance.""" super(MixerCalibrationExperiment, self).connect_instruments() if isinstance(self.awg, bbn.APS2): + self.awg.set_offset(0,0.0) self.awg.set_offset(1,0.0) - self.awg.set_offset(2,0.0) self.awg.set_mixer_amplitude_imbalance(1.0) - self.awg.set_mixer_phase_skew(1.0) + self.awg.set_mixer_phase_skew(0.0) else: self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) self.awg.set_offset(int(self._phys_chan.label[-1]), 0.0) From 783f57291669aa3f03dd8739dab536bce02ff0d3 Mon Sep 17 00:00:00 2001 From: Guilhem Ribeill Date: Thu, 1 Aug 2019 16:15:40 -0400 Subject: [PATCH 050/109] Comment the mixer calibration experiment classes. --- src/auspex/qubit/mixer_calibration.py | 76 +++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 11 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index ea7df726..a45d1cad 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -25,7 +25,18 @@ from auspex.instruments import instrument_map, bbn def find_null_offset(xpts, powers, default=0.0, use_fit=True): - """Finds the offset corresponding to the minimum power using a fit to the measured data""" + """Finds the offset corresponding to minimum power in a mixer calibration. Optionally will fit a quadratic to the + linearized power and use the minimum of the fit to set the offset. + + Args: + xpts (numpy.ndarray): Swept axis. + powers (numpy.ndarray): Measured powers, in logarithmic scale. + default (float, optional): Value to return if fit fails. Defaults to 0.0. + use_fit (bool, optional): If true, use a fit to data. If false, return minimum value of data. Defaults to True. + + Returns: + A tuple containing the offset with minumum power, a finer x points array, and the fit points. + """ def model(x, a, b, c): return a*(x - b)**2 + c powers = np.power(10, powers/10.) @@ -34,7 +45,7 @@ def model(x, a, b, c): fit = curve_fit(model, xpts, powers, p0=[1, xpts[min_idx], powers[min_idx]]) except RuntimeError: logger.warning("Mixer null offset fit failed.") - return default, np.zeros(len(powers)) + return default, np.zeros(len(powers)), np.zeros(len(powers)) if use_fit: best_offset = np.real(fit[0][1]) best_offset = np.minimum(best_offset, xpts[-1]) @@ -48,6 +59,10 @@ def model(x, a, b, c): class MixerCalibration(Calibration): + """A calibration experiment that determines the optimal I and Q offsets, amplitude imbalance and phase imbalance + for single-sideband modulation of a mixer. Please see Analog Devices Application Note AN-1039 for an explanation + of the optimization of a mixer for maximum sideband supression. + """ MIN_OFFSET = -0.4 MAX_OFFSET = 0.4 @@ -56,7 +71,27 @@ class MixerCalibration(Calibration): MIN_PHASE = -0.3 MAX_PHASE = 0.3 - def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase", offset_range = (-0.2,0.2), amp_range = (0.4,0.8), phase_range = (-np.pi/2,np.pi/2), nsteps = 101, use_fit=True, plot=True): + def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase", + offset_range = (-0.2,0.2), amp_range = (0.4,0.8), phase_range = (-np.pi/2,np.pi/2), + nsteps = 101, use_fit=True, plot=True): + """A mixer calibration for a specific LogicalChannel and mixer. + + Args: + channel: The Auspex LogicalChannel for which to calibrate the mixer. + spectrum_analyzer: The spectrum analyzer instrument to use for calibration. + mixer (optional): 'control' or 'measure', the corresponding mixer to calibrate. + Defaults to 'control'. + first_cal (optional): 'phase' or 'amplitude'. Calibrate the phase skew or amplitude imbalance first. + Defaults to 'phase'. + offset_range (optional): I and Q offset range to sweep over. Defaults to (-0.2, 0.2). + amp_range (optional): Amplitude imbalance range to sweep over. Defaults to (0.4, 0.8). + phase_range (optional): Phase range to sweep over. Defaults to (-pi/2, pi/2). + nsteps (optional): Number of points to sweep over for each calibraton. + use_fit (optional): If true, find power minumum using a fit; otherwise use minimum value from data. + Defaults to True. + plot (optional): Plot the calibration as it happens. + """ + self.channel = channel self.spectrum_analyzer = spectrum_analyzer self.mixer = mixer @@ -70,6 +105,9 @@ def __init__(self, channel, spectrum_analyzer, mixer="control", first_cal="phase super(MixerCalibration, self).__init__() def init_plots(self): + """Initialize manual plotters for the mixer calibration. Three plot tabs open, one for I,Q offset, one for + phase skew, one for amplitude imbalance. + """ self.plt1 = ManualPlotter(name="Mixer offset calibration", x_label='{} {} offset (V)'.format(self.channel, self.mixer), y_label='Power (dBm)') self.plt1.add_data_trace("I-offset", {'color': 'C1'}) self.plt1.add_data_trace("Q-offset", {'color': 'C2'}) @@ -88,7 +126,8 @@ def init_plots(self): return self.plotters def _calibrate(self): - + """Run the actual calibration routine. + """ offset_pts = np.linspace(self.offset_range[0], self.offset_range[1], self.nsteps) amp_pts = np.linspace(self.amp_range[0], self.amp_range[1], self.nsteps) phase_pts = np.linspace(self.phase_range[0], self.phase_range[1], self.nsteps) @@ -175,19 +214,25 @@ def _calibrate(self): self.config_dict = config_dict def update_settings(self): + """Update the channel library settings to those found by calibration routine. + """ self.exp._phys_chan.amp_factor = self.config_dict["amplitude_factor"] self.exp._phys_chan.phase_skew = self.config_dict["phase_skew"] self.exp._phys_chan.I_channel_offset = self.config_dict["I_offset"] self.exp._phys_chan.Q_channel_offset = self.config_dict["Q_offset"] def run_sweeps(self, sweep_parameter, pts, config_dict): + """Run the calibration sweeps. + """ self.exp = MixerCalibrationExperiment(self.channel, self.spectrum_analyzer, config_dict, mixer=self.mixer) self.exp.add_sweep(getattr(self.exp, sweep_parameter), pts) self.exp.run_sweeps() return self.exp.buff.get_data()[0] class MixerCalibrationExperiment(Experiment): - + """A mixer calibration experiment, using an APS1 or APS2 unit to calibrate the mixer offsets. Pulls sideband + modulation frequency (IF) and LO frequency from the channel library. + """ SSB_FREQ = 10e6 amplitude = OutputConnector(unit='dBc') @@ -202,9 +247,10 @@ class MixerCalibrationExperiment(Experiment): def __init__(self, channel, spectrum_analyzer, config_dict, mixer="control"): """Initialize MixerCalibrationExperiment Experiment. Args: - channel: channel identifier (qubit or edge) - spectrum_analyzer: which spectrum analyzer should be used. - mixer: One of 'control', 'measure' to select which mixer to cal. + channel: LogicalChannel to perform calibration. + spectrum_analyzer: Which spectrum analyzer should be used. + config_dict: Dictionary of values to store calibration results. + mixer: One of 'control', 'measure' to select which mixer to cal. """ super(MixerCalibrationExperiment, self).__init__() @@ -248,8 +294,8 @@ def __init__(self, channel, spectrum_analyzer, config_dict, mixer="control"): self.set_graph(edges) def connect_instruments(self): - """Extend connect_instruments to reset I,Q offsets and amplitude and phase - imbalance.""" + """Connect instruments, resetting all mixer offsets to default values. + """ super(MixerCalibrationExperiment, self).connect_instruments() if isinstance(self.awg, bbn.APS2): self.awg.set_offset(0,0.0) @@ -266,6 +312,8 @@ def connect_instruments(self): self.reset_calibration() def init_instruments(self): + """Initialize instruments for mixer calibration. + """ for k,v in self.config_dict.items(): if k != "sideband_modulation": getattr(self, k).value = v @@ -300,13 +348,17 @@ def init_instruments(self): time.sleep(0.1) def reset_calibration(self): + """Set calibration back to default values. + """ try: if isinstance(self.awg, bbn.APS2): self.awg.set_mixer_amplitude_imbalance(1.0) self.awg.set_mixer_phase_skew(0.0) self.awg.set_offset(0, 0.0) self.awg.set_offset(1, 0.0) - else: + else:abel[-1]), 1) + self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) + self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) @@ -315,6 +367,8 @@ def reset_calibration(self): raise Exception("Could not reset mixer calibration. Is the AWG connected?") from ex def _setup_awg_ssb(self): + """Set up AWGS for single sideband modulation, playing back a continuous tone. + """ #set up single sideband modulation IQ playback on the AWG self.awg.stop() if isinstance(self.awg, bbn.APS2): From 41e9cdc96d06f76fd5ab251c9bd814f2a112df20 Mon Sep 17 00:00:00 2001 From: gribeill Date: Thu, 1 Aug 2019 16:32:07 -0400 Subject: [PATCH 051/109] Remove random characters that snuck in... --- src/auspex/qubit/mixer_calibration.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index a45d1cad..169f4702 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -356,9 +356,9 @@ def reset_calibration(self): self.awg.set_mixer_phase_skew(0.0) self.awg.set_offset(0, 0.0) self.awg.set_offset(1, 0.0) - else:abel[-1]), 1) - self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) - self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) + else: + self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) + self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) self.awg.set_mixer_amplitude_imbalance(self._phys_chan.label[-2:],1.0) self.awg.set_mixer_phase_skew(self._phys_chan.label[-2:],0.0) self.awg.set_offset(int(self._phys_chan.label[-2]), 0.0) From 17b235dccd198b15bd3d015d2ffe33daf97e1711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 2 Aug 2019 15:03:19 -0400 Subject: [PATCH 052/109] WIP add param. optimization --- src/auspex/qubit/qubit_exp.py | 2 +- src/auspex/qubit/single_shot_fidelity.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 50dd0ea8..34287ea6 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -486,7 +486,7 @@ def method(value, channel=chan, instr=instr, prop=attribute,thing=thing): param.add_post_push_hook(lambda: time.sleep(0.05)) else: raise ValueError("The instrument {} has no method {}".format(name, "set_"+attribute)) - # param.instr_tree = [instr.name, attribute] #TODO: extend tree to endpoint + param.instr_tree = [instr.name, attribute] #TODO: extend tree to endpoint self.add_sweep(param, values) # Create the requested sweep on this parameter def add_avg_sweep(self, num_averages): diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index dcc5f3a2..03d84a08 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -97,6 +97,18 @@ def run_sweeps(self): if not self.sweeper.axes: self._update_histogram_plots() self.stop_manual_plotters() + else: + fidelities = [f['Max I Fidelity'] for f in self.pdf_data] + opt_ind = np.argmax(fidelities) + for k, axis in enumerate(self.sweeper.axes): + instr_tree = axis.parameter.instr_tree + opt_value = axis.points[opt_ind] + param_key = self.instruments + for key in instr_tree[:-1]: + # go through the tree + param_key = param_key[key] + #TODO: update database + pass def find_single_shot_filter(self): """Make sure there is one single shot measurement filter in the pipeline.""" From 49ee83074372b616436844b7d938b95bc7d6d7c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 2 Aug 2019 17:19:07 -0400 Subject: [PATCH 053/109] Fix s.s.typos Still need to properly update database --- src/auspex/qubit/single_shot_fidelity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index 03d84a08..950a14ea 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -100,6 +100,7 @@ def run_sweeps(self): else: fidelities = [f['Max I Fidelity'] for f in self.pdf_data] opt_ind = np.argmax(fidelities) + import pdb; pdb.set_trace() for k, axis in enumerate(self.sweeper.axes): instr_tree = axis.parameter.instr_tree opt_value = axis.points[opt_ind] From 8d49509c383a61cd21e96159473280dc738d0e6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 5 Aug 2019 12:12:30 -0400 Subject: [PATCH 054/109] Optimize over s.shot sweep --- src/auspex/qubit/qubit_exp.py | 4 ++-- src/auspex/qubit/single_shot_fidelity.py | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 34287ea6..d1adae05 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -473,7 +473,7 @@ def method(value, channel=chan, instr=instr, prop=attribute,thing=thing): getattr(instr, "set_"+prop)(chan, value, thing) except: getattr(instr, "set_"+prop)(chan, value) - + param.set_pair = (thing.phys_chan.label, attribute) if method: # Custom method @@ -486,7 +486,7 @@ def method(value, channel=chan, instr=instr, prop=attribute,thing=thing): param.add_post_push_hook(lambda: time.sleep(0.05)) else: raise ValueError("The instrument {} has no method {}".format(name, "set_"+attribute)) - param.instr_tree = [instr.name, attribute] #TODO: extend tree to endpoint + param.set_pair = (instr.name, attribute) self.add_sweep(param, values) # Create the requested sweep on this parameter def add_avg_sweep(self, num_averages): diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index 950a14ea..2b0f3720 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -34,10 +34,11 @@ class SingleShotFidelityExperiment(QubitExperiment): """Experiment to measure single-shot measurement fidelity of a qubit.""" - def __init__(self, qubit, output_nodes=None, meta_file=None, **kwargs): + def __init__(self, qubit, output_nodes=None, meta_file=None, optimize=True, **kwargs): self.pdf_data = [] self.qubit = qubit + self.optimize = optimize if meta_file: self.meta_file = meta_file @@ -97,19 +98,22 @@ def run_sweeps(self): if not self.sweeper.axes: self._update_histogram_plots() self.stop_manual_plotters() - else: + elif self.optimize: fidelities = [f['Max I Fidelity'] for f in self.pdf_data] opt_ind = np.argmax(fidelities) - import pdb; pdb.set_trace() for k, axis in enumerate(self.sweeper.axes): - instr_tree = axis.parameter.instr_tree + set_pair = axis.parameter.set_pair opt_value = axis.points[opt_ind] - param_key = self.instruments - for key in instr_tree[:-1]: - # go through the tree - param_key = param_key[key] - #TODO: update database - pass + if set_pair[1] == 'amplitude' or set_pair[1] == "offset": + # special case for APS chans + param = [c for c in self.chan_db.channels if c.label == set_pair[0]][0] + attr = 'amp_factor' if set_pair[1] == 'amplitude' else 'offset' + setattr(param, f'I_channel_{attr}', opt_value) + setattr(param, f'Q_channel_{attr}', opt_value) + else: + param = [c for c in self.chan_db.all_instruments() if c.label == set_pair[0]][0] + setattr(param, set_pair[1], opt_value) + logger.info(f'Set {set_pair[0]} {set_pair[1]} to optimum value {opt_value}') def find_single_shot_filter(self): """Make sure there is one single shot measurement filter in the pipeline.""" From 4e7be6894d3de74c9605ee3e5009ee13d7986008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 5 Aug 2019 12:22:34 -0400 Subject: [PATCH 055/109] Docstring --- src/auspex/qubit/single_shot_fidelity.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index 2b0f3720..bbe64883 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -32,8 +32,15 @@ import auspex.config class SingleShotFidelityExperiment(QubitExperiment): - """Experiment to measure single-shot measurement fidelity of a qubit.""" + """Experiment to measure single-shot measurement fidelity of a qubit. + Args: + qubit: qubit object + output_nodes (optional): the output node of the filter pipeline to use for single-shot readout. The default is choses, if single output. + meta_file (string, optional): path to the QGL sequence meta_file. Default to standard SingleShot sequence + optimize (bool, optional): if True and a qubit_sweep is added, set the parameter corresponding to the maximum measured fidelity at the end of the sweep + + """ def __init__(self, qubit, output_nodes=None, meta_file=None, optimize=True, **kwargs): self.pdf_data = [] From f4469ca55fc4304f04b570ef30f3eba8eba0c49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 5 Aug 2019 16:34:59 -0400 Subject: [PATCH 056/109] Fix empty pdf data --- src/auspex/qubit/single_shot_fidelity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index bbe64883..bb2db6ed 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -132,12 +132,12 @@ def find_single_shot_filter(self): return ssf def get_fidelity(self): - if self.pdf_data is None: + if not self.pdf_data: raise Exception("Could not find single shot PDF data in results. Did you run the sweeps?") return [p["Max I Fidelity"] for p in self.pdf_data] def get_threshold(self): - if self.pdf_data is None: + if not self.pdf_data: raise Exception("Could not find single shot PDF data in results. Did you run the sweeps?") return [p["I Threshold"] for p in self.pdf_data] From 861bea9341dcbbc073cea57596087b0d1e8c4d91 Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 6 Aug 2019 09:26:37 -0400 Subject: [PATCH 057/109] Added support for APS3 --- src/auspex/instruments/aps3.py | 184 ++++++++++++++++++++------------- src/auspex/qubit/qubit_exp.py | 3 +- 2 files changed, 114 insertions(+), 73 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 3f8d4037..38a7c0e8 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -162,7 +162,7 @@ def connect(self, resource=("192.168.2.200", "COM1")): self.socket.connect((self.ip_addr, self.PORT)) self.ser = serial.Serial(resource[1], 115200) self.connected = True - + def disconnect(self): if self.connected: self.connected = False @@ -243,7 +243,7 @@ def read_memory(self, addr, num_words): self.send_bytes(datagram) resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) - + def serial_read_dac0(self, addr): self.ser.reset_output_buffer() self.ser.reset_input_buffer() @@ -253,14 +253,14 @@ def serial_read_dac0(self, addr): start_index = len('Read value = ') end_index = resp.find('@') return int(resp[start_index:end_index], 16) - + def serial_write_dac0(self, addr, val): self.ser.reset_output_buffer() self.ser.reset_input_buffer() self.ser.write(bytearray('wd d0 {0:#x} {1:#x}\n'.format(addr, val), 'ascii')) self.ser.readline() # Throw out the echo line from the terminal interface return self.ser.readline() # Echo back the "wrote xx to xx" line - + def serial_configure_JESD(self): # Configure the JESD interface properly logger.info(self.serial_write_dac0(0x300, 0x00)) # disable all links @@ -279,7 +279,7 @@ def serial_configure_JESD(self): sleep(0.01) logger.info(self.serial_write_dac0(0x300, 0x01)) # enable all links sleep(0.01) - + def serial_set_switch_mode(self, mode): ''' Sets DAC output switch mode to one of NRZ, Mix-Mode, or RZ. @@ -294,12 +294,12 @@ def serial_set_switch_mode(self, mode): code = 0x02 else: raise Exception('DAC switch mode "' + mode + '" not recognized.') - + if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: logger.info(self.serial_write_dac0(0x152, code)) - + def serial_get_switch_mode(self): ''' Reads DAC output switch mode as one of NRZ, Mix-Mode, or RZ. @@ -309,7 +309,7 @@ def serial_get_switch_mode(self): if self.ser is None: logger.info('Fake read mix-mode.') return 'MIX' - + code = self.serial_read_dac0(0x152) & 0x03 if code == 0x00: return 'NRZ' @@ -317,25 +317,25 @@ def serial_get_switch_mode(self): return 'MIX' if code == 0x02: return 'RZ' - + raise Exception('Unrecognized DAC switch mode ' + code + '.') - + def serial_set_analog_full_scale_current(self, current): ''' - Sets DAC full-scale current, rounding to nearest LSB of current register. + Sets DAC full-scale current, rounding to nearest LSB of current register. Parameters: current (float): Full-scale current in mA - + Returns: (float) actual programmed current in mA ''' if current < 8 or current > 40: raise Exception('DAC full-scale current must be between 8 mA and 40 mA.') - + # From AD9164 datasheet: # IOUTFS = 32 mA × (ANA_FULL_SCALE_CURRENT[9:0]/1023) + 8 mA reg_value = int(1023 * (current - 8) / 32) - + if self.ser is None: logger.info('{:#x}'.format(reg_value & 0x3)) logger.info('{:#x}'.format((reg_value >> 2) & 0xFF)) @@ -344,9 +344,9 @@ def serial_set_analog_full_scale_current(self, current): sleep(0.01) logger.info(self.serial_write_dac0(0x042, (reg_value >> 2) & 0xFF)) sleep(0.01) - + return 32 * (reg_value / 1023) + 8 - + def serial_get_analog_full_scale_current(self): ''' Reads programmed full-scale current. @@ -355,12 +355,12 @@ def serial_get_analog_full_scale_current(self): ''' if self.ser is None: return 0 - + LSbits = self.serial_read_dac0(0x041) & 0x03 MSbits = self.serial_read_dac0(0x042) & 0xFF reg_value = (MSbits << 2) & LSbits return 32 * (reg_value / 1023) + 8 - + def serial_set_nco_enable(self, en): ''' Enables the DAC NCO. @@ -375,31 +375,31 @@ def serial_set_nco_enable(self, en): code = 0x00 else: code = self.serial_read_dac0(0x111) - + if en: code |= (1 << 6) else: code &= ~(1 << 6) - + if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: logger.info(self.serial_write_dac0(0x111, code)) - + sleep(0.1) - + def serial_get_nco_enable(self): ''' Checks whether the DAC NCO is enabled. Returns: True if DAC NCO is enabled, otherwise False ''' - if self.ser is None: + if self.ser is None: logger.info('Fake reported DAC NCO enabled.') return True - + return (self.serial_read_dac0(0x111) & (1 << 6)) != 0 - + def serial_set_FIR85_enable(self, FIR85): ''' Enables the DAC NCO FIR85 filter. @@ -411,56 +411,56 @@ def serial_set_FIR85_enable(self, FIR85): code = 0x00 else: code = self.serial_read_dac0(0x111) - + if FIR85: code |= (1 << 0) else: code &= ~(1 << 0) - + if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: logger.info(self.serial_write_dac0(0x111, code)) - + sleep(0.1) - + def serial_get_FIR85_enable(self): ''' Checks whether the DAC NCO FIR85 filter is enabled. Returns: True if DAC NCO FIR85 filter is enabled, otherwise False ''' - if self.ser is None: + if self.ser is None: logger.info('Fake reported DAC NCO FIR85 enabled.') return True - + return (self.serial_read_dac0(0x111) & (1 << 0)) != 0 - + def serial_set_nco_frequency(self, f): ''' Writes the given frequency, assuming not in NCO-only mode. Follows procedure in Table 44 of AD9164 datasheet. ''' logger.info('Setting frequency to {}...'.format(f)) - + # Configure DC_TEST_EN bit: 0b0 = NCO operation with data interface logger.info(self.serial_write_dac0(0x150, 0x00)) sleep(0.01) - + # Ensure the frequency tuning word write request is low. logger.info(self.serial_write_dac0(0x113, 0x00)) sleep(0.01) - + # Write FTW. ftw = [(int((f/5e9)*(1 << 48)) >> x) & 0xFF for x in range(0, 48, 8)] for index, b in enumerate(ftw): logger.info(self.serial_write_dac0(0x114 + index, b)) sleep(0.01) - + # Load the FTW to the NCO. logger.info(self.serial_write_dac0(0x113, 0x01)) sleep(0.1) - + def serial_get_nco_frequency(self): ''' Reads the current NCO frequency, assuming not in NCO-only mode. @@ -469,14 +469,14 @@ def serial_get_nco_frequency(self): for index, shift in enumerate(range(0, 48, 8)): ftw |= self.serial_read_dac0(0x114 + index) << shift sleep(0.01) - + return ftw * 5e9 - + def serial_set_reference(self, ref): ''' Sets the SOF200 PLL reference to either the front panel or the FPGA. Parameters: - ref (str): Either "REF IN" or "FPGA" + ref (str): Either "REF IN" or "FPGA" ''' if ref == 'REF IN': self.ref = ref @@ -492,10 +492,35 @@ def serial_set_reference(self, ref): self.ser.readline() # Throw out the echo line from the terminal interface else: logger.info('Error: unrecognized reference input "' + ref + '".') - + def serial_get_reference(self): return self.ref + def serial_set_shuffle_mode(self, value): + ''' + Sets DAC shuffle mode. + Parameters: + value (int): Sets the shuffle register bits + ''' + if self.ser is None: + logger.info('Fake wrote {:#x}'.format(value & 0x7)) + else: + logger.info(self.serial_write_dac0(0x151, value & 0x7)) + + sleep(0.1) + + def serial_get_shuffle_mode(self): + ''' + Checks whether DAC shuffle mode is enabled. + Returns: + True if DAC shuffle is enabled, otherwise False + ''' + if self.ser is None: + logger.info('Fake reported DAC shuffle mode enabled.') + return True + + return self.serial_read_dac0(0x151) & 0x7 + ##################################################################### #APS3 AMC599 Control and Status Register offsets @@ -525,7 +550,7 @@ def serial_get_reference(self): CSR_CMAT_R0 = 0x0068 #correction matrix row 0 CSR_CMAT_R1 = 0x006C #correction matrix row 1 -#### NOT CONNECTED TO ANY LOGIC -- USE FOR VALUE STORAGE ############ +#### NOT CONNECTED TO _enableANY LOGIC -- USE FOR VALUE STORAGE ############ CSR_A_AMPLITUDE = 0x0070 #Channel A amplitude CSR_B_AMPLITUDE = 0x0074 #Channel B amplitude CSR_MIX_AMP = 0x0078 #Mixer amplitude correction @@ -571,6 +596,13 @@ def connect(self, resource_name=None): raise ValueError("Must supply a resource name!") elif resource_name is not None: self.resource_name = resource_name + + if len(self.resource_name) != 2: + raise ValueError("Resource name must have 2 elements!") + if self.resource_name[0] == None: + raise ValueError("Resource name must contain IP address!") + if self.resource_name[1] == None: + raise ValueError("Resource name must contain serial port!") if is_valid_ipv4(self.resource_name[0]): self.board.connect(resource = self.resource_name) @@ -592,7 +624,7 @@ def read_dram(self, offset, num_words = 1): return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) ####### CACHE CONTROL REGSITER ############################################# - + cache_controller = BitFieldCommand(register=CSR_CACHE_CONTROL, shift=0, doc="""Cache controller enable bit.""") @@ -607,7 +639,7 @@ def read_dram(self, offset, num_words = 1): doc="""Sequencer, trigger input, modulator, SATA, VRAM, debug stream enable bit.""") trigger_source = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=1, - value_map={"EXTERNAL": 0b00, "INTERNAL": 0b01, "SOFTWARE": 0b10, "MESSAGE": 0b11}) + value_map={"external": 0b00, "internal": 0b01, "software": 0b10, "message": 0b11}) soft_trigger = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=3) trigger_enable = BitFieldCommand(register=CSR_SEQ_CONTROL, shift=4) @@ -747,9 +779,9 @@ def stop(self): self.sequencer_enable = False ###### SEQUENCE LOADING #################################################### - + sequence_filename = '' - + def load_sequence(self, sequence): packed_seq = [] for instr in sequence: @@ -773,85 +805,93 @@ def load_sequence_file(self, seq_file): instrument_type = file.read(4) if instrument_type != b'APS3': raise ValueError('Sequence file not designated for APS3; header for ' + str(instrument_type) + ' found.') - + version = np.frombuffer(file.read(4), dtype=np.float32)[0] min_firmware_version = np.frombuffer(file.read(4), dtype=np.float32)[0] num_channels = int(np.frombuffer(file.read(2), dtype=np.uint16)[0]) - + if num_channels != 2: raise ValueError('Unexpected number of channels, sequence file reports ' + str(num_channels) + ' channels.') - + instructions_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) instructions = [int(x) for x in np.frombuffer(file.read(instructions_size*8), dtype=np.uint64)] - - load_sequence(instructions) - + + self.load_sequence(instructions) + data = [] for chan in range(num_channels): - data_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) - data.append(np.frombuffer(file.read(data_size*8), dtype=np.uint64)) - - load_waveforms(data[0], data[1]) - + data_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) + data.append([int(x) for x in np.frombuffer(file.read(data_size*2), dtype=np.uint16)]) + + self.load_waveforms(data[0], data[1]) + def serial_check_alive(self): return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 - + @property def sequence_file(self): return self.sequence_filename - + @property def sequence_file(self): return self.sequence_filename - + @sequence_file.setter def sequence_file(self, value): self.load_sequence_file(value) - + @property def dac_switch_mode(self): return self.board.serial_get_switch_mode() - + @dac_switch_mode.setter def dac_switch_mode(self, value): self.board.serial_set_switch_mode(value) - + @property def dac_full_scale_current(self): return self.board.serial_get_analog_full_scale_current() - + @dac_full_scale_current.setter def dac_full_scale_current(self, value): self.board.serial_set_analog_full_scale_current(value) - + @property def dac_nco_enable(self): return self.board.serial_get_nco_enable() - + @dac_nco_enable.setter def dac_nco_enable(self, value): self.board.serial_set_nco_enable(value) - + @property def dac_FIR85_enable(self): return self.board.serial_get_FIR85_enable() - + @dac_FIR85_enable.setter def dac_FIR85_enable(self, value): self.board.serial_set_FIR85_enable(value) - + @property def dac_nco_frequency(self): return self.board.serial_get_nco_frequency() - + @dac_nco_frequency.setter def dac_nco_frequency(self, value): self.board.serial_set_nco_frequency(value) - + @property def dac_pll_reference(self): return self.board.serial_get_reference() - + @dac_pll_reference.setter def dac_pll_reference(self, value): self.board.serial_set_reference(value) + + @property + def dac_shuffle_mode(self): + return self.board.serial_get_shuffle_mode() + + @dac_shuffle_mode.setter + def dac_shuffle_mode(self, value): + self.board.serial_set_shuffle_mode(value) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 50dd0ea8..2b9b65c7 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -210,7 +210,8 @@ def create_from_meta(self, meta_file, averages): self.instrument_proxies = self.generators + self.receivers + self.transmitters + self.all_standalone + self.processors self.instruments = [] for instrument in self.instrument_proxies: - instr = instrument_map[instrument.model](instrument.address, instrument.label) # Instantiate + address = (instrument.address, instrument.serial_port) if hasattr(instrument, 'serial_port') else instrument.address + instr = instrument_map[instrument.model](address, instrument.label) # Instantiate # For easy lookup instr.proxy_obj = instrument instrument.instr = instr # This shouldn't be relied upon From 83dab9a83b52a17f5738a7559c42489ca13c8a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 6 Aug 2019 09:33:59 -0400 Subject: [PATCH 058/109] Set threshold --- src/auspex/qubit/single_shot_fidelity.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/auspex/qubit/single_shot_fidelity.py b/src/auspex/qubit/single_shot_fidelity.py index bb2db6ed..dff0ddd1 100644 --- a/src/auspex/qubit/single_shot_fidelity.py +++ b/src/auspex/qubit/single_shot_fidelity.py @@ -41,11 +41,12 @@ class SingleShotFidelityExperiment(QubitExperiment): optimize (bool, optional): if True and a qubit_sweep is added, set the parameter corresponding to the maximum measured fidelity at the end of the sweep """ - def __init__(self, qubit, output_nodes=None, meta_file=None, optimize=True, **kwargs): + def __init__(self, qubit, output_nodes=None, meta_file=None, optimize=True, set_threshold = True, **kwargs): self.pdf_data = [] self.qubit = qubit self.optimize = optimize + self.set_threshold = set_threshold if meta_file: self.meta_file = meta_file @@ -105,6 +106,8 @@ def run_sweeps(self): if not self.sweeper.axes: self._update_histogram_plots() self.stop_manual_plotters() + if self.set_threshold: + self.stream_selectors[0].threshold = self.get_threshold()[0] elif self.optimize: fidelities = [f['Max I Fidelity'] for f in self.pdf_data] opt_ind = np.argmax(fidelities) @@ -121,6 +124,9 @@ def run_sweeps(self): param = [c for c in self.chan_db.all_instruments() if c.label == set_pair[0]][0] setattr(param, set_pair[1], opt_value) logger.info(f'Set {set_pair[0]} {set_pair[1]} to optimum value {opt_value}') + if self.set_threshold: + self.stream_selectors[0].threshold = self.get_threshold()[opt_ind] + logger.info(f'Set threshold to {self.stream_selectors[0].threshold}') def find_single_shot_filter(self): """Make sure there is one single shot measurement filter in the pipeline.""" From 22c16f89f00104a3224a126535ade284ed654630 Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 8 Aug 2019 16:02:18 -0400 Subject: [PATCH 059/109] Updated APS3 driver --- src/auspex/instruments/aps3.py | 13 +++++++------ src/auspex/instruments/tektronix.py | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 38a7c0e8..44574dbd 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -796,8 +796,11 @@ def load_sequence(self, sequence): self.cache_controller = True def load_waveforms(self, wfA, wfB): - self.write_dram(self.WFA_OFFSET(), wfA) - self.write_dram(self.WFB_OFFSET(), wfB) + wfA_32 = [((wfA[2*i+1] << 16) | wfA[2*i]) for i in range(len(wfA) // 2)] + wfB_32 = [((wfB[2*i+1] << 16) | wfB[2*i]) for i in range(len(wfB) // 2)] + + self.write_dram(self.WFA_OFFSET(), wfA_32) # I + self.write_dram(self.WFB_OFFSET(), wfB_32) # Q def load_sequence_file(self, seq_file): self.sequence_filename = seq_file @@ -816,6 +819,7 @@ def load_sequence_file(self, seq_file): instructions_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) instructions = [int(x) for x in np.frombuffer(file.read(instructions_size*8), dtype=np.uint64)] + self.load_sequence(instructions) self.load_sequence(instructions) data = [] @@ -824,6 +828,7 @@ def load_sequence_file(self, seq_file): data.append([int(x) for x in np.frombuffer(file.read(data_size*2), dtype=np.uint16)]) self.load_waveforms(data[0], data[1]) + self.load_waveforms(data[0], data[1]) def serial_check_alive(self): return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 @@ -832,10 +837,6 @@ def serial_check_alive(self): def sequence_file(self): return self.sequence_filename - @property - def sequence_file(self): - return self.sequence_filename - @sequence_file.setter def sequence_file(self, value): self.load_sequence_file(value) diff --git a/src/auspex/instruments/tektronix.py b/src/auspex/instruments/tektronix.py index 913a2090..1b0e6923 100644 --- a/src/auspex/instruments/tektronix.py +++ b/src/auspex/instruments/tektronix.py @@ -30,11 +30,13 @@ class DPO72004C(SCPIInstrument): record_length = IntCommand(get_string="HOR:ACQLENGTH?;") record_duration = FloatCommand(get_string="HOR:ACQDURATION?;") + button_press = StringCommand(set_string="FPAnel:PRESS {:s};", + allowed_values=["RUnstop", "SINGleseq"]) + def __init__(self, resource_name, *args, **kwargs): resource_name += "::4000::SOCKET" #user guide recommends HiSLIP protocol super(DPO72004C, self).__init__(resource_name, *args, **kwargs) self.name = "Tektronix DPO72004C Oscilloscope" - self.interface._resource.read_termination = u"\n" def clear(self): self.interface.write("CLEAR ALL;") From 8cee442a8630e8d4e3dcea1f6a6ca9419a11205c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 9 Aug 2019 10:36:19 -0400 Subject: [PATCH 060/109] One more fix for APS1 --- src/auspex/qubit/mixer_calibration.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index 169f4702..861e25f6 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -377,13 +377,12 @@ def _setup_awg_ssb(self): self.awg.waveform_frequency = -self.SSB_FREQ self.awg.run_mode = "CW_WAVEFORM" else: - iwf = 0.5 * np.cos(2*np.pi*self.SSB_FREQ*np.ones(1200, dtype=np.float64)) - qwf = -0.5 * np.sin(2*np.pi*self.SSB_FREQ*np.ones(1200, dtype=np.float64)); - self.awg.set_amplitude(1, awg_amp); #TODO: ampl. to be set by looking at phys. chan - self.awg.load_waveform(1, iwf) - self.awg.load_waveform(2, qwf) + iwf = 0.5 * np.cos(2*np.pi*self.SSB_FREQ*np.arange(1200,dtype=np.float64)*1e-6/self.awg.sampling_rate) + qwf = -0.5 * np.sin(2*np.pi*self.SSB_FREQ*np.arange(1200,dtype=np.float64)*1e-6/self.awg.sampling_rate) + self.awg.load_waveform(int(self._phys_chan.label[-2]), iwf) + self.awg.load_waveform(int(self._phys_chan.label[-1]), qwf) self.awg.run_mode = "RUN_WAVEFORM" - #self.awg.repeat_mode = "CONTINUOUS" + self.awg.repeat_mode = "CONTINUOUS" self.awg.trigger_source = "internal" #start playback self.awg.run() From ed1f087f5529b29cca28e0986c233b02967b6894 Mon Sep 17 00:00:00 2001 From: Leonardo Ranzani Date: Fri, 9 Aug 2019 11:15:01 -0400 Subject: [PATCH 061/109] Fix meas_chan.autodyne_freq --DR, LR --- src/auspex/qubit/mixer_calibration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auspex/qubit/mixer_calibration.py b/src/auspex/qubit/mixer_calibration.py index 861e25f6..eea4f207 100644 --- a/src/auspex/qubit/mixer_calibration.py +++ b/src/auspex/qubit/mixer_calibration.py @@ -67,7 +67,7 @@ class MixerCalibration(Calibration): MIN_OFFSET = -0.4 MAX_OFFSET = 0.4 MIN_AMPLITUDE = 0.2 - MAX_AMPLITUDE = 1.2 + MAX_AMPLITUDE = 1.5 MIN_PHASE = -0.3 MAX_PHASE = 0.3 @@ -264,7 +264,7 @@ def __init__(self, channel, spectrum_analyzer, config_dict, mixer="control"): self._awg = channel.measure_chan.phys_chan.transmitter self._phys_chan = channel.measure_chan.phys_chan self._source = channel.measure_chan.phys_chan.generator - self.SSB_FREQ = channel.measure_chan.frequency + self.SSB_FREQ = channel.measure_chan.autodyne_freq elif mixer.lower() == "control": self._awg = channel.phys_chan.transmitter self._phys_chan = channel.phys_chan From f32326039a283da1aafd7327489434afc6c3c528 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Mon, 12 Aug 2019 09:46:00 -0400 Subject: [PATCH 062/109] Fix complex buffer unit test --- test/test_buffer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test_buffer.py b/test/test_buffer.py index c9e12228..2a57587a 100644 --- a/test/test_buffer.py +++ b/test/test_buffer.py @@ -58,7 +58,7 @@ def run(self): time_step = 0.1 time.sleep(0.002) if self.complex_data: - data_row = np.exp(2j*np.pi*self.time_val)*np.ones(5) + 0.1j*np.random.random(5) + data_row = np.sin(2*np.pi*self.time_val)*np.ones(5) + 2.0j*np.sin(2*np.pi*self.time_val)*np.ones(5) else: data_row = np.sin(2*np.pi*self.time_val)*np.ones(5) + 0.1*np.random.random(5) self.time_val += time_step @@ -160,8 +160,7 @@ def test_buffer_complex(self): data, desc = db.get_data() - self.assertAlmostEqual(np.sum(data.real-data.imag), 0.0, places=3) - # self.assertTrue(np.all(desc['field'] == np.linspace(0,100.0,4))) + self.assertAlmostEqual(np.mean(data.imag)/np.mean(data.real), 2.0, places=3) if __name__ == '__main__': unittest.main() From 47ef2df538b67ba938007205355123d0d0204d36 Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Mon, 12 Aug 2019 10:54:56 -0400 Subject: [PATCH 063/109] Fix remaining alazar test --- test/test_alazar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_alazar.py b/test/test_alazar.py index 9f77817b..89e989d7 100644 --- a/test/test_alazar.py +++ b/test/test_alazar.py @@ -121,7 +121,7 @@ def test_qubit_experiment(self): exp.set_fake_data(dig_1, np.cos(np.linspace(-np.pi, np.pi, 51))) exp.run_sweeps() data, desc = exp.buffers[0].get_data() - self.assertAlmostEqual(np.abs(data).sum(),610.5,places=0) + self.assertAlmostEqual(np.abs(data).sum(),459.1,places=0) if __name__ == '__main__': unittest.main() From e034fba1675b873f188674083c689b9df6b7b1b8 Mon Sep 17 00:00:00 2001 From: Billy Date: Wed, 14 Aug 2019 14:12:34 -0400 Subject: [PATCH 064/109] Working APS3 driver --- src/auspex/instruments/aps3.py | 16 +++++++--------- src/auspex/qubit/qubit_exp.py | 2 +- utils/auspex-plot-client.py | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 44574dbd..0a545c19 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -725,13 +725,16 @@ def correction_bypass(self): doc="Select SOF200 test output or APS sequencer output from DAC.") trigger_output_select = BitFieldCommand(register=CSR_BD_CONTROL, shift=5, doc="""True: select trigger to be output on front panel. - False: select marker to be output on front pane.""") + False: select marker to be output on front panel.""") + marker_mode = BitFieldCommand(register=CSR_BD_CONTROL, shift=6, + doc="""True: Marker value is set as specified in 'state' field of instruction. + False: Marker is set high for one clock cycle, then cleared.""") ####### MARKER_DELAY ####################################################### @property def marker_delay(self): - """Gets/sets the marker delay in seconds, based on a 300 MHz clock.""" + """Gets/sets the marker delay in seconds, based on a 312.5 MHz clock.""" return (1 + int(self.read_register(CSR_MARKER_DELAY))) / 312.5e6 @marker_delay.setter def marker_delay(self, value): @@ -759,7 +762,7 @@ def WFB_OFFSET(self): ###### UTILITIES ########################################################### def run(self): logger.info("Configuring JESD...") - self.board.serial_configure_JESD() + #self.board.serial_configure_JESD() sleep(0.01) logger.info("Taking cache controller out of reset...") self.cache_controller = True @@ -788,12 +791,10 @@ def load_sequence(self, sequence): packed_seq.append(instr & U32) packed_seq.append(instr >> 32) - self.cache_controller = False sleep(0.01) self.write_dram(self.SEQ_OFFSET(), packed_seq) logger.info(f"Wrote {len(packed_seq)} words to sequence memory.") sleep(0.01) - self.cache_controller = True def load_waveforms(self, wfA, wfB): wfA_32 = [((wfA[2*i+1] << 16) | wfA[2*i]) for i in range(len(wfA) // 2)] @@ -819,16 +820,13 @@ def load_sequence_file(self, seq_file): instructions_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) instructions = [int(x) for x in np.frombuffer(file.read(instructions_size*8), dtype=np.uint64)] - self.load_sequence(instructions) - self.load_sequence(instructions) - data = [] for chan in range(num_channels): data_size = int(np.frombuffer(file.read(8), dtype=np.uint64)[0]) data.append([int(x) for x in np.frombuffer(file.read(data_size*2), dtype=np.uint16)]) self.load_waveforms(data[0], data[1]) - self.load_waveforms(data[0], data[1]) + self.load_sequence(instructions) def serial_check_alive(self): return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 2b9b65c7..c7ed0d11 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -210,7 +210,7 @@ def create_from_meta(self, meta_file, averages): self.instrument_proxies = self.generators + self.receivers + self.transmitters + self.all_standalone + self.processors self.instruments = [] for instrument in self.instrument_proxies: - address = (instrument.address, instrument.serial_port) if hasattr(instrument, 'serial_port') else instrument.address + address = (instrument.address, instrument.serial_port) if hasattr(instrument, 'serial_port') and instrument.serial_port is not None else instrument.address instr = instrument_map[instrument.model](address, instrument.label) # Instantiate # For easy lookup instr.proxy_obj = instrument diff --git a/utils/auspex-plot-client.py b/utils/auspex-plot-client.py index 47460d38..38980252 100755 --- a/utils/auspex-plot-client.py +++ b/utils/auspex-plot-client.py @@ -18,7 +18,7 @@ import json import ctypes -single_window = False +single_window = True plot_windows = [] import logging @@ -521,7 +521,7 @@ def new_plotter_window_mdi(message): desc = json.loads(desc) pw = MatplotClientSubWindow() - + pw.setWindowTitle("%s" % progname) pw.construct_plots(desc) pw.listen_for_data(uuid, len(desc)) @@ -563,7 +563,7 @@ def closeEvent(self, ce): if self.desc_listener_thread: self.stop_listening(True) self.close() - + class PlotMDI(ListenerMixin,QtWidgets.QMainWindow): def __init__(self, parent = None): From 7f33ca7410c5c71154ad3ffe7a251e7463024519 Mon Sep 17 00:00:00 2001 From: gribeill Date: Wed, 14 Aug 2019 15:44:49 -0400 Subject: [PATCH 065/109] Set port power depending on meas direction --- src/auspex/instruments/agilent.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index fe3460e5..9dae628e 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -717,7 +717,8 @@ class AgilentE8363C(SCPIInstrument): TIMEOUT = 10. * 1000. #milliseconds - power = FloatCommand(scpi_string=":SOURce:POWer:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) + power_port1 = FloatCommand(scpi_string=":SOURce:POWer1:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) + power_port2 = FloatCommand(scpi_string=":SOURce:POWer2:LEVel:IMMediate:AMPLitude", value_range=(-27, 20)) frequency_center = FloatCommand(scpi_string=":SENSe:FREQuency:CENTer") frequency_span = FloatCommand(scpi_string=":SENSe:FREQuency:SPAN") frequency_start = FloatCommand(scpi_string=":SENSe:FREQuency:STARt") @@ -730,7 +731,7 @@ class AgilentE8363C(SCPIInstrument): sweep_time = FloatCommand(get_string=":SENSe:SWEep:TIME?") def __init__(self, resource_name=None, *args, **kwargs): - #If we only have an IP address then tack on the raw socket port to the VISA resource string + #If wenumber only have an IP address then tack on the raw socket port to the VISA resource string super(AgilentE8363C, self).__init__(resource_name, *args, **kwargs) def connect(self, resource_name=None, interface_type="VISA"): @@ -768,6 +769,17 @@ def averaging_enable(self, value): else: self.interface.write(":SENSe1:AVERage:STATe OFF") + @property + def power(self): + return self.power_port1 if self.measurement[-1] == '1' else self.power_port2 + + @power.setter + def power(self, value): + if self.measurement[-1] == '1': + self.power_port1 = value + else: + self.power_port2 = value + @property def measurement(self): meas = self.interface.query(":CALC:PAR:CAT?") From e77f506512b7a36f80b06c1aa5e9cd70ef82d9a4 Mon Sep 17 00:00:00 2001 From: gribeill Date: Wed, 14 Aug 2019 15:48:26 -0400 Subject: [PATCH 066/109] Typo --- src/auspex/instruments/agilent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/instruments/agilent.py b/src/auspex/instruments/agilent.py index 9dae628e..83c43aa0 100644 --- a/src/auspex/instruments/agilent.py +++ b/src/auspex/instruments/agilent.py @@ -731,7 +731,7 @@ class AgilentE8363C(SCPIInstrument): sweep_time = FloatCommand(get_string=":SENSe:SWEep:TIME?") def __init__(self, resource_name=None, *args, **kwargs): - #If wenumber only have an IP address then tack on the raw socket port to the VISA resource string + #If we only have an IP address then tack on the raw socket port to the VISA resource string super(AgilentE8363C, self).__init__(resource_name, *args, **kwargs) def connect(self, resource_name=None, interface_type="VISA"): From f6f906928754f7b2b93823ee959a2b5c513198cb Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Thu, 15 Aug 2019 10:01:13 -0400 Subject: [PATCH 067/109] Fix for #386, use descriptor to set buffer size (with sensible limit) using a new final_init method for the DataStreams --- src/auspex/experiment.py | 7 +++++++ src/auspex/stream.py | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index db88d299..9889fd64 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -186,6 +186,9 @@ def __init__(self): self.manual_plotter_callbacks = [] # These are called at the end of run self._extra_plots_to_streams = {} + # Keep track of additional DataStreams created for manual plotters, etc. + self.extra_streams = [] + # Furthermore, keep references to all of the file writers and buffers. self.writers = [] self.buffers = [] @@ -462,6 +465,9 @@ def final_init(self): for n in self.nodes + self.extra_plotters: if n != self and hasattr(n, 'final_init'): n.final_init() + # Call final init on the DataStreams to fix their shared memory buffer sizes + for _,_,data in self.graph.dag.edges(data=True): + data['object'].final_init() self.init_progress_bars() def init_progress_bars(self): @@ -681,6 +687,7 @@ def add_direct_plotter(self, plotter): """A plotter that lives outside the filter pipeline, intended for advanced use cases when plotting data during refinement.""" plotter_stream = DataStream() + self.extra_streams.append(plotter_stream) plotter.sink.add_input_stream(plotter_stream) self.extra_plotters.append(plotter) self._extra_plots_to_streams[plotter] = plotter_stream diff --git a/src/auspex/stream.py b/src/auspex/stream.py index b45663ee..82f7cf6b 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -489,8 +489,15 @@ def __init__(self, name=None, unit=None): # Shared memory interface self.buffer_lock = mp.Lock() - self.buffer_size = 500000 + # self.buffer_size = 500000 self.buff_idx = Value('i', 0) + + def final_init(self): + self.buffer_size = self.descriptor.num_points() + # logger.info(f"{self.start_connector} to {self.end_connector} buffer of size {self.buffer_size}") + if self.buffer_size > 50e6: + logger.info("Limiting buffer size of {self} to 50 Million Points") + self.buffer_size = 50e6 self.buff_shared_re = RawArray(ctypes.c_double, self.buffer_size) self.buff_shared_im = RawArray(ctypes.c_double, self.buffer_size) self.re_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) From 87e38a75f1286d60a63c8b01e1df52f5c71e7bcb Mon Sep 17 00:00:00 2001 From: Leonardo Ranzani Date: Thu, 15 Aug 2019 13:42:05 -0400 Subject: [PATCH 068/109] Forces buffer size to be int, required for RawArray to work (50e6 makes a float) --- src/auspex/stream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auspex/stream.py b/src/auspex/stream.py index 82f7cf6b..3f24474f 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -498,8 +498,8 @@ def final_init(self): if self.buffer_size > 50e6: logger.info("Limiting buffer size of {self} to 50 Million Points") self.buffer_size = 50e6 - self.buff_shared_re = RawArray(ctypes.c_double, self.buffer_size) - self.buff_shared_im = RawArray(ctypes.c_double, self.buffer_size) + self.buff_shared_re = RawArray(ctypes.c_double, int(self.buffer_size)) + self.buff_shared_im = RawArray(ctypes.c_double, int(self.buffer_size)) self.re_np = np.frombuffer(self.buff_shared_re, dtype=np.float64) self.im_np = np.frombuffer(self.buff_shared_im, dtype=np.float64) From 602c900bb2b9f4e70dd1f4bae62d04598ccc174a Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Mon, 19 Aug 2019 10:17:38 -0400 Subject: [PATCH 069/109] Fix buffer sizing for partial averages --- src/auspex/filters/average.py | 7 ++++++- src/auspex/stream.py | 11 ++++++++++- test/test_average.py | 1 + 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/auspex/filters/average.py b/src/auspex/filters/average.py index f58fb22c..1a34ea5c 100644 --- a/src/auspex/filters/average.py +++ b/src/auspex/filters/average.py @@ -142,7 +142,12 @@ def update_descriptors(self): else: descriptor.visited_tuples = np.empty((0), dtype=desc_out_dtype) - for stream in self.partial_average.output_streams + self.source.output_streams: + for stream in self.partial_average.output_streams: + stream.set_descriptor(descriptor) + stream.descriptor.buffer_mult_factor = 20 + stream.end_connector.update_descriptors() + + for stream in self.source.output_streams: stream.set_descriptor(descriptor) stream.end_connector.update_descriptors() diff --git a/src/auspex/stream.py b/src/auspex/stream.py index 3f24474f..f9dff5dc 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -253,6 +253,12 @@ def __init__(self, dtype=np.float32): self.dtype = dtype self.metadata = {} + # Buffer size multiplier: use this to inflate the size of the + # shared memory buffer. This is needed for partial averages, which + # may require more space than their descriptors would indicate + # since they are emitted as often as possible. + self.buffer_mult_factor = 1 + # Keep track of the parameter permutations we have actually used... self.visited_tuples = [] @@ -493,7 +499,7 @@ def __init__(self, name=None, unit=None): self.buff_idx = Value('i', 0) def final_init(self): - self.buffer_size = self.descriptor.num_points() + self.buffer_size = self.descriptor.num_points()*self.descriptor.buffer_mult_factor # logger.info(f"{self.start_connector} to {self.end_connector} buffer of size {self.buffer_size}") if self.buffer_size > 50e6: logger.info("Limiting buffer size of {self} to 50 Million Points") @@ -559,6 +565,9 @@ def push(self, data): with self.buffer_lock: start = self.buff_idx.value re = np.real(data).flatten() + if start+re.size > self.re_np.size: + raise ValueError(f"Stream {self} received more data than fits in the shared memory buffer. \ + This is probably due to digitizer raw streams producing data too quickly for the pipeline.") self.re_np[start:start+re.size] = re if np.issubdtype(self.descriptor.dtype, np.complexfloating): im = np.imag(data).flatten() diff --git a/test/test_average.py b/test/test_average.py index ca901191..16c04b6f 100644 --- a/test/test_average.py +++ b/test/test_average.py @@ -53,6 +53,7 @@ def run(self): time.sleep(0.002) data_row = np.ones(self.samples*self.num_trials) + 0.1*np.random.random(self.samples*self.num_trials) self.time_val += time_step + # logger.info(f"IN RUN METHOD PUSHING: {data_row} {data_row.size} {data_row.shape}") self.chan1.push(data_row) logger.debug("Stream pushed points {}.".format(data_row)) logger.debug("Stream has filled {} of {} points".format(self.chan1.points_taken, self.chan1.num_points() )) From 32c3906a63ec4d12aa2d287b340c885543c82f2b Mon Sep 17 00:00:00 2001 From: Graham Rowlands Date: Mon, 19 Aug 2019 10:39:48 -0400 Subject: [PATCH 070/109] Fix final_init call for exp graph with multiple edges betwen nodes --- src/auspex/experiment.py | 5 +++-- src/auspex/stream.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 9889fd64..257312b9 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -466,8 +466,9 @@ def final_init(self): if n != self and hasattr(n, 'final_init'): n.final_init() # Call final init on the DataStreams to fix their shared memory buffer sizes - for _,_,data in self.graph.dag.edges(data=True): - data['object'].final_init() + for edge in self.graph.edges: + edge.final_init() + self.init_progress_bars() def init_progress_bars(self): diff --git a/src/auspex/stream.py b/src/auspex/stream.py index f9dff5dc..41abd571 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -500,7 +500,7 @@ def __init__(self, name=None, unit=None): def final_init(self): self.buffer_size = self.descriptor.num_points()*self.descriptor.buffer_mult_factor - # logger.info(f"{self.start_connector} to {self.end_connector} buffer of size {self.buffer_size}") + # logger.info(f"{self.start_connector.parent}:{self.start_connector} to {self.end_connector.parent}:{self.end_connector} buffer of size {self.buffer_size}") if self.buffer_size > 50e6: logger.info("Limiting buffer size of {self} to 50 Million Points") self.buffer_size = 50e6 From 3e242dd7a5bd0e41132b7c06f356308d37480ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 19 Aug 2019 14:12:58 -0400 Subject: [PATCH 071/109] Fix open data with dialog box Choose experiment folder instead of file (specificed by groupname) --- src/auspex/analysis/helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auspex/analysis/helpers.py b/src/auspex/analysis/helpers.py index 84a85081..5be639ea 100644 --- a/src/auspex/analysis/helpers.py +++ b/src/auspex/analysis/helpers.py @@ -14,7 +14,7 @@ def get_file_name(): root = tk.Tk() root.withdraw() # remove edges - filepath = filedialog.askopenfilename() + filepath = [filedialog.askdirectory()] root.update() # fixes OSX hanging issue #https://stackoverflow.com/questions/21866537/what-could-cause-an-open-file-dialog-window-in-tkinter-python-to-be-really-slow @@ -28,7 +28,7 @@ def open_data(num=None, folder=None, groupname="main", datasetname="data", date= num (int) File number to be loaded. folder (string) - Base folder where file is stored. If the `date` parameter is not None, assumes file is a dated folder. + Base folder where file is stored. If the `date` parameter is not None, assumes file is a dated folder. If no folder is specified, open a dialogue box. Open the folder with the desired ExperimentName-NNNN.auspex, then press OK groupname (string) Group name of data to be loaded. datasetname (string, optional) @@ -51,6 +51,7 @@ def open_data(num=None, folder=None, groupname="main", datasetname="data", date= if num is None or folder is None: # pull up dialog box data_file = get_file_name() + folder = "" else: if date is not None: folder = path.join(folder, date) From e25e5f7e02820afdf3afed4e869a79c8f7875762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 19 Aug 2019 14:18:54 -0400 Subject: [PATCH 072/109] Get date at runtime As opposed to when the function is imported --- src/auspex/analysis/helpers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auspex/analysis/helpers.py b/src/auspex/analysis/helpers.py index 5be639ea..7fc10c27 100644 --- a/src/auspex/analysis/helpers.py +++ b/src/auspex/analysis/helpers.py @@ -20,7 +20,7 @@ def get_file_name(): return filepath -def open_data(num=None, folder=None, groupname="main", datasetname="data", date=datetime.date.today().strftime('%y%m%d')): +def open_data(num=None, folder=None, groupname="main", datasetname="data", date=None): """Convenience Load data from an `AuspexDataContainer` given a file number and folder. Assumes that files are named with the convention `ExperimentName-NNNNN.auspex` @@ -53,8 +53,9 @@ def open_data(num=None, folder=None, groupname="main", datasetname="data", date= data_file = get_file_name() folder = "" else: - if date is not None: - folder = path.join(folder, date) + if date == None: + date = datetime.date.today().strftime('%y%m%d') + folder = path.join(folder, date) assert path.isdir(folder), f"Could not find data folder: {folder}" p = re.compile(r".+-(\d+).auspex") From 8e5d7f83cf2806d25ac681aa30fd95db6596e236 Mon Sep 17 00:00:00 2001 From: Billy Date: Mon, 19 Aug 2019 18:21:14 -0400 Subject: [PATCH 073/109] Added manual sweep --- src/auspex/qubit/qubit_exp.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 6336bde4..47c61042 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -439,6 +439,15 @@ def method(value, channel=channel, instr=instr, prop=attribute): param.assign_method(method) self.add_sweep(param, values) # Create the requested sweep on this parameter + def add_manual_sweep(self, label, prompt, values, channel=None): + param = FloatParameter() # Create the parameter + param.name = label + def method(value): + print(f'Manually set {label} to {value}, then press enter.') + input() + param.assign_method(method) + self.add_sweep(param, values) # Create the requested sweep on this parameter + def add_qubit_sweep(self, qubit, measure_or_control, attribute, values): """ Add a *ParameterSweep* to the experiment. Users specify a qubit property that auspex From 143bf42937600291542c96e61e2a54cbe488b664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 20 Aug 2019 09:07:10 -0400 Subject: [PATCH 074/109] Restore free proliferation of windows --- utils/auspex-plot-client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/auspex-plot-client.py b/utils/auspex-plot-client.py index 38980252..acc4fce8 100755 --- a/utils/auspex-plot-client.py +++ b/utils/auspex-plot-client.py @@ -18,7 +18,7 @@ import json import ctypes -single_window = True +single_window = False plot_windows = [] import logging From 466f14d58b3a009c036f85960518960a30c48a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 20 Aug 2019 09:15:52 -0400 Subject: [PATCH 075/109] Require serial Require serial Require serial --- .travis.yml | 2 +- requirements.txt | 1 + setup.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 935f1821..678d4e3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ install: - pip install git+https://github.com/BBN-Q/bbndb.git - pip install git+https://github.com/BBN-Q/QGL.git@develop - pip install git+https://github.com/spatialaudio/nbsphinx.git@master - - pip install pyvisa coveralls scikit-learn pyusb future python-usbtmc setproctitle progress + - pip install pyvisa coveralls scikit-learn pyusb future python-usbtmc setproctitle progress serial - export GIT_LFS_SKIP_SMUDGE=0 - export PYTHONPATH=$PYTHONPATH:$PWD/src diff --git a/requirements.txt b/requirements.txt index 380bb250..9babe703 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,4 +16,5 @@ pyusb >= 1.0.2 ipykernel>=5.0.0 ipywidgets>=7.0.0 setproctitle +serial git+https://github.com/spatialaudio/nbsphinx.git@master diff --git a/setup.py b/setup.py index 78b8c279..ccd579a2 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ "ipywidgets>=7.0.0", "sqlalchemy >= 1.2.15", "setproctitle", + "serial", "progress" ] From b98e02e5fbca9dad23345ba9774693b8d904c171 Mon Sep 17 00:00:00 2001 From: gribeill Date: Fri, 23 Aug 2019 17:20:25 -0400 Subject: [PATCH 076/109] Data stream np array -SF, AW --- src/auspex/stream.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auspex/stream.py b/src/auspex/stream.py index 41abd571..7d3824d9 100644 --- a/src/auspex/stream.py +++ b/src/auspex/stream.py @@ -253,7 +253,7 @@ def __init__(self, dtype=np.float32): self.dtype = dtype self.metadata = {} - # Buffer size multiplier: use this to inflate the size of the + # Buffer size multiplier: use this to inflate the size of the # shared memory buffer. This is needed for partial averages, which # may require more space than their descriptors would indicate # since they are emitted as often as possible. @@ -564,7 +564,7 @@ def push(self, data): raise ValueError("Got data {} that is neither an array nor a float".format(data)) with self.buffer_lock: start = self.buff_idx.value - re = np.real(data).flatten() + re = np.real(np.array(data)).flatten() if start+re.size > self.re_np.size: raise ValueError(f"Stream {self} received more data than fits in the shared memory buffer. \ This is probably due to digitizer raw streams producing data too quickly for the pipeline.") @@ -573,7 +573,7 @@ def push(self, data): im = np.imag(data).flatten() self.im_np[start:start+im.size] = im message = {"type": "data", "data": None} - self.buff_idx.value = start + data.size + self.buff_idx.value = start + np.array(data).size self.queue.put(message) def pop(self): From 5560e6f4d1f34c0489dfa8f2610888260a573a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 27 Aug 2019 16:21:58 -0400 Subject: [PATCH 077/109] Save exp. chanDb In a qubit_exp, save a channel library associated to each experiment --- src/auspex/experiment.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 257312b9..4891f8a6 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -31,7 +31,7 @@ pass import inspect -import time +import time, datetime import copy import itertools import logging @@ -513,6 +513,14 @@ def run_sweeps(self): for w in wrs: w.filename.value = inc_filename self.filenames = [w.filename.value for w in self.writers] + # Save ChannelLibrary version + if hasattr(self, 'chan_db') and self.filenames: + import bbndb + exp_chandb = bbndb.deepcopy_sqla_object(self.chan_db, self.cl_session) + exp_chandb.label = os.path.basename(self.filenames[0]) + exp_chandb.time = datetime.datetime.now() + exp_chandb.notes = '' + self.cl_session.commit() # Remove the nodes with 0 dimension self.nodes = [n for n in self.nodes if not(hasattr(n, 'input_connectors') and n.input_connectors['sink'].descriptor.num_dims()==0)] From c3ddc27a528d58a609953e5757a329be7c1f0f98 Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 29 Aug 2019 09:42:49 -0400 Subject: [PATCH 078/109] Merge conflicts --- src/auspex/instruments/X6.py | 4 +- src/auspex/instruments/alazar.py | 4 +- src/auspex/instruments/tektronix.py | 72 ++++++++++++++++++++++++++- src/auspex/qubit/pulse_calibration.py | 19 +++---- 4 files changed, 84 insertions(+), 15 deletions(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index cce159f1..0bc39c91 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -256,7 +256,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) # for chan, wsock in self._chan_to_wsocket.items(): for i in range(segs): if chan.stream_type == "integrated": - # random_mag*(np.random.random(length).astype(chan.dtype) + 1j*np.random.random(length).astype(chan.dtype)) + + # random_mag*(np.random.random(length).astype(chan.dtype) + 1j*np.random.random(length).astype(chan.dtype)) + buff[i,:] = ideal_data[i] elif chan.stream_type == "demodulated": buff[i, int(length/4):int(3*length/4)] = 1.0 if ideal_data[i] == 0 else ideal_data[i] @@ -279,7 +279,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) def receive_data(self, channel, oc, exit, ready, run): try: sock = self._chan_to_rsocket[channel] - sock.settimeout(2) + sock.settimeout(4) self.last_timestamp.value = datetime.datetime.now().timestamp() total = 0 ready.value += 1 diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index a98b6647..b4d06bf9 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -155,7 +155,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) for i in range(self.number_segments): signal = np.sin(np.linspace(0,10.0*np.pi,int(length/2))) buff[i, int(length/4):int(length/4)+len(signal)] = signal * (1.0 if ideal_data[i] == 0 else ideal_data[i]) - + buff += random_mag*np.random.random((self.number_segments, length)) wsock.send(struct.pack('n', self.number_segments*length*np.float32().itemsize) + buff.flatten().tostring()) @@ -272,7 +272,7 @@ def configure_with_dict(self, settings_dict): 'triggerLevel': 100, 'triggerSlope': "rising", 'triggerSource': "Ext", - 'verticalCoupling': "AC", + 'verticalCoupling': "DC", 'verticalOffset': 0.0, 'verticalScale': self.proxy_obj.vertical_scale } diff --git a/src/auspex/instruments/tektronix.py b/src/auspex/instruments/tektronix.py index 1b0e6923..2b9450c7 100644 --- a/src/auspex/instruments/tektronix.py +++ b/src/auspex/instruments/tektronix.py @@ -9,7 +9,7 @@ __all__ = ['DPO72004C'] from auspex.log import logger -from .instrument import SCPIInstrument, StringCommand, FloatCommand, IntCommand +from .instrument import SCPIInstrument, Command, StringCommand, BoolCommand, FloatCommand, IntCommand, is_valid_ipv4 import numpy as np class DPO72004C(SCPIInstrument): @@ -49,7 +49,7 @@ def snap(self): def get_curve(self, channel=1, byte_depth=2): channel_string = "CH{:d}".format(channel) self.interface.write("DAT:SOU {:s};".format(channel_string)) - self.source_channel = 1 + #self.source_channel = 1 self.encoding = "SRI" # Signed ints record_length = self.record_length @@ -83,3 +83,71 @@ def get_fastaq_curve(self, channel=1): def get_math_curve(self, channel=1): pass + +class RSA3308A(SCPIInstrument): + """Tektronix RSA3308A SA""" + instrument_type = "Spectrum Analyzer" + + frequency_center = FloatCommand(scpi_string=":FREQuency:CENTer") + frequency_span = FloatCommand(scpi_string=":FREQuency:SPAN") + frequency_start = FloatCommand(scpi_string=":FREQuency:STARt") + frequency_stop = FloatCommand(scpi_string=":FREQuency:STOP") + + num_sweep_points = FloatCommand(scpi_string=":SWEep:POINTs") + resolution_bandwidth = FloatCommand(scpi_string=":BANDwidth") + sweep_time = FloatCommand(scpi_string=":SWEep:TIME") + averaging_count = IntCommand(scpi_string=':AVER:COUN') + + marker1_amplitude = FloatCommand(scpi_string=':CALC:MARK1:Y') + marker1_position = FloatCommand(scpi_string=':CALC:MARK1:X') + + mode = StringCommand(scpi_string=":INSTrument", allowed_values=["SA", "BASIC", "PULSE", "PNOISE"]) + + # phase noise application commands + pn_offset_start = FloatCommand(scpi_string=":LPLot:FREQuency:OFFSet:STARt") + pn_offset_stop = FloatCommand(scpi_string=":LPLot:FREQuency:OFFSet:STOP") + pn_carrier_freq = FloatCommand(scpi_string=":FREQuency:CARRier") + + def __init__(self, resource_name=None, *args, **kwargs): + super(RSA3308A, self).__init__(resource_name, *args, **kwargs) + + def connect(self, resource_name=None, interface_type=None): + if resource_name is not None: + self.resource_name = resource_name + #If we only have an IP address then tack on the raw socket port to the VISA resource string + if is_valid_ipv4(self.resource_name): + self.resource_name += "::5025::SOCKET" + super(RSA3308A, self).connect(resource_name=self.resource_name, interface_type=interface_type) + self.interface._resource.read_termination = u"\n" + self.interface._resource.write_termination = u"\n" + self.interface._resource.timeout = 3000 #seem to have trouble timing out on first query sometimes + + def get_axis(self): + return np.linspace(self.frequency_start, self.frequency_stop, self.num_sweep_points) + + def get_trace(self, num=1): + self.interface.write(':FORM:DATA REAL,32') + return self.interface.query_binary_values(":TRACE:DATA? TRACE{:d}".format(num), + datatype="f", is_big_endian=True) + + def get_pn_trace(self, num=3): + # num = 3 is raw data + # num = 4 is smoothed data + # returns a tuple of (freqs, dBc/Hz) + self.interface.write(":FORM:DATA ASCII") + response = self.interface.query(":FETCH:LPLot{:d}?".format(num)) + xypts = np.array([float(x) for x in response.split(',')]) + return xypts[::2], xypts[1::2] + + def restart_sweep(self): + """ Aborts current sweep and restarts. """ + self.interface.write(":INITiate:RESTart") + + def peak_search(self, marker=1): + self.interface.write(':CALC:MARK{:d}:MAX'.format(marker)) + + def marker_to_center(self, marker=1): + self.interface.write(':CALC:MARK{:d}:CENT'.format(marker)) + + def clear_averaging(self): + self.interface.write(':AVER:CLE') diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index d3dff272..ab05d9fb 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -704,11 +704,12 @@ def update_settings(self): for edge in self.qubit.edge_target: edge_source = edge.phys_chan.generator edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency - # # TODO: fix this for db backend - - # qubit_set_freq = self.saved_settings['instruments'][qubit_source]['frequency'] + self.saved_settings['qubits'][self.qubit.label]['control']['frequency'] - # logger.info("Qubit set frequency = {} GHz".format(round(float(qubit_set_freq/1e9),5))) - # return ('frequency', qubit_set_freq) + if self.sample: + frequency = self.qubit_source.frequency if self.set_source else self.qubit.frequency + c = bbndb.calibration.Calibration(value=frequency, sample=self.sample, name="Ramsey") + c.date = datetime.datetime.now() + bbndb.get_cl_session().add(c) + bbndb.get_cl_session().commit() class PhaseEstimation(QubitCalibration): @@ -949,8 +950,8 @@ def _calibrate(self): self.plot["Fit 0"] = (finer_xaxis, np.polyval(all_params_0, finer_xaxis) if self.cal_type == CR_cal_type.AMP else sinf(finer_xaxis, **all_params_0)) self.plot["Data 1"] = (xaxis, data_t[len(data_t)//2:]) self.plot["Fit 1"] = (finer_xaxis, np.polyval(all_params_1, finer_xaxis) if self.cal_type == CR_cal_type.AMP else sinf(finer_xaxis, **all_params_1)) - - # Optimal parameter within range of original data! + + # Optimal parameter within range of original data! if self.opt_par > np.min(xaxis) and self.opt_par < np.max(xaxis): self.succeeded = True @@ -968,7 +969,7 @@ def __init__(self, edge, lengths=np.linspace(20, 1020, 21)*1e-9, phase=0, amp=0. def sequence(self): qc, qt = self.qubits seqs = [[Id(qc)] + echoCR(qc, qt, length=l, phase = self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] - seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] + seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] seqs += create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) return seqs @@ -977,7 +978,7 @@ def descriptor(self): delay_descriptor(np.concatenate((self.lengths, self.lengths))), cal_descriptor(tuple(self.qubits), 2) ] - + class CRPhaseCalibration(CRCalibration): cal_type = CR_cal_type.PHASE From 9a7daa58ff84c055f38502fc242202aa3424f0bf Mon Sep 17 00:00:00 2001 From: Billy Kalfus Date: Thu, 29 Aug 2019 09:51:06 -0400 Subject: [PATCH 079/109] Read aps3 seq --- src/auspex/instruments/aps3.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index 0a545c19..fd39e90e 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -797,11 +797,28 @@ def load_sequence(self, sequence): sleep(0.01) def load_waveforms(self, wfA, wfB): + print(len(wfA)) + wfA_32 = [((wfA[2*i+1] << 16) | wfA[2*i]) for i in range(len(wfA) // 2)] wfB_32 = [((wfB[2*i+1] << 16) | wfB[2*i]) for i in range(len(wfB) // 2)] self.write_dram(self.WFA_OFFSET(), wfA_32) # I self.write_dram(self.WFB_OFFSET(), wfB_32) # Q + + def read_waveforms(self, wf_len): + wfA_32 = self.read_dram(self.WFA_OFFSET(), wf_len // 2) + wfB_32 = self.read_dram(self.WFB_OFFSET(), wf_len // 2) + + wfA = [] + wfB = [] + + for i in range(wf_len // 2): + wfA.append(wfA_32[i] & 0xFFFF) + wfA.append((wfA_32[i] >> 16) & 0xFFFF) + wfB.append(wfB_32[i] & 0xFFFF) + wfB.append((wfB_32[i] >> 16) & 0xFFFF) + + return wfA, wfB def load_sequence_file(self, seq_file): self.sequence_filename = seq_file From f846d4c6274d6a54180b36fc3ce3beb4bbb7a973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 17 Sep 2019 09:46:15 -0400 Subject: [PATCH 080/109] Make chan db saving optional At every experiment --- src/auspex/experiment.py | 5 ++++- src/auspex/qubit/qubit_exp.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/auspex/experiment.py b/src/auspex/experiment.py index 4891f8a6..ff1887ad 100644 --- a/src/auspex/experiment.py +++ b/src/auspex/experiment.py @@ -205,6 +205,9 @@ def __init__(self): # add date to data files? self.add_date = False + # save channel library + self.save_chanddb = False + # Things we can't metaclass self.output_connectors = {} for oc in self._output_connectors.keys(): @@ -514,7 +517,7 @@ def run_sweeps(self): w.filename.value = inc_filename self.filenames = [w.filename.value for w in self.writers] # Save ChannelLibrary version - if hasattr(self, 'chan_db') and self.filenames: + if hasattr(self, 'chan_db') and self.filenames and self.save_chandb: import bbndb exp_chandb = bbndb.deepcopy_sqla_object(self.chan_db, self.cl_session) exp_chandb.label = os.path.basename(self.filenames[0]) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 47c61042..c25b0b85 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -68,7 +68,7 @@ class QubitExperiment(Experiment): """ - def __init__(self, meta_file, averages=100, exp_name=None, **kwargs): + def __init__(self, meta_file, averages=100, exp_name=None, save_chandb=True, **kwargs): super(QubitExperiment, self).__init__(**kwargs) if not pipeline.pipelineMgr: @@ -82,6 +82,7 @@ def __init__(self, meta_file, averages=100, exp_name=None, **kwargs): self.outputs_by_qubit = {} self.progressbars = None + self.save_chandb = save_chandb self.create_from_meta(meta_file, averages) From 67b7b4d1542105ef3b4983d08b4596c52398067d Mon Sep 17 00:00:00 2001 From: William Kalfus Date: Tue, 17 Sep 2019 15:53:39 -0400 Subject: [PATCH 081/109] Support for both APS3 channels --- src/auspex/instruments/aps3.py | 262 ++++++++++++++++++++++----------- src/auspex/qubit/qubit_exp.py | 8 +- 2 files changed, 180 insertions(+), 90 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index fd39e90e..aa35faa2 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -244,43 +244,47 @@ def read_memory(self, addr, num_words): resp_header = self.recv_bytes(2 * 4) #4 bytes per word... return self.recv_bytes(4 * num_words) - def serial_read_dac0(self, addr): + def serial_read_dac_register(self, dac, addr): + if dac not in [0, 1]: + raise ValueError('Invalid DAC number ' + str(dac)) self.ser.reset_output_buffer() self.ser.reset_input_buffer() - self.ser.write(bytearray('rd d0 {0:#x}\n'.format(addr), 'ascii')) + self.ser.write(bytearray('rd d{} {:#x}\n'.format(dac, addr), 'ascii')) self.ser.readline() # Throw out the echo line from the terminal interface resp = self.ser.readline().decode() start_index = len('Read value = ') end_index = resp.find('@') return int(resp[start_index:end_index], 16) - def serial_write_dac0(self, addr, val): + def serial_write_dac_register(self, dac, addr, val): + if dac not in [0, 1]: + raise ValueError('Invalid DAC number ' + str(dac)) self.ser.reset_output_buffer() self.ser.reset_input_buffer() - self.ser.write(bytearray('wd d0 {0:#x} {1:#x}\n'.format(addr, val), 'ascii')) + self.ser.write(bytearray('wd d{} {:#x} {:#x}\n'.format(dac, addr, val), 'ascii')) self.ser.readline() # Throw out the echo line from the terminal interface return self.ser.readline() # Echo back the "wrote xx to xx" line - def serial_configure_JESD(self): + def serial_configure_JESD(self, dac): # Configure the JESD interface properly - logger.info(self.serial_write_dac0(0x300, 0x00)) # disable all links + logger.debug(self.serial_write_dac_register(dac, 0x300, 0x00)) # disable all links sleep(0.01) - logger.info(self.serial_write_dac0(0x475, 0x09)) # soft reset DAC0 deframer + logger.debug(self.serial_write_dac_register(dac, 0x475, 0x09)) # soft reset DAC0 deframer sleep(0.01) - logger.info(self.serial_write_dac0(0x110, 0x81)) # set interpolation to 2 + logger.debug(self.serial_write_dac_register(dac, 0x110, 0x81)) # set interpolation to 2 sleep(0.01) - logger.info(self.serial_write_dac0(0x456, 0x01)) # set M=2 + logger.debug(self.serial_write_dac_register(dac, 0x456, 0x01)) # set M=2 sleep(0.01) - logger.info(self.serial_write_dac0(0x459, 0x21)) # set S=2 + logger.debug(self.serial_write_dac_register(dac, 0x459, 0x21)) # set S=2 sleep(0.01) - logger.info(self.serial_write_dac0(0x477, 0x00)) # disable ILS_MODE for DAC0 + logger.debug(self.serial_write_dac_register(dac, 0x477, 0x00)) # disable ILS_MODE for DAC0 sleep(0.01) - logger.info(self.serial_write_dac0(0x475, 0x01)) # bring DAC0 deframer out of reset + logger.debug(self.serial_write_dac_register(dac, 0x475, 0x01)) # bring DAC0 deframer out of reset sleep(0.01) - logger.info(self.serial_write_dac0(0x300, 0x01)) # enable all links + logger.debug(self.serial_write_dac_register(dac, 0x300, 0x01)) # enable all links sleep(0.01) - def serial_set_switch_mode(self, mode): + def serial_set_switch_mode(self, dac, mode): ''' Sets DAC output switch mode to one of NRZ, Mix-Mode, or RZ. Parameters: @@ -298,9 +302,9 @@ def serial_set_switch_mode(self, mode): if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: - logger.info(self.serial_write_dac0(0x152, code)) + logger.debug(self.serial_write_dac_register(dac, 0x152, code)) - def serial_get_switch_mode(self): + def serial_get_switch_mode(self, dac): ''' Reads DAC output switch mode as one of NRZ, Mix-Mode, or RZ. Parameters: @@ -310,7 +314,7 @@ def serial_get_switch_mode(self): logger.info('Fake read mix-mode.') return 'MIX' - code = self.serial_read_dac0(0x152) & 0x03 + code = self.serial_read_dac_register(dac, 0x152) & 0x03 if code == 0x00: return 'NRZ' if code == 0x01: @@ -320,7 +324,7 @@ def serial_get_switch_mode(self): raise Exception('Unrecognized DAC switch mode ' + code + '.') - def serial_set_analog_full_scale_current(self, current): + def serial_set_analog_full_scale_current(self, dac, current): ''' Sets DAC full-scale current, rounding to nearest LSB of current register. Parameters: @@ -340,14 +344,14 @@ def serial_set_analog_full_scale_current(self, current): logger.info('{:#x}'.format(reg_value & 0x3)) logger.info('{:#x}'.format((reg_value >> 2) & 0xFF)) else: - logger.info(self.serial_write_dac0(0x041, reg_value & 0x3)) + logger.debug(self.serial_write_dac_register(dac, 0x041, reg_value & 0x3)) sleep(0.01) - logger.info(self.serial_write_dac0(0x042, (reg_value >> 2) & 0xFF)) + logger.debug(self.serial_write_dac_register(dac, 0x042, (reg_value >> 2) & 0xFF)) sleep(0.01) return 32 * (reg_value / 1023) + 8 - def serial_get_analog_full_scale_current(self): + def serial_get_analog_full_scale_current(self, dac): ''' Reads programmed full-scale current. Returns: @@ -356,12 +360,12 @@ def serial_get_analog_full_scale_current(self): if self.ser is None: return 0 - LSbits = self.serial_read_dac0(0x041) & 0x03 - MSbits = self.serial_read_dac0(0x042) & 0xFF + LSbits = self.serial_read_dac_register(dac, 0x041) & 0x03 + MSbits = self.serial_read_dac_register(dac, 0x042) & 0xFF reg_value = (MSbits << 2) & LSbits return 32 * (reg_value / 1023) + 8 - def serial_set_nco_enable(self, en): + def serial_set_nco_enable(self, dac, en): ''' Enables the DAC NCO. Parameters: @@ -374,7 +378,7 @@ def serial_set_nco_enable(self, en): logger.info('Fake read 0x00.') code = 0x00 else: - code = self.serial_read_dac0(0x111) + code = self.serial_read_dac_register(dac, 0x111) if en: code |= (1 << 6) @@ -384,11 +388,11 @@ def serial_set_nco_enable(self, en): if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: - logger.info(self.serial_write_dac0(0x111, code)) + logger.debug(self.serial_write_dac_register(dac, 0x111, code)) sleep(0.1) - def serial_get_nco_enable(self): + def serial_get_nco_enable(self, dac): ''' Checks whether the DAC NCO is enabled. Returns: @@ -398,9 +402,9 @@ def serial_get_nco_enable(self): logger.info('Fake reported DAC NCO enabled.') return True - return (self.serial_read_dac0(0x111) & (1 << 6)) != 0 + return (self.serial_read_dac_register(dac, 0x111) & (1 << 6)) != 0 - def serial_set_FIR85_enable(self, FIR85): + def serial_set_FIR85_enable(self, dac, FIR85): ''' Enables the DAC NCO FIR85 filter. Parameters: @@ -410,7 +414,7 @@ def serial_set_FIR85_enable(self, FIR85): logger.info('Fake read 0x00.') code = 0x00 else: - code = self.serial_read_dac0(0x111) + code = self.serial_read_dac_register(dac, 0x111) if FIR85: code |= (1 << 0) @@ -420,11 +424,11 @@ def serial_set_FIR85_enable(self, FIR85): if self.ser is None: logger.info('Fake wrote {:#x}'.format(code)) else: - logger.info(self.serial_write_dac0(0x111, code)) + logger.debug(self.serial_write_dac_register(dac, 0x111, code)) sleep(0.1) - def serial_get_FIR85_enable(self): + def serial_get_FIR85_enable(self, dac): ''' Checks whether the DAC NCO FIR85 filter is enabled. Returns: @@ -434,9 +438,9 @@ def serial_get_FIR85_enable(self): logger.info('Fake reported DAC NCO FIR85 enabled.') return True - return (self.serial_read_dac0(0x111) & (1 << 0)) != 0 + return (self.serial_read_dac_register(dac, 0x111) & (1 << 0)) != 0 - def serial_set_nco_frequency(self, f): + def serial_set_nco_frequency(self, dac, f): ''' Writes the given frequency, assuming not in NCO-only mode. Follows procedure in Table 44 of AD9164 datasheet. @@ -444,33 +448,33 @@ def serial_set_nco_frequency(self, f): logger.info('Setting frequency to {}...'.format(f)) # Configure DC_TEST_EN bit: 0b0 = NCO operation with data interface - logger.info(self.serial_write_dac0(0x150, 0x00)) + logger.debug(self.serial_write_dac_register(dac, 0x150, 0x00)) sleep(0.01) # Ensure the frequency tuning word write request is low. - logger.info(self.serial_write_dac0(0x113, 0x00)) + logger.debug(self.serial_write_dac_register(dac, 0x113, 0x00)) sleep(0.01) # Write FTW. ftw = [(int((f/5e9)*(1 << 48)) >> x) & 0xFF for x in range(0, 48, 8)] for index, b in enumerate(ftw): - logger.info(self.serial_write_dac0(0x114 + index, b)) + logger.debug(self.serial_write_dac_register(dac, 0x114 + index, b)) sleep(0.01) # Load the FTW to the NCO. - logger.info(self.serial_write_dac0(0x113, 0x01)) + logger.debug(self.serial_write_dac_register(dac, 0x113, 0x01)) sleep(0.1) - def serial_get_nco_frequency(self): + def serial_get_nco_frequency(self, dac): ''' Reads the current NCO frequency, assuming not in NCO-only mode. ''' ftw = 0 for index, shift in enumerate(range(0, 48, 8)): - ftw |= self.serial_read_dac0(0x114 + index) << shift + ftw |= self.serial_read_dac_register(dac, 0x114 + index) << shift sleep(0.01) - return ftw * 5e9 + return (ftw / float(1 << 48)) * 5e9 def serial_set_reference(self, ref): ''' @@ -496,7 +500,7 @@ def serial_set_reference(self, ref): def serial_get_reference(self): return self.ref - def serial_set_shuffle_mode(self, value): + def serial_set_shuffle_mode(self, dac, value): ''' Sets DAC shuffle mode. Parameters: @@ -505,11 +509,11 @@ def serial_set_shuffle_mode(self, value): if self.ser is None: logger.info('Fake wrote {:#x}'.format(value & 0x7)) else: - logger.info(self.serial_write_dac0(0x151, value & 0x7)) + logger.debug(self.serial_write_dac_register(dac, 0x151, value & 0x7)) sleep(0.1) - def serial_get_shuffle_mode(self): + def serial_get_shuffle_mode(self, dac): ''' Checks whether DAC shuffle mode is enabled. Returns: @@ -519,7 +523,7 @@ def serial_get_shuffle_mode(self): logger.info('Fake reported DAC shuffle mode enabled.') return True - return self.serial_read_dac0(0x151) & 0x7 + return self.serial_read_dac_register(dac, 0x151) & 0x7 ##################################################################### @@ -528,7 +532,8 @@ def serial_get_shuffle_mode(self): #Registers are read/write unless otherwise noted #Current as of 6/20/19 -CSR_AXI_ADDR_BASE = 0x44b40000 +CSR_AXI_ADDR_BASE0 = 0x44b40000 +CSR_AXI_ADDR_BASE1 = 0x44b10000 CSR_CACHE_CONTROL = 0x0010 #Cache control register CSR_SEQ_CONTROL = 0x0024 #Sequencer control register @@ -550,7 +555,7 @@ def serial_get_shuffle_mode(self): CSR_CMAT_R0 = 0x0068 #correction matrix row 0 CSR_CMAT_R1 = 0x006C #correction matrix row 1 -#### NOT CONNECTED TO _enableANY LOGIC -- USE FOR VALUE STORAGE ############ +#### NOT CONNECTED TO ANY LOGIC -- USE FOR VALUE STORAGE ############ CSR_A_AMPLITUDE = 0x0070 #Channel A amplitude CSR_B_AMPLITUDE = 0x0074 #Channel B amplitude CSR_MIX_AMP = 0x0078 #Mixer amplitude correction @@ -576,19 +581,62 @@ def serial_get_shuffle_mode(self): ##################################################################### DRAM_AXI_BASE = 0x80000000 +DRAM_WFA_0_LOC = 0x80000000 +DRAM_WFB_0_LOC = 0x90000000 +DRAM_SEQ_0_LOC = 0xA0000000 +DRAM_WFA_1_LOC = 0xB0000000 +DRAM_WFB_1_LOC = 0xC0000000 +DRAM_SEQ_1_LOC = 0xD0000000 + ##################################################################### +class APS3CommunicationManager(object): + instances = {} # Open instances of AMC599 objects, referenced by (IP, serialport) tuple + + @staticmethod + def board(resource): + if resource not in APS3CommunicationManager.instances: + APS3CommunicationManager.instances[resource] = {'board': AMC599(), 'connected': False, 'running': False} + return APS3CommunicationManager.instances[resource]['board'] + + @staticmethod + def connect(resource): + if resource not in APS3CommunicationManager.instances: + APS3CommunicationManager.instances[resource] = {'board': AMC599(), 'connected': False, 'running': False} + if not APS3CommunicationManager.connected(resource): + APS3CommunicationManager.instances[resource]['board'].connect(resource) + APS3CommunicationManager.instances[resource]['connected'] = True + + @staticmethod + def connected(resource): + if resource not in APS3CommunicationManager.instances: + return False + return APS3CommunicationManager.instances[resource]['connected'] + + @staticmethod + def disconnect(resource): + if APS3CommunicationManager.connected(resource): + APS3CommunicationManager.instances[resource]['board'].disconnect() + APS3CommunicationManager.instances[resource]['connected'] = False + + @staticmethod + def set_run(resource): + APS3CommunicationManager.instances[resource]['running'] = True + + @staticmethod + def set_stop(resource): + APS3CommunicationManager.instances[resource]['running'] = False + class APS3(Instrument, metaclass=MakeBitFieldParams): instrument_type = "AWG" - serial_port = '' + dac = -1 + address = None - def __init__(self, resource_name=None, name="Unlabeled APS3", debug=False, serial_port=''): + def __init__(self, resource_name=None, name="Unlabeled APS3", debug=False): self.name = name self.resource_name = resource_name - self.board = AMC599(debug=debug) - self.serial_port = serial_port super().__init__() def connect(self, resource_name=None): @@ -597,31 +645,46 @@ def connect(self, resource_name=None): elif resource_name is not None: self.resource_name = resource_name - if len(self.resource_name) != 2: - raise ValueError("Resource name must have 2 elements!") + if len(self.resource_name) != 3: + raise ValueError("Resource name must have 3 elements!") if self.resource_name[0] == None: raise ValueError("Resource name must contain IP address!") if self.resource_name[1] == None: raise ValueError("Resource name must contain serial port!") - if is_valid_ipv4(self.resource_name[0]): - self.board.connect(resource = self.resource_name) + if self.resource_name[2] == None: + raise ValueError("Resource name must contain channel!") + if not isinstance(self.resource_name[2], int) or not self.resource_name[2] in [0, 1]: + raise ValueError("Channel name must be 0 or 1!") + + if not is_valid_ipv4(self.resource_name[0]): + raise ValueError("IP address must be valid!") + + self.address = (self.resource_name[0], self.resource_name[1]) + self.dac = self.resource_name[2] + + APS3CommunicationManager.connect(self.address) + + # Write the memory locations immediately + self.write_register(CSR_WFA_OFFSET, (DRAM_WFA_0_LOC if self.dac == 0 else DRAM_WFA_1_LOC)) + self.write_register(CSR_WFB_OFFSET, (DRAM_WFB_0_LOC if self.dac == 0 else DRAM_WFB_1_LOC)) + self.write_register(CSR_SEQ_OFFSET, (DRAM_SEQ_0_LOC if self.dac == 0 else DRAM_SEQ_1_LOC)) def disconnect(self): - if self.board.connected: - self.board.disconnect() + if APS3CommunicationManager.connected(self.address): + APS3CommunicationManager.disconnect(self.address) def write_register(self, offset, data): - logger.info(f"Setting CSR: {hex(offset)} to: {hex(data)}") - self.board.write_memory(CSR_AXI_ADDR_BASE + offset, data) + logger.debug(f"Setting CSR: {hex(offset)} to: {hex(data)}") + APS3CommunicationManager.board(self.address).write_memory((CSR_AXI_ADDR_BASE0 if self.dac == 0 else CSR_AXI_ADDR_BASE1) + offset, data) def read_register(self, offset, num_words = 1): - return self.board.read_memory(CSR_AXI_ADDR_BASE + offset, num_words) + return APS3CommunicationManager.board(self.address).read_memory((CSR_AXI_ADDR_BASE0 if self.dac == 0 else CSR_AXI_ADDR_BASE1) + offset, num_words) def write_dram(self, offset, data): - self.board.write_memory(DRAM_AXI_BASE + offset, data) + APS3CommunicationManager.board(self.address).write_memory(DRAM_AXI_BASE + offset, data) def read_dram(self, offset, num_words = 1): - return self.board.read_memory(DRAM_AXI_BASE + offset, num_words) + return APS3CommunicationManager.board(self.address).read_memory(DRAM_AXI_BASE + offset, num_words) ####### CACHE CONTROL REGSITER ############################################# @@ -718,17 +781,34 @@ def correction_bypass(self): ####### BOARD_CONTROL ###################################################### + csr0_master = BitFieldCommand(register=CSR_BD_CONTROL, shift=0, + doc="""True: CSR0 is the Master CSR; when this bit is set the Cache Reset and Sequencer Resets + are controlled by CSR0 for both DAC0 and DAC1; so in effect both DACs circuits are reset at the same time. + False: CSR0 only controls DAC0 resets; CSR1 controls the DAC1 resets.""") + microblaze_reset = BitFieldCommand(register=CSR_BD_CONTROL, shift=1, doc="True resets Microblaze softcore. False takes Microblaze out of reset.") + dac_output_mux = BitFieldCommand(register=CSR_BD_CONTROL, shift=4, value_map={"SOF200": 0x0, "APS": 0x1}, doc="Select SOF200 test output or APS sequencer output from DAC.") - trigger_output_select = BitFieldCommand(register=CSR_BD_CONTROL, shift=5, - doc="""True: select trigger to be output on front panel. - False: select marker to be output on front panel.""") - marker_mode = BitFieldCommand(register=CSR_BD_CONTROL, shift=6, - doc="""True: Marker value is set as specified in 'state' field of instruction. - False: Marker is set high for one clock cycle, then cleared.""") + + @property + def trigger_output_select(self): + """Gets/sets the marker delay in seconds, based on a 312.5 MHz clock.""" + return (self.read_register(CSR_BD_CONTROL) >> 5) & 0x3 + @trigger_output_select.setter + def trigger_output_select(self, value): + reg = self.read_register(CSR_BD_CONTROL) + reg &= ~(0x3 << 5) + reg |= (value & 0x3) << 5 + self.write_register(CSR_BD_CONTROL, reg) + + trigger_input_select = BitFieldCommand(register=CSR_BD_CONTROL, shift=7, + doc="""True: Use the trigger form the other DAC as the trigger source. When this bit is set then + for CSR0: DAC0 use DAC1 trigger; For CSR1 for DAC1 use DAC0 trigger. + False: Use the trigger from the same DAC as the source. + for CSR0: DAC0 use DAC0 trigger; For CSR1 for DAC1 use DAC1 trigger.""") ####### MARKER_DELAY ####################################################### @@ -762,7 +842,7 @@ def WFB_OFFSET(self): ###### UTILITIES ########################################################### def run(self): logger.info("Configuring JESD...") - #self.board.serial_configure_JESD() + #APS3CommunicationManager.board(self.address).serial_configure_JESD() sleep(0.01) logger.info("Taking cache controller out of reset...") self.cache_controller = True @@ -798,26 +878,26 @@ def load_sequence(self, sequence): def load_waveforms(self, wfA, wfB): print(len(wfA)) - + wfA_32 = [((wfA[2*i+1] << 16) | wfA[2*i]) for i in range(len(wfA) // 2)] wfB_32 = [((wfB[2*i+1] << 16) | wfB[2*i]) for i in range(len(wfB) // 2)] self.write_dram(self.WFA_OFFSET(), wfA_32) # I self.write_dram(self.WFB_OFFSET(), wfB_32) # Q - + def read_waveforms(self, wf_len): wfA_32 = self.read_dram(self.WFA_OFFSET(), wf_len // 2) wfB_32 = self.read_dram(self.WFB_OFFSET(), wf_len // 2) - + wfA = [] wfB = [] - + for i in range(wf_len // 2): wfA.append(wfA_32[i] & 0xFFFF) wfA.append((wfA_32[i] >> 16) & 0xFFFF) wfB.append(wfB_32[i] & 0xFFFF) wfB.append((wfB_32[i] >> 16) & 0xFFFF) - + return wfA, wfB def load_sequence_file(self, seq_file): @@ -846,7 +926,11 @@ def load_sequence_file(self, seq_file): self.load_sequence(instructions) def serial_check_alive(self): - return self.board.serial_read_dac0(0x005) == 0x91 and self.board.serial_read_dac0(0x004) == 0x64 + return (APS3CommunicationManager.board(self.address).serial_read_dac_register(self.dac, 0x005) == 0x91 and + APS3CommunicationManager.board(self.address).serial_read_dac_register(self.dac,0x004) == 0x64) + + def configure_with_proxy(self, proxy_obj): + super(APS3, self).configure_with_proxy(proxy_obj) @property def sequence_file(self): @@ -858,56 +942,56 @@ def sequence_file(self, value): @property def dac_switch_mode(self): - return self.board.serial_get_switch_mode() + return APS3CommunicationManager.board(self.address).serial_get_switch_mode(self.dac) @dac_switch_mode.setter def dac_switch_mode(self, value): - self.board.serial_set_switch_mode(value) + APS3CommunicationManager.board(self.address).serial_set_switch_mode(self.dac, value) @property def dac_full_scale_current(self): - return self.board.serial_get_analog_full_scale_current() + return APS3CommunicationManager.board(self.address).serial_get_analog_full_scale_current(self.dac) @dac_full_scale_current.setter def dac_full_scale_current(self, value): - self.board.serial_set_analog_full_scale_current(value) + APS3CommunicationManager.board(self.address).serial_set_analog_full_scale_current(self.dac, value) @property def dac_nco_enable(self): - return self.board.serial_get_nco_enable() + return APS3CommunicationManager.board(self.address).serial_get_nco_enable(self.dac) @dac_nco_enable.setter def dac_nco_enable(self, value): - self.board.serial_set_nco_enable(value) + APS3CommunicationManager.board(self.address).serial_set_nco_enable(self.dac, value) @property def dac_FIR85_enable(self): - return self.board.serial_get_FIR85_enable() + return APS3CommunicationManager.board(self.address).serial_get_FIR85_enable(self.dac) @dac_FIR85_enable.setter def dac_FIR85_enable(self, value): - self.board.serial_set_FIR85_enable(value) + APS3CommunicationManager.board(self.address).serial_set_FIR85_enable(self.dac, value) @property def dac_nco_frequency(self): - return self.board.serial_get_nco_frequency() + return APS3CommunicationManager.board(self.address).serial_get_nco_frequency(self.dac) @dac_nco_frequency.setter def dac_nco_frequency(self, value): - self.board.serial_set_nco_frequency(value) + APS3CommunicationManager.board(self.address).serial_set_nco_frequency(self.dac, value) @property def dac_pll_reference(self): - return self.board.serial_get_reference() + return APS3CommunicationManager.board(self.address).serial_get_reference() @dac_pll_reference.setter def dac_pll_reference(self, value): - self.board.serial_set_reference(value) + APS3CommunicationManager.board(self.address).serial_set_reference(value) @property def dac_shuffle_mode(self): - return self.board.serial_get_shuffle_mode() + return APS3CommunicationManager.board(self.address).serial_get_shuffle_mode() @dac_shuffle_mode.setter def dac_shuffle_mode(self, value): - self.board.serial_set_shuffle_mode(value) + APS3CommunicationManager.board(self.address).serial_set_shuffle_mode(value) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 47c61042..54dc230e 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -213,7 +213,13 @@ def create_from_meta(self, meta_file, averages): self.instrument_proxies = self.generators + self.receivers + self.transmitters + self.all_standalone + self.processors self.instruments = [] for instrument in self.instrument_proxies: - address = (instrument.address, instrument.serial_port) if hasattr(instrument, 'serial_port') and instrument.serial_port is not None else instrument.address + if (hasattr(instrument, 'serial_port') and + instrument.serial_port is not None and + hasattr(instrument, 'dac') and + instrument.dac is not None): + address = (instrument.address, instrument.serial_port, instrument.dac) + else: + address = instrument.address instr = instrument_map[instrument.model](address, instrument.label) # Instantiate # For easy lookup instr.proxy_obj = instrument From 8a7f03261d84e91e08036396ca104355e1e11b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 17 Sep 2019 17:27:48 -0400 Subject: [PATCH 082/109] Delete unused connectors and selectors --- src/auspex/qubit/pulse_calibration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index d3dff272..c436d8a4 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -278,6 +278,7 @@ def modify_graph(self, graph): raise ValueError(f"Could not find specified qubit {qubit} in graph.") mapping = {} + self.output_connectors = {} for i in range(len(self.output_nodes)): output_node = self.output_nodes[i] if isinstance(output_node, bbndb.auspex.Write): @@ -287,6 +288,7 @@ def modify_graph(self, graph): # Disable any paths not involving the buffer new_graph = nx.DiGraph() new_output_nodes = [] + new_stream_selectors = [] for output_node, qubit in zip(self.output_nodes, self.qubits): new_output = mapping[output_node] new_output_nodes.append(new_output) @@ -296,7 +298,7 @@ def modify_graph(self, graph): if len(stream_sels) != 1: raise Exception(f"Expected to find one stream selector for {qubit}. Instead found {len(stream_sels)}") stream_sel = stream_sels[0] - + new_stream_selectors.append(stream_sel) old_path = nx.shortest_path(graph, stream_sel.hash_val, output_node.hash_val) path = old_path[:-1] + [new_output.hash_val] nx.add_path(new_graph, path) @@ -321,8 +323,10 @@ def modify_graph(self, graph): for plot_node in plot_nodes: plot_path = nx.shortest_path(graph, path[-2], plot_node) new_graph = nx.compose(new_graph, graph.subgraph(plot_path)) - self.output_nodes = new_output_nodes + self.stream_selectors = new_stream_selectors + for ss in new_stream_selectors: + self.output_connectors[self.connector_by_sel[ss].name] = self.connector_by_sel[ss] return new_graph def add_cal_sweep(self, method, values): From d03924241737801608742b8106fe0032d376faac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 19 Sep 2019 09:47:53 -0400 Subject: [PATCH 083/109] Few more chan updates A little hacky... --- src/auspex/qubit/pulse_calibration.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index c436d8a4..ff5de90d 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -289,6 +289,7 @@ def modify_graph(self, graph): new_graph = nx.DiGraph() new_output_nodes = [] new_stream_selectors = [] + connector_by_sel = {} for output_node, qubit in zip(self.output_nodes, self.qubits): new_output = mapping[output_node] new_output_nodes.append(new_output) @@ -299,6 +300,8 @@ def modify_graph(self, graph): raise Exception(f"Expected to find one stream selector for {qubit}. Instead found {len(stream_sels)}") stream_sel = stream_sels[0] new_stream_selectors.append(stream_sel) + connector_by_sel[stream_sel] = self.connector_by_sel[stream_sel] + old_path = nx.shortest_path(graph, stream_sel.hash_val, output_node.hash_val) path = old_path[:-1] + [new_output.hash_val] nx.add_path(new_graph, path) @@ -323,10 +326,18 @@ def modify_graph(self, graph): for plot_node in plot_nodes: plot_path = nx.shortest_path(graph, path[-2], plot_node) new_graph = nx.compose(new_graph, graph.subgraph(plot_path)) + + # Update nodes and connectors self.output_nodes = new_output_nodes self.stream_selectors = new_stream_selectors + self.connector_by_sel = connector_by_sel + for ss in new_stream_selectors: self.output_connectors[self.connector_by_sel[ss].name] = self.connector_by_sel[ss] + for ch in list(self.chan_to_oc): + if self.chan_to_oc[ch] not in self.output_connectors.values(): + self.chan_to_oc.pop(ch) + self.chan_to_dig.pop(ch) return new_graph def add_cal_sweep(self, method, values): From 113df4e7ec572cedfabb20469cb9f5745e09a1e1 Mon Sep 17 00:00:00 2001 From: Matthew Ware Date: Tue, 24 Sep 2019 16:10:59 -0400 Subject: [PATCH 084/109] Feature/example names (#404) * More useful names please :) -MW * Updated .rst file with new notebook names. Did we lose the tomography/benchmarking notebook? -MW --- ...xample-Q6.ipynb => Example-Calibrations.ipynb} | 0 ...Example-Q2.ipynb => Example-Channel-Lib.ipynb} | 0 .../{Example-Q1.ipynb => Example-Config.ipynb} | 0 ...Example-Q4.ipynb => Example-Experiments.ipynb} | 0 ...ple-Q3.ipynb => Example-Filter-Pipeline.ipynb} | 0 ...mple-Q7.ipynb => Example-SingleShot-Fid.ipynb} | 0 .../{Example-Q5.ipynb => Example-Sweeps.ipynb} | 0 doc/qubits.rst | 15 +++++++-------- 8 files changed, 7 insertions(+), 8 deletions(-) rename doc/examples/{Example-Q6.ipynb => Example-Calibrations.ipynb} (100%) rename doc/examples/{Example-Q2.ipynb => Example-Channel-Lib.ipynb} (100%) rename doc/examples/{Example-Q1.ipynb => Example-Config.ipynb} (100%) rename doc/examples/{Example-Q4.ipynb => Example-Experiments.ipynb} (100%) rename doc/examples/{Example-Q3.ipynb => Example-Filter-Pipeline.ipynb} (100%) rename doc/examples/{Example-Q7.ipynb => Example-SingleShot-Fid.ipynb} (100%) rename doc/examples/{Example-Q5.ipynb => Example-Sweeps.ipynb} (100%) diff --git a/doc/examples/Example-Q6.ipynb b/doc/examples/Example-Calibrations.ipynb similarity index 100% rename from doc/examples/Example-Q6.ipynb rename to doc/examples/Example-Calibrations.ipynb diff --git a/doc/examples/Example-Q2.ipynb b/doc/examples/Example-Channel-Lib.ipynb similarity index 100% rename from doc/examples/Example-Q2.ipynb rename to doc/examples/Example-Channel-Lib.ipynb diff --git a/doc/examples/Example-Q1.ipynb b/doc/examples/Example-Config.ipynb similarity index 100% rename from doc/examples/Example-Q1.ipynb rename to doc/examples/Example-Config.ipynb diff --git a/doc/examples/Example-Q4.ipynb b/doc/examples/Example-Experiments.ipynb similarity index 100% rename from doc/examples/Example-Q4.ipynb rename to doc/examples/Example-Experiments.ipynb diff --git a/doc/examples/Example-Q3.ipynb b/doc/examples/Example-Filter-Pipeline.ipynb similarity index 100% rename from doc/examples/Example-Q3.ipynb rename to doc/examples/Example-Filter-Pipeline.ipynb diff --git a/doc/examples/Example-Q7.ipynb b/doc/examples/Example-SingleShot-Fid.ipynb similarity index 100% rename from doc/examples/Example-Q7.ipynb rename to doc/examples/Example-SingleShot-Fid.ipynb diff --git a/doc/examples/Example-Q5.ipynb b/doc/examples/Example-Sweeps.ipynb similarity index 100% rename from doc/examples/Example-Q5.ipynb rename to doc/examples/Example-Sweeps.ipynb diff --git a/doc/qubits.rst b/doc/qubits.rst index 7adb36ae..1e03dcf3 100644 --- a/doc/qubits.rst +++ b/doc/qubits.rst @@ -22,14 +22,13 @@ The best way to gain experience is to follow through with these tutorials: .. toctree:: :maxdepth: 1 - Q1 Tutorial: Creating Channel Library - Q2 Tutorial: Saving/Loading Library Versions - Q3 Tutorial: Using the Pipeline Manager - Q4 Tutorial: Running a Basic Qubit Experiment - Q5 Tutorial: Adding Sweeps to Experiments - Q6 Tutorial: Pulse Calibration - Q7 Tutorial: Single Shot Fidelity - Q8 Tutorial: Benchmarking and Tomography + Q1 Tutorial: Creating Channel Library + Q2 Tutorial: Saving/Loading Library Versions + Q3 Tutorial: Using the Pipeline Manager + Q4 Tutorial: Running a Basic Qubit Experiment + Q5 Tutorial: Adding Sweeps to Experiments + Q6 Tutorial: Pulse Calibration + Q7 Tutorial: Single Shot Fidelity Instrument Drivers ****************** From e95d59f997d14b2f5dd3a4903d0ca766e4ac0e7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 25 Sep 2019 14:59:01 -0400 Subject: [PATCH 085/109] Log Ramsey cal results --- src/auspex/qubit/pulse_calibration.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index ff5de90d..32aa95f5 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -719,11 +719,12 @@ def update_settings(self): for edge in self.qubit.edge_target: edge_source = edge.phys_chan.generator edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency - # # TODO: fix this for db backend - - # qubit_set_freq = self.saved_settings['instruments'][qubit_source]['frequency'] + self.saved_settings['qubits'][self.qubit.label]['control']['frequency'] - # logger.info("Qubit set frequency = {} GHz".format(round(float(qubit_set_freq/1e9),5))) - # return ('frequency', qubit_set_freq) + if self.sample: + frequency = self.qubit_source.frequency if self.set_source else self.qubit.frequency + c = bbndb.calibration.Calibration(value=frequency, sample=self.sample, name="Ramsey") + c.date = datetime.datetime.now() + bbndb.get_cl_session().add(c) + bbndb.get_cl_session().commit() class PhaseEstimation(QubitCalibration): From e0603527cae536e04fbac4687bf5568308b3d525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 25 Sep 2019 14:59:52 -0400 Subject: [PATCH 086/109] Log cal by default Not sure what the default name should be. I'm here interpreting sample as qubit name --- src/auspex/qubit/pulse_calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 32aa95f5..f2aab566 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -142,7 +142,7 @@ def exp_config(self, exp): class QubitCalibration(Calibration): calibration_experiment = None - def __init__(self, qubits, sample_name=None, output_nodes=None, stream_selectors=None, quad="real", auto_rollback=True, do_plotting=True, **kwargs): + def __init__(self, qubits, sample_name='q1', output_nodes=None, stream_selectors=None, quad="real", auto_rollback=True, do_plotting=True, **kwargs): self.qubits = qubits if isinstance(qubits, list) else [qubits] self.qubit = None if isinstance(qubits, list) else qubits self.output_nodes = output_nodes if isinstance(output_nodes, list) else [output_nodes] From f56098aace8f571f8b051bff06fc869fb13434fc Mon Sep 17 00:00:00 2001 From: Billy Date: Mon, 30 Sep 2019 13:58:29 -0400 Subject: [PATCH 087/109] DAC shuffle mode --- src/auspex/instruments/aps3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auspex/instruments/aps3.py b/src/auspex/instruments/aps3.py index aa35faa2..91693449 100644 --- a/src/auspex/instruments/aps3.py +++ b/src/auspex/instruments/aps3.py @@ -990,8 +990,8 @@ def dac_pll_reference(self, value): @property def dac_shuffle_mode(self): - return APS3CommunicationManager.board(self.address).serial_get_shuffle_mode() + return APS3CommunicationManager.board(self.address).serial_get_shuffle_mode(self.dac) @dac_shuffle_mode.setter def dac_shuffle_mode(self, value): - APS3CommunicationManager.board(self.address).serial_set_shuffle_mode(value) + APS3CommunicationManager.board(self.address).serial_set_shuffle_mode(self.dac, value) From 6a671030d09d925ce4b4a3bc29063e03218bce3a Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 1 Oct 2019 16:22:20 -0400 Subject: [PATCH 088/109] Revert X6 timeout --- src/auspex/instruments/X6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/instruments/X6.py b/src/auspex/instruments/X6.py index 0bc39c91..232d17fb 100644 --- a/src/auspex/instruments/X6.py +++ b/src/auspex/instruments/X6.py @@ -279,7 +279,7 @@ def spew_fake_data(self, counter, ideal_data, random_mag=0.1, random_seed=12345) def receive_data(self, channel, oc, exit, ready, run): try: sock = self._chan_to_rsocket[channel] - sock.settimeout(4) + sock.settimeout(2) self.last_timestamp.value = datetime.datetime.now().timestamp() total = 0 ready.value += 1 From 60973753a3d56f574ed8bfe0fbd7450a48628774 Mon Sep 17 00:00:00 2001 From: Spencer Fallek <38664981+sfallek1@users.noreply.github.com> Date: Fri, 13 Sep 2019 10:52:29 -0400 Subject: [PATCH 089/109] Update CR cal --- src/auspex/qubit/pulse_calibration.py | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index f2aab566..50d6d2b9 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -716,15 +716,14 @@ def update_settings(self): else: self.qubit.frequency += float(round(self.fit_freq - self.orig_freq)) # update edges where this is the target qubit - for edge in self.qubit.edge_target: - edge_source = edge.phys_chan.generator - edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency - if self.sample: - frequency = self.qubit_source.frequency if self.set_source else self.qubit.frequency - c = bbndb.calibration.Calibration(value=frequency, sample=self.sample, name="Ramsey") - c.date = datetime.datetime.now() - bbndb.get_cl_session().add(c) - bbndb.get_cl_session().commit() + # for edge in self.qubit.edge_target: + # edge_source = edge.phys_chan.generator + # edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency + # # TODO: fix this for db backend + + # qubit_set_freq = self.saved_settings['instruments'][qubit_source]['frequency'] + self.saved_settings['qubits'][self.qubit.label]['control']['frequency'] + # logger.info("Qubit set frequency = {} GHz".format(round(float(qubit_set_freq/1e9),5))) + # return ('frequency', qubit_set_freq) class PhaseEstimation(QubitCalibration): @@ -965,8 +964,8 @@ def _calibrate(self): self.plot["Fit 0"] = (finer_xaxis, np.polyval(all_params_0, finer_xaxis) if self.cal_type == CR_cal_type.AMP else sinf(finer_xaxis, **all_params_0)) self.plot["Data 1"] = (xaxis, data_t[len(data_t)//2:]) self.plot["Fit 1"] = (finer_xaxis, np.polyval(all_params_1, finer_xaxis) if self.cal_type == CR_cal_type.AMP else sinf(finer_xaxis, **all_params_1)) - - # Optimal parameter within range of original data! + + # Optimal parameter within range of original data! if self.opt_par > np.min(xaxis) and self.opt_par < np.max(xaxis): self.succeeded = True @@ -984,7 +983,7 @@ def __init__(self, edge, lengths=np.linspace(20, 1020, 21)*1e-9, phase=0, amp=0. def sequence(self): qc, qt = self.qubits seqs = [[Id(qc)] + echoCR(qc, qt, length=l, phase = self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] - seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] + seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] seqs += create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) return seqs @@ -993,7 +992,7 @@ def descriptor(self): delay_descriptor(np.concatenate((self.lengths, self.lengths))), cal_descriptor(tuple(self.qubits), 2) ] - + class CRPhaseCalibration(CRCalibration): cal_type = CR_cal_type.PHASE @@ -1035,8 +1034,8 @@ def __init__(self, edge, amp_range = 0.4, amp = 0.8, rise_fall = 40e-9, num_CR = def sequence(self): qc, qt = self.qubits - seqs = [[Id(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths, phase=self.phases, amp=a, riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] - for a in self.amps]+ [[X(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths, phase= self.phases, amp=a, riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] + seqs = [[Id(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase=self.phases[0], amp=a, riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] + for a in self.amps]+ [[X(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase= self.phases[0], amp=a, riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for a in self.amps] + create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) return seqs @@ -1046,7 +1045,7 @@ def descriptor(self): 'points': list(self.amps)+list(self.amps), 'partition': 1 }, - cal_descriptor(tuple(self.qubit), 2)] + cal_descriptor(tuple(self.qubits), 2)] def restrict(phase): out = np.mod( phase + np.pi, 2*np.pi, ) - np.pi From bb4cd1f972c7673e1b5f6628c8a9c9168c224a51 Mon Sep 17 00:00:00 2001 From: Spencer Fallek <38664981+sfallek1@users.noreply.github.com> Date: Fri, 13 Sep 2019 17:23:37 -0400 Subject: [PATCH 090/109] WIP: fix crampfit, qb ordr 4 normalize_buffer_data --- src/auspex/analysis/CR_fits.py | 31 ++++++++++++--------------- src/auspex/qubit/pulse_calibration.py | 6 +++--- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/auspex/analysis/CR_fits.py b/src/auspex/analysis/CR_fits.py index 9eafb792..ae26d3d8 100644 --- a/src/auspex/analysis/CR_fits.py +++ b/src/auspex/analysis/CR_fits.py @@ -42,11 +42,11 @@ def __str__(self): def fit_CR(xpoints, data, cal_type): - + data0 = data[:len(data)//2] data1 = data[len(data)//2:] xpoints = [xp if len(xp)>1 else xp[0] for xp in xpoints] - + if cal_type == CR_cal_type.LENGTH: return fit_CR_length(xpoints, data0, data1) elif cal_type == CR_cal_type.PHASE: @@ -55,11 +55,11 @@ def fit_CR(xpoints, data, cal_type): return fit_CR_amp(xpoints, data0, data1) def fit_CR_length(xpoints, data0, data1): - + xpoints = xpoints[0] x_fine = np.linspace(min(xpoints), max(xpoints), 1001) - fit0 = SineFit(xpoints, data0, np.pi/2.0, 1/(2.0*xpoints[-1])) + fit0 = SineFit(xpoints, data0, np.pi/2.0, 1/(2.0*xpoints[-1])) fit1 = SineFit(xpoints, data1, np.pi/2.0, 1/(2.0*xpoints[-1])) #find the first zero crossing @@ -85,21 +85,18 @@ def fit_CR_phase(xpoints, data0, data1): #find the phase for maximum contrast contrast = (fit0.model(x_fine) - fit1.model(x_fine))/2.0 logger.info(f"CR Contrast = {np.max(contrast)}") - xopt = x_fine[np.argmax(contrast)] - np.pi + xopt = x_fine[np.argmax(contrast)] - np.pi logger.info(f"CR phase = {xopt}") return xopt, fit0.fit_params, fit1.fit_params -# def fit_CR(xpoints, data0, data1): -# xpoints = xpoints[2] -# x_fine = np.linspace(min(xpoints), max(xpoints), 1001) -# popt0 = np.polyfit(xpoints, data0, 1) # tentatively linearize -# popt1 = np.polyfit(xpoints, data1, 1) -# #average between optimum amplitudes -# xopt = -(popt0[1]/popt0[0] + popt1[1]/popt1[0])/2 -# logger.info('CR amplitude = {}'.format(xopt)) -# return xopt, popt0, popt1 - - - +def fit_CR_amp(xpoints, data0, data1): + xpoints = xpoints[2] + x_fine = np.linspace(min(xpoints), max(xpoints), 1001) + popt0 = np.polyfit(xpoints, data0, 1) # tentatively linearize + popt1 = np.polyfit(xpoints, data1, 1) + #average between optimum amplitudes + xopt = -(popt0[1]/popt0[0] + popt1[1]/popt1[0])/2 + logger.info('CR amplitude = {}'.format(xopt)) + return xopt, popt0, popt1 diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 50d6d2b9..9818eb8e 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -984,7 +984,7 @@ def sequence(self): qc, qt = self.qubits seqs = [[Id(qc)] + echoCR(qc, qt, length=l, phase = self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] - seqs += create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) + seqs += create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) return seqs def descriptor(self): @@ -1006,7 +1006,7 @@ def sequence(self): qc, qt = self.qubits seqs = [[Id(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*Id(qc), MEAS(qt)*MEAS(qc)] for ph in self.phases] seqs += [[X(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*X(qc), MEAS(qt)*MEAS(qc)] for ph in self.phases] - seqs += create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) + seqs += create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) return seqs def descriptor(self): @@ -1036,7 +1036,7 @@ def sequence(self): qc, qt = self.qubits seqs = [[Id(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase=self.phases[0], amp=a, riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] for a in self.amps]+ [[X(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase= self.phases[0], amp=a, riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] - for a in self.amps] + create_cal_seqs((qt,qc), 2, measChans=(qt,qc)) + for a in self.amps] + create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) return seqs def descriptor(self): From b6666ab8072727bc2da1eb1f0e0f01f2bcb130ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 2 Oct 2019 10:50:07 -0400 Subject: [PATCH 091/109] Fix Ramsey update again --- src/auspex/qubit/pulse_calibration.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 9818eb8e..62a9bdc1 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -716,14 +716,15 @@ def update_settings(self): else: self.qubit.frequency += float(round(self.fit_freq - self.orig_freq)) # update edges where this is the target qubit - # for edge in self.qubit.edge_target: - # edge_source = edge.phys_chan.generator - # edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency - # # TODO: fix this for db backend - - # qubit_set_freq = self.saved_settings['instruments'][qubit_source]['frequency'] + self.saved_settings['qubits'][self.qubit.label]['control']['frequency'] - # logger.info("Qubit set frequency = {} GHz".format(round(float(qubit_set_freq/1e9),5))) - # return ('frequency', qubit_set_freq) + for edge in self.qubit.edge_target: + edge_source = edge.phys_chan.generator + edge.frequency = self.source_proxy.frequency + self.qubit_source.frequency - edge_source.frequency + if self.sample: + frequency = self.qubit_source.frequency if self.set_source else self.qubit.frequency + c = bbndb.calibration.Calibration(value=frequency, sample=self.sample, name="Ramsey") + c.date = datetime.datetime.now() + bbndb.get_cl_session().add(c) + bbndb.get_cl_session().commit() class PhaseEstimation(QubitCalibration): From 8e023240001041f535760aaf296b9bae2e0a33e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 2 Oct 2019 10:53:39 -0400 Subject: [PATCH 092/109] Update CR cal database --- src/auspex/qubit/pulse_calibration.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 62a9bdc1..55d915c3 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -974,6 +974,11 @@ def update_settings(self): print("updating settings...") self.edge.pulse_params[str.lower(self.cal_type.name)] = float(self.opt_par) super(CRCalibration, self).update_settings() + if self.sample: + c = bbndb.calibration.Calibration(value=float(self.opt_par), sample=sample, name="CR"+str.lower(self.cal_type.name)) + c.date = datetime.datetime.now() + bbndb.get_cl_session().add(c) + bbndb.get_cl_session().commit() class CRLenCalibration(CRCalibration): cal_type = CR_cal_type.LENGTH From 1aa02052e67171db52a4e5505cacaf942724a5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 2 Oct 2019 11:06:35 -0400 Subject: [PATCH 093/109] self.sample --- src/auspex/qubit/pulse_calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 55d915c3..7fded949 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -975,7 +975,7 @@ def update_settings(self): self.edge.pulse_params[str.lower(self.cal_type.name)] = float(self.opt_par) super(CRCalibration, self).update_settings() if self.sample: - c = bbndb.calibration.Calibration(value=float(self.opt_par), sample=sample, name="CR"+str.lower(self.cal_type.name)) + c = bbndb.calibration.Calibration(value=float(self.opt_par), sample=self.sample, name="CR"+str.lower(self.cal_type.name)) c.date = datetime.datetime.now() bbndb.get_cl_session().add(c) bbndb.get_cl_session().commit() From d8deaa8f2fdd11ebb4bda05e9e5d9ffb25ac6b3d Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 3 Oct 2019 18:30:31 -0400 Subject: [PATCH 094/109] Match data with metadata for norm. --WK, DR --- src/auspex/qubit/pulse_calibration.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 7fded949..a82106bb 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -198,6 +198,12 @@ def run_sweeps(self): data = {} var = {} + + #sort nodes by qubit name to match data with metadata when normalizing + qubit_indices = {q.label: idx for idx, q in enumerate(exp.qubits)} + exp.output_nodes.sort(key=lambda x: qubit_indices[x.qubit_name]) + exp.var_buffers.sort(key=lambda x: qubit_indices[x.qubit_name]) + for i, (qubit, output_buff, var_buff) in enumerate(zip(exp.qubits, [exp.proxy_to_filter[on] for on in exp.output_nodes], [exp.proxy_to_filter[on] for on in exp.var_buffers])): From d3dbd5f8e5db767ff507877dba611562906c25b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 7 Oct 2019 15:15:08 -0400 Subject: [PATCH 095/109] Select measured qubits in CRcal Recommended to measure only target qubit to prevent readout crosstalk --- src/auspex/qubit/pulse_calibration.py | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index a82106bb..efe6a507 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -925,12 +925,23 @@ def update_settings(self): '''Two-qubit gate calibrations''' class CRCalibration(QubitCalibration): + """Template for CR calibrations. Currently available steps: length, phase, amplitude + + Args: + edge: Edge in the channel library defining the connection between control and target qubit + lengths (array): CR pulse length(s). Longer than 1 for CRLenCalibration + phases (array): CR pulse phase(s). Longer than 1 for CRPhaseCalibration + amps (array): CR pulse amp(s). Longer than 1 for CRAmpCalibration + rise_fall (float): length of rise/fall of CR pulses + meas_qubits (list): specifies a subset of qubits to be measured (both by default) + """ def __init__(self, edge, lengths = np.linspace(20, 1020, 21)*1e-9, phases = [0], amps = [0.8], rise_fall = 40e-9, + meas_qubits = None, **kwargs): self.lengths = lengths self.phases = phases @@ -939,7 +950,7 @@ def __init__(self, self.filename = 'CR/CR' self.edge = edge - qubits = [edge.source, edge.target] + qubits = meas_qubits if meas_qubits else [edge.source, edge.target] super().__init__(qubits, **kwargs) def init_plots(self): @@ -960,7 +971,7 @@ def _calibrate(self): self.norm_points = {qs.label: (0, 1), qt.label: (0, 1)} data, _ = self.run_sweeps() - data_t = data[qt.label] + data_t = data[qt.label] if isinstance(data, dict) else data # fit self.opt_par, all_params_0, all_params_1 = fit_CR([self.lengths, self.phases, self.amps], data_t, self.cal_type) # plot the result @@ -989,14 +1000,16 @@ def update_settings(self): class CRLenCalibration(CRCalibration): cal_type = CR_cal_type.LENGTH - def __init__(self, edge, lengths=np.linspace(20, 1020, 21)*1e-9, phase=0, amp=0.8, rise_fall=40e-9, **kwargs): - super().__init__(edge, lengths=lengths, phases=[phase], amps=[amp], rise_fall=rise_fall, **kwargs) + def __init__(self, edge, lengths=np.linspace(20, 1020, 21)*1e-9, phase=0, amp=0.8, rise_fall=40e-9, meas_qubits=None, **kwargs): + super().__init__(edge, lengths=lengths, phases=[phase], amps=[amp], rise_fall=rise_fall, meas_qubits = meas_qubits, **kwargs) def sequence(self): - qc, qt = self.qubits - seqs = [[Id(qc)] + echoCR(qc, qt, length=l, phase = self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] - seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] for l in self.lengths] - seqs += create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) + qc = self.edge.source + qt = self.edge.target + measBlock = reduce(operator.mul, [MEAS(q) for q in self.qubits]) + seqs = [[Id(qc)] + echoCR(qc, qt, length=l, phase = self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [Id(qc), measBlock] for l in self.lengths] + seqs += [[X(qc)] + echoCR(qc, qt, length=l, phase= self.phases[0], amp=self.amps[0], riseFall=self.rise_fall).seq + [X(qc), measBlock] for l in self.lengths] + seqs += create_cal_seqs(self.qubits, 2) return seqs def descriptor(self): From 0408d3f10aeb85503d235cee9f4bcd347cb3e296 Mon Sep 17 00:00:00 2001 From: gribeill Date: Mon, 7 Oct 2019 16:51:13 -0400 Subject: [PATCH 096/109] Add code and tests to fit single and sum-of-gaussian functions. --- src/auspex/analysis/fits.py | 124 ++++++++++++++++++++++++++++++++++++ test/test_fits.py | 35 ++++++++++ 2 files changed, 159 insertions(+) diff --git a/src/auspex/analysis/fits.py b/src/auspex/analysis/fits.py index 6cf25f39..4b130aa2 100644 --- a/src/auspex/analysis/fits.py +++ b/src/auspex/analysis/fits.py @@ -3,6 +3,7 @@ from auspex.log import logger from collections.abc import Iterable import matplotlib.pyplot as plt +from sklearn.mixture import GaussianMixture from .signal_analysis import * @@ -171,3 +172,126 @@ def _fit_dict(self, p): def __str__(self): return "A /((x-b)^2 + (c/2)^2) + d" + +class GaussianFit(AuspexFit): + """A fit to a gaussian function""" + + xlabel = "X Data" + ylabel = "Y Data" + title = "Gaussian Fit" + + @staticmethod + def _model(x, *p): + return p[0] + p[1]*np.exp(-0.5*((x-p[2])/p[3])**2) + + def _initial_guess(self): + ## Initial guess using modified Caruana's algorithm + ## See: H. Guo. "A Simple Algorithm for Fitting a Gaussian Function [DSP Tips and Tricks]" + ### IEEE Signal Processing Magazine. September 2011. DOI: 10.1109/MSP.2011.941846 + N = len(self.xpts) + + #use first and last points to + B = 0.5*(self.ypts[-1] + self.ypts[0]) + y = self.ypts - B + mask = y>0 + y = y[mask] + x = self.xpts[mask] + + M = np.array([[np.sum(y**2), np.sum(x*y**2), np.sum(x**2*y**2)], + [np.sum(x*y**2), np.sum(x**2*y**2), np.sum(x**3*y**2)], + [np.sum(x**2*y**2), np.sum(x**3*y**2), np.sum(x**4*y**2)]]) + v = np.array([np.sum(y**2*np.log(y)), + np.sum(x*y**2*np.log(y)), + np.sum(x*y**2*np.log(y))]) + a, b, c = np.linalg.inv(M) @ v.T + + mu = -b/(2.0*c) + sigma = np.sqrt(-1/(2.0*c)) + A = np.exp(a - b**2/(4.0*c)) + + return (B, A, mu, sigma) + + def _fit_dict(self, p): + return {"B": p[0], + "A": p[1], + "μ": p[2], + "σ": p[3]} + + def __str__(self): + return "A exp(-(x-μ)^2/2σ^2) + B" + +class MultiGaussianFit(AuspexFit): + """A fit to a sum of gaussian function. Use with care!""" + + xlabel = "X Data" + ylabel = "Y Data" + title = "Sum of Gaussians Fit" + + def __init__(self, x, y, make_plots=False, n_gaussians=2, n_samples=int(1e5)): + """Fit data to a sum of Gaussians. + + Args: + n_gaussians: Expected number of Gaussian peaks. + n_samples: Number of random samples to generate for GMM estimation. (see `MultiGaussianFit._initial_guess`) + """ + self.n_gaussians = n_gaussians + self.n_samples = n_samples + super().__init__(x, y, make_plots=make_plots) + + @staticmethod + def _model(x, *p): + + ngauss = int((len(p)-1)/3) + + assert ngauss > 1, "For a single Gaussian fit, use the `GaussianFit` class!" + + def one_gaussian(x, *p): + return p[0]*np.exp(-0.5*((x-p[1])/p[2])**2) + + out = p[0] + for j in range(ngauss): + out += one_gaussian(x, *p[3*j+1:3*j+4]) + return out + + def _initial_guess(self): + ## Initial guess for the multi-gaussian fit + ## The idea is to draw random samples using the data as a PDF, then run a + ## Gaussian mixture model (ie. clustering) to get a good initial guess for the gaussians. + ## Note that this is pretty slow... + + B = 0.5*(self.ypts[-1] + self.ypts[0]) + #normalize and center + y = self.ypts - B + mask = y>0 + x0 = self.xpts[np.argmax(y)] + xn = self.xpts[mask] - x0 + yn = y[mask] / np.sum(y[mask]) + #Generate random samples + samples = np.random.choice(a=xn, size=self.n_samples, p=yn) + gmm = GaussianMixture(n_components=self.n_gaussians) + gmm.fit(samples.reshape(len(samples),1)) + means = gmm.means_.flatten() + x0 + sigmas = np.sqrt(gmm.covariances_.flatten()) + amps = gmm.weights_.flatten() * (2*np.pi) + output = np.zeros(1+3*self.n_gaussians) + output[0] = B + for j in range(self.n_gaussians): + output[3*j+1] = amps[j] + output[3*j+2] = means[j] + output[3*j+3] = sigmas[j] + + return output + + def _fit_dict(self, p): + fdict = {"B": p[0]} + for j in range(self.n_gaussians): + fdict[f"A{j}"] = p[3*j+1] + fdict[f"μ{j}"] = p[3*j+2] + fdict[f"σ{j}"] = p[3*j+3] + return fdict + + def __str__(self): + return f"Sum of Gaussians with N={self.n_gaussians}" + + + diff --git a/test/test_fits.py b/test/test_fits.py index 78432dbf..f5a43a1b 100644 --- a/test/test_fits.py +++ b/test/test_fits.py @@ -74,6 +74,41 @@ def test_LorentzFit(self): self.assertFitInterval(p0[1], "b", fit) self.assertFitInterval(p0[2], "c", fit) + def test_GaussianFit(self): + p0 = [0.23, 3.1, 0.54, 0.89] + x = np.linspace(-4, 4, 201) + y = fits.GaussianFit._model(x, *p0) + noise = np.random.randn(y.size) * 0.2 + y += noise + fit = fits.GaussianFit(x, y, make_plots=False) + self.assertFitInterval(p0[0], "B", fit) + self.assertFitInterval(p0[1], "A", fit) + self.assertFitInterval(p0[2], "μ", fit) + self.assertFitInterval(p0[3], "σ", fit) + + def test_MultiGaussianFit(self): + p = [0.35, 2.8, 2.04, 0.88, 1.93, -2.3, 1.19] + x = np.linspace(-10, 10) + y = fits.MultiGaussianFit._model(x, *p) + noise = np.random.randn(y.size) * 0.2 + y += noise + fit = fits.MultiGaussianFit(x, y, make_plots=False, n_gaussians=2) + + #Be careful since no guarantee of order of fits + #also only testing means and std devs since the other parameters are still a + #little flaky... + if fit.fit_params["μ0"] < fit.fit_params["μ1"]: + self.assertFitInterval(p[5], "μ0", fit) + self.assertFitInterval(p[2], "μ1", fit) + self.assertFitInterval(p[6], "σ0", fit) + self.assertFitInterval(p[3], "σ1", fit) + else: + self.assertFitInterval(p[2], "μ0", fit) + self.assertFitInterval(p[5], "μ1", fit) + self.assertFitInterval(p[3], "σ0", fit) + self.assertFitInterval(p[6], "σ1", fit) + + def test_T1Fit(self): p0 = [2.0, 15, -1] From fc82ddfc8b1176074267ee9230af8d2b8ce27d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 7 Oct 2019 17:22:35 -0400 Subject: [PATCH 097/109] CR amp cal. with phase estimation Reintroduce it --- src/auspex/qubit/pulse_calibration.py | 31 ++++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index efe6a507..3c926982 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -43,8 +43,6 @@ import numpy as np from itertools import product -import bbndb - class Calibration(object): def __init__(self): @@ -736,7 +734,7 @@ class PhaseEstimation(QubitCalibration): amp2offset = 0.5 - def __init__(self, qubit, num_pulses= 1, amplitude= 0.1, direction = 'X', + def __init__(self, channel, num_pulses= 1, amplitude= 0.1, direction = 'X', target=np.pi/2, epsilon=1e-2, max_iter=5, **kwargs): #for now, only do one qubit at a time self.num_pulses = num_pulses @@ -747,15 +745,15 @@ def __init__(self, qubit, num_pulses= 1, amplitude= 0.1, direction = 'X', self.epsilon = epsilon self.max_iter = max_iter - super(PhaseEstimation, self).__init__(qubit, **kwargs) + super(PhaseEstimation, self).__init__(channel, **kwargs) self.filename = 'PhaseCal/PhaseCal' def sequence(self): # Determine whether it is a single- or a two-qubit pulse calibration - if isinstance(self.qubit, list): - qubit = self.qubit[1] - cal_pulse = [ZX90_CR(*self.qubit, amp=self.amplitude)] + if isinstance(self.qubit, bbndb.qgl.edge): # slight misnomer... + qubit = self.qubit.target + cal_pulse = [ZX90_CR(self.qubit.source, self.qubit.target), amp=self.amplitude)] else: qubit = self.qubit cal_pulse = [Xtheta(self.qubit, amp=self.amplitude)] @@ -844,13 +842,16 @@ def update_settings(self): bbndb.get_cl_session().add(c) bbndb.get_cl_session().commit() -# class CRAmpCalibration_PhEst(PhaseEstimation): -# def __init__(self, qubit_names, num_pulses= 9): -# super(CRAmpCalibration_PhEst, self).__init__(qubit_names, num_pulses = num_pulses) -# self.CRchan = ChannelLibraries.EdgeFactory(*self.qubit) -# self.amplitude = self.CRchan.pulse_params['amp'] -# self.target = np.pi/2 -# self.edge_name = self.CRchan.label +class CRAmpCalibration_PhEst(PhaseEstimation): + def __init__(self, edge, num_pulses= 5): + super(CRAmpCalibration_PhEst, self).__init__(edge, num_pulses = num_pulses, amplitude=edge.pulse_params['amp'], direction=direction, target=no.pi/2, epsilon=epsilon, max_iter=max_iter,**kwargs) + + + if self.sample: + c = bbndb.calibration.Calibration(value=self.amplitude, sample=self.sample, name="CRamp", category="PhaseEstimation") + c.date = datetime.datetime.now() + bbndb.get_cl_session().add(c) + bbndb.get_cl_session().commit() class DRAGCalibration(QubitCalibration): def __init__(self, qubit, deltas = np.linspace(-1,1,21), num_pulses = np.arange(8, 48, 4), **kwargs): @@ -933,7 +934,7 @@ class CRCalibration(QubitCalibration): phases (array): CR pulse phase(s). Longer than 1 for CRPhaseCalibration amps (array): CR pulse amp(s). Longer than 1 for CRAmpCalibration rise_fall (float): length of rise/fall of CR pulses - meas_qubits (list): specifies a subset of qubits to be measured (both by default) + meas_qubits (list): specifies a subset of qubits to be measured (both by default) """ def __init__(self, edge, From dc7d8e0a9a0049a4e1ae70f666cf33dd356a21ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 7 Oct 2019 17:28:43 -0400 Subject: [PATCH 098/109] Fix typo in ZX90_CR --- src/auspex/qubit/pulse_calibration.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 3c926982..047b5510 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -753,7 +753,7 @@ def sequence(self): # Determine whether it is a single- or a two-qubit pulse calibration if isinstance(self.qubit, bbndb.qgl.edge): # slight misnomer... qubit = self.qubit.target - cal_pulse = [ZX90_CR(self.qubit.source, self.qubit.target), amp=self.amplitude)] + cal_pulse = [ZX90_CR(self.qubit.source, self.qubit.target, amp=self.amplitude)] else: qubit = self.qubit cal_pulse = [Xtheta(self.qubit, amp=self.amplitude)] @@ -846,6 +846,8 @@ class CRAmpCalibration_PhEst(PhaseEstimation): def __init__(self, edge, num_pulses= 5): super(CRAmpCalibration_PhEst, self).__init__(edge, num_pulses = num_pulses, amplitude=edge.pulse_params['amp'], direction=direction, target=no.pi/2, epsilon=epsilon, max_iter=max_iter,**kwargs) + def update_settings(self): + self.qubit.pulse_params['amp'] = round(self.amplitude, 5) if self.sample: c = bbndb.calibration.Calibration(value=self.amplitude, sample=self.sample, name="CRamp", category="PhaseEstimation") From e70602327c376aa958b3b2bb6ece37ed608f7576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 7 Oct 2019 17:41:45 -0400 Subject: [PATCH 099/109] typo Edge --- src/auspex/qubit/pulse_calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 047b5510..6188f1ca 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -751,7 +751,7 @@ def __init__(self, channel, num_pulses= 1, amplitude= 0.1, direction = 'X', def sequence(self): # Determine whether it is a single- or a two-qubit pulse calibration - if isinstance(self.qubit, bbndb.qgl.edge): # slight misnomer... + if isinstance(self.qubit, bbndb.qgl.Edge): # slight misnomer... qubit = self.qubit.target cal_pulse = [ZX90_CR(self.qubit.source, self.qubit.target, amp=self.amplitude)] else: From 105cae452a6135ad6bada200597582a9bc6dda43 Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 8 Oct 2019 09:45:16 -0400 Subject: [PATCH 100/109] Add plots to the same figure --- src/auspex/analysis/fits.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/auspex/analysis/fits.py b/src/auspex/analysis/fits.py index 6cf25f39..c261bd20 100644 --- a/src/auspex/analysis/fits.py +++ b/src/auspex/analysis/fits.py @@ -23,14 +23,16 @@ class AuspexFit(object): ylabel = "Y points" title = "Auspex Fit" bounds = None + ax = None - def __init__(self, xpts, ypts, make_plots=False): + def __init__(self, xpts, ypts, make_plots=False, ax=None): """Perform a least squares fit of 1-D data. Args: xpts (numpy.array): Independent fit variable data. ypts (numpy.array): Dependent fit variable data. make_plots (bool, optional): Generate a plot of the data and fit. + ax (Axes, optional): Axes on which to draw plot. If None, new figure is created """ @@ -39,6 +41,7 @@ def __init__(self, xpts, ypts, make_plots=False): self.ypts = ypts self._do_fit(self.bounds) if make_plots: + self.ax = ax self.make_plots() def _initial_guess(self): @@ -68,15 +71,23 @@ def make_plots(self): """Create a plot of the input data and the fitted model. By default will include any annotation defined in the `annotation()` class method. """ - plt.figure() - plt.plot(self.xpts, self.ypts, ".", markersize=15, label="Data") - plt.plot(self.xpts, self.model(self.xpts), "-", linewidth=3, label="Fit") - plt.xlabel(self.xlabel, fontsize=14) - plt.ylabel(self.ylabel, fontsize=14) - plt.title(self.title, fontsize=14) - plt.annotate(self.annotation(), xy=(0.4, 0.10), - xycoords='axes fraction', size=12) - + if self.ax is None: + plt.figure() + plt.plot(self.xpts, self.ypts, ".", markersize=15, label="Data") + plt.plot(self.xpts, self.model(self.xpts), "-", linewidth=3, label="Fit") + plt.xlabel(self.xlabel, fontsize=14) + plt.ylabel(self.ylabel, fontsize=14) + plt.title(self.title, fontsize=14) + plt.annotate(self.annotation(), xy=(0.4, 0.10), + xycoords='axes fraction', size=12) + else: + self.ax.plot(self.xpts, self.ypts, ".", markersize=15, label="Data") + self.ax.plot(self.xpts, self.model(self.xpts), "-", linewidth=3, label="Fit") + self.ax.set_xlabel(self.xlabel, fontsize=14) + self.ax.set_ylabel(self.ylabel, fontsize=14) + self.ax.set_title(self.title, fontsize=14) + self.ax.annotate(self.annotation(), xy=(0.4, 0.10), + xycoords='axes fraction', size=12) def annotation(self): """Annotation for the `make_plot()` method. Should return a string that is passed to `matplotlib.pyplot.annotate`. From 481c17f717a31e398ad4cb32ad687f1da6f4cc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 8 Oct 2019 10:28:43 -0400 Subject: [PATCH 101/109] Fix some CRAmp_PhEst typos --- src/auspex/qubit/pulse_calibration.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 6188f1ca..41b99332 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -843,8 +843,9 @@ def update_settings(self): bbndb.get_cl_session().commit() class CRAmpCalibration_PhEst(PhaseEstimation): - def __init__(self, edge, num_pulses= 5): - super(CRAmpCalibration_PhEst, self).__init__(edge, num_pulses = num_pulses, amplitude=edge.pulse_params['amp'], direction=direction, target=no.pi/2, epsilon=epsilon, max_iter=max_iter,**kwargs) + def __init__(self, edge, num_pulses= 5, **kwargs): + super(CRAmpCalibration_PhEst, self).__init__(edge, num_pulses = num_pulses, amplitude=edge.pulse_params['amp'], direction='X', target=np.pi/2, epsilon=1e-2, max_iter=5,**kwargs) + self.qubits = [edge.target] def update_settings(self): self.qubit.pulse_params['amp'] = round(self.amplitude, 5) @@ -1172,9 +1173,9 @@ def phase_to_amplitude(phase, sigma, amp, target, epsilon=1e-2): phase_error = phase - target if np.abs(phase_error) < epsilon or np.abs(phase_error/sigma) < 1: if np.abs(phase_error) < epsilon: - logger.info('Reached target rotation angle accuracy'); + logger.info('Reached target rotation angle accuracy. Set amplitude: %.4f\n'%amp) elif abs(phase_error/sigma) < 1: - logger.info('Reached phase uncertainty limit'); + logger.info('Reached phase uncertainty limit. Set amplitude: %.4f\n'%amp) done_flag = 1 if amp > 1.0 or amp < epsilon: From f92c193d4196a0822a732839f8644c1ae820f14c Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 8 Oct 2019 10:32:47 -0400 Subject: [PATCH 102/109] Plot mods for qubit fits --- src/auspex/analysis/qubit_fits.py | 49 ++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/src/auspex/analysis/qubit_fits.py b/src/auspex/analysis/qubit_fits.py index bf7b7c17..5d0ec387 100644 --- a/src/auspex/analysis/qubit_fits.py +++ b/src/auspex/analysis/qubit_fits.py @@ -151,7 +151,7 @@ class RamseyFit(AuspexFit): ylabel = r"<$\sigma_z$>" title = "Ramsey Fit" - def __init__(self, xpts, ypts, two_freqs=False, AIC=True, make_plots=False, force=False): + def __init__(self, xpts, ypts, two_freqs=False, AIC=True, make_plots=False, force=False, ax=None): """One or two frequency Ramsey experiment fit. If a two-frequency fit is selected by the user or by comparing AIC scores, fit parameters are returned as tuples instead of single numbers. @@ -163,6 +163,7 @@ def __init__(self, xpts, ypts, two_freqs=False, AIC=True, make_plots=False, forc AIC (Bool): Decide between one and two frequency fits using the Akaike information criterion. make_plots (Bool): Display a plot of data and fit result. + ax (Axes, optional): Axes on which to draw plot. If None, new figure is created force (Bool): Force the selection of a two-frequency fit regardless of AIC score. """ @@ -170,6 +171,7 @@ def __init__(self, xpts, ypts, two_freqs=False, AIC=True, make_plots=False, forc self.two_freqs = two_freqs self.force = force self.plots = make_plots + self.ax = ax assert len(xpts) == len(ypts), "Length of X and Y points must match!" self.xpts = xpts @@ -274,7 +276,7 @@ class SingleQubitRBFit(AuspexFit): ylabel = r"<$\sigma_z$>" title = "Single Qubit RB Fit" - def __init__(self, lengths, data, make_plots=False, log_scale_x=True, bounded_fit=True): + def __init__(self, lengths, data, make_plots=False, log_scale_x=True, bounded_fit=True, ax=None): repeats = len(data) // len(lengths) xpts = np.array(lengths) @@ -285,6 +287,7 @@ def __init__(self, lengths, data, make_plots=False, log_scale_x=True, bounded_fi self.data_points = np.reshape(data,(len(lengths),repeats)) self.errors = np.std(self.data_points, 1) self.log_scale_x = log_scale_x + self.ax = ax if log_scale_x: self.xlabel = r"$log_2$ Clifford Number" @@ -294,7 +297,7 @@ def __init__(self, lengths, data, make_plots=False, log_scale_x=True, bounded_fi if bounded_fit: self.bounds = ((0, -np.inf, 0), (1, np.inf, 1)) - super().__init__(xpts, ypts, make_plots=make_plots) + super().__init__(xpts, ypts, make_plots=make_plots, ax=ax) @staticmethod def _model(x, *p): @@ -329,20 +332,32 @@ def annotation(self): return r'avg. error rate r = {:.2e} {} {:.2e}'.format(self.fit_params["r"], chr(177), self.fit_errors["r"]) def make_plots(self): - - plt.figure() - #plt.plot(self.xpts, self.data,'.',markersize=15, label='data') - plt.errorbar(self.lengths, self.ypts, yerr=self.errors/np.sqrt(len(self.lengths)), - fmt='*', elinewidth=2.0, capsize=4.0, label='mean') - plt.plot(range(int(self.lengths[-1])), self.model(range(int(self.lengths[-1]))), label='fit') - if self.log_scale_x: - plt.xscale('log') - - plt.xlabel(self.xlabel) - plt.ylabel(self.ylabel) - plt.legend() - plt.annotate(self.annotation(), xy=(0.4, 0.10), - xycoords='axes fraction', size=12) + if self.ax is None: + plt.figure() + #plt.plot(self.xpts, self.data,'.',markersize=15, label='data') + plt.errorbar(self.lengths, self.ypts, yerr=self.errors/np.sqrt(len(self.lengths)), + fmt='*', elinewidth=2.0, capsize=4.0, label='mean') + plt.plot(range(int(self.lengths[-1])), self.model(range(int(self.lengths[-1]))), label='fit') + if self.log_scale_x: + plt.xscale('log') + + plt.xlabel(self.xlabel) + plt.ylabel(self.ylabel) + plt.legend() + plt.annotate(self.annotation(), xy=(0.4, 0.10), + xycoords='axes fraction', size=12) + else: + self.ax.errorbar(self.lengths, self.ypts, yerr=self.errors/np.sqrt(len(self.lengths)), + fmt='*', elinewidth=2.0, capsize=4.0, label='mean') + self.ax.plot(range(int(self.lengths[-1])), self.model(range(int(self.lengths[-1]))), label='fit') + if self.log_scale_x: + self.ax.set_xscale('log') + + self.ax.set_xlabel(self.xlabel) + self.ax.set_ylabel(self.ylabel) + self.ax.legend() + self.ax.annotate(self.annotation(), xy=(0.4, 0.10), + xycoords='axes fraction', size=12) class PhotonNumberFit(AuspexFit): """Fit number of measurement photons before a Ramsey. See McClure et al., Phys. Rev. App. 2016 From f1d48671799b2ce0e857ff559e36e8788748eb45 Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 8 Oct 2019 16:57:06 -0400 Subject: [PATCH 103/109] Add transmitters with slave triggers --- src/auspex/qubit/qubit_exp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auspex/qubit/qubit_exp.py b/src/auspex/qubit/qubit_exp.py index 54dc230e..78533a6e 100644 --- a/src/auspex/qubit/qubit_exp.py +++ b/src/auspex/qubit/qubit_exp.py @@ -125,9 +125,10 @@ def create_from_meta(self, meta_file, averages): else: self.edges = [] self.phys_chans = list(set([e.phys_chan for e in self.controlled_qubits + self.measurements + self.edges])) - self.transmitters = list(set([e.phys_chan.transmitter for e in self.controlled_qubits + self.measurements + self.edges])) self.receiver_chans = list(set([e.receiver_chan for e in self.measurements])) - self.trig_chans = list(set([e.trig_chan.phys_chan for e in self.measurements])) + self.slave_trigs = [c for c in self.chan_db.channels if c.label == 'slave_trig'] + self.trig_chans = list(set([e.trig_chan.phys_chan for e in self.measurements])) + [c.phys_chan for c in self.slave_trigs] + self.transmitters = list(set([e.phys_chan.transmitter for e in self.controlled_qubits + self.measurements + self.edges + self.slave_trigs])) self.receivers = list(set([e.receiver_chan.receiver for e in self.measurements])) self.generators = list(set([q.phys_chan.generator for q in self.measured_qubits + self.controlled_qubits + self.measurements if q.phys_chan.generator])) self.qubits_by_name = {q.label: q for q in self.measured_qubits + self.controlled_qubits} From 7641acef990d65a8b78f8c87f6ecffe4470ab38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 9 Oct 2019 13:49:41 -0400 Subject: [PATCH 104/109] Misplaced if else on load kernel --- src/auspex/filters/integrator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/auspex/filters/integrator.py b/src/auspex/filters/integrator.py index e816ab3d..0c46eb8e 100644 --- a/src/auspex/filters/integrator.py +++ b/src/auspex/filters/integrator.py @@ -54,13 +54,14 @@ def update_descriptors(self): if self.kernel.value: if os.path.exists(os.path.join(config.KernelDir, self.kernel.value+'.txt')): kernel = np.loadtxt(os.path.join(config.KernelDir, self.kernel.value+'.txt'), dtype=complex, converters={0: lambda s: complex(s.decode().replace('+-', '-'))}) - if self.simple_kernel.value: - logger.warning("Using specified kernel. To use a box car filter instead, clear kernel.value") else: try: kernel = eval(self.kernel.value.encode('unicode_escape')) except: raise ValueError('Kernel invalid. Provide a file name or an expression to evaluate') + if self.simple_kernel.value: + logger.warning("Using specified kernel. To use a box car filter instead, clear kernel.value") + elif self.simple_kernel.value: time_pts = self.sink.descriptor.axes[-1].points time_step = time_pts[1] - time_pts[0] From b81477d1418a94904af5fd6efab3f88f771e609b Mon Sep 17 00:00:00 2001 From: Billy Date: Thu, 10 Oct 2019 16:47:36 -0400 Subject: [PATCH 105/109] Fix typos CRPhase and Amp --- src/auspex/qubit/pulse_calibration.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/auspex/qubit/pulse_calibration.py b/src/auspex/qubit/pulse_calibration.py index 41b99332..7ba76afc 100644 --- a/src/auspex/qubit/pulse_calibration.py +++ b/src/auspex/qubit/pulse_calibration.py @@ -1032,10 +1032,12 @@ def __init__(self, edge, length=None, phases=np.linspace(0,2*np.pi,21), amp=0.8, super().__init__(edge, lengths=[length], phases=phases, amps=[amp], rise_fall=rise_fall, **kwargs) def sequence(self): - qc, qt = self.qubits - seqs = [[Id(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*Id(qc), MEAS(qt)*MEAS(qc)] for ph in self.phases] - seqs += [[X(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*X(qc), MEAS(qt)*MEAS(qc)] for ph in self.phases] - seqs += create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) + qc = self.edge.source + qt = self.edge.target + measBlock = reduce(operator.mul, [MEAS(q) for q in self.qubits]) + seqs = [[Id(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*Id(qc), measBlock] for ph in self.phases] + seqs += [[X(qc)] + echoCR(qc, qt, length=self.lengths[0], phase=ph, amp=self.amps[0], riseFall=self.rise_fall).seq + [X90(qt)*X(qc), measBlock] for ph in self.phases] + seqs += create_cal_seqs(self.qubits, 2) return seqs def descriptor(self): @@ -1062,10 +1064,12 @@ def __init__(self, edge, amp_range = 0.4, amp = 0.8, rise_fall = 40e-9, num_CR = super().__init__(edge, lengths=[length], phases=[phase], amps=amps, rise_fall=rise_fall, **kwargs) def sequence(self): - qc, qt = self.qubits - seqs = [[Id(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase=self.phases[0], amp=a, riseFall=self.rise_fall).seq + [Id(qc), MEAS(qt)*MEAS(qc)] - for a in self.amps]+ [[X(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase= self.phases[0], amp=a, riseFall=self.rise_fall).seq + [X(qc), MEAS(qt)*MEAS(qc)] - for a in self.amps] + create_cal_seqs((qc,qt), 2, measChans=(qc,qt)) + qc = self.edge.source + qt = self.edge.target + measBlock = reduce(operator.mul, [MEAS(q) for q in self.qubits]) + seqs = [[Id(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase=self.phases[0], amp=a, riseFall=self.rise_fall).seq + [Id(qc), measBlock] + for a in self.amps]+ [[X(qc)] + self.num_CR*echoCR(qc, qt, length=self.lengths[0], phase= self.phases[0], amp=a, riseFall=self.rise_fall).seq + [X(qc), measBlock] + for a in self.amps] + create_cal_seqs(self.qubits, 2) return seqs def descriptor(self): From 33de256eef00dff683275e13dd3f876b425bf52c Mon Sep 17 00:00:00 2001 From: Billy Date: Tue, 15 Oct 2019 15:36:46 -0400 Subject: [PATCH 106/109] Fixed Alazar completion condition for multiple channels --- src/auspex/instruments/alazar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auspex/instruments/alazar.py b/src/auspex/instruments/alazar.py index b4d06bf9..dfcbdbd5 100644 --- a/src/auspex/instruments/alazar.py +++ b/src/auspex/instruments/alazar.py @@ -115,7 +115,7 @@ def data_available(self): def done(self): # logger.warning(f"Checking alazar doneness: {self.total_received.value} {self.number_segments * self.number_averages * self.record_length}") - return self.total_received.value >= (self.number_segments * self.number_averages * self.record_length) + return self.total_received.value >= (self.number_segments * self.number_averages * self.record_length * len(self.channels)) def get_socket(self, channel): if channel in self._chan_to_rsocket: From 85ea3ff57e514b83ece1584da0a44cdea5f16a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 16 Oct 2019 10:25:56 -0400 Subject: [PATCH 107/109] Try fixing conda update error --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 678d4e3f..f28d15bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,7 @@ install: # - echo "Database file at $BBN_DB" - hash -r - conda config --set always_yes yes --set changeps1 no + - conda install -c anaconda setuptools - conda update -q conda # Useful for debugging any issues with conda - conda info -a From bd4e71893a13a7471e9dee30e7b0a0a371c14326 Mon Sep 17 00:00:00 2001 From: Matthew Ware Date: Mon, 21 Oct 2019 18:40:18 -0400 Subject: [PATCH 108/109] Added a note about the ulimit error for ZMQ in OSX. -MW --- doc/index.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/index.rst b/doc/index.rst index 5f0f08be..377c7c3f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -42,6 +42,12 @@ Qubit Experiments Auspex is agnostic to the type of experiment being performed, but we include infrastructure for configuring and executing :ref:`qubit experiments ` using the gate-level `QGL `_ language. In this case, auspex relies on `bbndb `_ as a database backend for sharing state and keeping track of configurations. Depending on the experiments being run, one may need to install a number of additional driver libraries. +If you're running on a system with a low file descriptor limit you may see a +`ulimit` error when trying to run or simulate experiments. This will look like a +`too many files error` in python. This stems from ZMQ asynchronously opening +and closing a large number of files. OSX has a default limit per notebook of +256 open files. You can easily change this number at the terminal before +launching a notebook: `ulimit -n 4096` or put this line in your `.bash_prifile`. Genealogy and Etymology *********************** From e9271fa0961044069ea85206505a2c9d48edfbad Mon Sep 17 00:00:00 2001 From: Matthew Ware Date: Wed, 23 Oct 2019 11:58:48 -0400 Subject: [PATCH 109/109] Add example of basic qubit tune up workflow. -MW (#419) * Add example of basic qubit tune up workflow. -MW * Added actual calibrations to example notebook. -MW --- .../QubitExperiments/APS2-2Q-Example.ipynb | 1236 +++++++++++++++++ 1 file changed, 1236 insertions(+) create mode 100755 doc/examples/QubitExperiments/APS2-2Q-Example.ipynb diff --git a/doc/examples/QubitExperiments/APS2-2Q-Example.ipynb b/doc/examples/QubitExperiments/APS2-2Q-Example.ipynb new file mode 100755 index 00000000..4aec4797 --- /dev/null +++ b/doc/examples/QubitExperiments/APS2-2Q-Example.ipynb @@ -0,0 +1,1236 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.environ['AWG_DIR'] = \"./AWG\"\n", + "\n", + "import matplotlib\n", + "import matplotlib.pyplot as plt\n", + "import QGL.config\n", + "import auspex.config\n", + "auspex.config.AWGDir = \"/home/qlab/BBN/AWG\"\n", + "QGL.config.AWGDir = \"/home/qlab/BBN/AWG\"\n", + "auspex.config.KernelDir = \"/home/qlab/BBN/Kernels\"\n", + "QGL.config.KernelDir = \"/home/qlab/BBN/Kernels\"\n", + "\n", + "%matplotlib inline\n", + "\n", + "from auspex.analysis.qubit_fits import *\n", + "from auspex.analysis.helpers import *\n", + "\n", + "from QGL import *\n", + "from auspex.qubit import *\n", + "\n", + "#import seaborn as sns\n", + "#sns.set_style('whitegrid')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Channel Library Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# this will all be system dependent\n", + "cl = ChannelLibrary(db_resource_name=\":memory:\")\n", + "\n", + "q1 = cl.new_qubit(\"q1\")\n", + "q2 = cl.new_qubit(\"q2\")\n", + "ip_addresses = [f\"192.168.4.{i}\" for i in [21, 22, 23, 24, 25, 26, 28, 29]]\n", + "aps2 = cl.new_APS2_rack(\"Maxwell\", ip_addresses, tdm_ip=\"192.168.4.11\")\n", + "cl.set_master(aps2.px(\"TDM\"))\n", + "dig_1 = cl.new_X6(\"MyX6\", address=0)\n", + "\n", + "dig_1.record_length = 4096\n", + "\n", + "# qubit 1\n", + "AM1 = cl.new_source(\"AutodyneM1\", \"HolzworthHS9000\", \"HS9004A-492-1\", \n", + " power=16.0, frequency=6.4762e9, reference=\"10MHz\")\n", + "\n", + "q1src = cl.new_source(\"q1source\", \"HolzworthHS9000\", \"HS9004A-492-2\", \n", + " power=16.0, frequency=4.2e9, reference=\"10MHz\")\n", + "\n", + "cl.set_measure(q1, aps2.tx(4), dig_1.channels[1], gate=False, trig_channel=aps2.tx(6).ch(\"m3\"), generator=AM1)\n", + "cl.set_control(q1, aps2.tx(5), generator=q1src)\n", + "\n", + "\n", + "cl[\"q1\"].measure_chan.autodyne_freq = 11e6\n", + "cl[\"q1\"].measure_chan.pulse_params = {\"length\": 3e-6,\n", + " \"amp\": 1.0,\n", + " \"sigma\": 1.0e-8,\n", + " \"shape_fun\": \"tanh\"}\n", + "\n", + "\n", + "cl[\"q1\"].frequency = 67.0e6\n", + "cl[\"q1\"].pulse_params = {\"length\": 100e-9,\n", + " \"pi2Amp\": 0.4,\n", + " \"piAmp\": 0.8,\n", + " \"shape_fun\": \"drag\",\n", + " \"drag_scaling\": 0.0,\n", + " \"sigma\": 5.0e-9}\n", + "\n", + "#qubit 2\n", + "AM2 = cl.new_source(\"AutodyneM2\", \"HolzworthHS9000\", \"HS9004A-492-3\", \n", + " power=16.0, frequency=6.4762e9, reference=\"10MHz\")\n", + "\n", + "q2src = cl.new_source(\"q2source\", \"HolzworthHS9000\", \"HS9004A-492-4\", \n", + " power=16.0, frequency=4.2e9, reference=\"10MHz\")\n", + "\n", + "cl.set_measure(q2, aps2.tx(7), dig_1.channels[0], gate=False, trig_channel=aps2.tx(6).ch(\"m3\"), generator=AM2)\n", + "cl.set_control(q2, aps2.tx(8), generator=q2src)\n", + "\n", + "cl[\"q2\"].measure_chan.autodyne_freq = 11e6\n", + "cl[\"q2\"].measure_chan.pulse_params = {\"length\": 3e-6,\n", + " \"amp\": 1.0,\n", + " \"sigma\": 1.0e-8,\n", + " \"shape_fun\": \"tanh\"}\n", + "\n", + "cl.commit()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# initialize all four APS2 to linear regime\n", + "for i in range(4,8):\n", + " aps2.tx(i).ch(1).I_channel_amp_factor = 0.5\n", + " aps2.tx(i).ch(1).Q_channel_amp_factor = 0.5\n", + " aps2.tx(i).ch(1).amp_factor = 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "pl = PipelineManager()\n", + "pl.create_default_pipeline(qubits=[q1,q2])\n", + "\n", + "for ql in ['q1','q2']:\n", + " qb = cl[ql]\n", + " pl[ql].clear_pipeline()\n", + "\n", + " pl[ql].stream_type = \"raw\"\n", + "\n", + " pl[ql].create_default_pipeline(buffers=False)\n", + " \n", + " pl[ql][\"Demodulate\"].frequency = qb.measure_chan.autodyne_freq\n", + "\n", + " # only enable this filter when you're running single shot fidelity\n", + " #pl[ql].add(FidelityKernel(save_kernel=True, logistic_regression=True, set_threshold=True, label=\"Q1_SSF\"))\n", + "\n", + " pl[ql][\"Demodulate\"][\"Integrate\"].box_car_start = 3e-7\n", + " pl[ql][\"Demodulate\"][\"Integrate\"].box_car_stop = 2.3e-6\n", + "\n", + " #pl[ql].add(Display(label=ql+\" - Raw\", plot_dims=0))\n", + " #pl[ql][\"Demodulate\"].add(Display(label=ql+\" - Demod\", plot_dims=0))\n", + " pl[ql][\"Demodulate\"][\"Integrate\"][\"Average\"].add(Display(label=ql+\" - Final Average\", plot_dims=0))\n", + " \n", + " # if you want to see partial averages: \n", + " #pl[ql][\"Demodulate\"][\"Integrate\"][\"Average\"].add(Display(label=ql+\" - Partial Average\", plot_dims=0), connector_out=\"partial_average\")\n", + " \n", + " #pl[ql][\"Demodulate\"][\"Integrate\"][\"Average\"].add(Display(label=ql+\" - Partial Average1d\", plot_dims=1), connector_out=\"partial_average\")\n", + "pl.show_pipeline()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cavity Spectroscopy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pf = PulsedSpec(q1, specOn=False)\n", + "plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(pf,averages=256)\n", + "#exp.add_qubit_sweep(q2, \"measure\", \"frequency\", np.linspace(6.38e9, 6.395e9, 51))\n", + "exp.add_qubit_sweep(q1, \"measure\", \"frequency\", np.linspace(6.424e9, 6.432e9, 45))\n", + "#exp.add_qubit_sweep(q1,\"measure\",\"amplitude\",np.linspace(0.2,0.8,10))\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# data, desc = exp.writers[0].get_data()\n", + "# plt.plot(desc.axes[0].points, np.abs(data))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "AM1.frequency = 6.42843e9" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "AM2.frequency = 6.3863e9" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Qubit Spectroscopy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qb = q1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qb.frequency = 0.0\n", + "qb.pulse_params['length'] = 5e-6\n", + "qb.pulse_params['shape_fun'] = \"tanh\"\n", + "pf = PulsedSpec(qb, specOn=True)\n", + "plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "pf = PulsedSpec(qb, specOn=True)\n", + "exp = QubitExperiment(pf,averages=256)\n", + "exp.add_qubit_sweep(qb, \"control\", \"frequency\", np.linspace(5.28e9, 5.34e9, 61))\n", + "#exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# data, desc = exp.writers[0].get_data()\n", + "# plt.plot(desc.axes[0].points, np.abs(data))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q1.frequency = -63e6\n", + "q1.pulse_params['length'] = 200e-9\n", + "q1.pulse_params['shape_fun'] = \"tanh\"\n", + "q1.pulse_params['piAmp'] = 0.4\n", + "q1.pulse_params['pi2Amp'] = 0.2\n", + "pf = PulsedSpec(q1, specOn=True)\n", + "#plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fq = 5.26e9 #5.2525e9 \n", + "q1src.frequency = fq - q1.frequency\n", + "q1.phys_chan.I_channel_amp_factor = 0.2\n", + "q1.phys_chan.Q_channel_amp_factor = 0.2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q1src.frequency" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q2.frequency = 81e6\n", + "q2.pulse_params['length'] = 200e-9\n", + "q2.pulse_params['shape_fun'] = \"tanh\"\n", + "q2.pulse_params['piAmp'] = 0.4\n", + "q2.pulse_params['pi2Amp'] = 0.2\n", + "pf = PulsedSpec(q2, specOn=True)\n", + "#plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fq2 = 5.2113e9\n", + "q2src.frequency = fq2 - q2.frequency\n", + "q2.phys_chan.I_channel_amp_factor = 0.2\n", + "q2.phys_chan.Q_channel_amp_factor = 0.2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q2src.frequency" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mixer calibration " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "salo = cl.new_source(\"salo\", \"HolzworthHS9000\", \"HS9004A-381-4\", \n", + " power=10.0, frequency=6.5e9, reference=\"10MHz\")\n", + "specAn = cl.new_spectrum_analzyer('specAn','ASRL/dev/ttyACM0',salo)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from auspex.instruments import enumerate_visa_instruments, SpectrumAnalyzer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "enumerate_visa_instruments()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# from here out, uncomment cal.calibrate() if you want to actually run the cal\n", + "cal = MixerCalibration(q1,specAn,'control', phase_range = (-0.5, 0.5), amp_range=(0.8, 1.2))\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# listed here only if manual adjustment is needed\n", + "\n", + "q1.phys_chan.I_channel_offset = -0.0004\n", + "q1.phys_chan.Q_channel_offset = -0.019\n", + "q1.phys_chan.amp_factor = 1.004\n", + "q1.phys_chan.phase_skew = 0.053" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "cal = MixerCalibration(q2,specAn,'control', phase_range = (-0.5, 0.5), amp_range=(0.8, 1.2))\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q2.phys_chan.I_channel_offset = -0.0004\n", + "q2.phys_chan.Q_channel_offset = -0.019\n", + "q2.phys_chan.amp_factor = 0.985\n", + "q2.phys_chan.phase_skew = 0.074" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cal = MixerCalibration(q1,specAn,'measure', phase_range = (-0.5, 0.5), amp_range=(0.8, 1.2))\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cal = MixerCalibration(q2,specAn,'measure', phase_range = (-0.5, 0.5), amp_range=(0.8, 1.2))\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rabi Width" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "pf = RabiWidth(q1, np.arange(20e-9, 0.602e-6, 10e-9))\n", + "exp = QubitExperiment(pf, averages=200)\n", + "plot_pulse_files(pf)\n", + "#exp.add_qubit_sweep(q1, \"control\", \"frequency\", q1src.frequency + np.linspace(-6e6, 6e6, 61))\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "pf = RabiWidth(q2, np.arange(20e-9, 0.602e-6, 10e-9))\n", + "exp = QubitExperiment(pf, averages=200)\n", + "plot_pulse_files(pf)\n", + "#exp.add_qubit_sweep(q2, \"control\", \"frequency\", q2src.frequency + np.linspace(-2e6, 2e6, 21))\n", + "#exp.add_qubit_sweep(q2, \"measure\", \"frequency\", AM2.frequency + np.linspace(-2e6, 2e6, 11))\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Rabi Amp" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pf = RabiAmp(q1, np.linspace(-1, 1, 101))\n", + "exp = QubitExperiment(pf, averages=128)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cal = RabiAmpCalibration(q1,quad='imag')\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q1.pulse_params['piAmp'] = 0.6179\n", + "q1.pulse_params['pi2Amp'] = q1.pulse_params['piAmp']/2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "pf = RabiAmp(q2, np.linspace(-1, 1, 101))\n", + "exp = QubitExperiment(pf, averages=128)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cal = RabiAmpCalibration(q2,quad='imag')\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q2.pulse_params['piAmp'] = 0.743\n", + "q2.pulse_params['pi2Amp'] = q2.pulse_params['piAmp']/2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Ramsey Calibration" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# need to be in the neighbourhood for this to work\n", + "\n", + "cal = RamseyCalibration(q1,quad='imag')\n", + "#cal.calibrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## T1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qb = q2\n", + "icpts = np.linspace(20e-9, 201.02e-6, 101)\n", + "pf = InversionRecovery(qb, icpts)\n", + "exp = QubitExperiment(pf, averages=400)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data, desc = exp.writers[0].get_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from auspex.analysis.qubit_fits import T1Fit, RamseyFit\n", + "from auspex.analysis.helpers import cal_scale" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sdata = cal_scale(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fit = T1Fit(icpts, abs(data[0:-4]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fit.make_plots()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### T2" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "rpts = np.linspace(20e-9, 50.02e-6, 101)\n", + "pf = Ramsey(q1, rpts ,TPPIFreq=0e3)\n", + "#exp.add_qubit_sweep(q1, \"control\", \"frequency\", q1src.frequency + np.linspace(-2e6, 2e6, 21))\n", + "exp = QubitExperiment(pf, averages=200)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data, desc = exp.writers[0].get_data()\n", + "sdata = cal_scale(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fit = RamseyFit(rpts, abs(data[0:-4]),make_plots = True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fcorrect = fit.fit_params['f']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fcorrect" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#q1src.frequency -= fcorrect" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pf = Ramsey(q2, rpts,TPPIFreq=0e3)\n", + "exp = QubitExperiment(pf, averages=200)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data, desc = exp.writers[0].get_data()\n", + "sdata = cal_scale(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fit = RamseyFit(rpts, abs(data[0:-4]),make_plots = True)\n", + "fcorrect = fit.fit_params['f']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fcorrect*1e-6" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#q2src.frequency += fcorrect" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Echo experiments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(HahnEcho(q2, np.linspace(20e-9, 80.02e-6, 81), periods=5), averages=512)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data, desc = exp.writers[0].get_data()\n", + "cdata = cal_scale(np.real(data))\n", + "fit = RamseyFit(desc.axes[0].points[:-4], cdata, make_plots=True)\n", + "fit.fit_params" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single Qubit Cals" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "RabiAmpCalibration(q1, quad=\"imag\").calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PiCalibration(q1, quad=\"imag\", num_pulses=7).calibrate()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Pi2Calibration(q1, quad=\"imag\", num_pulses=7).calibrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Single-Qubit RB" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from auspex.analysis.qubit_fits import *\n", + "from auspex.analysis.helpers import *" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rb_lens = [2, 4, 8, 16, 32, 128, 256, 512]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "rb_seqs = create_RB_seqs(1, rb_lens)\n", + "pf = SingleQubitRB(q1, rb_seqs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(pf, averages=256)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data, desc = exp.writers[0].get_data()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lens = [int(l) for l in desc.axes[0].points[:-4]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SingleQubitRBFit(lens, cal_scale(np.imag(data)), make_plots=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Fancier things" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Maybe you want to see how $T_1$ varies with repeated measurement..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from auspex.parameter import IntParameter\n", + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "N = 1000\n", + "lengths = np.linspace(20e-9, 500.02e-6, 101)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "T1seq = [[X(q2), Id(q2, length=d), MEAS(q2)] for d in lengths]\n", + "T1seq += create_cal_seqs((q2,), 2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "wait_param = IntParameter(default=0)\n", + "wait_param.name = \"Repeat\"\n", + "#wait = lambda x: print(f\"{x}\")\n", + "def wait(x):\n", + " print(f\"{x}\")\n", + " time.sleep(2)\n", + "wait_param.assign_method(wait)\n", + "\n", + "\n", + "mf = compile_to_hardware(T1seq, \"T1/T1\")\n", + "exp = QubitExperiment(mf, averages=512)\n", + "exp.wait_param = wait_param\n", + "# with these params each shot is roughly 22 secs apart\n", + "\n", + "exp.add_sweep(exp.wait_param, list(range(N))) # repeat T1 scan 1000 times\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# load data\n", + "# get T1s\n", + "T1s = []\n", + "T1_error = []\n", + "#data, desc = exp.writers[0].get_data()\n", + "for i in range(N):\n", + " cdata = cal_scale(np.imag(data[i,:]))\n", + " fit = T1Fit(lengths, cdata, make_plots=False)\n", + " T1s.append(fit.fit_params[\"T1\"])\n", + " T1_error.append(fit.fit_errors[\"T1\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plt.figure(figsize=(8,6))\n", + "#plt.errorbar(range(1000),np.array(T1s)*1e6, yerr=np.array(T1_error)*1e6, fmt='+', elinewidth=0.5, barsabove=True, capsize=0.7)\n", + "plt.plot(range(1000),np.array(T1s)*1e6, '+')\n", + "plt.title(r'Q2 $T_1$ Variability')\n", + "plt.xlabel('N repeats')\n", + "plt.ylabel(r'$T_1$ (us)')\n", + "#plt.savefig('T1vsTime.png', dpi=300, bbox_inches='tight')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2Q RB" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "lengths = [2,4,6,8,10] # range(2,10) #[2**n for n in range(1,6)]\n", + "seqs = create_RB_seqs(2, lengths=lengths)\n", + "exp = qef.create(TwoQubitRB(q1, q2, seqs=seqs), expname='Q2Q1RB')\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2Q Gates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "edge12 = cl.new_edge(q1,q2)\n", + "cl.set_control(edge12, aps2.tx(5), generator=q1src)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q1_10 = q1.phys_chan.generator.frequency + q1.frequency\n", + "q2_10 = q2.phys_chan.generator.frequency + q2.frequency" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "edge12.frequency = q2_10 - q1.phys_chan.generator.frequency" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "edge12.pulse_params = {'length': 400e-9, 'amp': 0.8, 'shape_fun': 'tanh', 'sigma':10e-9}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q1.measure_chan.pulse_params['amp'] = 1.0\n", + "q2.measure_chan.pulse_params['amp'] = 1.0" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CR length cal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "crlens = np.arange(100e-9,2.1e-6,100e-9)\n", + "pf = EchoCRLen(q1,q2,lengths=crlens)\n", + "plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(pf, averages=512)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Above just runs the experiment used by the calibration routine. Here is the actual calibration:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "crlens = np.arange(100e-9,2.1e-6,100e-9)\n", + "# phase, amp and rise_fall have defaults but you can overwrite them\n", + "pce = CRLenCalibration(cl[\"q1->q2\"], lengths=lengths, phase = 0, amp = 0.8, rise_fall = 40e-9,\n", + " do_plotting=True, sample_name=\"CRLen\", averages=512)\n", + "pec.calibrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CR phase cal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "phases = np.arange(0,np.pi*2,np.pi/20)\n", + "pf = EchoCRPhase(q1,q2,phases,length=1000e-9)\n", + "plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(pf, averages=512)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "phases = np.linspace(0, 2*np.pi, 21)\n", + "pce = CRPhaseCalibration(edge12, phases = phases, amp = 0.8, rise_fall = 40e-9,\n", + " do_plotting=True, sample_name=\"CRPhase\", averages=512)\n", + "pec.calibrate()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CR amp cal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "amps = np.arange(0.7,0.9,0.1)\n", + "pf = EchoCRAmp(q1,q2,amps,length=1000e-9)\n", + "plot_pulse_files(pf)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "exp = QubitExperiment(pf, averages=512)\n", + "exp.run_sweeps()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pce = CRAmpCalibration(cl[\"q1->q2\"], amp_range = 0.2, rise_fall = 40e-9,\n", + " do_plotting=True, sample_name=\"CRAmp\", averages=512)\n", + "pec.calibrate()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.4" + }, + "latex_envs": { + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 0 + }, + "nav_menu": {}, + "toc": { + "navigate_menu": true, + "number_sections": true, + "sideBar": true, + "threshold": 6, + "toc_cell": false, + "toc_section_display": "block", + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}