From 1da816c683e6985200a396c7390256178f554f03 Mon Sep 17 00:00:00 2001 From: Evan Jeffrey Date: Wed, 27 Jul 2016 16:41:58 -0700 Subject: [PATCH] Remove obsolete GHz dac calibration / server --- GHzDACs/.project | 17 - GHzDACs/.pydevproject | 7 - GHzDACs/FPGA_simulation.py | 189 --- GHzDACs/dac_emulator.py | 219 --- GHzDACs/direct_ethernet_proxy.py | 372 ----- GHzDACs/ethernet_spoofer.py | 140 -- GHzDACs/sequencer.py | 607 -------- GHz_DAC_bringup.py | 291 ---- ghz_fpga_server.py | 2221 ------------------------------ ghzdac/__init__.py | 246 ---- ghzdac/calibrate.py | 683 --------- ghzdac/correction.py | 1286 ----------------- ghzdac/keys.py | 57 - ghzdac_cal/GHz_DAC_calibrate.py | 275 ---- ghzdac_cal/__init__.py | 0 15 files changed, 6610 deletions(-) delete mode 100644 GHzDACs/.project delete mode 100644 GHzDACs/.pydevproject delete mode 100644 GHzDACs/FPGA_simulation.py delete mode 100644 GHzDACs/dac_emulator.py delete mode 100644 GHzDACs/direct_ethernet_proxy.py delete mode 100644 GHzDACs/ethernet_spoofer.py delete mode 100644 GHzDACs/sequencer.py delete mode 100644 GHz_DAC_bringup.py delete mode 100644 ghz_fpga_server.py delete mode 100644 ghzdac/__init__.py delete mode 100644 ghzdac/calibrate.py delete mode 100644 ghzdac/correction.py delete mode 100644 ghzdac/keys.py delete mode 100644 ghzdac_cal/GHz_DAC_calibrate.py delete mode 100644 ghzdac_cal/__init__.py diff --git a/GHzDACs/.project b/GHzDACs/.project deleted file mode 100644 index 8d3ed867..00000000 --- a/GHzDACs/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - GHzFPGA_python - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/GHzDACs/.pydevproject b/GHzDACs/.pydevproject deleted file mode 100644 index 793da044..00000000 --- a/GHzDACs/.pydevproject +++ /dev/null @@ -1,7 +0,0 @@ - - - - -Default -python 2.6 - diff --git a/GHzDACs/FPGA_simulation.py b/GHzDACs/FPGA_simulation.py deleted file mode 100644 index 866b7173..00000000 --- a/GHzDACs/FPGA_simulation.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright (C) 2007 Daniel Sank -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -""" -### BEGIN NODE INFO -[info] -name = FPGA Simulation -version = 1.0.0 -description = Simulate ethernet communication with FPGA boards - -[startup] -cmdline = %PYTHON% %FILE% -timeout = 20 - -[shutdown] -message = 987654321 -timeout = 5 -### END NODE INFO -""" - -from twisted.internet.defer import inlineCallbacks, returnValue - -from labrad.types import Error -from labrad.server import LabradServer, setting - -import numpy as np - -DAC_SRAM_LEN = 10240 #words -DAC_SRAM_PAGE_LEN = 256 #words -DAC_SRAM_PAGES = DAC_SRAM_LEN/DAC_SRAM_PAGE_LEN -DAC_SRAM_DTYPE = np.uint8 - -class FPGAWrapper(object): - def __init__(self): - pass - -class DACWrapper(FPGAWrapper): - """ Represents a GHzDAC board. - ATTRIBUTES - sram - numpy array representing the board's SRAM. - each element is of type . - -""" -### BEGIN NODE INFO -[info] -name = DAC Emulator -version = 1.0 -description = A DAC emulator. - -[startup] -cmdline = %PYTHON% %FILE% -timeout = 20 - -[shutdown] -message = 987654321 -timeout = 20 -### END NODE INFO -""" - -''' -How to define DACs to be emulated: --- In the registry, under >> Servers >> DAC Emulator --- One folder for each DAC, with the DAC's name as folder name --- Recognized keys (* = can be changed with server function) - address *(MAC address) - device (what ethernet device (NIC) number to emulate on) - build (DAC build number) - SRAM Length *(in bytes) - plotting *(boolean--whether to plot SRAM on run.) -''' - -# doo dee doop, just a dac emulator - -import time -import sys -import string - -import numpy as np -import matplotlib.pyplot as plt - -from twisted.internet import defer, reactor, task -from twisted.internet.defer import inlineCallbacks, returnValue -from labrad.server import LabradServer, setting -from labrad.devices import DeviceServer, DeviceWrapper - -import ethernet_spoofer as es - -REGISTRY_PATH = ['', 'Servers', 'DAC Emulator'] - -DEFAULT_DEVICE = 0 -DEFAULT_MAC = '11:22:33:44:55:66' -DEFAULT_BUILD = 0 -DEFAULT_SRAM_LENGTH = 10240 - -def _convertTwosComplement(data): - ''' convert from 14-bits-in-32-bit-unsigned - to 14-bits-2s-complement-in-32-bit-unsigned. ''' - d = data.astype(np.int32) - d[d > 2**13 - 1] = d[d > 2**13 - 1] - 2**14 - return d - -class DacEmulator (DeviceWrapper): - - def connect(self, *args, **kwargs): - print "Creating emulator with %s" % str(kwargs) - # set up spoofer - address = kwargs.get("address", DEFAULT_MAC) - device = kwargs.get("device", DEFAULT_DEVICE) - self.spoof = es.EthernetSpoofer(address, device) - # set up DAC - self.build = kwargs.get("build", DEFAULT_BUILD) - self.sram_length = kwargs.get("SRAM Length", DEFAULT_SRAM_LENGTH) - self.initRegister() - self.initSRAM() - - # other options - self.plotting = kwargs.get("plotting", False) - - # run the loop - self.loop = task.LoopingCall(self.next_packet) - self.loopDone = self.loop.start(0.1) - - def shutdown(self): - self.loop.stop() - yield self.loopDone - del self.loop - - def next_packet(self): - ''' handle next packet ''' - packet = self.spoof.getPacket() - if not packet: - return - if packet['length'] == 1026: - # we have an SRAM write packet - adrstart = 256*ord(packet['data'][0]) + \ - 256*256*ord(packet['data'][1]) - if adrstart > self.sram_length - 256: - print "Bad SRAM packet: adrstart too big: %s" % adrstart - return - print "Writing SRAM at derp %s" % (adrstart / 256) - self.sram[adrstart:adrstart+256] = \ - np.fromstring(packet['data'][2:], '> 14 & 0x3FFF # second 14 bits - dacA, dacB = _convertTwosComplement(dacA), _convertTwosComplement(dacB) - ax = plt.figure().add_subplot(111) - ax.plot(dacA, 'b.') - ax.plot(dacB, 'r.') - ax.set_xlim(0, 10240) - ax.set_ylim(-2**13 -1, 2**13-1) - plt.show() - - def send(self, dest, data): - return self.spoof.sendPacket(dest, data) - -class DacEmulatorServer(DeviceServer): - name = "DAC Emulator" - deviceName = "DAC Emulator" - deviceWrapper = DacEmulator - - @inlineCallbacks - def findDevices(self): - '''Create DAC emulators.''' - reg = self.client.registry - yield reg.cd(REGISTRY_PATH) - resp = yield reg.dir() - names = resp[0].aslist - devs = [] - for n in names: - yield reg.cd(n) - keys = yield reg.dir() - keys = keys[1].aslist - p = reg.packet() - for k in keys: - p.get(k, key=k) - a = yield p.send() - devs.append((n, [], dict([(k, a[k]) for k in keys]))) - reg.cd(1) - returnValue(devs) - - @setting(10, 'Address', mac='s', returns='s') - def address(self, c, mac=''): - '''get/set the MAC address''' - dev = self.selectedDevice(c) - if mac: - dev.spoof.setAddress(mac) - return dev.spoof.mac - - @setting(11, 'Send', dest='s', data='s', returns='?') - def send(self, c, dest, data): - ''' - Send a packet, to destination mac "dest", with data "data". returns 0 - for success, error message otherwise - ''' - return self.selectedDevice(c).send(dest, data) - - @setting(20, 'Plotting', on='b', returns='b') - def plotting(self, c, on=None): - ''' get / set plotting mode ''' - dev = self.selectedDevice(c) - if on is not None: - dev.plotting = on - return dev.plotting - - @setting(21, "SRAM Length", len='w', returns='w') - def sram_length(self, c, len=None): - '''Get or set(!) the SRAM length. Note that this clears the SRAM.''' - dev = self.selectedDevice(c) - if len: - dev.sram_length = len - dev.initSRAM() - return dev.sram_length - - @setting(22, "Plot SRAM") - def plot_sram(self, c): - '''Plots the SRAM.''' - self.selectedDevice(c).plotSRAM() - -__server__ = DacEmulatorServer() - -if __name__ == '__main__': - from labrad import util - util.runServer(__server__) diff --git a/GHzDACs/direct_ethernet_proxy.py b/GHzDACs/direct_ethernet_proxy.py deleted file mode 100644 index a2c0eb55..00000000 --- a/GHzDACs/direct_ethernet_proxy.py +++ /dev/null @@ -1,372 +0,0 @@ -# Copyright (C) 2007 Daniel Sank -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import random -import time - -import numpy as np -from twisted.internet import defer, reactor -from twisted.internet.defer import inlineCallbacks, returnValue - -from labrad.server import LabradServer, setting - - -class EthernetAdapter(object): - """Proxy for an ethernet adapter.""" - def __init__(self, name, mac): - self.name = name - self.mac = mac - self.listeners = [] - - def send(self, pkt): - """Send a packet on this adapter.""" - for listener in self.listeners: - listener(pkt) - - def addListener(self, listener): - """Add a listener to be called for each sent packet.""" - self.listeners.append(listener) - - def removeListener(self, listener): - """Remove a listener from this adapter.""" - self.listeners.remove(listener) - - -class LossyEthernetAdapter(EthernetAdapter): - """Proxy for a lossy ethernet adapter that can drop packets.""" - def __init__(self, name, mac, pLoss=0.01): - EthernetAdapter.__init__(self, name, mac) - self.pLoss = pLoss - - def send(self, pkt): - """Send a packet on this adapter.""" - for listener in listeners: - if random.random() < self.pLoss: - continue # simulate dropped packet - listener(pkt) - - -class EthernetListener(object): - """Listens for packets on an adapter. - - Filters can be added which examine incoming packets and - return a boolean result. Only if all filters match will - a given packet be passed on. - """ - def __init__(self, packetFunc): - self.packetFunc = packetFunc - self.listening = False - self.filters = [] - - def __call__(self, packet): - if self.listening and all(filter(packet) for filter in self.filters): - self.packetFunc(packet) - - def addFilter(self, filter): - self.filters.append(filter) - - -class DeferredBuffer(object): - """Buffer for packets/triggers received in a given context.""" - def __init__(self): - self.buf = [] - self.waiter = None - self.waitCount = 0 - - def put(self, packet): - self.buf.append(packet) - if self.waiter and len(self.buf) >= self.waitCount: - d = self.waiter - self.waiter = None - d.callback(None) - - def collect(self, n=1, timeout=None): - assert (self.waiter is None), 'already waiting' - if len(self.buf) >= n: - return defer.succeed() - else: - d = defer.Deferred() - if timeout is not None: - timeoutCall = reactor.callLater(timeout, d.errback, Exception('timeout')) - d.addCallback(self._cancelTimeout, timeoutCall) - self.waiter = d - self.waitCount = n - return d - - def _cancelTimeout(self, result, timeoutCall): - if timeoutCall.active(): - timeoutCall.cancel() - return result - - def get(self, n=1, timeout=None): - def _get(result): - pkts = self.buf[:n] - self.buf = self.buf[n:] - return pkts - d = self.collect(n) - d.addCallback(_get) - return d - - def discard(self, n=1, timeout=None): - def _discard(result): - self.buf = self.buf[n:] - d = self.collect(n) - d.addCallback(_discard) - return d - - def clear(self): - self.buf = [] - - -def parseMac(mac): - """Convert a string or tuple of words into a valid mac address.""" - if isinstance(mac, str): - mac = tuple(int(s, 16) for s in mac.split(':')) - return '%02X:%02X:%02X:%02X:%02X:%02X' % mac - - -class DirectEthernetProxy(LabradServer): - name = 'Direct Ethernet Proxy' - - def __init__(self, adapters=[]): - LabradServer.__init__(self) - - # make a dictionary of adapters, indexable by id or name - d = {} - for i, adapter in enumerate(adapters): - d[i] = d[adapter.name] = adapter - self.adapters = d - - def initServer(self): - pass - - def initContext(self, c): - c['triggers'] = DeferredBuffer() - c['buf'] = DeferredBuffer() - c['timeout'] = None - c['listener'] = EthernetListener(c['buf'].put) - c['listening'] = False - c['src'] = None - c['dest'] = None - c['typ'] = -1 - - def expireContext(self, c): - if c['listening']: - c['adapter'].removeListener(c['listener']) - - def getAdapter(self, c): - """Get the selected adapter in this context.""" - try: - return c['adapter'] - except KeyError: - raise Exception('Need to connect to an adapter') - - - # adapters - - @setting(1, 'Adapters', returns='*(ws)') - def adapters(self, c): - """Retrieves a list of network adapters""" - adapterList = sorted((id, a.name) for id, a in self.adapters) - return adapterList - - @setting(2, 'Connect', key=['s', 'w'], returns='s') - def connect(self, c, key): - try: - adapter = self.adapters[key] - except KeyError: - raise Exception('Adapter not found: %s' % key) - if 'adapter' in c: - c['adapter'].removeListener(listener) - adapter.addListener(c['listener']) - c['adapter'] = adapter - return adapter.name - - @setting(3, 'Listen', returns='') - def listen(self, c): - """Starts listening for SRAM packets""" - c['listener'].listening = True - - - # packet control and buffering - - @setting(10, 'Timeout', t='v[s]', returns='') - def timeout(self, c, t): - c['timeout'] = float(t) - - @setting(11, 'Collect', num='w', returns='') - def collect(self, c, num=1): - yield c['buf'].collect(num, timeout=c['timeout']) - - @setting(12, 'Discard', num=['w'], returns='') - def discard(self, c, num=1): - yield c['buf'].discard(num, timeout=c['timeout']) - - @setting(13, 'Read', num=['w'], returns=['(ssis)', '*(ssis)']) - def read(self, c, num=1): - def toStr(pkt): - src, dest, typ, data = pkt - data = np.tostring(data) - return (src, dest, typ, data) - return self._read(c, num, toStr) - - @setting(14, 'Read as Words', num=['w'], returns=['(ssi*w)', '*(ssi*w)']) - def read_as_words(self, c, num): - def toWords(pkt): - src, dest, typ, data = pkt - data = np.fromstring(data, dtype='uint8').astype('uint32') - return (src, dest, typ, data) - return self._read(c, num, toWords) - - @inlineCallbacks - def _read(self, c, num, func=None): - pkts = yield c['buf'].read(num, timeout=c['timeout']) - if func is None: - pkts = [func(pkt) for pkt in pkts] - if num == 1: - returnValue(pkts[0]) - else: - returnValue(pkts) - - @setting(15, 'Clear', returns='') - def clear(self, c): - c['buf'].clear() - - - # writing packets - - @setting(20, 'Source MAC', mac=['s', 'wwwwww'], returns='s') - def source_mac(self, c, mac=None): - if mac is not None: - mac = parseMac(mac) - c['src'] = mac - return mac - - @setting(21, 'Destination MAC', mac=['s', 'wwwwww'], returns='s') - def destination_mac(self, c, mac=None): - if mac is not None: - mac = parseMac(mac) - c['dest'] = mac - return mac - - @setting(22, "Ether Type", typ='i', returns='') - def ether_type(self, c, typ): - c['typ'] = typ - - @setting(23, 'Write', data=['s', '*w'], returns='') - def write(self, c, data): - adapter = self.getAdapter(c) - src, dest, typ = c['src'], c['dest'], c['typ'] - if src is None: - src = adapter.mac - if dest is None: - raise Exception('no destination mac specified!') - if isinstance(data, str): - data = np.fromstring(data, dtype='uint8') - else: - data = data.asarray.astype('uint8') - pkt = (src, dest, typ, data) - adapter.send(pkt) - - - # packet filters - - @setting(100, 'Require Source MAC', mac=['s', 'wwwwww'], returns='s') - def require_source_mac(self, c, mac): - mac = parseMac(mac) - c['listener'].addFilter(lambda pkt: pkt[0] == mac) - return mac - - @setting(101, 'Reject Source MAC', mac=['s', 'wwwwww'], returns='s') - def reject_source_mac(self, c, mac): - mac = parseMac(mac) - c['listener'].addFilter(lambda pkt: pkt[0] != mac) - return mac - - @setting(110, 'Require Destination MAC', mac=['s', 'wwwwww'], returns='s') - def require_destination_mac(self, c, mac): - mac = parseMac(mac) - c['listener'].addFilter(lambda pkt: pkt[1] == mac) - return mac - - @setting(111, 'Reject Destination MAC', mac=['s', 'wwwwww'], returns='s') - def reject_destination_mac(self, c, mac): - mac = parseMac(mac) - c['listener'].addFilter(lambda pkt: pkt[1] != mac) - return mac - - @setting(120, 'Require Length', length='w', returns='') - def require_length(self, c, length): - c['listener'].addFilter(lambda pkt: len(pkt[3]) == length) - - @setting(121, 'Reject Length', length='w', returns='') - def reject_length(self, c, length): - c['listener'].addFilter(lambda pkt: len(pkt[3]) != length) - - @setting(130, 'Require Ether Type', typ='i', returns='') - def require_ether_type(self, c, typ): - c['listener'].addFilter(lambda pkt: pkt[2] == typ) - - @setting(131, 'Reject Ether Type', typ='i', returns='') - def reject_ether_type(self, c, typ): - c['listener'].addFilter(lambda pkt: pkt[2] != typ) - - @setting(140, 'Require Content', offset='w', data=['s', '*w'], returns='') - def require_content(self, c, offset, data): - raise Exception('not implemented') - - @setting(141, 'Reject Content', offset='w', data=['s', '*w'], returns='') - def reject_content(self, c, offset, data): - raise Exception('not implemented') - - - # triggers - - @setting(200, 'Send Trigger', context='ww', returns='') - def send_trigger(self, c, context): - # have to send this to another context - # this next line is a bit of a hack that depends on internal labrad details - # should probably use an external bus object, like the ethernet adapter itself - other = self.contexts[context].data['triggers'].put('trigger from %s' % c.ID) - - @setting(201, 'Wait For Trigger', num='w', returns='v[s]: Elapsed wait time') - def wait_for_trigger(self, c, num=1): - start = time.time() - yield c['triggers'].discard(num) # does the real direct ethernet server have timeouts here? - end = time.time() - returnValue(end - start) - - - -if __name__ == '__main__': - from labrad import util - import adc - import dac - - # create ethernet - adapter0 = EthernetAdapter('proxy0', '01:23:45:67:89:00') - adapter1 = EthernetAdapter('proxy1', '01:23:45:67:89:01') - - # create devices - dev00 = dac.DACProxy(0, adapter0) - dev01 = dac.ADCProxy(1, adapter0) - dev02 = dac.DACProxy(2, adapter0) - - dev10 = dac.DACProxy(0, adapter1) - dev11 = dac.DACProxy(1, adapter1) - dev12 = dac.ADCProxy(2, adapter1) - - server = DirectEthernetProxy([adapter0, adapter1]) - util.runServer(server) diff --git a/GHzDACs/ethernet_spoofer.py b/GHzDACs/ethernet_spoofer.py deleted file mode 100644 index e704c4e1..00000000 --- a/GHzDACs/ethernet_spoofer.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -ethernet_spoofer.py - -set of functions for listening and replying to ethernet packets, by MAC -address. - -usage: - -spoofer = EthernetSpoofer('11:22:33:44:55:66') -packet = None -while packet is None: - packet = spoofer.getPacket() -print "From %s: %s" % (packet['src'], packet['data']) - -""" - -import winpcapy as wp - -class EthernetSpoofer(object): - def __init__(self, address, device=0): - ''' - Create a new spoofer. - - required: MAC address, in form 11:22:33:44:55:66 - optional: device number (i.e. NIC index), default=0 - ''' - self.adhandle = self.openDevice(device) - self.setAddress(address) - - def openDevice(self, device): - '''Open our ethernet device for listening''' - # set up variables - alldevs = wp.POINTER(wp.pcap_if_t)() - errbuf = wp.create_string_buffer(wp.PCAP_ERRBUF_SIZE) - # get all devices - if (wp.pcap_findalldevs(wp.byref(alldevs), errbuf) == -1): - raise RuntimeError("Error in pcap_findalldevs: %s\n" \ - % errbuf.value) - # find our device - d = alldevs - # d is a linked list, but not wrapped as a python list. Therefore, to - # get element number device we can't do d[device]. Instead we just do - # .next as many times as need to arrive at the device^th element. - # These elements are all pointers, so to finally get the actual object - # we call d.contents. - for i in range(device): - d = d.contents.next - d = d.contents - # open our device - adhandle = wp.pcap_open_live(d.name, 65536, - wp.PCAP_OPENFLAG_PROMISCUOUS, 1000, - errbuf) - wp.pcap_freealldevs(alldevs) - if (adhandle == None): - raise RuntimeError("Unable to open adapter %s" % d.contents.name) - return adhandle - - def setFilter(self): - ''' set the mac address filter. ''' - program = wp.bpf_program() - filter = "ether dst %s" % self.mac - # no idea what optimize should be, couldn't find doc, saw 1 in example code - # netmask: 0xffffff ?? - if wp.pcap_compile(self.adhandle, wp.byref(program), filter, 1, 0xffffff) < 0: - raise RuntimeError("Failed to compile filter: %s" % filter) - if wp.pcap_setfilter(self.adhandle, wp.byref(program)) < 0: - raise RuntimeError("Failed to set filter: %s" % filter) - wp.pcap_freecode(wp.byref(program)) - - def setAddress(self, address): - ''' change the MAC address ''' - self.mac = address - self.setFilter() - - def getPacket(self, returnErrors=False): - ''' returns the data from the next packet, or None, if no packet is received. - if returnErrors, then return -1 for error (or -2 if EOF reached, should never - happen) - data is returned as dict: - { "dest" : "11:22:33:44:55:66", - "src" : "aa:bb:cc:dd:ee:ff", - "length" : 12, - "data" : "hello there!" - } - ''' - header = wp.POINTER(wp.pcap_pkthdr)() - data = wp.POINTER(wp.c_ubyte)() - r = wp.pcap_next_ex(self.adhandle, wp.byref(header), wp.byref(data)) - if r == 1: - # process this packet - pystr = wp.string_at(data, header.contents.len) - # parse the header - dest = self._macToString(pystr[0:6]) - src = self._macToString(pystr[6:12]) - length = ord(pystr[12])*256 + ord(pystr[13]) - return { "dest": dest, "src": src, "length": length, "data": pystr[14:] } - elif r == 0: - return None - else: - return r if returnErrors else None - - def sendPacket(self, destMac, data): - ''' send a packet to destMat with given data. - source mac is self.mac. - returns 0 for success, otherwise returns error''' - packet = self._macToData(destMac) # bytes 0-5: destination address - packet += self._macToData() # 6-11: source address - packet += chr(len(data) / 256) # 12, 13 are packet length (big endian?) - packet += chr(len(data) % 256) - packet += data - packet_c = (wp.c_ubyte*len(packet))() - for i in range(len(packet)): - packet_c[i] = ord(packet[i]) - if wp.pcap_sendpacket(self.adhandle, packet_c, len(packet)) != 0: - return wp.pcap_geterr(self.adhandle) - else: - return 0 - - def _macToData(self, mac=None): - ''' Take a MAC address in form "11:22:33:44:55:66" - and read each byte as hex numbers, return as 6-byte string. - if mac=None, use self.mac ''' - if mac is None: - mac = self.mac - bytes = ''.join([chr(int(x, 16)) for x in mac.split(':')]) - if len(bytes) != 6: - raise ValueError("MAC address is not 6 bytes: %s" % mac) - return bytes - - def _macToString(self, bytes): - ''' inverse of _macToData: take a 6-byte string (of numbers) - and return string of form "11:22:33:44:55:66. ''' - if len(bytes) != 6: - raise ValueError("MAC address is not 6 bytes: %s" % bytes) - return ':'.join(['%02X' % ord(x) for x in bytes]) - - def __del__ (self): - ''' closes the device handle. __del__ is a bit untrustworthy, - should probably come up with a better way to do this. ''' - wp.pcap_close(self.adhandle) \ No newline at end of file diff --git a/GHzDACs/sequencer.py b/GHzDACs/sequencer.py deleted file mode 100644 index abf5fc2c..00000000 --- a/GHzDACs/sequencer.py +++ /dev/null @@ -1,607 +0,0 @@ -# Copyright (C) 2007 Matthew Neeley -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -""" -### BEGIN NODE INFO -[info] -name = Sequencer -version = 1.0 -description = Talks to old DAC sequencer board - -[startup] -cmdline = %PYTHON% %FILE% -timeout = 20 - -[shutdown] -message = 987654321 -timeout = 20 -### END NODE INFO -""" - -from labrad import types as T, util -from labrad.devices import DeviceWrapper, DeviceServer -from labrad.server import setting -from twisted.python import log -from twisted.internet import defer, reactor -from twisted.internet.defer import inlineCallbacks, returnValue - -from array import array -from datetime import datetime, timedelta -from math import sin, cos - -#NUMRETRIES = 1 -SRAM_LEN = 7936 -MEM_LEN = 256 -BUILD = 1 - -class FPGADevice(DeviceWrapper): - @inlineCallbacks - def connect(self, de, port, board, build): - print 'connecting to: %s (build #%d)' % (boardMAC(board), build) - - self.server = de - self.ctx = de.context() - self.port = port - self.board = board - self.build = build - self.MAC = boardMAC(board) - self.serverName = de.name - - self.sram = [0L] * SRAM_LEN - self.sramAddress = 0 - self.mem = [0L] * MEM_LEN - self.seqTime = 0 # estimated sequence runtime in seconds - self.DACclocks = 0 - self.timeout = T.Value(1000, 'ms') - - # set up the direct ethernet server for this board - # in our own context - p = de.packet() - p.connect(port) - p.require_length(70) - p.destination_mac(self.MAC) - p.require_source_mac(self.MAC) - p.timeout(self.timeout) - p.listen() - yield p.send(context=self.ctx) - - @inlineCallbacks - def sendRegisters(self, packet, asWords=True): - """Send a register packet and readback answer.""" - # do we need to clear waiting packets first? - p = self.server.packet() - p.write(words2str(packet)) - p.read(1) - ans = yield p.send(context=self.ctx) - src, dst, eth, data = ans.read[0] - if asWords: - data = [ord(c) for c in data] - returnValue(data) - - def sendRegistersNoReadback(self, packet): - """Send a register packet but don't readback anything.""" - d = self.server.write(words2str(packet), context=self.ctx) - return d.addCallback(lambda r: True) - - @inlineCallbacks - def runSequence(self, slave, delay, reps, getTimingData=True): - server, ctx = self.server, self.ctx - - pkt = [1, 3] + [0]*54 - #pkt[13:15] = reps & 0xFF, (reps >> 8) & 0xFF - #pkt[43:45] = int(slave), int(delay) - r = yield self.sendRegistersNoReadback(pkt) - - if not getTimingData: - returnValue(r) - - # TODO: handle multiple timers per cycle - npackets = reps/30 - totalTime = 2 * (self.seqTime * reps + 1) - p = server.packet() - p.timeout(T.Value(totalTime * 1000, 'ms')) - p.read(npackets) - ans = yield p.send(context=ctx) - sdata = [ord(data[j*2+3]) + (ord(data[j*2+4]) << 8) - for j in range(30) - for src, dst, eth, data in ans.read] - returnValue(sdata) - - @inlineCallbacks - def sendSRAM(self, data): - """Write SRAM data to the FPGA. - - The data is written at the position specified by self.sramAddress. - Data is only written if it needs to be changed. - """ - totallen = len(data) - adr = startadr = (self.sramAddress + 255) & SRAM_LEN - endadr = startadr + totallen - current = self.sram[startadr:endadr] - if data != current: # only send if the SRAM is new - p = self.server.packet() - while len(data) > 0: - page, data = data[:256], data[256:] - if len(page) < 256: - page += [0]*(256-len(page)) - pkt = [(adr >> 8) & 31, 0] + \ - [(n >> j) & 255 for n in page for j in [0, 8, 16, 24]] - p.write(words2str(pkt)) - adr += 256 - start = datetime.now() - yield p.send(context=self.ctx) - #print 'update time:', datetime.now() - start - self.sram[startadr:endadr] = data - self.sramAddress = endadr - returnValue((startadr, endadr)) - - @inlineCallbacks - def sendMemory(self, data, cycles): - """Write Memory data to the FPGA. - - At most one page of memory is written at address 0. - Data is only written if it needs to be changed. - """ - # try to estimate the time in seconds - # to execute this memory sequence - self.seqTime = sequenceTime(data) - - data = data[0:256] # only one page supported - totallen = len(data) - adr = startadr = 0 - endadr = startadr + totallen - current = self.mem[startadr:endadr] - if data != current: # only send if the MEM is new - p = self.server.packet() - while len(data) > 0: - page, data = data[:256], data[256:] - if len(page) < 256: - page += [0]*(256-len(page)) - pkt = [cycles & 0xFF, (cycles >> 8) & 0xFF, (adr >> 8)] + \ - [(n >> j) & 0xFF for n in page for j in (0, 8, 16)] - p.write(words2str(pkt)) - adr += 256 - yield p.send(context=self.ctx) - self.mem[startadr:endadr] = data - returnValue((startadr, endadr)) - - @inlineCallbacks - def runI2C(self, data): - pkt = [0, 2] + [0]*54 - answer = [] - while data[:1] == [258]: - data = data[1:] - - while len(data): - cnt = min(8, len(data)) - i = 0 - while i < cnt: - if data[i] == 258: - cnt = i - i += 1 - - stopI2C, readwriteI2C, ackI2C = (256 >> cnt) & 255, 0, 0 - cur = 128 - for i in range(cnt): - if data[i] in [256, 257]: - readwriteI2C |= cur - elif data[i] == 256: - ackI2C |= cur - elif data[i] < 256: - pkt[12-i] = data[i] - else: - pkt[12-i] = 0 - cur >>= 1 - - pkt[2:5] = stopI2C, readwriteI2C, ackI2C - - r = yield self.sendRegisters(pkt) - - for i in range(cnt): - if data[i] in [256, 257]: - answer += [r[61+cnt-i]] - - data = data[cnt:] - while data[:1] == [258]: - data = data[1:] - - returnValue(answer) - - -class FPGAServer(DeviceServer): - name = 'Sequencer' - deviceWrapper = FPGADevice - possibleLinks = [] - - #retryStats = [0] * NUMRETRIES - @inlineCallbacks - def initServer(self): - print 'loading config info...', - yield self.loadConfigInfo() - print 'done.' - yield DeviceServer.initServer(self) - - @inlineCallbacks - def loadConfigInfo(self): - """Load configuration information from the registry.""" - reg = self.client.registry - p = reg.packet() - p.cd(['', 'Servers', 'GHz DACs'], True) - p.get('sequencer', key='links') - ans = yield p.send() - self.possibleLinks = ans['links'] #[(, , port),...] - - - def initContext(self, c): - c.update(daisy_chain=[], start_delay=[]) - - @inlineCallbacks - def findDevices(self): - cxn = self.client - found = [] - print self.possibleLinks - for name, server, port in self.possibleLinks: - if server not in cxn.servers: - continue - - print 'Checking %s...' % name - de = cxn.servers[server] - ctx = cxn.context() - adapters = yield de.adapters(context=ctx) - if len(adapters): - ports, names = zip(*adapters) - else: - ports, names = [], [] - if port not in ports: - continue - #MAC = names[list(ports).index(port)][-17:] - - # make a list of the boards currently known - skips = {} - for dname in self.devices: - dev = self.devices[dname] - if dev.serverName == de.name and dev.port == port: - skips[dev.board] = dev - print 'skipping:', skips.keys() - - p = de.packet() - p.connect(port) - p.require_length(70) - p.listen() - - # ping all boards - for i in xrange(256): - if i in skips: - found.append(skips[i].name) - else: - p.destination_mac(boardMAC(i)) - p.write(words2str([0, 1] + [0] * 54)) - yield p.send(context=ctx) - - # get ID packets from all boards - for i in xrange(256): - try: - ans = yield de.read(1, context=ctx) - src, dst, eth, data = ans[0] - board, build = int(src[-2:], 16), ord(data[51]) - if build != BUILD: - continue - devName = '%s FPGA %d' % (name, board) - args = de, port, board, build - found.append((devName, args)) - except T.Error: - #print 'timeout', i - pass # probably a timeout error - - # expire this context to stop listening - yield cxn.manager.expire_context(de.ID, context=ctx) - returnValue(found) - - - @setting(20, 'SRAM Address', addr=['w'], returns=['w']) - def sram_address(self, c, addr=None): - """Sets the next SRAM address to be written to by SRAM.""" - dev = self.selectedDevice(c) - if addr is None: - addr = dev.sramAddress - else: - dev.sramAddress = addr - return long(addr) - - - @setting(21, 'SRAM', data=['*w: SRAM Words to be written'], - returns=['(ww): Start address, Length']) - def sram(self, c, data): - """Writes data to the SRAM at the current starting address.""" - dev = self.selectedDevice(c) - return dev.sendSRAM(data) - - - @setting(31, 'Memory', data=['*w: Memory Words to be written'], - returns=['']) - def memory(self, c, data): - """Writes data to the Memory at the current starting address.""" - dev = self.selectedDevice(c) - c['mem'] = data - #return dev.sendMemory(data) - - - @setting(40, 'Run Sequence', reps=['w'], getTimingData=['b'], - returns=['*2w', '']) - def run_sequence(self, c, reps=30, getTimingData=True): - """Executes a sequence on one or more boards.""" - # Round stats up to multiple of 30 - reps += 29 - reps -= reps % 30 - - if len(c['daisy_chain']) != len(c['start_delay']): - raise Exception('daisy_chain and start_delay must be same length.') - - if len(c['daisy_chain']): - # run multiple boards, with first board as master - devs = [self.getDevice(c, n) for n in c['daisy_chain']] - delays = c['start_delay'] - else: - # run the selected device only - devs = [self.selectedDevice(c)] - delays = [0] - slaves = [i != 0 for i in range(len(devs))] - devices = zip(devs, delays, slaves) - - devs[0].sendMemory(c['mem'], reps) - - # run boards in reverse order to ensure synchronization - #print 'starting to run sequence.' - start = datetime.now() - attempts = [dev.runSequence(slave, delay, reps, - getTimingData=getTimingData) - for dev, delay, slave in reversed(devices)] - #print 'trying on boards:', self.daisy_chain - results = yield defer.DeferredList(attempts) - #print 'runtime:', datetime.now() - start - #print 'all boards done.' - okay = True - switches = [] - failures = [] - for dev, (success, result) in zip(devs, results): - if success: - switches.append(result) - else: - print 'Board %d timed out.' % dev.board - failures.append(dev.board) - okay = False - if not okay: - raise Exception('Boards %s timed out.' % failures) - if getTimingData: # send data back in daisy chain order - #print list(reversed(switches)) - returnValue(list(reversed(switches))) - - @setting(42, 'Daisy Chain', boards=['*s'], returns=['*s']) - def daisy_chain(self, c, boards=None): - """Set or get daisy chain board order. - - Set this to an empty list to run the selected board only. - """ - if boards is None: - boards = c['daisy_chain'] - else: - c['daisy_chain'] = boards - return boards - - @setting(43, 'Start Delay', delays=['*w'], returns=['*w']) - def start_delay(self, c, delays=None): - """Set start delays in ns for SRAM in the daisy chain. - - Must be the same length as daisy_chain for sequence to execute. - """ - if delays is None: - delays = c['start_delay'] - else: - c['start_delay'] = delays - return delays - - - @setting(50, 'Debug Output', data=['(wwww)'], returns=['']) - def debug_output(self, c, data): - """Outputs data directly to the output bus.""" - dev = self.selectedDevice(c) - pkt = [2, 1] + [0]*11 - for d in data: - pkt += [d & 0xFF, - (d >> 8) & 0xFF, - (d >> 16) & 0xFF, - (d >> 24) & 0xFF] - pkt += [0]*16 + [0]*11 - yield dev.sendRegisters(pkt) - - - @setting(51, 'Run SRAM', data=['*w'], loop=['b'], returns=['(ww)']) - def run_sram(self, c, data, loop=False): - """Loads data into the SRAM and executes. - - If loop is True, the sequence will be repeated forever, - otherwise it will be executed just once. Sending - an empty list of data will clear the SRAM. - """ - dev = self.selectedDevice(c) - - pkt = [0, 1] + [0]*54 - yield dev.sendRegisters(pkt) - - if not len(data): - returnValue((0, 0)) - - if loop: - # make sure data is at least 20 words long by repeating it - data *= (20-1)/len(data) + 1 - hdr = 3 - else: - # make sure data is at least 20 words long by repeating first value - data += [data[0]] * (20-len(data)) - hdr = 4 - - dev.sramAddress = 0 - r = yield dev.sendSRAM(data) - - encode = lambda a: [a & 0xFF, (a>>8) & 0xFF, (a>>16) & 0xFF] - pkt = [hdr, 0] + [0]*11 + encode(r[0]) + encode(r[1]-1) + [0]*37 - yield dev.sendRegistersNoReadback(pkt) - returnValue(r) - - - @setting(100, 'I2C', data=['*w'], returns=['*w']) - def i2c(self, c, data): - """Runs an I2C Sequence - - The entries in the WordList to be sent have the following meaning: - 0..255 : send this byte - 256: read back one byte without acknowledging it - 257: read back one byte with ACK - 258: sent data and start new packet - For each 256 or 257 entry in the WordList to be sent, the read-back byte is appended to the returned WordList. - In other words: the length of the returned list is equal to the count of 256's and 257's in the sent list. - """ - dev = self.selectedDevice(c) - return dev.runI2C(data) - - - @setting(110, 'LEDs', data=['w', '(bbbbbbbb)'], returns=['w']) - def leds(self, c, data): - """Sets the status of the 8 I2C LEDs.""" - dev = self.selectedDevice(c) - - if isinstance(data, tuple): - # convert to a list of digits, and interpret as binary int - data = long(''.join(str(int(b)) for b in data), 2) - - yield dev.runI2C([192 , 68, data & 255]) - returnValue(data) - - - @setting(120, 'Reset Phasor', returns=['b: phase detector output']) - def reset_phasor(self, c): - """Resets the clock phasor.""" - dev = self.selectedDevice(c) - - pkt = [152, 0, 127, 0, 258, # set I to 0 deg - 152, 34, 254, 0, 258, # set Q to 0 deg - 112, 65, 258, # set enable bit high - 112, 193, 258, # set reset high - 112, 65, 258, # set reset low - 112, 1, 258, # set enable low - 113, 256] # read phase detector - - r = yield dev.runI2C(pkt) - returnValue((r[0] & 1) > 0) - - - @setting(121, 'Set Phasor', - data=[': poll phase detector only', - 'v[rad]: set angle (in rad, deg, \xF8, \', or ")'], - returns=['b: phase detector output']) - def set_phasor(self, c, data=None): - """Sets the clock phasor angle and reads the phase detector bit.""" - dev = self.selectedDevice(c) - - if data is None: - pkt = [112, 1, 258, 113, 256] - else: - sn = int(round(127 + 127*sin(data))) & 255 - cs = int(round(127 + 127*cos(data))) & 255 - pkt = [152, 0, sn, 0, 258, - 152, 34, cs, 0, 258, - 112, 1, 258, 113, 256] - - r = yield dev.runI2C(pkt) - returnValue((r[0] & 1) > 0) - - def getCommand(self, cmds, chan): - """Get a command from a dictionary of commands. - - Raises a helpful error message if the given channel is not allowed. - """ - try: - return cmds[chan] - except: - raise Exception("Allowed channels are %s." % sorted(cmds.keys())) - - @setting(130, 'Vout', chan=['s'], V=['v[V]'], returns=['w']) - def vout(self, c, chan, V): - """Sets the output voltage of any Vout channel, A, B, C or D.""" - cmd = self.getCommand({'A': 16, 'B': 18, 'C': 20, 'D': 22}, chan) - dev = self.selectedDevice(c) - val = int(max(min(round(V*0x3333), 0x10000), 0)) - pkt = [154, cmd, (val >> 8) & 0xFF, val & 0xFF] - yield dev.runI2C(pkt) - returnValue(val) - - - @setting(135, 'Ain', returns=['v[V]']) - def ain(self, c): - """Reads the voltage on Ain.""" - dev = self.selectedDevice(c) - pkt = [144, 0, 258, 145, 257, 256] - r = yield dev.runI2C(pkt) - returnValue(T.Value(((r[0]<<8) + r[1])/819.0, 'V')) - - -# some helper methods - -def bistChecksum(data): - bist = [0, 0] - for i in range(0, len(data), 2): - for j in range(2): - if data[i+j] & 0x3FFF != 0: - bist[j] = (((bist[j] << 1) & 0xFFFFFFFE) | ((bist[j] >> 31) & 1)) ^ ((data[i+j] ^ 0x3FFF) & 0x3FFF) - return bist - -def boardMAC(board): - """Get the MAC address of a board as a string.""" - return '00:01:CA:AA:00:' + ('0'+hex(int(board))[2:])[-2:].upper() - -def listify(data): - return data if isinstance(data, list) else [data] - -def words2str(list): - return array('B', list).tostring() - -def sequenceTime(sequence): - """Conservative estimate of the length of a sequence in seconds.""" - cycles = sum([cmdTime(c) for c in sequence]) - return cycles * 40e-9 - -def cmdTime(cmd): - """A conservative estimate of the number of cycles a given command takes.""" - opcode = (cmd & 0xF00000) >> 20 - abcde = (cmd & 0x0FFFFF) - xy = (cmd & 0x00FF00) >> 8 - ab = (cmd & 0x0000FF) - - if opcode in [0x0, 0x1, 0x2, 0x4, 0x8, 0xA]: - return 1 - if opcode == 0xF: - return 2 - if opcode == 0x3: - return abcde + 1 # delay - if opcode == 0xC: - return 250*8 # maximum SRAM length is 8us - - - -__server__ = FPGAServer() - -if __name__ == '__main__': - from labrad import util - util.runServer(__server__) diff --git a/GHz_DAC_bringup.py b/GHz_DAC_bringup.py deleted file mode 100644 index 4247c82a..00000000 --- a/GHz_DAC_bringup.py +++ /dev/null @@ -1,291 +0,0 @@ -""" -Version Info -version = 3.0 -server: ghz_fpgas -server version: 3.3.0 -""" - -# CHANGELOG: -# -# 2013 September 2013 - Daniel Sank -# -# Major code clean up and simplification -# -# 2011 November 4 - Jim Wenner -# -# Revised calls to ghz_fpga server to match v3.3.0 call signatures and outputs. -# Incorporating usage of new bringup functions. Revised print outputs. Added -# ability to bring up all devices on a board group. - -from __future__ import with_statement - -import random -import time - -import labrad -from math import sin, pi - -FPGA_SERVER = 'ghz_fpgas' -DACS = ['A','B'] -NUM_TRIES = 2 - -def bringupReportMessage(dac, data): - """Build a report string from bringup data - - data is a dictionary of bringup results that should be built as follows: - resp = fpga.dac_bringup(...) - data = dict(resp[i]) (i=0 for DAC A, i=1 for DAC B) - """ - report = '' - #LVDS - report += '\r\n'.join(['DAC %s LVDS parameters:'%dac, - ' SD: %d'%data['lvdsSD'], - ' Check: %d - What is this?'%data['lvdsCheck'], - ' Plot MSD: ' + ''.join('_-'[i] for i in data['lvdsTiming'][1]), - ' Plot MHD: ' + ''.join('_-'[i] for i in data['lvdsTiming'][2]), - '', '']) - #FIFO - report += '\r\n'.join(['DAC %s FIFO Parameters:' %dac, - ' FIFO calibration succeeded after %d tries'%data['fifoTries'], - '']) - if data['fifoSuccess']: - report += '\r\n'.join([\ - ' FIFO PHOF: %d'%data['fifoPHOF'], - ' Clk polarity: %d' %data['fifoClockPolarity'], - ' FIFO counter: %d (should be 3)'%data['fifoCounter'], - '', '']) - else: - report += '\r\n'.join(['FIFO failure', '', '']) - #BIST - report += '\r\n'.join(['DAC %s BIST:' %dac, - ' Success: %s'%str(data['bistSuccess']), - '', '']) - return report - -def bringupBoard(fpga, board, printOutput=True, optimizeSD=False, sdVal=None,fullOutput=False): - """Bringup a single FPGA board - - This is the main bringup routine. All other bringup routines should call - this one. - - RETURNS - (boardType, results, {'bist':bool, 'fifo':bool, 'lvds':bool}) - - results maps DAC -> data where DAC is 'A' or 'B' and data is another dict - that maps parameterName -> value. For a complete specifications of all - parameterName,value pairs, see the documentation for dac_bringup setting - in the GHz FPGA server. - """ - fpga.select_device(board) - - if board in fpga.list_adcs(): - fpga.adc_bringup() - if printOutput: - print('') - print('ADC bringup on %s complete.' %board) - print('No feedback information available %s' %board) - return ('ADC', None, {}) - - elif board in fpga.list_dacs(): - if sdVal is None: - resp = fpga.dac_bringup(optimizeSD) - else: - resp = fpga.dac_bringup(False, sdVal) - results={} - #pass/fail indicators, one entry per DAC, ie. DAC A and DAC B - fifoOkay = [] - bistOkay = [] - lvdsOkay = [] - for dacdata in resp: - dacDict = dict(dacdata) - dac = dacDict.pop('dac') - results[dac] = dacDict - - fifoOkay.append(dacDict['fifoSuccess']) - bistOkay.append(dacDict['bistSuccess']) - lvdsOkay.append(dacDict['lvdsSuccess']) - - if fullOutput: - print(bringupReportMessage(dac, dacDict)) - - if printOutput: - if all(fifoOkay) and all(bistOkay): - print('%s ok' %board) - if not all(lvdsOkay): - print('but LVDS warning') - else: - print('%s ---Bringup Failure---' %board) - - return ('DAC', results, {'bist':all(bistOkay), 'fifo':all(fifoOkay), - 'lvds':all(lvdsOkay)}) - - else: - raise RuntimeError("Board type not recognized. Something BAD happened") - -def bringupBoards(fpga, boards, noisy=False): - """Bringup a list of boards and return the Bist/FIFO and lvds success for - each one. - - TODO: - Put in code to keep track of retries and report this to the user - """ - successes = {} - triesDict = {} - for board in boards: - if noisy: - print 'bringing up %s' %board - #Temporarily set a value to False so while loop will run at least once - allOk = False - tries = 0 - #Try to bring up the board until it succeeds or we exceed maximum - #number of tries. - while tries < NUM_TRIES and not allOk: - tries += 1 - triesDict[board] = tries - boardType, result, successDict = bringupBoard(fpga, board, - fullOutput=False, - printOutput=True) - allOk = all(successDict.values()) - successes[board] = successDict - failures = [] - for board in boards: - #Warn the user if a board took more than one try to bring up. - if triesDict[board]>1: - print 'WARNING: Board %s took %d tries to succeed' %(board,triesDict[board]) - #If this board failed, add it to the list of failures - if not all(successes[board].values()): - failures.append(board) - if not failures: - print 'All boards successful!' - else: - print 'The following boards failed:' - for board in failures: - print board - -def interactiveBringup(fpga, board): - """ - """ - boardType,_,_ = bringupBoard(fpga, board,fullOutput=True) - - if boardType == 'DAC': - ccset = 0 - while True: - print - print - print 'Choose:' - print - print ' [1] : Output 0x0000s' - print ' [2] : Output 0x1FFFs' - print ' [3] : Output 0x2000s' - print ' [4] : Output 0x3FFFs' - print - print ' [5] : Output 100MHz sine wave' - print ' [6] : Output 200MHz sine wave' - print ' [7] : Output 100MHz and 175MHz sine wave' - print - print ' Current Cross Controller Setting: %d' % ccset - print ' [+] : Increase Cross Controller Adjustment by 1' - print ' [-] : Decrease Cross Controller Adjustment by 1' - print ' [*] : Increase Cross Controller Adjustment by 10' - print ' [/] : Decrease Cross Controller Adjustment by 10' - print - print ' [I] : Reinitialize' - print - print ' [Q] : Quit' - - k = getChoice('1234567+-*/IQ') - - # run various debug sequences - if k in '1234567': - if k == '1': fpga.dac_debug_output(0xF0000000, 0, 0, 0) - if k == '2': fpga.dac_debug_output(0xF7FFDFFF, 0x07FFDFFF, 0x07FFDFFF, 0x07FFDFFF) - if k == '3': fpga.dac_debug_output(0xF8002000, 0x08002000, 0x08002000, 0x08002000) - if k == '4': fpga.dac_debug_output(0xFFFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF, 0x0FFFFFFF) - - def makeSines(freqs, T): - """Build sram sequence consisting of superposed sine waves.""" - wave = [sum(sin(2*pi*t*f) for f in freqs) for t in range(T)] - sram = [(long(round(0x1FFF*y/len(freqs))) & 0x3FFF)*0x4001 for y in wave] - sram[0] = 0xF0000000 # add triggers at the beginning - return sram - - if k == '5': fpga.dac_run_sram(makeSines([0.100], 40), True) - if k == '6': fpga.dac_run_sram(makeSines([0.200], 40), True) - if k == '7': fpga.dac_run_sram(makeSines([0.100, 0.175], 40), True) - - print 'running...' - - if k in '+-*/': - if k == '+': ccset += 1 - if k == '-': ccset -= 1 - if k == '*': ccset += 10 - if k == '/': ccset -= 10 - - if ccset > +63: ccset = +63 - if ccset < -63: ccset = -63 - fpga.dac_cross_controller('A', ccset) - fpga.dac_cross_controller('B', ccset) - - if k == 'I': bringupBoard(fpga, board,fullOutput=True) - if k == 'Q': break - -# User interface utilities - -def getChoice(keys): - """Get a keypress from the user from the specified keys.""" - r = '' - while not r or r not in keys: - r = raw_input().upper() - return r - -def selectFromList(options, title): - """Get a user-selected option - - Returns an element from options. - """ - print - print - print 'Select %s:' % title - print - keys = {} - for i, opt in enumerate(options): - key = '%d' % (i+1) - keys[key] = opt - print ' [%s] : %s' % (key, opt) - keys['A'] = 'All' - print ' [A] : All' - keys['Q'] = None - print ' [Q] : Quit' - - k = getChoice(keys) - - return keys[k] - -def selectBoardGroup(fpga): - """Gets user selected board group""" - groups = fpga.list_board_groups() - group = selectFromList(groups, 'Board Group') - return group - -def doBringup(): - with labrad.connect() as cxn: - fpga = cxn[FPGA_SERVER] - group = selectBoardGroup(fpga) - while True: - if group is None: - break - else: - boards = fpga.list_devices(group) \ - if group in fpga.list_board_groups() \ - else fpga.list_devices() - boardSelect = selectFromList(boards, 'FPGA Board') - if boardSelect is None: - break - elif boardSelect == 'All': - bringupBoards(fpga,[board[1] for board in boards],noisy=True) - else: - board = boardSelect[1] - interactiveBringup(fpga, board) - -if __name__ == '__main__': - doBringup() diff --git a/ghz_fpga_server.py b/ghz_fpga_server.py deleted file mode 100644 index c6dd9a94..00000000 --- a/ghz_fpga_server.py +++ /dev/null @@ -1,2221 +0,0 @@ -# Copyright (C) 2007, 2008, 2009, 2010 Matthew Neeley -# Copyright (C) 2010, 2011, 2012, 2013 -# 2014 Daniel Sank, James Wenner -# -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# CHANGELOG: -# -# 2011 November 30 - Daniel Sank / Jim Wenner -# -# Run Sequence setting now only tries to get ADC demodulation ranges when -# getTimingData is True. If getTimingData is False, no data is extracted, -# so it's impossible to store the I and Q ranges. -# -# 2011 November 29 - Jim Wenner -# -# Removed adc_recalibrate from adc_bringup since this may randomize order of -# I, Q outputs. -# -# 2011 November 16 - Dan Sank/Jim Wenner -# -# Fixed documentation for dac_lvds and dac_fifo. -# Changed return type tag for dac_bringup. Now an array of clusters instead of -# a cluster of clusters. -# -# For dac and adc device objects, changed device.params to device.buildParams. -# This was done because we now have _build_ specific, and _board_ specific -# parameters stored in the registry and we need to distinguish these in the -# variable names. -# -# In other words, build specific data are in device.buildParams -# and board specific data are in device.boardParams -# -# 2011 November 10 - Jim Wenner -# -# Fixed bug where, in list_dacs and list_adcs, looked for (name, id) in devices -# when checking board groups even though only name present by this point. -# -# 2011 November 4 - Daniel Sank -# -# The code around line 1172 which read "c[runner.dev]['ranges']=runner.ranges" -# didn't work because I had never assigned runner.ranges. This is now assigned -# near line 601. -# -# 2011 November 2 - Jim Wenner -# -# Moved bringup code into the server. This was done to help keep bringup and -# server code synchronized with each other. -# -# DAC bringup now has signed data as default. Added -# functions to return list of DACs or of ADCs. -# -# In setFIFO, removed reset of LVDS sample delay. Changed how check FIFO -# counter to ensure final value same as what thought setting to. In both -# setFIFO and setLVDS, added success/ failure checks and modified return -# parameters. Changed default FIFO counter from 3 to value in registry provided -# for each board. Changed default setLVDS behavior from optimizing LVDS SD to -# getting SD from board-specific registry key while adding option to use -# optimal SD instead. setLVDS returns MSD, MHD even if sample delay specified. -# -# Board specific registry keys are located in ['Servers', 'GHz FPGAs'] and are -# of form: -# dacN=[('fifoCounter', 3), ('lvdsSD', 3), ('lvdsPhase', 180)] -# -# 2011 February 9 - Daniel Sank -# Removed almost all references to hardcoded hardware parameters, for example -# the various SRAM lengths. These values are now board specific. -# As an example of how this is implemented, we used to have something like -# this: -# def adc_filter_func(self, c, bytes, stretchLen=0, stretchAt=0): -# assert len(bytes) <= FILTER_LEN, 'Filter function max length is %d' \ -# % FILTER_LEN -# dev = self.selectedADC(c) -# ... -# where FILTER_LEN was a global constant. We now instead have this: -# def adc_filter_func(self, c, bytes, stretchLen=0, stretchAt=0): -# dev = self.selectedADC(c) -# assert len(bytes) <= dev.buildParams['FILTER_LEN'], 'Filter function max\ -# length is %d' % dev.buildParams['FILTER_LEN'] -# ... -# so that the filter length is board specific. These board specific parameters -# are loaded by the board objects when they are created, See dac.py and adc.py -# for details on how these parameters are loaded. - - -# + DOCUMENTATION -# -# Communication between the computer and the FPGA boards works over ethernet. -# This server and the associated board type definition files dac.py and adc.py -# abstract away this ethernet communication. This means that you don't have -# to explicitly tell the direct ethernet server to send packets to the boards. -# Instead you call, for example, this server's Memory command and the server -# will build the appropriate packets for the board you've selected and the -# memory sequence you want to send. No packets are actually sent to the boards -# until you tell them to turn using one of the following commands: -# DAC Run SRAM - Runs SRAM on one board without waiting for a daisychain -# pulse. -# ADC Run Demod - Runs ADC demod mode on one board without waiting for a -# daisychain pulse. -# ADC Run Average - Runs ADC average mode on one board without waiting for a -# daisychain pulse. -# Run Sequence - Runs multiple boards synchronously using the daisychain -# (DACs and ADCs). -# When one of the one-off (no daisychain) commands is sent, whichever DAC -# or ADC you have selected in your context will run and return data as -# appropriate. The use of Run Sequence is slightly more complicated. See below. -# -# ++ USING RUN SEQUENCE -# The Run Sequence command is used to run multiple boards synchronously using -# daisychain pulses to trigger SRAM execution. Here are the steps to use it: -# 1. "Daisy Chain" to specify what boards will run -# 2. "Timing Order" to specify which boards' data you will collect -# 3. Set up DACS - for each DAC you want to run call Select Device and then: -# a. SRAM or SRAM dual block -# b. Memory -# c. Start Delay -# 4. Set up ADCs - for each ADC you want to run call Select Device and then: -# a. ADC Run Mode -# b. ADC Filter Func (set this even if you aren't using demodulation mode) -# c. ADC Demod Phase -# d. ADC Trig Magnitude -# e. Start Delay -# For information on the format of the data returned by Run Sequence see its -# docstring. -# -# ++ REGISTRY KEYS -# In order for the server to set up the board groups and fpga devices properly -# there are a couple of registry entries that need to be set up. Registry keys -# for this server live in ['', 'Servers', 'GHz FPGAs'] -# -# boardGroups: *(ssw*(sw)), [(groupName, directEthernetServername, portNumber, -# [(boardName, daisychainDelay), ...]), ...] -# This key tells the server what boards groups should exist, what direct -# ethernet server controlls that group, which ethernet port is connected to the -# boards (via an ethernet switch) and what boards exist on each group. Board -# names should be of the form "DAC N" or "ADC N" where N is the number -# determined by the DIP switches on the board. The number after each board name -# is the number of clock cycles that board should wait after receiving a -# daisychain pulse before starting to run its SRAM. -# -# dacBuildX: *(s?), [(parameterName, value), (parameterName, value), ...] -# adcBuildX: *(s?), [(parameterName, value), (parameterName, value), ...] -# When FPGA board objects are created they read the registry to find hardware -# parameter values. For example, the DAC board objects need to know how long -# their SRAM memory is, and each board may have a different value depending on -# its specific FPGA chip. Details, lists of necessary parameters and example -# values for each board type are given in dac.py and adc.py -# -# dacN: *(s?), [(parameterName, value), (parameterName, value), ...] Parameters -# which are specific to individual boards. This is used for the default FIFO -# counter, LVDS SD, etc. See examples in dac.py - -from __future__ import with_statement - -""" -TODO -cmdTime_cycles does not properly estimate sram length - - -""" - - -""" -### BEGIN NODE INFO -[info] -name = GHz FPGAs -version = 5.2.0 -description = Talks to DAC and ADC boards - -[startup] -cmdline = %PYTHON% %FILE% -timeout = 20 - -[shutdown] -message = 987654321 -timeout = 20 -### END NODE INFO -""" - -import itertools -import logging -import os -import random -import struct -import sys -import time - -import numpy as np - -from twisted.internet import defer -from twisted.internet.defer import inlineCallbacks, returnValue - -from labrad import types as T, units as U -from labrad.devices import DeviceServer -from labrad.server import setting -from labrad.units import Unit, Value -import labrad.util - -import fpgalib.adc as adc -import fpgalib.dac as dac -import fpgalib.fpga as fpga -from fpgalib.util import TimedLock, LoggingPacket - - -# The logging level is set at the bottom of the file where the server starts. -# To get additional info about what the server is doing (i.e. to see if it -# gets to a certain part of run_sequence), change the logging level to -# logging.INFO. To print the Direct Etherenet packets, set it to -# logging.DEBUG. - -def timeString(): - ts = ('{0.tm_year} {0.tm_mon} {0.tm_mday} {0.tm_hour} {0.tm_min} {0.tm_sec}' - .format(time.localtime())) - return ts - - -LOGGING_PACKET = False - - -NUM_PAGES = 2 - -I2C_RB = 0x100 -I2C_ACK = 0x200 -I2C_RB_ACK = I2C_RB | I2C_ACK -I2C_END = 0x400 - -# TODO: Remove the constants from above and put them in the registry to be -# read by individual DAC board instances. See DacDevice.connect to see how -# this is done -# TODO: make sure paged operations (datataking) don't conflict with e.g. -# bringup -# - want to do this by having two modes for boards, either 'test' mode -# (when a board does not belong to a board group) or 'production' mode -# (when a board does belong to a board group). It would be nice if boards -# could be dynamically moved between groups, but we'll see about that... -# TODO: store memory and SRAM as numpy arrays, rather than lists and strings, -# respectively -# TODO: run sequences to verify the daisy-chain order automatically -# TODO: when running adc boards in demodulation (streaming mode), check -# counters to verify that there is no packet loss -# TODO: think about whether page selection and pipe semaphore can interact -# badly to slow down pipelining - - -class TimeoutError(Exception): - """Error raised when boards timeout.""" - - -class BoardGroup(object): - """Manages a group of GHz DAC boards that can be run simultaneously. - - All the fpga boards must be daisy-chained to allow for synchronization, - and also must be connected to the same network card. Currently, one - board group is created automatically for each detected network card. - Only one sequence at a time can be run on a board group, but memory - and SRAM updates can be pipelined so that while a sequence is running - on some set of the boards in the group, new sequence data for the next - point can be uploaded. - """ - def __init__(self, fpgaServer, directEthernetServer, port): - self.fpgaServer = fpgaServer - self.directEthernetServer = directEthernetServer - self.port = port - self.ctx = None - self.pipeSemaphore = defer.DeferredSemaphore(NUM_PAGES) - self.pageNums = itertools.cycle(range(NUM_PAGES)) - self.pageLocks = [TimedLock() for _ in range(NUM_PAGES)] - self.runLock = TimedLock() - self.readLock = TimedLock() - self.setupState = set() - self.runWaitTimes = [] - self.prevTriggers = 0 - - @inlineCallbacks - def init(self): - """Set up the direct ethernet server in our own context.""" - self.ctx = self.directEthernetServer.context() - p = self.directEthernetServer.packet(context=self.ctx) - p.connect(self.port) - yield p.send() - - @inlineCallbacks - def shutdown(self): - """Clean up when this board group is removed.""" - # expire our context with the manager - cxn = self.directEthernetServer._cxn - servers = yield cxn.manager.servers() - server_ids = set(id for id, name in servers) - if self.directEthernetServer.ID in server_ids: - yield cxn.manager.expire_context( - self.directEthernetServer.ID, context=self.ctx) - - def configure(self, name, boards): - """Update configuration for this board group.""" - self.name = name - self.boardOrder = ['{} {}'.format(name, boardName) for - (boardName, delay) in boards] - self.boardDelays = [delay for (boardName, delay) in boards] - - @inlineCallbacks - def detectBoards(self): - """Detect boards on the ethernet adapter managed by this board group. - - The autodetect operation is guarded by board group locks so that it - will not conflict with sequences running on this board group. - """ - try: - # Acquire all locks so we can ping boards without interfering with - # board group operations. - for i in xrange(NUM_PAGES): - yield self.pipeSemaphore.acquire() - for pageLock in self.pageLocks: - yield pageLock.acquire() - yield self.runLock.acquire() - yield self.readLock.acquire() - - # Detect each board type in its own context. - detections = [self.detectDACs(), self.detectADCs()] - answer = yield defer.DeferredList(detections, consumeErrors=True) - found = [] - for success, result in answer: - if success: - found.extend(result) - else: - print 'autodetect error:' - result.printTraceback() - - # Clear detection packets which may be buffered in device contexts. - # TODO: check that this actually clears packets. - devices = self.devices() - clears = [] - for dev in devices: - clears.append(dev.clear().send()) - - returnValue(found) - finally: - # Release all locks once we're done with autodetection. - for i in xrange(NUM_PAGES): - self.pipeSemaphore.release() - for pageLock in self.pageLocks: - pageLock.release() - self.runLock.release() - self.readLock.release() - - def detectDACs(self, timeout=1.0): - """Try to detect DAC boards on this board group.""" - def callback(src, data): - board = int(src[-2:], 16) - build = dac.DAC.readback2BuildNumber(data) - devName = '{} DAC {}'.format(self.name, board) - args = (devName, self, self.directEthernetServer, self.port, board, - build) - return (devName, args) - macs = [dac.DAC.macFor(board) for board in range(256)] - return self._doDetection(macs, dac.DAC.regPing(), - dac.DAC.READBACK_LEN, callback) - - def detectADCs(self, timeout=1.0): - """Try to detect ADC boards on this board group.""" - def callback(src, data): - board = int(src[-2:], 16) - build = adc.ADC.readback2BuildNumber(data) - devName = '{} ADC {}'.format(self.name, board) - args = (devName, self, self.directEthernetServer, self.port, board, - build) - return (devName, args) - macs = [adc.ADC.macFor(board) for board in range(256)] - return self._doDetection(macs, adc.ADC.regPing(), - adc.ADC.READBACK_LEN, callback) - - @inlineCallbacks - def _doDetection(self, macs, packet, respLength, callback, timeout=1.0): - """ - Try to detect a boards at the specified mac addresses. - - For each response of the correct length received within the timeout - from one of the given mac addresses, the callback function will be - called and should return data to be added to the list of found - devices. - """ - try: - ctx = self.directEthernetServer.context() - - # Prepare and send detection packets. - p = self.directEthernetServer.packet() - p.connect(self.port) - p.require_length(respLength) - p.timeout(T.Value(timeout, 's')) - p.listen() - for mac in macs: - p.destination_mac(mac) - p.write(packet.tostring()) - yield p.send(context=ctx) - # Listen for responses. - start = time.time() - found = [] - while (len(found) < len(macs)) and (time.time() - start < timeout): - try: - ans = yield self.directEthernetServer.read(1, context=ctx) - src, dst, eth, data = ans[0] - if src in macs: - devInfo = callback(src, data) - found.append(devInfo) - except T.Error as e: - logging.error('timeout exception: {}'.format(e)) - break # Read timeout. - returnValue(found) - finally: - # Expire the detection context. - cxn = self.directEthernetServer._cxn - yield cxn.manager.expire_context(self.directEthernetServer.ID, - context=ctx) - - def devices(self): - """ - Return a list of known device objects belonging to this board group. - """ - return [dev for dev in self.fpgaServer.devices.values() - if dev.boardGroup == self] - - @inlineCallbacks - def testMode(self, func, *a, **kw): - """ - Call a function in test mode. - - This makes sure that all currently-executing pipeline stages - are finished by acquiring the pipe semaphore for all pages, - then runs the function, and finally releases the semaphore - to allow the pipeline to continue. - """ - for i in xrange(NUM_PAGES): - yield self.pipeSemaphore.acquire() - try: - ans = yield func(*a, **kw) - returnValue(ans) - finally: - for i in xrange(NUM_PAGES): - self.pipeSemaphore.release() - - - def makePackets(self, runners, page, reps, timingOrder, sync=249): - """Make packets to run a sequence on this board group. - - Running a sequence has 4 stages: - - Load memory and SRAM into all boards in parallel. - If possible, this is done in the background using a separate - page while another sequence is running. - - - Run sequence by firing a single packet that starts all boards. - To ensure synchronization the slaves are started first, in - daisy-chain order, followed at the end by the master. - - - Collect timing data to ensure that the sequence is finished. - We instruct the direct ethernet server to collect the packets - but not send them yet. Once collected, direct ethernet triggers - are used to immediately start the next sequence if one was - loaded into the next page. - - - Read timing data. - Having started the next sequence (if one was waiting) we now - read the timing data collected by the direct ethernet server, - process it and return it. - - This function prepares the LabRAD packets that will be sent for - each of these steps, but does not actually send anything. By - preparing these packets in advance we save time later when we - are in the time-critical pipeline sections - - - loadPkts: list of packets, one for each board - setupPkts: list of (packet, setup state). Only for ADC - runPkts: wait, run, both. These packets are sent in the master - context, and are placed carefully in order so that the - master board runs last. - collectPkts: list of packets, one for each board. These packets - tell the direct ethernet server to collect, and then - if successful, send triggers to the master context. - readPkts: list of packets. Simply read back data from direct - ethernet buffer for each board's context. - - Packets generated by dac and adc objects are make with the - context set to that device's context. This ensures that the - packets have the right destination MAC and therefore arrive in - the right place. - """ - # Dictionary of devices to be run. - runnerInfo = dict((runner.dev.devName, runner) for runner in runners) - - # Upload sequence data (pipelined). - loadPkts = [] - for board in self.boardOrder: - if board in runnerInfo: - runner = runnerInfo[board] - isMaster = len(loadPkts) == 0 - p = runner.loadPacket(page, isMaster) - if p is not None: - loadPkts.append(p) - - # Setup board state (not pipelined). - # Build a list of (setupPacket, setupState). - setupPkts = [] - for board in self.boardOrder: - if board in runnerInfo: - runner = runnerInfo[board] - p = runner.setupPacket() - if p is not None: - setupPkts.append(p) - # Run all boards (master last). - # Set the first board which is both in the boardOrder and also in the - # list of runners for this sequence as the master. Any subsequent boards - # for which we have a runner are set to slave mode, while subsequent - # unused boards are set to idle mode. For example: - # All boards: 000000 - # runners: --XX-X - # mode: msis (i: idle, m: master, s: slave) -DTS - boards = [] # List of (, ). - for board, delay in zip(self.boardOrder, self.boardDelays): - if board in runnerInfo: - runner = runnerInfo[board] - slave = len(boards) > 0 - regs = runner.runPacket(page, slave, delay, sync) - boards.append((runner.dev, regs)) - elif len(boards): - # This board is after the master, but will not itself run, so - # we put it in idle mode. - dev = self.fpgaServer.devices[board] # Look up device wrapper. - if isinstance(dev, dac.DAC): - regs = dev.regIdle(delay) - boards.append((dev, regs)) - elif isinstance(dev, adc.ADC): - # ADC boards always pass through signals, so no need for - # Idle mode. - pass - boards = boards[1:] + boards[:1] # move master to the end. - runPkts = self.makeRunPackets(boards) - # Collect and read (or discard) timing results. - seqTime = max(runner.seqTime for runner in runners) - collectPkts = [runner.collectPacket(seqTime, self.ctx) - for runner in runners] - readPkts = [runner.readPacket(timingOrder) for runner in runners] - - return loadPkts, setupPkts, runPkts, collectPkts, readPkts - - def makeRunPackets(self, data): - """Create packets to run a set of boards. - - There are two options as to how this can work, depending on - whether the setup state from the previous run is the same as - for this run. If no changes to the setup state are required, - then we can wait for triggers and immediately start the next - run; this is what the 'both' packet does. If the setup state - has changed, we must wait for triggers, then send setup packets, - and then start the next run. This two-stage operation is what - the 'wait' and 'run' packets do. We create both here because - we can't tell until it is our turn in the pipe which method - will be used. - """ - - wait = self.directEthernetServer.packet(context=self.ctx) - run = self.directEthernetServer.packet(context=self.ctx) - both = self.directEthernetServer.packet(context=self.ctx) - if LOGGING_PACKET: - wait = LoggingPacket(wait, name='run=wait') - run = LoggingPacket(run, name='run=run') - both = LoggingPacket(both, name='run=both') - # Wait for triggers and discard them. The actual number of triggers to - # wait for will be decide later. The 0 is a placeholder here. - wait.wait_for_trigger(0, key='nTriggers') - both.wait_for_trigger(0, key='nTriggers') - # Run all boards. - for dev, regs in data: - bytes = regs.tostring() - # We must switch to each board's destination MAC each time we write - # data because our packets for the direct ethernet server is in the - # main context of the board group, and therefore does not have a - # specific destination MAC. - run.destination_mac(dev.MAC).write(bytes) - both.destination_mac(dev.MAC).write(bytes) - return wait, run, both - - @inlineCallbacks - def run(self, runners, reps, setupPkts, setupState, sync, getTimingData, - timingOrder): - """Run a sequence on this board group.""" - - # Check whether this sequence will fit in just one page. - if all(runner.pageable() for runner in runners): - # Lock just one page. - page = self.pageNums.next() - pageLocks = [self.pageLocks[page]] - else: - # Start on page 0 and set pageLocks to all pages. - print 'Paging off: SRAM too long.' - page = 0 - pageLocks = self.pageLocks - - # Prepare packets. - logging.info('making packets') - pkts = self.makePackets(runners, page, reps, timingOrder, sync) - loadPkts, boardSetupPkts, runPkts, collectPkts, readPkts = pkts - - # Add setup packets from boards (ADCs) to that provided in the args: - # setupPkts is a list. - # setupState is a set. - setupPkts.extend(pkt for pkt, state in boardSetupPkts) - setupState.update(state for pkt, state in boardSetupPkts) - - try: - yield self.pipeSemaphore.acquire() - logging.info('pipe semaphore acquired') - try: - # Stage 1: load. - for pageLock in pageLocks: # Lock pages to be written. - yield pageLock.acquire() - logging.info('page locks acquired') - # Send load packets. Do not wait for response. We already - # acquired the page lock, so sending data to SRAM and memory is - # kosher at this time. - # TODO: Need to check what 'load packets' is for ADC and make - # sure sending load packets here is ok. - loadDone = self.sendAll(loadPkts, 'Load') - # stage 2: run - # Send a request for the run lock, do not wait for response. - runNow = self.runLock.acquire() - try: - yield loadDone # wait until load is finished. - yield runNow # Wait for acquisition of the run lock. - logging.info('run lock acquired') - # Set the number of triggers needed before we can actually - # run. We expect to get one trigger for each board that - # had to run and return data. This is the number of - # runners in the previous sequence. - logging.info( - 'num prev triggers: {}'.format(self.prevTriggers)) - waitPkt, runPkt, bothPkt = runPkts - waitPkt['nTriggers'] = self.prevTriggers - bothPkt['nTriggers'] = self.prevTriggers - # store the number of triggers for the next run - self.prevTriggers = len(runners) - logging.info('num runners: {}'.format(len(runners))) - # If the passed in setup state setupState, or the current - # actual setup state, self.setupState are empty, we need - # to set things up. Also if the desired setup state isn't - # a subset of the actual one, we need to set up. - # XXX Check what setup state means for ADC. Should this - # include the trigger/demodulator tables or not? - needSetup = ((not setupState) or (not self.setupState) or - (not (setupState <= self.setupState))) - if needSetup: - logging.info('needSetup = True') - # we require changes to the setup state so first, wait - # for triggers indicating that the previous run has - # collected. - # If this fails, something BAD happened! - r = yield waitPkt.send() - logging.info('waitPkt sent') - try: - # Then set up - logging.info('sending setupPkts...') - yield self.sendAll(setupPkts, 'Setup') - logging.info('...setupPkts sent') - self.setupState = setupState - except Exception as e: - # if there was an error, clear setup state - logging.info('catching setupPkts exception') - self.setupState = set() - logging.error( - 'Exception in setupPkts: {}'.format(e)) - raise e - # and finally run the sequence - logging.info('sending runPkt...') - yield runPkt.send() - logging.info('...runPkt sent') - else: - # if this fails, something BAD happened! - logging.info('need setup = false') - r = yield bothPkt.send() - - # Keep track of how long the packet waited before being - # able to run. - # XXX How does this work? Why is r['nTriggers'] the wait - # time? - # print "fpga server: r['nTriggers']: %s" % (r['nTriggers']) - self.runWaitTimes.append(r['nTriggers']['s']) - if len(self.runWaitTimes) > 100: - self.runWaitTimes.pop(0) - - yield self.readLock.acquire() # wait for our turn to read - logging.info('read lock acquired') - # stage 3: collect - # Collect appropriate number of packets and then trigger - # the master context. - collectAll = defer.DeferredList( - [p.send() for p in collectPkts], consumeErrors=True) - logging.info('waiting for collect packets') - finally: - # by releasing the runLock, we allow the next sequence to - # send its run packet. if our collect fails due to a - # timeout, however, our triggers will not all be sent to - # the run context, so that it will stay blocked until - # after we cleanup and send the necessary triggers - - # We now release the run lock. Other users will now be - # able to send their run packet to the direct ethernet, - # but the direct ethernet will not actually send run - # commands to the FPGA boards until the master context - # receives all expected triggers. These triggers are sent - # along with the collect packets, and succeed only if the - # collect commands do not time out. This means that the - # boards won't run until either our collect succeeds, - # meaning we're finished running and got all expected - # data, or we clean up from a timeout and manually send - # the necessary number of triggers. Note that we release - # the run lock IMMEDIATELY after sending the request to - # collect so that other users can get going ASAP. - # The direct ethernet server will allow other run commands - # to go as soon as our triggers are received, but only if - # that run command has been sent! - self.runLock.release() - logging.info('run lock released') - # Wait for data to be collected. - results = yield collectAll - logging.info('results collected') - finally: - for pageLock in pageLocks: - pageLock.release() - logging.info('page lock released') - - # check for a timeout and recover if necessary - if not all(success for success, result in results): - for success, result in results: - if not success: - result.printTraceback() - yield self.recoverFromTimeout(runners, results) - self.readLock.release() - raise TimeoutError(self.timeoutReport(runners, results)) - - # stage 4: read - # no timeout, so go ahead and read data - boardOrder = [runner.dev.devName for runner in runners] - readAll = self.sendAll(readPkts, 'Read', boardOrder) - self.readLock.release() - # This line scales really badly with incrasing stats - # At 9600 stats the next line takes 10s out of 20s per - # sequence. - results = yield readAll # wait for read to complete - - if getTimingData: - answers = [] - # Cache of already-parsed data from a particular board. - # Prevents un-flattening a packet more than once. - extractedData = {} - for dataChannelName in timingOrder: - if '::' in dataChannelName: - # If dataChannelName has :: in it, it's an ADC - # with specified demod channel - boardName, channel = dataChannelName.split('::') - channel = int(channel) - elif 'DAC' in dataChannelName: - raise RuntimeError('DAC data readback not supported') - elif 'ADC' in dataChannelName: - # ADC average mode - boardName = dataChannelName - channel = None - else: - raise RuntimeError('channel format not understood') - - if boardName in extractedData: - # If we have already parsed the packet for this - # board, fetch the cached result. - extracted = extractedData[boardName] - else: - # Otherwise, extract data, cache it, and add - # relevant part to the list of returned data - idx = boardOrder.index(boardName) - runner = runners[idx] - result = [data for src, dest, eth, data in - results[idx]['read']] - # Array of all timing results (DAC) - extracted = runner.extract(result) - extractedData[boardName] = extracted - # Add extracted data to list of data to be returned - if channel != None: - # If this is an ADC demod channel, grab that - # channel's data only - extractedChannel = extracted[0][channel] - else: - extractedChannel = extracted - answers.append(extractedChannel) - returnValue(tuple(answers)) - finally: - self.pipeSemaphore.release() - - @inlineCallbacks - def sendAll(self, packets, info, infoList=None): - """Send a list of packets and wrap them up in a deferred list.""" - # Remove packets which contain no actual requests. - packets = [p for p in packets if p._packet] - results = yield defer.DeferredList([p.send() for p in packets], - consumeErrors=True) # [(success, result)...] - if all(s for s, r in results): - # return the list of results - returnValue([r for s, r in results]) - else: - # create an informative error message - msg = 'Error(s) occured during {}:\n'.format(info) - if infoList is None: - msg += (''.join(r.getBriefTraceback() - for s, r in results if not s)) - else: - for i, (s, r) in zip(infoList, results): - m = 'OK' if s else ('error!\n' + r.getBriefTraceback()) - msg += '{} : {}\n\n'.format(i, m) - raise Exception(msg) - - def extractTiming(self, packets): - """Extract timing data coming back from a readPacket.""" - data = ''.join(data[3:63] for data in packets) - return np.fromstring(data, dtype=' 1: - raise Exception('Can only run multiboard sequence if all boards ' - 'are in the same board group!') - bg = devs[0].boardGroup - - # build a list of runners which have necessary sequence information - # for each board - # print "fpga server: buildRunner reps: %s" % (reps, ) - runners = [dev.buildRunner(reps, c.get(dev, {})) for dev in devs] - - # build setup requests - setupReqs = _process_setup_packets(self.client, setupPkts) - logging.debug('Setup Reqs: {}'.format(setupReqs)) - - # run the sequence, with possible retries if it fails - retries = self.retries - attempt = 1 - while True: - try: - ans = yield bg.run(runners, reps, setupReqs, set(setupState), - c['master_sync'], getTimingData, - timingOrder) - # For ADCs in demodulate mode, store their I and Q ranges to - # check for possible clipping. - for runner in runners: - if (getTimingData and isinstance(runner, adc.AdcRunner) and - runner.runMode == 'demodulate' and - runner.dev.devName in timingOrder): - c[runner.dev]['ranges'] = runner.ranges - if ans is not None: - ans = np.asarray(ans) - returnValue(ans) - except TimeoutError as err: - # log attempt to stdout and file - userpath = os.path.expanduser('~') - logpath = os.path.join(userpath, 'dac_timeout_log.txt') - with open(logpath, 'a') as logfile: - t = timeString() - msg = '{}: attempt {} - error: {}'.format(t, attempt, err) - print(msg) - logfile.write(msg+'\n') - if attempt == retries: - logfile.write('FAIL\n') - # TODO: notify users via SMS. - raise - else: - print('retrying...') - logfile.write('retrying...') - attempt += 1 - - @setting(52, 'Daisy Chain', boards='*s', returns='*s') - def sequence_boards(self, c, boards=None): - """ - Set or get the boards to run. - - The actual daisy chain order is determined automatically, as - configured in the registry for each board group. This setting controls - which set of boards to run, but does not determine the order. Set - daisy_chain to an empty list to run the currently-selected board only. - - DACs not listed here will be set to idle mode, and will pass the - daisychain pulse through to the next board. - - ADCs always pass the daisychain pulse. - """ - if boards is None: - boards = c['daisy_chain'] - else: - c['daisy_chain'] = boards - return boards - - @setting(54, 'Timing Order', boards='*s', returns='*s') - def sequence_timing_order(self, c, boards=None): - """Set or get the timing order for boards. - - This specifies the boards from which you want to receive timing - data, and the order in which the timing data should be returned. - In addition, this will determine what kind of boards will return - data (ADCs or DACs). For ADC boards, the data specified here must - agree with the selection made for run mode for that board. - - To get DAC timing data or ADC data in average mode, specify the - device name as a string. To get ADC data in demodulation mode, - specify a string in the form "::" where - channel is the demodulation channel number. - - Note that you can get data from more than one demodulation channel, - so that a given ADC board can appear multiple times in the timing - order, however each ADC board must be run either in average mode - or demodulation mode, not both. - - Note that the boards parameter must be a list of strings, *s! If you - send in a single string, pylabrad will accept it as a *s but it will - be treated as a list of single character strings and you'll get - unexpected behavior. For example, if you send in 'abcde' it will be - treated like ['a', 'b', 'c', 'd', 'e']. - - Parameters: - boards: INSERT EXAMPLE!!! - """ - if boards is None: - # Get timing order. - boards = c['timing_order'] - else: - # Set timing order. - c['timing_order'] = boards - - return boards - - @setting(55, 'Master Sync', sync='w', returns='w') - def sequence_master_sync(self, c, sync=None): - """Set or get the master sync. - - This specifies a counter that determines when the master - is allowed to start, to control the microwave phase. The - default is 249, which sets the master to start only every 1 us. - """ - if sync is None: - sync = c['master_sync'] - else: - c['master_sync'] = sync - return sync - - @setting(59, 'Performance Data', returns='*((sw)(*v, *v, *v, *v, *v))') - def sequence_performance_data(self, c): - """Get data about the pipeline performance. - - For each board group (as defined in the registry), - this returns times for: - page lock (page 0) - page lock (page 1) - run lock - run packet (on the direct ethernet server) - read lock - - If the pipe runs dry, the first time that will go to zero will - be the run packet wait time. In other words, if you have non-zero - times for the run-packet wait, then the pipe is saturated, - and the experiment is running at full capacity. - """ - ans = [] - for (server, port), group in sorted(self.boardGroups.items()): - pageTimes = [lock.times for lock in group.pageLocks] - runTime = group.runLock.times - runWaitTime = group.runWaitTimes - readTime = group.readLock.times - ans.append(((server, port), (pageTimes[0], pageTimes[1], runTime, - runWaitTime, readTime))) - return ans - - @setting(200, 'PLL Init', returns='') - def pll_init(self, c, data): - """Sends the initialization sequence to the PLL. (DAC and ADC) - - The sequence is [0x1FC093, 0x1FC092, 0x100004, 0x000C11]. - """ - dev = self.selectedDevice(c) - yield dev.initPLL() - - @setting(201, 'PLL Reset', returns='') - def pll_reset(self, c): - """Resets the FPGA internal GHz serializer PLLs. (DAC only)""" - dev = self.selectedDAC(c) - yield dev.resetPLL() - - @setting(202, 'PLL Query', returns='b') - def pll_query(self, c): - """ - Checks the FPGA internal GHz serializer PLLs for lock failures. - (DAC and ADC) - - Returns True if the PLL has lost lock since the last reset. - """ - dev = self.selectedDevice(c) - unlocked = yield dev.queryPLL() - returnValue(unlocked) - - @setting(203, 'Build Number', returns='s') - def build_number(self, c): - """Gets the build number of selected device (DAC and ADC)""" - dev = self.selectedDevice(c) - buildNumber = yield dev.buildNumber() - returnValue(buildNumber) - - @setting(204, 'Execution count', returns='i') - def execution_counter(self, c): - """Query sequence executions since last start command""" - dev = self.selectedDevice(c) - count = yield dev.executionCount() - returnValue(int(count)) - - @setting(1080, 'DAC Debug Output', data='wwww', returns='') - def dac_debug_output(self, c, data): - """Outputs data directly to the output bus. (DAC only)""" - dev = self.selectedDAC(c) - yield dev.debugOutput(*data) - - @setting(1081, 'DAC Run SRAM', - data='*w', loop='b', blockDelay='w', returns='') - def dac_run_sram(self, c, data, loop=False, blockDelay=0): - """Loads data into the SRAM and executes as master. (DAC only) - - If loop is True, the sequence will be repeated forever, - otherwise it will be executed just once. Sending - an empty list of data will clear the SRAM. The blockDelay - parameters specifies the number of microseconds to delay - for a multiblock sequence. - """ - if len(data) < 20: - raise ValueError('Cannot play less than 20 ns of data.') - - dev = self.selectedDAC(c) - yield dev.runSram(data, loop, blockDelay) - - @setting(2081, 'DAC Write SRAM', data='*w') - def dac_write_sram(self, c, data): - """Write data to SRAM. - - Args: - data(iterable of int): List-like series of SRAM data. The data must - already be packed. - - The data is written immediately, although no start commands are sent. - This command just writes data into the board's SRAM buffer, that's it. - """ - dev = self.selectedDAC(c) - yield dev._sendSRAM(np.array(data, dtype=' 0: - return [l[:i]] + rest - else: - return rest - except ValueError: # No more sentinels. - return [l] - - # Split data into packets delimited by I2C_END. - pkts = partition(data, I2C_END) - return dev.runI2C(pkts) - - @setting(1110, 'DAC LEDs', data=['w', 'bbbbbbbb'], returns='w') - def dac_leds(self, c, data): - """Sets the status of the 8 I2C LEDs. (DAC only)""" - dev = self.selectedDAC(c) - - if isinstance(data, tuple): - # convert to a list of digits, and interpret as binary int - data = long(''.join(str(int(b)) for b in data), 2) - - pkts = [[200, 68, data & 0xFF]] # 192 for build 1 - yield dev.runI2C(pkts) - returnValue(data) - - @setting(1120, 'DAC Reset Phasor', returns='b: phase detector output') - def dac_reset_phasor(self, c): - """Resets the clock phasor. (DAC only)""" - dev = self.selectedDAC(c) - - pkts = [[152, 0, 127, 0], # set I to 0 deg - [152, 34, 254, 0], # set Q to 0 deg - [112, 65], # set enable bit high - [112, 193], # set reset high - [112, 65], # set reset low - [112, 1], # set enable low - [113, I2C_RB]] # read phase detector - - r = yield dev.runI2C(pkts) - returnValue((r[0] & 1) > 0) - - @setting(1121, 'DAC Set Phasor', - data=[': poll phase detector only', - 'v[rad]: set angle (in rad, deg, \xF8, \', or ")'], - returns='b: phase detector output') - def dac_set_phasor(self, c, data=None): - """ - Sets the clock phasor angle and reads the phase detector bit. - (DAC only) - """ - dev = self.selectedDAC(c) - - if data is None: - pkts = [[112, 1], - [113, I2C_RB]] - else: - sn = int(round(127 + 127*np.sin(data))) & 0xFF - cs = int(round(127 + 127*np.cos(data))) & 0xFF - pkts = [[152, 0, sn, 0], - [152, 34, cs, 0], - [112, 1], - [113, I2C_RB]] - - r = yield dev.runI2C(pkts) - returnValue((r[0] & 1) > 0) - - @setting(1130, 'DAC Vout', chan='s', V='v[V]', returns='w') - def dac_vout(self, c, chan, V): - """Sets the output voltage of any Vout channel, A, B, C or D. (DAC only) - """ - cmd = dac.DAC.getCommand({'A': 16, 'B': 18, 'C': 20, 'D': 22}, chan) - dev = self.selectedDAC(c) - val = int(max(min(round(V*0x3333), 0x10000), 0)) - pkts = [[154, cmd, (val >> 8) & 0xFF, val & 0xFF]] - yield dev.runI2C(pkts) - returnValue(val) - - @setting(1135, 'DAC Ain', returns='v[V]') - def dac_ain(self, c): - """Reads the voltage on Ain. (DAC only)""" - dev = self.selectedDAC(c) - pkts = [[144, 0], - [145, I2C_RB_ACK, I2C_RB]] - r = yield dev.runI2C(pkts) - returnValue(T.Value(((r[0] << 8) + r[1]) / 819.0, 'V')) - - @setting(1200, 'DAC PLL', data='*w', returns='*w') - def dac_pll(self, c, data): - """Sends a sequence of commands to the PLL. (DAC only) - - The returned WordList contains any read-back values. - It has the same length as the sent list. - """ - dev = self.selectedDAC(c) - return dev.runSerial(1, data) - - @setting(1204, 'DAC Serial Command', chan='s', data='*w', returns='*w') - def dac_cmd(self, c, chan, data): - """Send a sequence of commands to either DAC. (DAC only) - - The DAC channel must be either 'A' or 'B'. - The returned list of words contains any read-back values. - It has the same length as the sent list. - """ - cmd = dac.DAC.getCommand({'A': 2, 'B': 3}, chan) - dev = self.selectedDAC(c) - return dev.runSerial(cmd, data) - - @setting(1206, 'DAC Clock Polarity', chan='s', invert='b', returns='b') - def dac_pol(self, c, chan, invert): - """Sets the clock polarity for either DAC. (DAC only)""" - dev = self.selectedDAC(c) - yield dev.setPolarity(chan, invert) - returnValue(invert) - - @setting(1220, 'DAC Init', chan='s', signed='b', returns='b') - def dac_init(self, c, chan, signed=False): - """Sends an initialization sequence to either DAC. (DAC only) - - For unsigned data, this sequence is 0026, 0006, 1603, 0500 - For signed data, this sequence is 0024, 0004, 1603, 0500 - """ - cmd = dac.DAC.getCommand({'A': 2, 'B': 3}, chan) - dev = self.selectedDAC(c) - pkt = ([0x0024, 0x0004, 0x1603, 0x0500] if signed else - [0x0026, 0x0006, 0x1603, 0x0500]) - yield dev.runSerial(cmd, pkt) - returnValue(signed) - - @setting(1221, 'DAC LVDS', chan='s', optimizeSD='b', data='w', - returns='biii(*w*b*b)w') - def dac_lvds(self, c, chan, optimizeSD=False, data=None): - """Calibrate LVDS Phase Shift. (DAC Only) - - Align DAC clocks for LVDS phase shift, varying SD (sample delay). - - If optimizeSD=False but sd is None, SD will be set to a value retrieved - from the registry entry for this board. - - Args: - chan: Which DAC channel ('A','B') - optimizeSD: Whether to follow data sheet procedure to determine SD - data: If int, SD value to be set. If None, SD set to value in - registry. Ignored if optimizeSD=True. - - Returns: - success: If LVDS bringup successful. MSD and MHD should only flip - once with flip locations within one bit of each other. If - varies, could mean clock noise. - MSD: Measured sample delay. If optimizeSD, where MSD flips when - MHD=SD=0. Else -1. - MHD: Measured hold delay. If optimizeSD, where MHD flips when - MSD=SD=0. Else -1. - SD: SD value set - timing profile: Cluster (SDs 0-15, MSD(SDs), MHD(SDs)) - checkHex: In binary, '0bABC', where - A=1: An LVDS input was above specification - B=1: An LVDS input was below specification - C=1: Sampling in correct data cycle - """ - cmd = dac.DAC.getCommand({'A': 2, 'B': 3}, chan) - dev = self.selectedDAC(c) - ans = yield dev.setLVDS(cmd, data, optimizeSD) - returnValue(ans) - - @setting(1222, 'DAC FIFO', chan='s', targetFifo='w', returns='bbiww') - def dac_fifo(self, c, chan, targetFifo=None): - """Adjust FIFO buffer. (DAC only) - - Adjust PHOF (phase offset) so FIFO (first-in-first-out) counter equals - targetFifo. If FIFO counter equals targetFifo, this PHOF is written and - the FIFO counter read back; the PHOF is deemed successful only if this - last FIFO counter is targetFifo. - - If no PHOF can be found to get an acceptable FIFO counter after 5 tries, - success=False. Here, return PHOF=-1 if the initial check failed and - otherwise the PHOF where the FIFO counter was targetFifo initially. - - Args: - chan: Which DAC channel ('A','B') - targetFifo: Desired targetFifo. If None, use value from board - registry entry. - - Returns: - success, clock polarity, PHOF, number of tries, FIFO counter - """ - op = dac.DAC.getCommand({'A': 2, 'B': 3}, chan) - dev = self.selectedDAC(c) - ans = yield dev.setFIFO(chan, op, targetFifo) - returnValue(ans) - - @setting(1223, 'DAC Cross Controller', chan='s', delay='i', returns='i') - def dac_xctrl(self, c, chan, delay=0): - """Sets the cross controller delay on either DAC. (DAC only) - - Range for delay is -63 to 63. - """ - dev = self.selectedDAC(c) - cmd = dac.DAC.getCommand({'A': 2, 'B': 3}, chan) - if delay < -63 or delay > 63: - raise T.Error(11, 'Delay must be between -63 and 63') - - seq = ([0x0A00, 0x0B00 - delay] if delay < 0 - else [0x0A00 + delay, 0x0B00]) - yield dev.runSerial(cmd, seq) - returnValue(delay) - - @setting(1225, 'DAC BIST', chan='s', data='*w', returns='b(ww)(ww)(ww)') - def dac_bist(self, c, chan, data): - """Run a Built-In Self Test on the given SRAM sequence. (DAC only) - - Returns success, theory, LVDS, FIFO - """ - cmd, shift = dac.DAC.getCommand({'A': (2, 0), 'B': (3, 14)}, chan) - dev = self.selectedDAC(c) - ans = yield dev.runBIST(cmd, shift, data) - # This is coming back with 64-bit ints, the coercing of which needs to - # be fixed in pylabrad for now we manually cast to 32-bit (long) - # See pylabrad github issue #43. - - def coerce(xs): - return tuple(long(x) for x in xs) - returnValue((ans[0], coerce(ans[1]), coerce(ans[2]), coerce(ans[3]))) - - @setting(1300, 'DAC Bringup', - lvdsOptimize='b', - lvdsSD='w', - signed='b', - targetFifo='w', - returns=('*((ss)(sb)(si)(si)(sw)(s(*w*b*b))(sw)(sb)(sb)(si)(sw)' - '(sw)(sb)(s(ww))(s(ww))(s(ww)))')) - def dac_bringup(self, c, lvdsOptimize=False, lvdsSD=None, signed=True, - targetFifo=None): - """ - Runs the bringup procedure. - - This code initializes the PLL, initializes the DAC, sets the LVDS SD, - sets the FIFO, and runs the BIST test on each DAC channel. The output - is (in tuple format) a list of two (one for each DAC) pairs of - (string, data) with all the calibration parameters. - """ - dev = self.selectedDAC(c) - ans = [] - yield dev.initPLL() - time.sleep(0.100) - yield dev.resetPLL() - for dac in ['A', 'B']: - ansDAC = [('dac', dac)] - cmd, shift = {'A': (2, 0), 'B': (3, 14)}[dac] - # Initialize DAC. - # See HardRegProgram.txt for byte sequence definition. - pkt = ([0x0024, 0x0004, 0x1603, 0x0500] if signed else - [0x0026, 0x0006, 0x1603, 0x0500]) - yield dev.runSerial(cmd, pkt) - lvdsAns = yield dev.setLVDS(cmd, lvdsSD, lvdsOptimize) - lvdsKeys = ['lvdsSuccess', 'lvdsMSD', 'lvdsMHD', 'lvdsSD', - 'lvdsTiming', 'lvdsCheck'] - for key, val in zip(lvdsKeys, lvdsAns): - ansDAC.append((key, val)) - fifoAns = yield dev.setFIFO(dac, cmd, targetFifo) - fifoKeys = ['fifoSuccess', 'fifoClockPolarity', 'fifoPHOF', - 'fifoTries', 'fifoCounter'] - for key, val in zip(fifoKeys, fifoAns): - ansDAC.append((key, val)) - bistData = [random.randint(0, 0x3FFF) for i in range(1000)] - bistAns = yield dev.runBIST(cmd, shift, bistData) - bistKeys = ['bistSuccess', 'bistTheory', 'bistLVDS', 'bistFIFO'] - for key, val in zip(bistKeys, bistAns): - ansDAC.append((key, val)) - ans.append(tuple(ansDAC)) - returnValue(ans) - - @setting(1313, 'DAC Serial', cmd='w', pkts='*w', returns='?') - def dac_serial(self, c, cmd, pkts): - dev = self.selectedDAC(c) - ans = yield dev.runSerial(cmd, pkts) - returnValue(ans) - - @setting(1100000, 'Debug Print Context') - def debug_print_context(self, c): - """Prints the context to the server's stdout.""" - print c - - @setting(1100001, 'Debug Clear Ethernet') - def debug_clear_ethernet(self, c): - for dev in self.devices.values(): - dev.clear().send() - - @setting(2500, 'ADC Recalibrate', returns='') - def adc_recalibrate(self, c): - """Recalibrate the analog-to-digital converters. (ADC only)""" - dev = self.selectedADC(c) - yield dev.recalibrate() - - @setting(2501, 'ADC Register Readback', returns='s') - def adc_register_readback(self, c): - """Register Readback. (ADC only)""" - dev = self.selectedADC(c) - ans = yield dev.registerReadback() - returnValue(str(ans)) - - @setting(2502, 'ADC Monitor Outputs', mon0=['s', 'w'], mon1=['s', 'w']) - def adc_monitor_outputs(self, c, mon0, mon1): - """Specify monitor outputs. (ADC only)""" - dev = self.selectedADC(c) - info = c.setdefault(dev, {}) - print 'monitor outputs: ', mon0, mon1 - info['mon0'] = mon0 - info['mon1'] = mon1 - - @setting(2600, 'ADC Run Average', returns='*i{I},*i{Q}') - def adc_run_average(self, c): - """Run the selected ADC board once in average mode. (ADC only) - - The board will start immediately using the trig lookup and demod - settings already specified in this context (although these settings - have no effect in average mode). Returns the acquired I and Q - waveforms. - - Returns: - (I: np.array(int), Q: np.array(int)) - """ - dev = self.selectedADC(c) - info = c.setdefault(dev, {}) - # demods = dict((i, info[i]) - # for i in range(dev.DEMOD_CHANNELS) if i in info) - ans = yield dev.runAverage() - returnValue(ans) - - @setting(2601, 'ADC Run Calibrate', returns='') - def adc_run_calibrate(self, c): - """Recalibrate the ADC chips""" - raise Exception('Depricated. Use ADC Recalibrate instead') - dev = self.selectedADC(c) - info = c.setdefault(dev, {}) - filterFunc = info.get('filterFunc', np.array([255], dtype='. - -# -# Version 1.1.0 -# -# History -# -# 1.1.0 2008/06/17 added recalibrations and possibility to load several -# calibration files -# 1.0.0 first stable version - - -from __future__ import with_statement - -from numpy import shape, array, size -import numpy as np - -import labrad - -from correction import (DACcorrection, IQcorrection, - cosinefilter, gaussfilter, flatfilter) -import keys -import calibrate -import logging - - -def aequal(a, b): - return (shape(a) == shape(b)) and (all(a == b)) - - -def getDataSets(cxn, boardname, caltype, errorClass=None): - reg = cxn.registry - ds = cxn.data_vault - reg.cd(['', keys.SESSIONNAME, boardname], True) - if caltype in (reg.dir())[1]: - calfiles = (reg.get(caltype)) - else: - calfiles = array([]) - - if not size(calfiles): - if isinstance(errorClass, Exception): - raise errorClass(caltype) - elif errorClass != 'quiet': - print 'Warning: No %s calibration loaded.' % caltype - print ' No %s correction will be performed.' % caltype - - return (calfiles) - - -def IQcorrector(fpganame, connection, - zerocor=True, pulsecor=True, iqcor=True, - lowpass=cosinefilter, bandwidth=0.4, errorClass='quiet'): - """ - Returns a DACcorrection object for the given DAC board. - The argument has the same form as the - dms.python_fpga_server.connect argument - """ - - if connection: - cxn = connection - logging.debug("using received cxn: {}".format(cxn)) - else: - cxn = labrad.connect() - - ds = cxn.data_vault - ds.cd(['', keys.SESSIONNAME, fpganame], True) - corrector = IQcorrection(fpganame, lowpass, bandwidth) - # Load Zero Calibration - if zerocor: - datasets = getDataSets(cxn, fpganame, keys.ZERONAME, errorClass) - logging.debug('datasets: {}'.format(datasets)) - for dataset in datasets: - filename = ds.open(long(dataset)) - logging.debug('Loading zero calibration from: {}'.format(filename[1])) - datapoints = ds.get() - datapoints = np.array(datapoints) - corrector.loadZeroCal(datapoints, dataset) - # Load pulse response - if pulsecor: - dataset = getDataSets(cxn, fpganame, keys.PULSENAME, errorClass) - if dataset != []: - dataset = dataset[0] - filename = ds.open(long(dataset)) - logging.debug('Loading pulse calibration from: {}'.format(filename[1])) - setupType = ds.get_parameter(keys.IQWIRING) - logging.info('setupType: {}'.format(setupType)) - IisB = (setupType == keys.SETUPTYPES[2]) - datapoints = ds.get() - datapoints = np.array(datapoints) - carrierfreq = (ds.get_parameter(keys.PULSECARRIERFREQ))['GHz'] - corrector.loadPulseCal(datapoints, carrierfreq, dataset, IisB) - # Load Sideband Calibration - if iqcor: - datasets = getDataSets(cxn, fpganame, keys.IQNAME, errorClass) - for dataset in datasets: - filename = ds.open(long(dataset)) - logging.debug('Loading sideband calibration from: {}'.format(filename[1])) - sidebandStep = \ - (ds.get_parameter('Sideband frequency step'))['GHz'] - sidebandCount = \ - ds.get_parameter('Number of sideband frequencies') - datapoints = ds.get() - datapoints = np.array(datapoints) - corrector.loadSidebandCal(datapoints, sidebandStep, dataset) - if not connection: - cxn.disconnect() - return corrector - - -def DACcorrector(fpganame, channel, connection=None, - lowpass=gaussfilter, bandwidth=0.13, errorClass='quiet', maxfreqZ=0.45): - """ - Returns a DACcorrection object for the given DAC board. - The argument has the same form as the - dms.python_fpga_server.connect argument - """ - if connection: - cxn = connection - else: - cxn = labrad.connect() - - ds = cxn.data_vault - - ds.cd(['', keys.SESSIONNAME, fpganame], True) - - corrector = DACcorrection(fpganame, channel, lowpass, bandwidth) - - if not isinstance(channel, str): - channel = keys.CHANNELNAMES[channel] - - dataset = getDataSets(cxn, fpganame, channel, errorClass) - if dataset != []: - logging.debug("Dataset - fpganame: {} channel: {}".format(fpganame, channel)) - dataset = dataset[0] - logging.debug("Loading pulse calibration from: {}".format(dataset)) - ds.open(dataset) - datapoints = ds.get() - datapoints = np.array(datapoints) - corrector.loadCal(datapoints, maxfreqZ=maxfreqZ) - if not connection: - cxn.disconnect() - - return corrector - - -def recalibrate(boardname, carrierMin, carrierMax, zeroCarrierStep=0.025, - sidebandCarrierStep=0.05, sidebandMax=0.35, sidebandStep=0.05, - corrector=None): - cxn = labrad.connect() - ds = cxn.data_vault - reg = cxn.registry - reg.cd(['', keys.SESSIONNAME, boardname]) - anritsuID = reg.get(keys.ANRITSUID) - anritsuPower = (reg.get(keys.ANRITSUPOWER))['dBm'] - if corrector is None: - corrector = IQcorrector(boardname, cxn) - if corrector.board != boardname: - logging.info('Provided corrector is not for: {}'.format(boardname)) - logging.info('Loading new corrector. Provided corrector will not be updated.') - corrector = IQcorrector(boardname, cxn) - - if zeroCarrierStep is not None: - # check if a corrector has been provided and if it is up to date - # or if we have to load a new one. - if not aequal(corrector.zeroCalFiles, - (getDataSets(cxn, boardname, keys.ZERONAME, 'quiet'))): - logging.info('Provided corrector is outdated.') - logging.info('Loading new corrector. Provided corrector will not be updated.') - corrector = IQcorrector(boardname, cxn) - - # do the zero calibration - dataset = calibrate.zeroScanCarrier(cxn, - {'carrierMin': carrierMin, - 'carrierMax': carrierMax, - 'carrierStep': zeroCarrierStep}, - boardname) - # load it into the corrector - ds.open(dataset) - datapoints = (ds.get()).asarray - corrector.loadZeroCal(datapoints, dataset) - # eliminate obsolete zero calibrations - datasets = corrector.eliminateZeroCals() - # and save which ones are being used now - reg.cd(['', keys.SESSIONNAME, boardname], True) - reg.set(keys.ZERONAME, datasets) - if sidebandCarrierStep is not None: - # check if a corrector has been provided and if it is up to date - # or if we have to load a new one. - if not (aequal(corrector.sidebandCalFiles, - (getDataSets(cxn, boardname, keys.IQNAME, 'quiet'))) and \ - aequal(array([corrector.pulseCalFile]), - (getDataSets(cxn, boardname, keys.PULSENAME, 'quiet')))): - logging.info('Provided correcetor is outdated.') - logging.info('Loading new corrector. Provided corrector will not be updated.') - corrector = IQcorrector(boardname, cxn) - - # do the pulse calibration - dataset = calibrate.sidebandScanCarrier(cxn, - {'carrierMin': carrierMin, - 'carrierMax': carrierMax, - 'sidebandCarrierStep': sidebandCarrierStep, - 'sidebandFreqStep': sidebandStep, - 'sidebandFreqCount': int(sidebandMax / sidebandStep + 0.5) * 2}, - boardname, corrector) - # load it into the corrector - ds.open(dataset) - sidebandStep = \ - (ds.get_parameter('Sideband frequency step'))['GHz'] - sidebandCount = \ - ds.get_parameter('Number of sideband frequencies') - datapoints = (ds.get()).asarray - corrector.loadSidebandCal(datapoints, sidebandStep, dataset) - # eliminate obsolete zero calibrations - datasets = corrector.eliminateSidebandCals() - # and save which ones are being used now - reg.cd(['', keys.SESSIONNAME, boardname], True) - reg.set(keys.IQNAME, datasets) - cxn.disconnect() - return corrector - - -def runsequence(sram, cor): - with labrad.connection() as cxn: - fpga = cxn.ghz_dacs - fpga.select_device(cor.board) - if hasattr(cor, 'channel') and cor.channel == 1: - sram = sram << 14 - sram[0:4] |= 0xF << 28 - fpga.run_sram(sram, True) - - - - diff --git a/ghzdac/calibrate.py b/ghzdac/calibrate.py deleted file mode 100644 index 2edaadfb..00000000 --- a/ghzdac/calibrate.py +++ /dev/null @@ -1,683 +0,0 @@ -# Copyright (C) 2007-2008 Max Hofheinz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# This module contains the calibration scripts. They must not require any -# user interaction because they are used not only for the initial -# calibration but also for recalibration. The user interface is provided -# by GHz_DAC_calibrate in "scripts". - -import time -import numpy as np -import labrad -from labrad.types import Value -import keys - -#trigger to be set: -#0x1: trigger S0 -#0x2: trigger S1 -#0x4: trigger S2 -#0x8: trigger S3 -#e.g. 0xA sets trigger S1 and S3 -trigger = 0xFL << 28 - -FPGA_SERVER_NAME = 'ghz_fpgas' - -DACMAX= 1 << 13 - 1 -DACMIN= 1 << 13 -PERIOD = 2000 -SCOPECHANNEL_infiniium = 1 -TRIGGERCHANNEL_infiniium = 2 - -SEQUENCE_LENGTH = 64 - - -def assertSpecAnalLock(server, device): - p = server.packet() - p.select_device(device) - p.query_10_mhz_ref(key='ref') - ans = p.send() - if ans['ref'] != 'EXT': - raise Exception('Spectrum analyzer %s external 10MHz reference not locked!' %device) - - -def microwaveSourceServer(cxn, ID): - anritsus = cxn.anritsu_server.list_devices() - anritsus = [dev[1] for dev in anritsus] - hittites = cxn.hittite_t2100_server.list_devices() - hittites = [dev[1] for dev in hittites] - if ID in anritsus: - server = 'anritsu_server' - elif ID in hittites: - server = 'hittite_t2100_server' - else: - raise Exception('Microwave source %s not found' %ID) - return (cxn.servers[server]) - - -def validSBstep(f): - return round(0.5*np.clip(f,2.0/PERIOD,1.0)*PERIOD)*2.0/PERIOD - - -def spectInit(spec): - spec.gpib_write(':POW:RF:ATT 0dB;:AVER:STAT OFF;:FREQ:SPAN 100Hz;:BAND 300Hz;:INIT:CONT OFF;:SYST:PORT:IFVS:ENAB OFF;:SENS:SWE:POIN 101') - -def spectDeInit(spec): - spec.gpib_write(':INIT:CONT ON') - - -def spectFreq(spec,freq): - spec.gpib_write(':FREQ:CENT %gGHz' % freq) - - -def signalPower(spec): - """returns the mean power in mW read by the spectrum analyzer""" - dBs = spec.gpib_query('*TRG;*OPC?;:TRAC:MATH:MEAN? TRACE1') - dBs=dBs.split(';')[1] - return (10.0**(0.1*float(dBs))) - - -def makeSample(a,b): - """computes sram sample from dac A and B values""" - if (np.max(a) > 0x1FFF) or (np.max(b) > 0x1FFF) or \ - (np.min(a) < -0x2000) or (np.min(b) < -0x2000): - print 'DAC overflow' - return long(a & 0x3FFFL) | (long(b & 0x3FFFL) << 14) - - -def measurePower(spec,fpga,a,b): - """returns signal power from the spectrum analyzer""" - dac = [makeSample(a,b)] * SEQUENCE_LENGTH - dac[0] |= trigger - # fpga.dac_run_sram(dac,True) - fpga.dac_write_sram(dac) - return ((signalPower(spec))) - - -def datasetNumber(dataset): - return int(dataset[1][:5]) - - -def datasetDir(dataset): - result = '' - dataset = dataset[0]+[dataset[1]] - for s in dataset[1:]: - result += " >> " + s - return result - - -def minPos(l, c, r): - """Calculates minimum of a parabola to three equally spaced points. - The return value is in units of the spacing relative to the center point. - It is bounded by -1 and 1. - """ - d = l+r-2.0*c - if d <= 0: - return 0 - d = 0.5*(l-r)/d - if d > 1: - d = 1 - if d < -1: - d = -1 - return d - - -#################################################################### -# DAC zero calibration # -#################################################################### - - -def zero(anr, spec, fpga, freq): - """Calibrates the zeros for DAC A and B using the spectrum analyzer""" - - anr.frequency(Value(freq,'GHz')) - spectFreq(spec,freq) - a = 0 - b = 0 - precision = 0x800 - print ' calibrating at %g GHz...' % freq - while precision > 0: - fpga.dac_run_sram([0] * SEQUENCE_LENGTH, True) - al = measurePower(spec, fpga, a-precision, b) - ar = measurePower(spec, fpga, a+precision, b) - ac = measurePower(spec, fpga, a, b) - corra = long(round(precision*minPos(al, ac, ar))) - a += corra - - bl = measurePower(spec, fpga, a, b-precision) - br = measurePower(spec, fpga, a, b+precision) - bc = measurePower(spec, fpga, a, b) - corrb = long(round(precision*minPos(bl, bc, br))) - b += corrb - optprec = 2*np.max([abs(corra), abs(corrb)]) - precision /= 2 - if precision > optprec: - precision = optprec - print ' a = %4d b = %4d uncertainty : %4d, power %6.1f dBm' % \ - (a, b, precision, 10 * np.log(bc) / np.log(10.0)) - return ([a, b]) - - -def zeroFixedCarrier(cxn, boardname, use_switch=True): - reg = cxn.registry - reg.cd(['',keys.SESSIONNAME,boardname]) - - fpga = cxn[FPGA_SERVER_NAME] - fpga.select_device(boardname) - - if use_switch: - switch = cxn.microwave_switch - switch.switch(boardname) - - spec = cxn.spectrum_analyzer_server - spectID = reg.get(keys.SPECTID) - spec.select_device(spectID) - spectInit(spec) - assertSpecAnalLock(spec, spectID) - uwaveSourceID = reg.get(keys.ANRITSUID) - uwaveSource = microwaveSourceServer(cxn, uwaveSourceID) - - uwavePower = reg.get(keys.ANRITSUPOWER) - frequency = (reg.get(keys.PULSECARRIERFREQ))['GHz'] - uwaveSource.select_device(uwaveSourceID) - uwaveSource.amplitude(uwavePower) - uwaveSource.output(True) - - print 'Zero calibration...' - - daczeros = zero(uwaveSource, spec, fpga, frequency) - - uwaveSource.output(False) - spectDeInit(spec) - if use_switch: - switch.switch(0) - return daczeros - - - -def zeroScanCarrier(cxn, scanparams, boardname, use_switch=True): - """Measures the DAC zeros in function of the carrier frequency.""" - reg = cxn.registry - reg.cd(['', keys.SESSIONNAME, boardname]) - - fpga = cxn[FPGA_SERVER_NAME] - fpga.select_device(boardname) - - if use_switch: - switch = cxn.microwave_switch - switch.switch(boardname) - - spec = cxn.spectrum_analyzer_server - spectID = reg.get(keys.SPECTID) - spec.select_device(spectID) - spectInit(spec) - assertSpecAnalLock(spec, spectID) - uwaveSourceID = reg.get(keys.ANRITSUID) - uwaveSource = microwaveSourceServer(cxn, uwaveSourceID) - uwavePower = reg.get(keys.ANRITSUPOWER) - uwaveSource.select_device(uwaveSourceID) - uwaveSource.amplitude(uwavePower) - uwaveSource.output(True) - - print 'Zero calibration from %g GHz to %g GHz in steps of %g GHz...' % \ - (scanparams['carrierMin'],scanparams['carrierMax'],scanparams['carrierStep']) - ds = cxn.data_vault - ds.cd(['', keys.SESSIONNAME, boardname], True) - dataset = ds.new(keys.ZERONAME, - [('Frequency', 'GHz')], - [('DAC zero', 'A', 'clics'), - ('DAC zero', 'B', 'clics')]) - ds.add_parameter(keys.ANRITSUPOWER, uwavePower) - - freq = scanparams['carrierMin'] - while freq < scanparams['carrierMax']+0.001*scanparams['carrierStep']: - ds.add([freq]+(zero(uwaveSource, spec, fpga, freq))) - freq += scanparams['carrierStep'] - uwaveSource.output(False) - spectDeInit(spec) - if use_switch: - cxn.microwave_switch.switch(0) - return (int(dataset[1][:5])) - -#################################################################### -# Pulse calibration # -#################################################################### - - -def measureImpulseResponse(fpga, scope, baseline, pulse, dacoffsettime=6, pulselength=1): - """Measure the response to a DAC pulse - fpga: connected fpga server - scope: connected scope server - dac: 'a' or 'b' - returns: list - list[0] : start time (s) - list[1] : time step (s) - list[2:]: actual data (V) - """ - #units clock cycles - dacoffsettime = int(round(dacoffsettime)) - triggerdelay = 30 - looplength = 2000 - pulseindex = triggerdelay-dacoffsettime - scope.start_time(Value(triggerdelay, 'ns')) - #calculate the baseline voltage by capturing a trace without a pulse - - data = np.resize(baseline, looplength) - data[pulseindex:pulseindex+pulselength] = pulse - data[0] |= trigger - fpga.dac_run_sram(data.astype('u4'),True) - data = (scope.get_trace(1)) - data[0] -= Value(triggerdelay*1e-9, 'V') # TODO: not sure about units here--pjjo - return (data) - - -def measureImpulseResponse_infiniium(fpga, scope, baseline, pulse, - dacoffsettime=6, pulselength=1, wait=75, looplength=6000): - """Measure the response to a DAC pulse - looplength: time between triggers, keep this above pulselength , 6000 for short dacs - fpga: connected fpga server - scope: connected scope server - dac: 'a' or 'b' - returns: list - list[0] : start time (s) - list[1] : time step (s) - list[2:]: actual data (V) - """ - #units clock cycles - dacoffsettime = int(round(dacoffsettime)) - triggerdelay = 30 #keep at least at 30 - pulseindex = triggerdelay-dacoffsettime - - data = np.resize(baseline, looplength) - data[pulseindex:pulseindex+pulselength] = pulse - data[0] |= trigger - fpga.dac_run_sram(data,True) - if wait: - time.sleep(wait) #keep this long enough!! 40 sec for 4096, 20 sec for 2048 - - t, y = scope.get_trace(SCOPECHANNEL_infiniium) #start and stop in ns - - # Truncate data before t=0 - after_zero_idx = np.argwhere(t > 0).flatten() - t = t[after_zero_idx] - y = y[after_zero_idx] - - return t, y - - -def calibrateACPulse(cxn, boardname, baselineA, baselineB, use_switch=True): - """Measures the impulse response of the DACs after the IQ mixer""" - pulseheight = 0x1800 - - reg = cxn.registry - reg.cd(['', keys.SESSIONNAME, boardname]) - - if use_switch: - switch = cxn.microwave_switch - - uwaveSourceID = reg.get(keys.ANRITSUID) - uwaveSource = microwaveSourceServer(cxn,uwaveSourceID) - uwaveSourcePower = reg.get(keys.ANRITSUPOWER) - carrierFreq = reg.get(keys.PULSECARRIERFREQ) - sens = reg.get(keys.SCOPESENSITIVITY) - offs = reg.get(keys.SCOPEOFFSET, True, Value(0, 'mV')) - if use_switch: - switch.switch(boardname) #Hack to select the correct microwave switch - switch.switch(0) - uwaveSource.select_device(uwaveSourceID) - uwaveSource.frequency(carrierFreq) - uwaveSource.amplitude(uwaveSourcePower) - uwaveSource.output(True) - - #Set up the scope - scope = cxn.sampling_scope - scopeID = reg.get(keys.SCOPEID) - scope.select_device(scopeID) - p = scope.packet().\ - reset().\ - channel(reg.get(keys.SSCOPECHANNEL, True, 2)).\ - trace(1).\ - record_length(5120L).\ - average(128).\ - sensitivity(sens).\ - offset(offs).\ - time_step(Value(2,'ns')).\ - trigger_level(Value(0.18,'V')).\ - trigger_positive() - p.send() - - fpga = cxn[FPGA_SERVER_NAME] - fpga.select_device(boardname) - offsettime = reg.get(keys.TIMEOFFSET) - - baseline = makeSample(baselineA,baselineB) -# print "Measuring offset voltage..." -# offset = (measureImpulseResponse(fpga, scope, baseline, baseline))[2:] -# offset = sum(offset) / len(offset) - - print "Measuring pulse response DAC A..." - traceA = measureImpulseResponse(fpga, scope, baseline, - makeSample(baselineA+pulseheight,baselineB), - dacoffsettime=offsettime['ns']) - - print "Measuring pulse response DAC B..." - traceB = measureImpulseResponse(fpga, scope, baseline, - makeSample(baselineA,baselineB+pulseheight), - dacoffsettime=offsettime['ns']) - - starttime = traceA[0] - timestep = traceA[1] - if (starttime != traceB[0]) or (timestep != traceB[1]) : - print """Time scales are different for measurement of DAC A and B. - Did you change settings on the scope during the measurement?""" - exit - #set output to zero - fpga.dac_run_sram([baseline]*20) - uwaveSource.output(False) - ds = cxn.data_vault - ds.cd(['',keys.SESSIONNAME,boardname],True) - dataset = ds.new(keys.PULSENAME,[('Time','ns')], - [('Voltage','A','V'),('Voltage','B','V')]) - setupType = reg.get(keys.IQWIRING) - ds.add_parameter(keys.IQWIRING, setupType) - ds.add_parameter(keys.PULSECARRIERFREQ, carrierFreq) - ds.add_parameter(keys.ANRITSUPOWER, uwaveSourcePower) - ds.add_parameter(keys.TIMEOFFSET, offsettime) - # begin unit strip party - starttime = starttime[starttime.unit] # stripping units - timestep = timestep[timestep.unit] # stripping units - traceA = traceA[traceA.unit] # stripping units - traceB = traceB[traceB.unit] # stripping units - data = np.transpose(\ - [1e9*(starttime+timestep*np.arange(np.alen(traceA)-2)), - traceA[2:],traceB[2:]]) - ds.add(data) -# traceA[2:]-offset, -# traceB[2:]-offset])) - if np.abs(np.argmax(np.abs(traceA-np.average(traceA))) - \ - np.argmax(np.abs(traceB-np.average(traceB)))) \ - * timestep > 0.5e-9: - print "Pulses from DAC A and B do not seem to be on top of each other!" - print "Sideband calibrations based on this pulse calibration will" - print "most likely mess up you sequences!" - print - print "Check the following pulse calibration file in the data vault:" - print datasetDir(dataset) - print "If the pulses are offset by more than 0.5 ns," - print "bring up the board and try the pulse calibration again." - print 5 - return (datasetNumber(dataset)) - - -def calibrateDCPulse(cxn,boardname,channel): - - reg = cxn.registry - reg.cd(['',keys.SESSIONNAME,boardname]) - - fpga = cxn[FPGA_SERVER_NAME] - fpga.select_device(boardname) - - dac_baseline = -0x2000 - dac_pulse = 0x1FFF - dac_neutral = 0x0000 - if channel: - pulse = makeSample(dac_neutral, dac_pulse) - baseline = makeSample(dac_neutral, dac_baseline) - else: - pulse = makeSample(dac_pulse, dac_neutral) - baseline = makeSample(dac_baseline, dac_neutral) - #Set up the scope - scope = cxn.sampling_scope - scopeID = reg.get(keys.SCOPEID) - print "scopeID:", scopeID - p = scope.packet().\ - select_device(scopeID).\ - reset().\ - channel(reg.get(keys.SSCOPECHANNEL, True, 2)).\ - trace(1).\ - record_length(5120).\ - average(128).\ - sensitivity(reg.get(keys.SSCOPESENSITIVITYDC, True, 200*labrad.units.mV)).\ - offset(Value(0,'mV')).\ - time_step(Value(5,'ns')).\ - trigger_level(Value(0.18,'V')).\ - trigger_positive() - p.send() - - offsettime = reg.get(keys.TIMEOFFSET) - - - - print 'Measuring step response...' - trace = measureImpulseResponse(fpga, scope, baseline, pulse, - dacoffsettime=offsettime['ns'], pulselength=100) - trace = trace[trace.unit] # strip units - # set the output to zero so that the fridge does not warm up when the - # cable is plugged back in - fpga.dac_run_sram([makeSample(dac_neutral, dac_neutral)]*20, False) - ds = cxn.data_vault - ds.cd(['', keys.SESSIONNAME, boardname],True) - dataset = ds.new(keys.CHANNELNAMES[channel], [('Time','ns')], - [('Voltage','','V')]) - ds.add_parameter(keys.TIMEOFFSET, offsettime) - ds.add(np.transpose([1e9*(trace[0]+trace[1]*np.arange(np.alen(trace)-2)), - trace[2:]])) - return (datasetNumber(dataset)) - - -def calibrateDCPulse_infiniium(cxn, boardname, channel, conf_10_MHz): - - reg = cxn.registry - reg.cd(['', keys.SESSIONNAME,boardname]) - - fpga = cxn[FPGA_SERVER_NAME] - fpga.select_device(boardname) - - dac_baseline = 0x000 - dac_pulse = 0x1000 - dac_neutral = 0x0000 - if channel: - pulse = makeSample(dac_neutral,dac_pulse) - baseline = makeSample(dac_neutral, dac_baseline) - else: - pulse = makeSample(dac_pulse, dac_neutral) - baseline = makeSample(dac_baseline, dac_neutral) - scope = cxn.agilent_infiniium_oscilloscope() - scope.select_device() - - scope.reset() - print 'scope reset' - - fpga.dac_run_sram([makeSample(dac_neutral, dac_neutral)]*20, False) # zero dac output - time.sleep(2) - - numberofaverages=4096 - - p = scope.packet().\ - gpib_write('TIM:REFC '+str(conf_10_MHz)).\ - channelonoff(SCOPECHANNEL_infiniium, 'ON').\ - channelonoff(TRIGGERCHANNEL_infiniium, 'ON').\ - scale(SCOPECHANNEL_infiniium, 0.1).\ - scale(TRIGGERCHANNEL_infiniium, 0.5).\ - position(SCOPECHANNEL_infiniium, 0.02).\ - position(TRIGGERCHANNEL_infiniium,0.0).\ - horiz_scale(500.0e-9).\ - horiz_position(0.0).\ - trigger_sweep('TRIG').\ - trigger_mode('EDGE').\ - trigger_edge_slope('POS').\ - trigger_at(TRIGGERCHANNEL_infiniium, 1.0).\ - averagemode(1).\ - numavg(numberofaverages) - - p.send() - print 'scope packet sent' - time.sleep(1) - ref_10_MHz = scope.gpib_query(':TIM:REFC?') - - if ref_10_MHz == '1': - ref_str_rep = 'EXT' - else: - ref_str_rep = 'INT' - print '10 MHz ref: ' + ref_str_rep - - offsettime = reg.get(keys.TIMEOFFSET) - - print 'Measuring step response...' - t, y = measureImpulseResponse_infiniium(fpga, scope, baseline, pulse, - dacoffsettime=offsettime['ns'], pulselength=3000, looplength=6000) - - # set the output to zero so that the fridge does not warm up when the - # cable is plugged back in - fpga.dac_run_sram([makeSample(dac_neutral, dac_neutral)]*20, False) # Zero output - ds = cxn.data_vault - ds.cd(['', keys.SESSIONNAME, boardname],True) - dataset = ds.new(keys.CHANNELNAMES[channel], [('Time', 'ns')], - [('Voltage', '', 'V')]) - ds.add_parameter(keys.TIMEOFFSET, offsettime) - ds.add_parameter('dac baseline', dac_baseline) - ds.add_parameter('dac pulse', dac_pulse) - ds.add_parameter('10 MHz ref', ref_str_rep) - ds.add_parameter('scope', 'Agilent13GHz') - ds.add_parameter('stats', numberofaverages) - ds.add(np.vstack((t['ns'], y['V'])).transpose()) - return datasetNumber(dataset) - - -#################################################################### -# Sideband calibration # -#################################################################### - - -def measureOppositeSideband(spec, fpga, corrector, - carrierfreq, sidebandfreq, compensation): - """Put out a signal at carrierfreq+sidebandfreq and return the power at - carrierfreq-sidebandfreq""" - - arg = -2.0j*np.pi*sidebandfreq*np.arange(PERIOD) - signal = corrector.DACify(carrierfreq, - 0.5 * np.exp(arg) + 0.5 * compensation * np.exp(-arg), - loop=True, iqcor=False, rescale=True) - for i in range(4): - signal[i] |= trigger - fpga.dac_run_sram(signal, True) - return ((signalPower(spec)) / corrector.last_rescale_factor) - - -def sideband(anr, spect, fpga, corrector, carrierfreq, sidebandfreq): - """When the IQ mixer is used for sideband mixing, imperfections in the - IQ mixer and the DACs give rise to a signal not only at - carrierfreq+sidebandfreq but also at carrierfreq-sidebandfreq. - This routine determines amplitude and phase of the sideband signal - for carrierfreq-sidebandfreq that cancels the undesired sideband at - carrierfreq-sidebandfreq.""" - reserveBuffer = corrector.dynamicReserve - corrector.dynamicReserve = 4.0 - - if abs(sidebandfreq) < 3e-5: - return (0.0j) - anr.frequency(Value(carrierfreq,'GHz')) - comp = 0.0j - precision = 1.0 - spectFreq(spect,carrierfreq-sidebandfreq) - while precision > 2.0**-14: - fpga.dac_run_sram(np.array([0] * PERIOD, dtype='. - - -import numpy as np - -# CHANGELOG -# -# 2012 April 12 - Jim Wenner -# -# Changed logic string in setSettling from np.any(self.decayRates!=rates) -# to not (np.array_equal(self.decayRates,rates). With any(!=), if -# self.decayRates is empty, the output will be an empty array and not -# False, so the section to change self.decayRates is not entered. - -# CHANGELOG -# -# 2013 April/May - R. Barends -# -# Changes in Z board (Single) DAC calibration: -# -# Rewrote LoadCal to be more intelligible -# -# Introduced cutoff frequency. This is necessary because high frequency signals in the correction window have a bad S/N ratio. -# In addition, the way the impulse response is calculated suppresses 1 GHz noise, but amplifies 500 MHz. Default value is to cut off at 450 MHz. Keep it above 350 MHz. -# Also, these high frequencies tend to give rise to long oscillations in the deconvolved timetrace, messing up dualblock. -# -# Truncation of large correction factors in the fourier domain: the correction window can have very large amplitudes (and low S/N), -# therefore the time domain signal can have large oscillations which will be truncated digitally, leading to deterioration of the waveform. -# Now, a maximum value is enforced in the fourier domain. The value is truncated but the phase is kept. -# This way we still have a partial correction, within the limits of the boards. Doing it this way also ensures that the waveforms are scalable. -# -# Cubic interpolation for the fourier transform. It visually reduced the ringing on the scope. Cubic interpolation algorithm is as fast as linear interpolation. -# -# The deconv now does NOT shift the timetrace. This arose from the rise being at t=10-20 ns, instead of t=0. -# However, this leads to the timetrace running out of its intendend memory block. In addition, the phase varies more slowly, easing interpolation. -# -# Fixed logical bug in setSettling -# -# Enforces borderValues at first and last 4 points. The deconvolved signal can be nonzero at the start and end of a sequence. -# This nonzero value persists, even when running the board with an empty envelope. Hence, the Z bias is in a small-valued but arbitrary state after each run, -# possibly even oscillating, if the 4 last values are not identical. To remove this, the last 4 (FOUR) values are set to 0.0. -# For dualblock, there is a function 'set_border_values' to set the bordervalues. Be sure to set it for both the first and last block, in fpgaseqtransmon. -# -# -# Changes to IQ board DAC calibration: -# -# Enforces zeros at first and last 4 points. The deconvolved signal can be nonzero at the start and end of a sequence. -# This nonzero value persists, even when running the board with an empty envelope. To remove this, the last 4 (FOUR) values must be set. -# -# Cubic interpolation for zero value -# -# Removed a tab - -def cosinefilter(n, width=0.4): - """cosinefilter(n,width) cosine lowpass filter - n samples from 0 to 1 GHz - 1 from 0 GHz to width GHz - rolls of from width GHz to 0.5 GHz like a quater cosine wave""" - nr = n/2 + 1 - result = np.ones(nr,dtype=float) - start = int(np.ceil(width*n)) - width = (0.5-width)*n - if start < nr: - result[start:] = 0.5+0.5*np.cos( - np.linspace(np.pi * (start-0.5*n+width) / width, - np.pi + np.pi / width*(nr-0.5*n), - nr-start, endpoint=False)) - return result - - -def gaussfilter(n, width=0.13): - """lowpassfilter(n,width) gaussian lowpass filter. - n samples from 0 to 1 GHz - -3dB frequency at width GHz - """ - nr = n/2 + 1 - x = 1.0 / width * np.sqrt(np.log(2.0)/2.0) - gauss = np.exp(-np.linspace(0, x*nr/n, nr, endpoint=False)**2) - x = np.exp(-(0.5*x)**2) - gauss -= x - gauss /= (1.0 - x) - return gauss - - -def flatfilter(n, width=0): - nr = n/2 + 1 - return 1.0 - #return np.ones(nr) - - -savedfftlens = np.zeros(8193,dtype=int) - - -def fastfftlen(n): - """ - Computes the smallest multiple of 2 3 and 5 larger or equal n. - FFT is fastest for sizes that factorize in small numbers. - Sizes up to 8192 are only calculated once and later looked up. - """ - def _fastfftlen(n): - logn = np.log(n) - n5 = 5L ** np.arange(long(logn/np.log(5.) + 2. + 1.e-6)) - n3 = 3L ** np.arange(long(logn/np.log(3.) + 2. + 1.e-6)) - n35 = np.outer(n3, n5).flat - n35 = np.compress(n35<2*n, n35) - n235 = ((-np.log(n35)+logn)/np.log(2.) + 0.999999).astype(int) - n235 *= (n235>0) - n235 = 2**n235 * n35 - return np.min(n235) - - if n < np.alen(savedfftlens): - nfft = savedfftlens[n] - if nfft < n: - nfft = _fastfftlen(n) - savedfftlens[n] = nfft - return nfft - else: - return _fastfftlen(n) - -def moving_average(x,m): - """Moving average on x, with length m. Expects a numpy array for x. Elements are given by - y[i] = Sum_{k=0..m-1} y[l] / m - with l=i-fix(m/2)+k between 0 and length(x)-1. Try to keep m odd. RB.""" - n=np.alen(x) - before=-np.fix(int(m)/2.0) - y=[] - for i in np.arange(len(x)): - a=0.0 - for tel in np.arange(int(m)): - idx=i+before+tel - if idx<0: - idx=0 - elif idx>=n: - idx=n-1 - a += x[idx]/np.float(m) - y.append(a) - return np.array(y) - - -def derivative(x,y): - """Taking derivative, uses both adjacent points for estimate of derivative. - Returns array with the same number of points (different than np.diff). RB.""" - n=np.alen(x) - deriv=np.array(np.linspace(0.0,0.0,n),dtype=complex) - for k in np.arange(n): - if k==0: - deriv[k]=1.0*(y[k+1]-y[k])/(x[k+1]-x[k]) - elif k==(n-1): - deriv[k]=1.0*(y[k]-y[k-1])/(x[k]-x[k-1]) - else: - deriv[k]=1.0*(y[k+1]-y[k-1])/(x[k+1]-x[k-1]) - return deriv - - -def interpol_cubic(h,x2,fill_value=None): - """Fast cubic interpolator (slightly faster than linear version of scipy interp1d; - much faster than cubic version of scipy interp1d). - Returns the values in in the same way interpol. Can deal with complex input. - Uses linear interpolation at the edges, and returns the values at the edges outside of the range. RB.""" - xlen=np.alen(h) - if type(h) is not np.ndarray: - #we need a numpy array - h=1.0*np.array(h) - def func(xdet): - if type(xdet) is not list and type(xdet) is not np.ndarray: - xdet=np.array([xdet]) - yout=np.zeros(np.alen(xdet)).astype(h.dtype) #predefine - x2=xdet #x2 = (xdet-xstart) #map xdet onto h index: x -> (x-xstart)/dx = 0... length - - #indices outside of the range - xdet_idx = x2<0 #maps which index in x2 it is - if xdet_idx.any(): - x2_idx = x2[ xdet_idx ] #maps x2 to x index - h_idx = np.array(x2_idx).astype(int) #maps which h,x to take - if fill_value is None: - yout[xdet_idx]=h[0] - else: - yout[xdet_idx]=fill_value - xdet_idx = x2>(xlen-1) #maps which index in x2 it is - if xdet_idx.any(): - x2_idx = x2[ xdet_idx ] #maps x2 to x index - h_idx = np.array(x2_idx).astype(int) #maps which h,x to take - if fill_value is None: - yout[xdet_idx]=h[xlen-1] - else: - yout[xdet_idx]=fill_value - - #indices on the rim: linear interpolation - xdet_idx = np.logical_and(x2>=0,x2<1) #maps which index in x2 it is - if xdet_idx.any(): - x2_idx = x2[ xdet_idx ] #maps x2 to x index - h_idx = np.array(x2_idx).astype(int) #maps which h,x to take - yout[xdet_idx]=(h[1]-h[0])*x2_idx + h[0] - xdet_idx = np.logical_and(x2>=(xlen-2),x2<=(xlen-1)) #maps which index in x2 it is - if xdet_idx.any(): - x2_idx = x2[ xdet_idx ] #maps x2 to x index - h_idx = np.array(x2_idx).astype(int) #maps which h,x to take - yout[xdet_idx]=(h[xlen-1]-h[xlen-2])*(x2_idx-h_idx[0]) + h[xlen-2] - - #indices inside the range: cubic interpolation - xdet_idx = np.logical_and(x2>=1,x2<(xlen-2)) #maps which index in x2 it is - if xdet_idx.any(): - x2_idx = x2[ xdet_idx ] #maps x2 to x index - h_idx = np.array(x2_idx).astype(int) #maps which h,x to take - hp2=h[h_idx+2] - hp1=h[h_idx+1] - hp0=h[h_idx] - hm1=h[h_idx-1] - d=hp0 - c=(hp1-hm1)/2. - b=(-hp2+4*hp1-5*hp0+2*hm1)/2. - a=(hp2-3*hp1+3*hp0-hm1)/2. - xi=(x2_idx - h_idx) - yout[xdet_idx]=((a * xi + b) * xi + c) * xi + d - - return np.array(yout) - return func(x2) - - -def interpol(signal, x, extrapolate=False): - """ - Linear interpolation of array signal at floating point indices x - (x can be an array or a scalar). If x is beyond range either the first or - last element is returned. If extrapolate=True, the linear extrapolation of - the first/last two points is returned instead. - """ - n = np.alen(signal) - if n == 1: - return signal[0] - i = np.floor(x).astype(int) - i = np.clip(i, 0, n-2) #assumes x is between 0 and n-1 - p = x - i - if not extrapolate: - p = np.clip(p,0.0,1.0) - return signal[i] * (1.0 - p) + signal[i+1] * p - - -def findRelevant(starts, ends): - n = np.size(starts) - relevant = np.resize(True, n) - for i in np.arange(n-1): - relevant[i] = not np.any((starts[i+1:] <= starts[i]) & - (ends[i+1:] >= ends[i])) - return np.argwhere(relevant)[:,0] - - -################################################## -# # -# Correction class for a DAC board with IQ mixer # -# # -################################################## - - -class IQcorrection: - - def __init__(self, board, lowpass=cosinefilter, bandwidth=0.4, - exceedCalLimits=0.001): - - """ - Returns a DACcorrection object for the given DAC board. - """ - - self.board = board - #Set dynamic reserve - self.dynamicReserve = 2.0 - - #Use this to see how much the last DACify call rescaled the output - self.last_rescale_factor = 1.0 - #Use this to see the smallest rescale factor DACify had to use - self.min_rescale_factor = 1.0 - - self.flipChannels = False - - # Set the Lowpass, i.e. the transfer function we want after correction - # Unless otherwise specified, the filter will be flat and then roll off - # between (1-bandwidth)*Nyquist and Nyquist - - if lowpass == False: - lowpass = flatfilter - - self.lowpass = lowpass - self.bandwidth = bandwidth - self.exceedCalLimits = exceedCalLimits - - # empty pulse calibration - self.correctionI = None - self.correctionQ = None - self.pulseCalFile = None - - # empty zero calibration - self.zeroTableStart = np.zeros(0,dtype=float) - self.zeroTableEnd = np.zeros(0,dtype=float) - self.zeroTableStep = np.zeros(0,dtype=float) - self.zeroCalFiles = np.zeros(0,dtype=int) - self.zeroTableI = [] - self.zeroTableQ = [] - - # empty sideband calibration - self.sidebandCarrierStart = np.zeros(0,dtype=float) - self.sidebandCarrierEnd = np.zeros(0,dtype=float) - self.sidebandCarrierStep = np.zeros(0,dtype=float) - self.sidebandStep = np.zeros(0,dtype=float) - self.sidebandCount = np.zeros(0) - self.sidebandCompensation = [] - self.sidebandCalFiles = np.zeros(0,dtype=int) - - self.selectCalAll() - - self.recalibrationRoutine = None - - - def loadZeroCal(self, zeroData, calfile): - l = np.shape(zeroData)[0] - self.zeroTableI.append(zeroData[:,(1 + self.flipChannels)]) - self.zeroTableQ.append(zeroData[:,(1 + (not self.flipChannels))]) - self.zeroTableStart = np.append(self.zeroTableStart, zeroData[0,0]) - self.zeroTableEnd = np.append(self.zeroTableEnd, zeroData[-1,0]) - self.zeroCalFiles = np.append(self.zeroCalFiles, calfile) - if l > 1: - self.zeroTableStep = np.append(self.zeroTableStep, - zeroData[1,0]-zeroData[0,0]) - print ' carrier frequencies: %g GHz to %g GHz in steps of %g MHz' % \ - (zeroData[0,0], zeroData[-1,0], - self.zeroTableStep[-1]*1000.0) - - else: - self.zeroTableStep = np.append(self.zeroTableStep, 1.0) - print ' carrier frequency: %g GHz' % zeroData[0,0] - - - def eliminateZeroCals(self): - """ - Eliminate zero calibrations that have become obsolete. - Returns the zero calibration files that are still used. - You should not need to call this function. It is used internally - during a recalibration. - """ - keep = findRelevant(self.zeroTableStart,self.zeroTableEnd) - self.zeroTableI = [self.zeroTableI[i] for i in keep] - self.zeroTableQ = [self.zeroTableQ[i] for i in keep] - self.zeroTableStart = self.zeroTableStart[keep] - self.zeroTableEnd = self.zeroTableEnd[keep] - self.zeroTableStep = self.zeroTableStep[keep] - self.zeroCalFiles = self.zeroCalFiles[keep] - return self.zeroCalFiles - - - def loadSidebandCal(self, sidebandData, sidebandStep, calfile): - """ - Load IQ sideband mixing calibration - """ - self.sidebandStep = np.append(self.sidebandStep, sidebandStep) - - l,sidebandCount = np.shape(sidebandData) - sidebandCount = (sidebandCount-1)/2 - - self.sidebandCarrierStart = np.append(self.sidebandCarrierStart, - sidebandData[0,0]) - self.sidebandCarrierEnd = np.append(self.sidebandCarrierEnd, - sidebandData[-1,0]) - if l>1: - self.sidebandCarrierStep = np.append(self.sidebandCarrierStep, - sidebandData[1,0] - sidebandData[0,0]) - print ' carrier frequencies: %g GHz to %g GHz in steps of %g MHz' % \ - (sidebandData[0,0], - sidebandData[-1,0], - self.sidebandCarrierStep[-1]*1000.0) - else: - self.sidebandCarrierStep = np.append(self.sidebandCarrierStep, - 1.0) - print ' carrier frequency: %g GHz' % sidebandData[0,0] - - sidebandData = np.reshape(sidebandData[:,1:],(l,sidebandCount, 2)) - self.sidebandCompensation.append( - sidebandData[:,:,0] + 1.0j * sidebandData[:,:,1]) - self.sidebandCalFiles = np.append(self.sidebandCalFiles, calfile) - print ' sideband frequencies: %g MHz to %g Mhz in steps of %g MHz' % \ - (-500.0*(sidebandCount-1)*sidebandStep, - 500.0*(sidebandCount-1)*sidebandStep, - sidebandStep*1000) - - - def eliminateSidebandCals(self): - """ - Eliminate sideband calibrations that have become obsolete. - Returns the sideband calibration files that are still used. - You should not need to call this function. It is used internally - during a recalibration. - """ - keep = findRelevant(self.sidebandCarrierStart,self.sidebandCarrierEnd) - self.sidebandCompensation = [self.sidebandCompensation[i] for i in keep] - self.sidebandStep = self.sidebandStep[keep] - self.sidebandCarrierStart = self.sidebandCarrierStart[keep] - self.sidebandCarrierEnd = self.sidebandCarrierEnd[keep] - self.sidebandCarrierStep = self.sidebandCarrierStep[keep] - self.sidebandCalFiles = self.sidebandCalFiles[keep] - return self.sidebandCalFiles - - - def loadPulseCal(self, dataPoints, carrierfreq, calfile, - flipChannels = False): - """ - Demodulates the IQ mixer output with the carrier frequency. - The result is inverted and multiplied with a lowpass filter, that rolls - off between 0.5-cufoffwidth GHz and 0.5 GHz. - It is stored in self.correctionI and self.correctionQ. - """ - #read pulse calibration from data server - self.flipChannels = flipChannels - dataPoints = np.asarray(dataPoints) - i = dataPoints[:,1 + self.flipChannels] - q = dataPoints[:,1 + (not self.flipChannels)] - # subtract DC offsets - i -= np.average(i) - q -= np.average(q) - length = len(i) - samplingfreq = int(np.round(1.0/(dataPoints[1,0]-dataPoints[0,0]))) - dataPoints = None - - #length for fft, long because we want good frequency resolution - finalLength = 10240 - n = finalLength*samplingfreq - print ' sampling frequency: %d GHz' % samplingfreq - - #convert carrier frequency to index - carrierfreqIndex = carrierfreq*n/samplingfreq - - #if the carrier frequency doesn't fall on a frequency sampling point - #we lose some precision - if np.floor(carrierfreqIndex) < np.ceil(carrierfreqIndex): - print """Warning: carrier frequency of calibration is not a multiple of %g MHz, accuracy may suffer.""" % 1000.0*samplingfreq/n - carrierfreqIndex = int(np.round(carrierfreqIndex)) - - #go to frequency space - i = np.fft.rfft(i, n=n) - q = np.fft.rfft(q, n=n) - - #demodulate - low = i[carrierfreqIndex:carrierfreqIndex-finalLength/2-1:-1] - high = i[carrierfreqIndex:carrierfreqIndex+finalLength/2+1:1] - #calcualte the phase of the carrier - phase = np.sqrt(np.sum(low*high)) - phase /= abs(phase) - if (phase.conjugate()*low[0]).real < 0: - phase *= -1 - - self.correctionI = 1.0 / \ - (0.5 / abs(low[0]) * (np.conjugate(low/phase) + high/phase)) - - low = q[carrierfreqIndex:carrierfreqIndex-finalLength/2-1:-1] - high = q[carrierfreqIndex:carrierfreqIndex+finalLength/2+1:1] - #calculate the phase of the carrier - phase = np.sqrt(np.sum(low*high)) - phase /= abs(phase) - if (phase.conjugate()*low[0]).real < 0: - phase *= -1 - self.correctionQ = 1.0 / \ - (0.5 / abs(low[0]) * (np.conjugate(low/phase) + high/phase)) - #Make sure the correction does not get too large - #If correction goes above 3 * dynamicReserve, - #scale to 3 * dynamicReserve but preserve phase - self.correctionI /= \ - np.clip(abs(self.correctionI) / 3. / self.dynamicReserve, - 1.0, np.Inf) - self.correctionQ /= \ - np.clip(abs(self.correctionQ) / 3. /self.dynamicReserve, - 1.0, np.Inf) - self.pulseCalFile = calfile - - - def selectCalAll(self): - """ - For each frequency use the lastest calibration available. This - is the default behaviour. - """ - self.zeroCalIndex = None - self.sidebandCalIndex = None - print 'For each correction the best calfile will be chosen.' - - - def selectCalLatest(self): - """ - Only use the latest calibration and extrapolate it if the - carrier frequency lies outside the calibrated range - """ - - self.zeroCalIndex = -1 - self.sidebandCalIndex = -1 - print 'Zero calibration: selecting calset %d' % \ - self.zeroCalFiles[-1] - print 'Sideband calibration: selecting calset %d' % \ - self.sidebandCalFiles[-1] - - - def findCalset(self, rangeStart, rangeEnd, calStarts, calEnds, calType): - - badness = np.max([np.resize(self.exceedCalLimits, - np.shape(calStarts)), - calStarts-rangeStart, - rangeEnd-calEnds], axis=0) - i = np.size(badness) - np.argmin(badness[::-1]) - 1 - if badness[i] > self.exceedCalLimits: - print '\n closest calset: %g, only covers %g GHz to %g GHz' \ - % (i,calStarts[i], calEnds[i]) - return i - - - def selectCalByRange(self, start,end): - """ - Use only the latest calibration covering the given range. If - there is no such calibration use the one that is closest to - covering it. - """ - print 'Zero calibration:', - self.zeroCalIndex = self.findCalset(start, end, self.zeroTableStart, - self.zeroTableEnd, 'zero') - print ' selecting calset %d' % \ - self.zeroCalFiles[self.zeroCalIndex] - print 'Sideband calibration:', - self.sidebandCalIndex = self.findCalset(start, end, - self.sidebandCarrierStart, - self.sidebandCarrierEnd, - 'sideband') - print ' selecting calset %d' % \ - self.sidebandCalFiles[self.sidebandCalIndex] - - def DACzeros(self, carrierFreq): - """ - Returns the DAC values for which, at the given carrier - frequency, the IQmixer output power is smallest. - Uses cubic interpolation - """ - if self.zeroTableI == []: - return [0.0,0.0] - i = self.zeroCalIndex - if i is None: - i = self.findCalset(carrierFreq, carrierFreq, self.zeroTableStart, - self.zeroTableEnd, 'zero') - carrierFreqFreq=carrierFreq - carrierFreq = (carrierFreq - self.zeroTableStart[i]) / self.zeroTableStep[i] #now it becomes and index - #zeroI=interpol_cubic(self.zeroTableI[i], carrierFreq) - #zeroQ=interpol_cubic(self.zeroTableQ[i], carrierFreq) - #print 'board:',self.board,' freq:',carrierFreqFreq,' zeroI,Q:',zeroI,zeroQ - return [interpol_cubic(self.zeroTableI[i], carrierFreq), interpol_cubic(self.zeroTableQ[i], carrierFreq)] - #return [interpol(self.zeroTableI[i], carrierFreq), interpol(self.zeroTableQ[i], carrierFreq)] #old - - def _IQcompensation(self, carrierFreq, n): - """ - Returns the sideband correction at the given carrierFreq and for - sideband frequencies - (0, 1, 2, ..., n/2, n/2+1-n, ..., -1, 0) * (1.0 / n) GHz - """ - if self.sidebandCompensation == []: - return np.zeros(n+1, dtype = complex) - i = self.sidebandCalIndex - if i is None: - i = self.findCalset(carrierFreq, carrierFreq, - self.sidebandCarrierStart, - self.sidebandCarrierEnd, 'sideband') - carrierFreq = (carrierFreq - self.sidebandCarrierStart[i]) / \ - self.sidebandCarrierStep[i] - w = np.shape(self.sidebandCompensation[i])[1] - maxfreq = 0.5 * self.sidebandStep[i] * (w-1) - p = self.sidebandStep[i]/(1-2*maxfreq) - freqs = np.zeros(n+1,dtype=float) - freqs[1:n/2+1] = np.arange(1,n/2+1) - freqs[n/2+1:n] = np.arange(n/2+1-n,0) - freqs /= n - compensation = np.zeros(w+2,complex) - compensation[1:w+1] = interpol(self.sidebandCompensation[i],carrierFreq) - compensation[0] = (1 - p) * compensation[1] + p * compensation[w] - compensation[w+1] = (1 - p) * compensation[w] + p * compensation[1] - return interpol(compensation, - (freqs + maxfreq + self.sidebandStep[i]) / self.sidebandStep[i], - extrapolate=True) - - - def DACify(self, carrierFreq, i, q=None, loop=False, rescale=False, - zerocor=True, deconv=True, iqcor=True, zipSRAM=True, - zeroEnds=False): - """ - Computes a SRAM sequence from I and Q values in the range from - -1 to 1. If Q is omitted, the imaginary part of I sets the Q - value - - Perfroms the following corrections at the given carrier frequency - (in GHz): - - DAC zeros - - deconvolution with filter chain response - (For length-1 i and q, this correction cannot be performed) - - IQ mixer - - DACify only sets the lowest 28 bits of the SRAM samples. Add - trigger signals to the highest 4 bits via bitwise or when - needed. - - If you use deconvolution and unless you have a periodic signal - (i.e. the signal given to DACify is looped without any dead - time), you should have at least 5ns before and 20ns after your - pulse where the signal is 0 (or very small). Otherwise your - signal will be deformed because the correction for the DAC - pulse response will either be clipped or wrapped around and - appear at the beginning of your signal! - - Keyword arguments: - - loop=True: Does the the FFT on exactly the length of i and q. - You need this if you have a periodic signal that is - non-zero at the borders of the signal (like a continous - sinewave). Otherwise DACify could do the fft on a larger - array (padded with 0) in order to have a faster fft (fft - is fastest for numbers that factorize into small numbers) - - rescale=True: If the corrected signal exceeds the DAC range, - it is rescaled to fit. Useful to drive as hard as possible - without signal distortions (e.g. for spectroscopy). - Otherwise the signal is clipped. After a DACify call - DACcorrection.last_rescale_factor contains the rescale - factor actually used. DACcorrection.min_rescale_factor - contains the smallest rescale factor used so far. - - zerocor=False: Do not perform zero correction. - - deconv=False: Do not perform deconvolution. Sideband frequency - dependence of the IQ compensation will be ignored. - - iqcor=False: Do not perform IQ mixer correction. - - zipSRAM=False: returns (I,Q) tuples instead of packed SRAM data, - tuples are not clipped to fit the DAC range. - - Example: - cor = DACcorrection('DR Lab FPGA 0') - t = arange(-50.0,50.0) - # 5 ns FWHM Gaussian at 6 GHz carrier frequency, - # sideband mixed to 6.1 GHz - signal=2.0**(-t**2/2.5**2) * exp(-2.0j*pi*0.1*t) - signal = cor.DACify(6.0, signal) - #add trigger - signal[0] |= 0xF<<28 - fpga.loop_sram(signal) - """ - i = np.asarray(i) - if q == None: - i = i.astype(complex) - else: - i = i + 1.0j * q - n = np.alen(i) - if loop: - nfft = n - else: - nfft = fastfftlen(n) - if n > 1: - # treat offset properly even when n != nfft - background = 0.5*(i[0]+i[-1]) - i = np.fft.fft(i-background,n=nfft) - i[0] += background * nfft - return self.DACifyFT(carrierFreq, i, n=n, loop=loop, rescale=rescale, - zerocor=zerocor, deconv=deconv, iqcor=iqcor, zipSRAM=zipSRAM, - zeroEnds=zeroEnds) - - - def DACifyFT(self, carrierFreq, signal, t0=0, n=8192, loop=False, - rescale=False, zerocor=True, deconv=True, iqcor=True, - zipSRAM=True, zeroEnds=False): - """ - Works like DACify but takes the Fourier transform of the - signal as input instead of the signal itself. Because of that - DACifyFT is faster and more acurate than DACify and you do not - have to lowpass filter the signal before sampling it. - n gives the number of points (or the length in ns), - t0 the start time. Signal can either be a function which will - be evaluated between -0.5 and 0.5 GHz, or an array of length - nfft with complex samples at frequencies in GHz of - np.linspace(0.5, 1.5, nfft, endpoint=False) % 1 - 0.5 - If you want DACifyFT to be fast nfft should factorize in 2 3 and 5. - If n < nfft, the result is truncated to n samples. - For the rest of the arguments see DACify. - """ - if n == 0: - return np.zeros(0) - if callable(signal): - if loop: - nfft = n - else: - nfft = fastfftlen(n) - elif signal is None: - if zerocor: - i,q = self.DACzeros(carrierFreq) - signal = np.uint32((int(np.round(i)) & 0x3FFF) \ - << (14 * self.flipChannels) | \ - (int(np.round(q)) & 0x3FFF) \ - << (14 * (not self.flipChannels))) - else: - signal = np.uint32(0) - return np.resize(signal, n) - else: - signal = np.asarray(signal) - nfft = np.alen(signal) - if n > nfft: - n = nfft - nrfft = nfft/2+1 - f = np.linspace(0.5, 1.5, nfft, endpoint=False) % 1 - 0.5 - if callable(signal): - signal = np.asarray(signal(f)).astype(complex) - if t0 != 0: - signal *= np.exp(2.0j*np.pi*t0*f) - - if n > 1: - #apply convolution and iq correction - #FT the input - #add the first point at the end so that the elements of signal and - #signal[::-1] are the Fourier components at opposite frequencies - signal = np.hstack((signal, signal[0])) - - #correct for the non-orthoganality of the IQ channels - if iqcor: - signal += signal[::-1].conjugate() * \ - self._IQcompensation(carrierFreq, nfft) - - - #separate I (FT of a real signal) and Q (FT of an imaginary signal) - i = 0.5 * (signal[0:nrfft] + \ - signal[nfft:nfft-nrfft:-1].conjugate()) - q = -0.5j * (signal[0:nrfft] - \ - signal[nfft:nfft-nrfft:-1].conjugate()) - - #resample the FT of the response function at intervals 1 ns / nfft - if deconv and (self.correctionI != None): - l = np.alen(self.correctionI) - freqs = np.arange(0,nrfft) * 2.0 * (l - 1.0) / nfft - #correctionI = interpol(self.correctionI, freqs,extrapolate=True) - #correctionQ = interpol(self.correctionQ, freqs,extrapolate=True) - correctionI = interpol_cubic(self.correctionI, freqs, fill_value=0.0) - correctionQ = interpol_cubic(self.correctionQ, freqs, fill_value=0.0) - lp = self.lowpass(nfft, self.bandwidth) - i *= correctionI * lp - q *= correctionQ * lp - #do the actual deconvolution and transform back to time space - i = np.fft.irfft(i, n=nfft)[:n] - q = np.fft.irfft(q, n=nfft)[:n] - else: - #only apply iq correction for sideband frequency 0 - if iqcor: - signal += signal.conjugate() * \ - self._IQcompensation(carrierFreq,1)[0] - i = signal.real - q = signal.imag - - # rescale or clip data to fit the DAC range - fullscale = 0x1FFF / self.dynamicReserve - - if zerocor: - zeroI, zeroQ = self.DACzeros(carrierFreq) - else: - zeroI = zeroQ = 0.0 - - if rescale: - rescale = np.min([1.0, - ( 0x1FFF - zeroI) / fullscale / np.max(i), - (-0x2000 - zeroI) / fullscale / np.min(i), - ( 0x1FFF - zeroQ) / fullscale / np.max(q), - (-0x2000 - zeroQ) / fullscale / np.min(q)]) - if rescale < 1.0: - print 'Corrected signal scaled by %g to fit DAC range.' % \ - rescale - # keep track of rescaling in the object data - self.last_rescale_factor = rescale - if not isinstance(self.min_rescale_factor, float) \ - or rescale < self.min_rescale_factor: - self.min_rescale_factor = rescale - fullscale *= rescale - - # Due to deconvolution, the signal to put in the dacs can be nonzero at - # the end of a sequence even with a short pulse. This nonzero value - # exists even when running the board with an empty envelope. To remove - # it, the first and last 4 (FOUR) values must be set to zero. - if zeroEnds: - i[:4] = 0.0 - i[-4:] = 0.0 - q[:4] = 0.0 - q[-4:] = 0.0 - i = np.round(i * fullscale + zeroI).astype(np.int32) - q = np.round(q * fullscale + zeroQ).astype(np.int32) - - - - if not rescale: - clippedI = np.clip(i,-0x2000,0x1FFF) - clippedQ = np.clip(q,-0x2000,0x1FFF) - if np.any((clippedI != i) | (clippedQ != q)): - print 'Corrected IQ signal beyond DAC range, clipping' - i = clippedI - q = clippedQ - - if not zipSRAM: - return (i, q) - - return ((i & 0x3FFF) << (14 * self.flipChannels) | \ - (q & 0x3FFF) << (14 * (not self.flipChannels))).\ - astype(np.uint32) - - - def recalibrate(self, carrierMin, carrierMax=None, zeroCarrierStep=0.02,sidebandCarrierStep=0.05, sidebandMax=0.35, sidebandStep=0.05): - if carrierMax is None: - carrierMax = carrierMin - if self.recalibrationRoutine is None: - print 'No calibration routine hooked in.' - return self - return self.recalibrationRoutine(self.board, carrierMin, carrierMax, - zeroCarrierStep, sidebandCarrierStep, - sidebandMax, sidebandStep, self) - - - -############################################# -# # -# Correction class for a single DAC channel # -# # -############################################# - - - -class DACcorrection: - - - def __init__(self, board, channel, lowpass=gaussfilter, bandwidth=0.15): - - """ - Returns a DACcorrection object for the given DAC board. - keywords: - - lowpass: Sets the low pass filter function, - i.e. the transfer function we want after - correction. It expects func of form func(n, - bandwidth). n is the number of samples between 0 - frequency and the Nyquist frequency (half the sample - freq). bandwidth is all the other parameters the - function needs, they are passed to func just as - specified by the bandwidth keyword to - DACcorrection. The return value is the transmission - (between 0 and 1) at f=0, 1/N, ... (n-1)/N where N is - the Nyquist frequency. Default: gaussfilter - - bandwidth: bandwidth are arguments passed to the lowpass - filter function (see above) - - RB: This filter setting is controlled in the __init__.py, not here - - """ - - self.board = board - self.channel = channel - #Set dynamic reserve - self.dynamicReserve = 2.0 - - #Use this to see how much the last DACify call rescaled the output - self.last_rescale_factor = 1.0 - #Use this to see the smallest rescale factor DACify had to use - self.min_rescale_factor = 1.0 - - # Set the Lowpass, i.e. the transfer function we want after correction - if lowpass == False: - lowpass = flatfilter - self.lowpass = lowpass - self.bandwidth = bandwidth - print lowpass.__name__ , bandwidth - self.correction = [] - - self.zero = 0.0 - - self.clicsPerVolt = None - - self.decayRates = np.array([]) - self.decayAmplitudes = np.array([]) - self.reflectionRates = np.array([]) - self.reflectionAmplitudes = np.array([]) - self.precalc = np.array([]) - - - - def loadCal(self, dataPoints, zero=0.0 , clicsPerVolt=None, - lowpass=flatfilter, bandwidth=0.15, replace=False,maxfreqZ=0.45): - """ - Adds a response function to the list of internal - calibrations. dataPoints contains a step response. It is a n x - 2 array. dataPoints[:,0] contains the time in dacIntervals, - dataPoints[:,1] the amplitude (scale does not matter). - - If you add a SECONDARY CALIBRATION (i.e. a calibration that - has been obtained with a input signal already numerically - corrected) then you have to PROVIDE THE OPTIONAL 'lowpass' - and 'bandwidth' ARGUMENTS to tell the deconvolution - about the numerical lowpass filter you used to generate the - input signal for the calibration. If you omit these - parameters, DACify will also correct for the numerical lowpass - filter: If you use the same numerical lowpass filter to - generate the calibration and when you call DACify, they will - cancel and you get no lowpass filter; If you use a narrower - filter to generate the calibration than when you call DACify, - you will end up with a band pass, certainly not what you want. - - The optional 'zero' argument gives the DAC value, giving 0 output, - defaults to 0. - - maxfreqZ=0.45 is optimal (10% below Nyquist frequency) - """ - - #read pulse calibration from data server - samplingfreq = int(np.round(1.0/(dataPoints[1,0]-dataPoints[0,0]))) - samplingtime = dataPoints[:,0] - stepResponse = dataPoints[:,1] - - #standard way of generating the impulse response function: - #apply moving average filter - #stepResponse = moving_average(stepResponse,np.round(samplingfreq/2.5)) - #The moving average filter by Max is a bit too much, it leads to visible ringing for short (~20 ns) Z pulse - #get impulse response from step response - #Normally: h(t) = d/dt u(t), and: - #impulseResponse = derivative(samplingtime,stepResponse) - - #however, we have spurious 1 GHz, 2 GHz etc signals. We can suppress that by subtracting one point from the other which is 1 ns away. - #This also averages over a ns. - #If we don't do this, we end up with the amplitude after a Z pulse being dissimilar to the idle amplitude, because of aliasing. - #One issue is that this amplifies noise at 500 MHz, therefore we HAVE to cut it off later - distance=samplingfreq - impulseResponse = stepResponse[distance:]-stepResponse[:-distance] - samplingtime = samplingtime[0:np.alen(impulseResponse)-1] #+ distance/2.0/samplingfreq - - #get time shift from the impulse response - idx = impulseResponse.argmax() - tshift = samplingtime[idx] - - self.dataPoints = impulseResponse #THIS CONTAINS THE TIME DOMAIN SIGNAL - - #compute correction to apply in frequency domain - finalLength = 102400 #length for fft, long because we want good frequency resolution - n = finalLength*samplingfreq #this is done, so we can later take 0:finalLength/2+1, i.e. 0 to 500 MHz. The progam expects this frequency range, so DON'T change it. - - #go to frequency space, and calculate the frequency domain correction function ~1/H - impulseResponse_FD = np.fft.rfft(impulseResponse,n=n) #THIS CONTAINS THE FREQ DOMAIN SIGNAL - freqs=samplingfreq/2.0*np.arange(np.alen(impulseResponse_FD))/np.alen(impulseResponse_FD) - - #Normally the deconv corrects for the measured impulse response not appearing at t=0. - #This is bad, because A) the phase will depend strongly on frequency, which is harder to interpolate and - #B) the time domain signal will run out of its memory block. - #Here we apply a timeshift tshift, so the deconv won't shift the pulse in time. - impulseResponse_FD *= np.exp(2.0j*np.pi*freqs*tshift) - - #the correction window ~1/H(f) - correction = lowpass(finalLength,bandwidth) * abs(impulseResponse_FD[0]) / impulseResponse_FD[0:finalLength/2+1] #0:finalLength/2+1 = 0 to 500 MHz. The progam expects this frequency range in other functions, so DON'T change it. - - #apply a cut off frequency, necessary to kick out 500 MHz signal which messes up dualblock scans with nonzero Z. - #Also, the 1 GHz suppression applied above amplifies noise at 500 MHz. - if maxfreqZ: - freqs=0.5*np.arange(np.alen(correction))/np.alen(correction) - correction = correction * 1.0 * (abs(freqs)<=maxfreqZ) - - self.correction += [correction] - self.zero = zero - self.clicsPerVolt = clicsPerVolt - self.precalc = np.array([]) - - - def setSettling(self, rates, amplitudes): - """ - If a calibration can be characterized by time constants, i.e. - the step response function is - 0 for t < 0 - 1 + sum(amplitudes[i]*exp(-decayrates[i]*t)) for t >= 0, - then you don't need to load the response function explicitly - but can just give the timeconstants and amplitudes. - All previously used time constants will be replaced. - """ - rates = np.asarray(rates) - amplitudes = np.asarray(amplitudes) - if np.shape(rates) != np.shape(amplitudes): - raise Error('arguments to setSettling must have same shape.') - s = np.size(rates) - rates = np.reshape(np.asarray(rates),s) - amplitudes = np.reshape(np.asarray(amplitudes),s) - if (not np.array_equal(self.decayRates,rates)) or (not np.array_equal(self.decayAmplitudes,amplitudes)): - print 'emptying precalc (settling)' - self.decayRates = rates - self.decayAmplitudes = amplitudes - self.precalc = np.array([]) - - def setReflection(self, rates, amplitudes): - """ Correct for reflections in the line. - Impulse response of a line reflection is H = (1-amplitude) / (1-amplitude * exp( -2i*pi*f/rate) ) - All previously used time constants for the reflections will be replaced. - """ - rates = np.asarray(rates) - amplitudes = np.asarray(amplitudes) - if np.shape(rates) != np.shape(amplitudes): - raise Error('arguments to setReflection must have same shape.') - s = np.size(rates) - rates = np.reshape(np.asarray(rates),s) - amplitudes = np.reshape(np.asarray(amplitudes),s) - if (not np.array_equal(self.reflectionRates,rates)) or (not np.array_equal(self.reflectionAmplitudes,amplitudes)): - print 'emptying precalc (reflection)' - self.reflectionRates = rates - self.reflectionAmplitudes = amplitudes - self.precalc = np.array([]) - - - def setFilter(self, lowpass=None, bandwidth=0.15): - """ - Set the lowpass filter used for deconvolution. - - lowpass: Sets the low pass filter function, i.e. the transfer - function we want after correction. It expects func of form - func(n, bandwidth). n is the number of samples between 0 - frequency and the Nyquist frequency (half the sample - freq). bandwidth is all the other parameters the function - needs, they are passed to func just as specified by the - bandwidth keyword to DACcorrection. The return value is - the transmission (between 0 and 1) at f=0, 1/N, - ... (n-1)/N where N is the Nyquist frequency. Default: - gaussfilter - - bandwidth: bandwidth are arguments passed to the lowpass - filter function (see above) - """ - if lowpass is None: - lowpass=self.lowpass - - if (self.lowpass != lowpass) or (self.bandwidth != bandwidth): - self.lowpass = lowpass - self.bandwidth = bandwidth - self.precalc = np.array([]) - - - def DACify(self, signal, loop=False, rescale=False, fitRange=True, - zerocor=True, deconv=True, volts=True, dither=False, - averageEnds=False): - """ - Computes a SRAM sequence for one DAC channel. If volts is - True, the input is expected in volts. Otherwise inputs of -1 - and 1 correspond to the DAC range divided by - self.dynamicReserve (default 2). In the latter case, clipping - may occur beyond -1 and 1. - DACify corrects for - - zero offsets (if zerocor is True) - - gain (if volts is True) - - pulse shape (if deconv is True) - DACify returns a long array in the range -0x2000 to 0x1FFF - - If you use deconvolution and unless you have a periodic signal - (i.e. the signal given to DACify is looped without any dead - time), you should have at least 5ns before and 200ns (or - however long the longest pulse calibration trace is) after - your pulse where the signal is constant with the same value at - the beginning and the end of the sequence. Otherwise your - signal will be deformed because the correction for the DAC - pulse response will either be clipped or wrapped around and - appear at the beginning of your signal! - - Keyword arguments: - - loop=True: Does the the FFT on exactly the length of the input - signal. You need this if you have a periodic signal that - is non-zero at the borders of the signal (like a continous - sinewave). Otherwise DACify pads the input with the - average of the first and the last datapoint to optain a - signal length for which fft is fast (fft is fastest for - numbers that factorize into small numbers and extremly - slow for large prime numbers) - - rescale=True: If the corrected signal exceeds the DAC range, - it is rescale to fit. Usefull to drive as hard as possible - without signal distorsions (e.g. for spectroscopy). - Otherwise the signal is clipped. After a DACify call - DACcorrection.last_rescale_factor contains the rescale factor - actually used. DACcorrection.min_rescale_factor contains the - smallest rescale factor used so far. - - fitRange=False: Do not clip data to fit into 14 bits. Only - effective without rescaling. - - zerocor=False: Do not perform zero correction. - - deconv=False: Do not perform deconvolution. - - volts=False: Do not correct the gain. A input signal of - amplitude 1 will then result in an output signal with - amplitude DACrange/dynamicReserve - """ - - signal = np.asarray(signal) - - if np.alen(signal) == 0: - return np.zeros(0) - - n = np.alen(signal) - - if loop: - nfft = n - else: - nfft = fastfftlen(n) - - nrfft = nfft/2+1 - background = 0.5*(signal[0] + signal[-1]) - signal_FD = np.fft.rfft(signal-background, n=nfft) #FT the input - signal = self.DACifyFT(signal_FD, t0=0, n=n, nfft=nfft, offset=background, - loop=loop, - rescale=rescale, fitRange=fitRange, deconv=deconv, - zerocor=zerocor, volts=volts, dither=dither, - averageEnds=averageEnds) - return signal - - - def DACifyFT(self, signal, t0=0, n=8192, offset=0, nfft=None, loop=False, - rescale=False, fitRange=True, deconv=True, zerocor=True, - volts=True, maxvalueZ=5.0, dither=False, averageEnds=False): - """ - Works like DACify but takes the Fourier transform of the - signal as input instead of the signal. n gives the number of - points (or the length in ns), t0 the start time. Signal can - either be an array of length n/2 + 1 giving the frequency - components from 0 to 500 MHz. or a function which will be - evaluated between 0 and 0.5 (GHz). For the rest of the - arguments see DACify - """ - - # TODO: Remove this hack that strips units - decayRates = np.array([x['GHz'] for x in self.decayRates]) - decayAmplitudes = self.decayAmplitudes - - reflectionRates = np.array([x['GHz'] for x in self.reflectionRates]) - reflectionAmplitudes = self.reflectionAmplitudes - - #read DAC zeros - if zerocor: - zero = self.zero - else: - zero = 0 - if volts and self.clicsPerVolt: - fullscale = 0x1FFF / self.clicsPerVolt - else: - fullscale = 0x1FFF / self.dynamicReserve - - - #evaluate the Fourier transform 'signal' - if callable(signal): - if loop: - nfft = n - elif nfft is None: - nfft = fastfftlen(n) - nrfft = nfft/2+1 - signal = np.asarray(signal(np.linspace(0.0, float(nrfft)/nfft, - nrfft, endpoint=False))).astype(complex) - elif signal is None: - signal = np.int32(np.round(fullscale*offset+zero)) - if fitRange: - signal = np.clip(signal, -0x2000,0x1FFF) - signal = np.uint32(signal & 0x3FFF) - return np.resize(signal, n) - else: - signal = np.asarray(signal) - nrfft = len(signal) - if nfft is None or nfft/2 + 1 != nrfft: - nfft = 2*(nrfft-1) - - - if t0 != 0: - signal *= np.exp(np.linspace(0.0, - 2.0j * np.pi * t0 * nrfft / nfft, nrfft, endpoint=False)) - signal[0] += nfft*offset - #do the actual deconvolution and transform back to time space - if deconv: - # check if the precalculated correction matches the - # length of the data, if not we have to recalculate - if np.alen(self.precalc) != nrfft: - # lowpass filter - precalc = self.lowpass(nfft, self.bandwidth).astype(complex) - - freqs = np.linspace(0, nrfft * 1.0 / nfft, - nrfft, endpoint=False) - i_two_pi_freqs = 2j*np.pi*freqs - - # pulse correction - for correction in self.correction: - l = np.alen(correction) - precalc *= interpol_cubic(correction, freqs*2.0*(l-1)) #cubic, as fast as linear interpol - - # Decay times: - # add to qubit registry the following keys: - # settlingAmplitudes=[-0.05] #relative amplitude - # settlingRates = [0.01 GHz] #rate is in GHz, (1/ns) - if np.alen(decayRates): - precalc /= (1.0 + np.sum(decayAmplitudes[:, None] * i_two_pi_freqs[None, :] / (i_two_pi_freqs[None, :] + decayRates[:, None]), axis=0)) - - # Reflections: - # add to qubit registry the following keys: - # reflectionAmplitudes=[0.05] #relative amplitude - # reflectionRates = [0.01 GHz] #rate is in GHz, (1/ns) - # - # Reflections are dealt with by modelling a wire with round-trip time 1/rate, - # and reflection coefficient amplitude. - # It's the simplest model which can describe the effect of reflections in wiring - # in for example the wiring between the DAC output and fridge ports. Think about echo, - # reflections give rise to an endless sum of copies of the original signal with decreasing amplitude: - # f(t) -> (1-amplitude) Sum_k=0^\infty (amplitude^k f(t-k 1/rate) ). - # - # Suppose X is an ideal pulse, H the impulse response of a piece of cable (with reflection, settling etc). - # To get X at the end of the cable you need to send Y = X/H. - # So if you have different impulse responses H1, H2, H3: Y = X / (H1 * H2 * H3) - if np.alen(reflectionRates): - for rate,amplitude in zip(reflectionRates,reflectionAmplitudes): - if abs(rate) > 0.0: - precalc /= (1.0 - amplitude) / (1.0-amplitude*np.exp(-i_two_pi_freqs/rate)) - - - # The correction window can have very large amplitudes, - # therefore the time domain signal can have large oscillations which will be truncated digitally, - # leading to deterioration of the waveform. The large amplitudes in the correction window have low S/N ratios. - # Here, we apply a maximum value, i.e. truncate the value, but keep the phase. - # This way we still have a partial correction, within the limits of the boards. - # Doing it this way also helps a lot with the waveforms being scalable. - if maxvalueZ: - precalc = precalc * (1.0 * (abs(precalc)<=maxvalueZ)) + np.exp(1j*np.angle(precalc))*maxvalueZ * 1.0 * (abs(precalc) > maxvalueZ) - - self.precalc = precalc - signal *= self.precalc - else: - signal *= self.lowpass(nfft, self.bandwidth) - - # transform to real space - signal = np.fft.irfft(signal, n=nfft) - signal = signal[0:n] - - # Due to deconvolution, the signal to put in the dacs can be nonzero at - # the end of a sequence with even a short pulse. This nonzero value - # exists even when running the board with an empty envelope. To remove - # this, the first and last 4 values must be set. - if averageEnds: - signal[0:4] = np.mean(signal[0:4]) - signal[-4:] = np.mean(signal[-4:]) - - if rescale: - rescale = np.min([1.0, - ( 0x1FFF - zero) / fullscale / np.max(signal), - (-0x2000 - zero) / fullscale / np.min(signal)]) - if rescale < 1.0: - print 'Corrected signal scaled by %g to fit DAC range.' % \ - rescale - # keep track of rescaling in the object data - self.last_rescale_factor = rescale - if not isinstance(self.min_rescale_factor, float) \ - or rescale < self.min_rescale_factor: - self.min_rescale_factor = rescale - fullscale *= rescale - - if dither: - ditheringspan = 2. #a dithering span of 3 goes from -1.5.. 1.5, i.e. 0..3 = 0,1,2,3 = 4 numbers = 2 bits exactly - else: - ditheringspan = 0. - dithering = ditheringspan * (np.random.rand( len(signal ) )-0.5) - dithering[0:4] = 0.0 - dithering[-4:] = 0.0 - - signal = np.round(1.0*signal * fullscale + zero + dithering).astype(np.int32) - - if not rescale: - if (np.max(signal) > 0x1FFF) or (np.min(signal) < -0x2000): - print 'Corrected Z signal beyond DAC range, clipping' - print 'max: ', np.max(signal) ,' min: ', np.min(signal) - signal = np.clip(signal,-0x2000,0x1FFF) - if not fitRange: - return signal #this returns the signal between -8192 .. + 8191 - - return (signal & 0x3FFF).astype(np.uint32) #this returns the signal between 0 .. 16383. -1 = 16382. It will lead to errors visible in fpgatest diff --git a/ghzdac/keys.py b/ghzdac/keys.py deleted file mode 100644 index dae15e95..00000000 --- a/ghzdac/keys.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright (C) 2007, 2008 Max Hofheinz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -IQWIRING = 'IQ mixer wiring' -ANRITSUPOWER = 'Anritsu power level' -PULSECARRIERFREQ = 'Carrier frequency for pulse calibration' -ANRITSUID = 'Anritsu ID' -SPECTID = 'Spectrum analyzer ID' -SCOPEID = 'Sampling Scope ID' -SCOPEOFFSET = 'Sampling Scope DC offset' -SCOPESENSITIVITY = 'Sampling Scope sensitivity' -SSCOPECHANNEL = 'Sampling Scope channel' -SSCOPESENSITIVITYDC = 'Sampling Scope sensitivity DC' -SWITCHPOSITION = 'Microwave switch position' -SWITCHNAME = 'Microwave switch name' -SWITCHUSE = 'Microwave switch use' -TIMEOFFSET = 'DAC delay for pulse calibration' -SETUPTYPES = ['no IQ mixer', - 'DAC A -> mixer I, DAC B -> mixer Q', - 'DAC A -> mixer Q, DAC B -> mixer I'] -SESSIONNAME = 'GHzDAC Calibration' -ZERONAME = 'zero' -PULSENAME = 'pulse' -IQNAME = 'IQ' -CHANNELNAMES = ['DAC A','DAC B'] -VERTICALSCALE = 'vertical scale' -HORIZONTALSCALE = 'horizontal scale' -HORIZONTALPOSITION = 'horizontal position' -AVERAGES = 'numavg' - -POSITION = 'position' -TRIGGERLEVEL = 'trigger level' -PULSELENGTH = 'pulse length' -DACDCPULSE = 'dac dc pulse' -DACACPULSE = 'dac ac pulse' -HARMONIC = 'Carrier frequency harmonic' -SERVERSETTINGVALUES = [ - 'deconvIQ', - 'deconvZ', - 'bandwidthIQ', - 'bandwidthZ', - 'maxfreqZ', - 'maxvalueZ', - 'dither' -] diff --git a/ghzdac_cal/GHz_DAC_calibrate.py b/ghzdac_cal/GHz_DAC_calibrate.py deleted file mode 100644 index 8ead9b61..00000000 --- a/ghzdac_cal/GHz_DAC_calibrate.py +++ /dev/null @@ -1,275 +0,0 @@ -"""This module runs the board calibrations. - -Quick start:: - -.. code:: python - - import ghzdac_cal.GHz_DAC_calibrate as gc - gc.calibrate_iq(cxn, ['Vince DAC 11']) - # or if you want to do all the boards in one go: - gc.calibrate_iq(cxn, gc.find_microwave_dacs(cxn)) - -Generally, this module calls calibration code in the ``ghzdac`` package. -Therefore, this module requires that the ``servers`` repository is in the Python -path (the ``servers/`` directory should be put on the Python path, not its -parent directory. When this is set up properly, ``import ghzdac`` will succeed. - -The most user-facing functions in this module are at the bottom; recommended -reading order is bottom-to-top (i.e. start with ``calibrate_iq``, then -``find_microwave_dacs``, and so on. - -""" -# Copyright (C) 2007 Max Hofheinz -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# This is the user interface to the calibration scripts for a initial -# calibration - -# HOW TO USE -# -# 1 calibrate_pulse. This calibrates the microwave envelope shape. -# 2 calibrate_iq. This calibrates the I and Q dc levels needed to zero the rf -# output, and also calibrates the I and Q amplitudes needed to null the -# unwanted sideband. - -from __future__ import with_statement, absolute_import - -from numpy import clip - -import labrad -import labrad.async - -# GHz_DAC_calibrate is only a front-end, the actual calibration -# routines are in ghzdac.calibrate - -import ghzdac -import ghzdac.calibrate as calibrate -import ghzdac.keys as keys - -FPGA_SERVER_NAME = 'ghz_fpgas' - -def calibrate_dc_pulse(fpga_name, channel, dc_scope): - """ Calls ghzdac.calibrate.calibrateDCPulse, synchronously. - - :param str fpga_name: e.g. "Vince DAC 11" - :param int channel: 0 for A, 1 for B - :param str dc_scope: 'sampling_scope' or 'infiniium' - :return int: dataset number - """ - with labrad.connect() as cxn: - if dc_scope == 'sampling_scope': - return calibrate.calibrateDCPulse(cxn, fpga_name, channel) - elif dc_scope == 'infiniium': - return calibrate.calibrateDCPulse_infiniium(cxn, fpga_name, channel, 'EXT') - else: - raise Exception("invalid scope: {}".format(dc_scope)) - - -def zero_fixed_carrier(fpga_name, use_switch=True): - """ Calls ghzdac.calibrate.zeroFixedCarrier, synchronously. - :param str fpga_name: e.g. "Vince DAC 11" - :param bool use_switch: Whether to use microwave switch - :return list[int]: zeros for A and B - """ - with labrad.connect() as cxn: - return calibrate.zeroFixedCarrier(cxn, fpga_name, use_switch=use_switch) - - -def calibrate_ac_pulse(fpga_name, zero_a, zero_b, use_switch=True): - """ Calls ghzdac.calibrate.calibrateACPulse, synchronously. - - :param str fpga_name: - :param int zero_a: Zero for channel A, in DAC units (i.e. 0 < x < 0x1FFF) - :param int zero_b: channel B - :param bool use_switch: Whether to use microwave switch - :return int: dataset number - """ - with labrad.connect() as cxn: - return calibrate.calibrateACPulse(cxn, fpga_name, zero_a, zero_b, - use_switch=use_switch) - - -def calibrate_pulse(cxn, fpga_name, dc_scope = 'infiniium'): - """ - :param labrad connection object cxn: - :param str fpga_name: corresponds with registry, e.g. "Vince DAC 11" - :param str dc_scope: 'sampling_scope' or 'infiniium' - :return: - """ - - if not isinstance(fpga_name, str): - node_name = cxn[FPGA_SERVER_NAME].list_dacs()[0].partition(' DAC')[0] # pull out node name by looking at dacs - fpga_name = node_name + ' DAC ' + str(fpga_name) - - reg = cxn.registry - reg.cd(['', keys.SESSIONNAME, fpga_name], True) - wiring = reg.get(keys.IQWIRING) - print "Board: %s, wiring: %s" % (fpga_name, wiring) - board_type = None - if wiring in keys.SETUPTYPES[1:3]: - board_type = 'ac' - elif wiring == keys.SETUPTYPES[0]: - board_type = 'dc' - - if board_type == 'ac': - use_switch = reg.get(keys.SWITCHUSE, 'b', True, True) - zero_a, zero_b = zero_fixed_carrier(fpga_name, use_switch=use_switch) - dataset = calibrate_ac_pulse(fpga_name, zero_a, zero_b, - use_switch=use_switch) - reg.set(keys.PULSENAME, [dataset]) - elif board_type == 'dc': - channel = int(raw_input('Select channel: 0 (DAC A) or 1 (DAC B): ')) - dataset = calibrate_dc_pulse(fpga_name, channel, dc_scope=dc_scope) - reg.set(keys.CHANNELNAMES[channel], [dataset]) - - -def zero_scan_carrier(fpga_name, scan_params, use_switch=True): - """ Calls ghzdac.calibrate.zeroScanCarrier, synchronously. - - :param str fpga_name: corresponds with registry, e.g. "Vince DAC 11" - :param dict scan_params: parameters dictionary - :return int: dataset number - """ - with labrad.connect() as cxn: - return calibrate.zeroScanCarrier(cxn, scan_params, fpga_name, use_switch=use_switch) - - -def iq_corrector(fpga_name): - """ Calls ghzdac.IQcorrectorAsync, synchronously. - - :param str fpga_name: e.g. "Vince DAC 11" - :return ghzdac.correction.IQcorrection: IQ correction object - """ - with labrad.connect() as cxn: - return ghzdac.IQcorrector(fpga_name, cxn, pulsecor=False) - - -def sideband_scan_carrier(fpga_name, scan_params, corrector, use_switch=True): - """ Calls ghzdac.calibrate.sidebandScanCarrier, synchronously. - - :param fpga_name: e.g. "Vince DAC 11" - :param dict scan_params: parameters dict - :param ghzdac.correction.IQcorrection corrector: corrector object, from e.g. iq_corrector - :return: - """ - with labrad.connect() as cxn: - corrector.dynamicReserve = 4.0 # TODO: I don't know why we do this. - return calibrate.sidebandScanCarrier(cxn, scan_params, fpga_name, corrector, use_switch=use_switch) - - -def modify_scan_params(carrier_start, carrier_stop, carrier_step, sideband_carrier_step, sideband_step, sideband_count): - """ - Generates a dictionary used to determine frequencies for calibration - - :param labrad.Value carrier_start: e.g. 4 GHz - :param labrad.Value carrier_stop: e.g. 7 GHz - :param labrad.Value carrier_step: e.g. 0.05 GHz - :param labrad.Value sideband_carrier_step: e.g. 0.05 GHz - :param labrad.Value sideband_step: e.g. 0.05 GHz - :param labrad.Value sideband_count: e.g. 14 - :return dict scan_params: dict with frequency parameters for calibration - """ - - scan_params = {} - - maxsidebandfreq = clip(0.5 * (sideband_count - 1.0) * sideband_step['GHz'], 1e-3, 0.5) - - scan_params['carrierMin'] = clip(carrier_start['GHz'], 0, 20) - scan_params['carrierMax'] = clip(carrier_stop['GHz'], 0, 20) - scan_params['carrierStep'] = carrier_step['GHz'] - scan_params['sidebandCarrierStep'] = sideband_carrier_step['GHz'] - scan_params['sidebandFreqStep'] = calibrate.validSBstep(sideband_step['GHz']) - scan_params['sidebandFreqCount'] = int(maxsidebandfreq / scan_params['sidebandFreqStep'] + 0.5) * 2 - - return scan_params - - -def find_microwave_dacs(cxn): - """ - :param cxn: labrad connection object - :return list microwave_dacs: list of microwave dacs (with IQ mixers) - """ - - microwave_dacs = [] - - fpgas = cxn[FPGA_SERVER_NAME].list_devices() - reg = cxn.registry - - for fpga in fpgas: - if 'DAC' in fpga[1]: - reg.cd(['', keys.SESSIONNAME, fpga[1]], True) - wiring = reg.get(keys.IQWIRING) - if wiring in keys.SETUPTYPES[1:3]: - microwave_dacs.append(fpga[1]) - - return microwave_dacs - -# TODO: make a pause before starting a uwave switch=0 board - - -def calibrate_iq(cxn, - dacs_to_calibrate, - zero=True, - sideband=True, - carrier_start= 4*labrad.units.GHz, - carrier_stop=7*labrad.units.GHz, - carrier_step=0.025*labrad.units.GHz, - sideband_carrier_step=0.05*labrad.units.GHz, - sideband_step=0.05*labrad.units.GHz, - sideband_count=14, - use_switch=True): - """Runs IQ mixer calibration for one or more DACs - - :param cxn: labrad connection object - :param list[string] or list[int] or string dacs_to_calibrate: DAC or list of - DACs to calibrate - :param bool zero: whether to run the zero calibration - :param bool sideband: whether to run the sideband calibration - :param labrad.Value carrier_start: e.g. 4 GHz - :param labrad.Value carrier_stop: e.g. 7 GHz - :param labrad.Value carrier_step: e.g. 0.05 GHz - :param labrad.Value sideband_carrier_step: e.g. 0.05 GHz - :param labrad.Value sideband_step: e.g. 0.05 GHz - :param labrad.Value sideband_count: e.g. 14 - """ - - if dacs_to_calibrate == 'all': - dacs_to_calibrate = find_microwave_dacs(cxn) - elif not isinstance(dacs_to_calibrate, list): - dacs_to_calibrate = [dacs_to_calibrate] - num_strings = len([x for x in dacs_to_calibrate if isinstance(x, str)]) - if 0 < num_strings < len(dacs_to_calibrate): - raise ValueError("Please pass in either all strings or no strings to dacs_to_calibrate.") - if len([x for x in dacs_to_calibrate if isinstance(x, int)]) > 0: - node_name = cxn[FPGA_SERVER_NAME].list_dacs()[0].partition(' DAC')[0] - for idx in range(len(dacs_to_calibrate)): - dac_number = dacs_to_calibrate[idx] - dacs_to_calibrate[idx] = node_name + ' DAC ' + str(dac_number) - - scan_params = modify_scan_params(carrier_start, carrier_stop, carrier_step, - sideband_carrier_step, sideband_step, sideband_count) - - # TODO: do we want to change how we get the registry keys? (i.e. away from ghzdac.keys) - reg = cxn.registry - for dac in dacs_to_calibrate: - if zero: - iq_dataset = zero_scan_carrier(dac, scan_params, use_switch=use_switch) - reg.cd(['', keys.SESSIONNAME, dac], True) - reg.set(keys.ZERONAME, [iq_dataset]) - if sideband: - corrector = iq_corrector(dac) - sideband_dataset = sideband_scan_carrier(dac, scan_params, corrector, use_switch=use_switch) - reg.cd(['', keys.SESSIONNAME, dac], True) - reg.set(keys.IQNAME, [sideband_dataset]) diff --git a/ghzdac_cal/__init__.py b/ghzdac_cal/__init__.py deleted file mode 100644 index e69de29b..00000000