From 6fe99890f14148e35b0fce3381b1f7ad0afaebda Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 13 Dec 2017 20:23:41 -0500 Subject: [PATCH 01/67] diagnostic -- remove later print a message when we see a measurement with a non-default measurement address, just to show that the information was available to the driver (even though this driver doesn't, and SHOULDN'T do anything with this info yet) --- QGL/drivers/APS2Pattern.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index dcee35ab..bcdca79e 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -719,6 +719,10 @@ def create_seq_instructions(seqs, offsets): if entry.length < MIN_ENTRY_LENGTH: warn("Dropping Waveform entry of length %s!" % entry.length) continue + + if entry.label == 'MEAS' and entry.maddr != -1: + print('GOT MEAS WAVEFORM WITH MADDR %d' % entry.maddr) + instructions.append(Waveform(offsets[wf_sig(entry)], entry.length, entry.isTimeAmp or From 8991538a7204c42d3a53690a310e901286057a28 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 13 Dec 2017 20:26:32 -0500 Subject: [PATCH 02/67] copy maddr from Pulse to Waveform this still seems more or less incorrect, because it's part of the control structure, not part of the waveform... but we don't have a separate way to represent that info --- QGL/Compiler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index 4ba9cb06..666cf54f 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -640,6 +640,8 @@ def __init__(self, pulse=None): self.frameChange = 0 self.isTimeAmp = False self.frequency = 0 + + self.maddr = -1 else: self.label = pulse.label self.key = pulse.hashshape() @@ -650,6 +652,8 @@ def __init__(self, pulse=None): self.isTimeAmp = pulse.isTimeAmp self.frequency = pulse.frequency + self.maddr = pulse.maddr + def __repr__(self): return self.__str__() From 2823ed367826da7b7ec3453e647c49f54c6b6f08 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 13 Dec 2017 20:29:40 -0500 Subject: [PATCH 03/67] add 'MEASA' primitive, for measument+address --- QGL/PulsePrimitives.py | 59 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/QGL/PulsePrimitives.py b/QGL/PulsePrimitives.py index fa600c57..40647236 100644 --- a/QGL/PulsePrimitives.py +++ b/QGL/PulsePrimitives.py @@ -718,12 +718,21 @@ def CNOT(source, target, **kwargs): cnot_impl = globals()[config.cnot_implementation] return cnot_impl(source, target, **kwargs) -## Measurement operators -@_memoize -def MEAS(qubit, **kwargs): +# The worker method for MEAS and MEASA +def _MEAS(qubit, **kwargs): + ''' - MEAS(q1) measures a qubit. Applies to the pulse with the label M-q1 + _MEAS(q1) implements both MEAS and MEASA, but because of + the way memoize works, we want to distinguish them and + memoize them separately. (this may change if the way + memoization works is changed) + + TODO: this is annoying because measuring the same qubit + but storing the result to two different addresses creates + two distinct pulses, unless we also memoize the waveforms + themselves. ''' + channelName = "M-" + qubit.label measChan = ChannelLibraries.MeasFactory(channelName) params = overrideDefaults(measChan, kwargs) @@ -735,8 +744,48 @@ def MEAS(qubit, **kwargs): ignoredStrParams = ['phase', 'frameChange'] if 'amp' not in kwargs: ignoredStrParams.append('amp') - return Pulse("MEAS", measChan, params, amp, 0.0, 0.0, ignoredStrParams) + if 'maddr' in kwargs: + maddr = kwargs['maddr'] + else: + maddr = -1 + + return Pulse("MEAS", measChan, params, + amp, 0.0, 0.0, ignoredStrParams, maddr=maddr) + +## Measurement operators +@_memoize +def MEAS(qubit, **kwargs): + ''' + MEAS(q1) measures a qubit (applying the pulse with the label M-q1) + + This is the 'traditional' measurement; it does not require + a measurement address, and will gripe if one is provided in + the kwargs + + Note that in the future there may be hardware that requires a + measurement address (even if it's just a placeholder) + ''' + + if 'maddr' in kwargs: + raise ValueError('MEAS must not have a maddr kwarg') + + return _MEAS(qubit, **kwargs) + +@_memoize +def MEASA(qubit, maddr, **kwargs): + ''' + MEASA(q1) measures a qubit (applying the pulse with the label M-q1) + and stores the result in the given address in "measurement memory". + + Note that a failure will occur if the hardware does not support + measurement memory and a MEASA is requested. + + There may be special values for maddr that change the behavior + of this operation, but they have not been specified yet. + ''' + + return _MEAS(qubit, maddr=maddr, **kwargs) #MEAS and ring-down time on one qubit, echo on every other def MeasEcho(qM, qD, delay, piShift=None, phase=0): From 6396eaf3d8177e62eca725703e851dd50de18533 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 13 Dec 2017 20:30:22 -0500 Subject: [PATCH 04/67] add memory address to pulses (for measurements) store the memory address that should be used to store the result of a measurement as part of the measurement pulse --- QGL/PulseSequencer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/QGL/PulseSequencer.py b/QGL/PulseSequencer.py index 8e76b1d2..f7d3d460 100644 --- a/QGL/PulseSequencer.py +++ b/QGL/PulseSequencer.py @@ -32,10 +32,10 @@ class Pulse(namedtuple("Pulse", ["label", "channel", "length", "amp", "phase", "frequency", "frameChange", "shapeParams", "isTimeAmp", - "isZero", "ignoredStrParams"])): + "isZero", "ignoredStrParams", "maddr"])): __slots__ = () - def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[]): + def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[], maddr=-1): if hasattr(channel, 'frequency'): frequency = channel.frequency else: @@ -49,7 +49,7 @@ def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, i return super(cls, Pulse).__new__(cls, label, channel, shapeParams['length'], amp, phase, frequency, frameChange, shapeParams, - isTimeAmp, isZero, ignoredStrParams) + isTimeAmp, isZero, ignoredStrParams, maddr) def __str__(self): kwvals = [] From 75f4201018765c31c82a6c14a549e51b409adc1e Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Fri, 19 Jan 2018 17:33:56 -0500 Subject: [PATCH 05/67] work in-progress: adding new TDM instructions this is done in a hackish way for the first pass, while I sort out the dependencies. Eventually it should find its way into a different module because the TDM is a fundamentally different creature than the APS2. --- QGL/Compiler.py | 2 +- QGL/ControlFlow.py | 37 ++++++++++++++++++++ QGL/PulsePrimitives.py | 2 +- QGL/PulseSequencer.py | 8 +++-- QGL/__init__.py | 2 ++ QGL/drivers/APS2Pattern.py | 70 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 114 insertions(+), 7 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index 666cf54f..c5c737a0 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -641,7 +641,7 @@ def __init__(self, pulse=None): self.isTimeAmp = False self.frequency = 0 - self.maddr = -1 + self.maddr = (-1, 0) else: self.label = pulse.label self.key = pulse.hashshape() diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 17b42b6e..fbacfb53 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -216,3 +216,40 @@ def __init__(self, *chanlist): def __str__(self): base = "BARRIER({0})".format(self.chanlist) return base + +class CustomInstruction(ControlInstruction): + + def __init__(self, name, channels=None, **kwargs): + super(CustomInstruction, self).__init__(name) + self.args = kwargs + +def MajorityVote(in_addr, out_addr): + return CustomInstruction( + 'MAJORITY', in_addr=in_addr, out_addr=out_addr) + +def MajorityMask(in_addr, out_addr): + return CustomInstruction( + 'MAJORITY', in_addr=in_addr, out_addr=out_addr) + + +def Invalidate(channel, addr, mask): + """ + See comment for WriteAddr + """ + + return CustomInstruction('INVALIDATE', addr=addr, mask=mask) + +def WriteAddr(channel, addr, value): + """ + This isn't a custom instruction, per se, but is similar in + the sense that it has unusual behavior. For convenience, + I'm prototyping it as one. + + The channel may be None, in which case it should be run on + the TDM instead of an APS. + """ + + return CustomInstruction( + 'WRITEADDR', channel=channel, addr=addr, value=value) + +# TODO: the rest of the CUSTOM instructions diff --git a/QGL/PulsePrimitives.py b/QGL/PulsePrimitives.py index 40647236..56d982d6 100644 --- a/QGL/PulsePrimitives.py +++ b/QGL/PulsePrimitives.py @@ -748,7 +748,7 @@ def _MEAS(qubit, **kwargs): if 'maddr' in kwargs: maddr = kwargs['maddr'] else: - maddr = -1 + maddr = (-1, 0) return Pulse("MEAS", measChan, params, amp, 0.0, 0.0, ignoredStrParams, maddr=maddr) diff --git a/QGL/PulseSequencer.py b/QGL/PulseSequencer.py index f7d3d460..b16b1947 100644 --- a/QGL/PulseSequencer.py +++ b/QGL/PulseSequencer.py @@ -32,10 +32,11 @@ class Pulse(namedtuple("Pulse", ["label", "channel", "length", "amp", "phase", "frequency", "frameChange", "shapeParams", "isTimeAmp", - "isZero", "ignoredStrParams", "maddr"])): + "isZero", "ignoredStrParams", + "maddr", "moffset"])): __slots__ = () - def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[], maddr=-1): + def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, ignoredStrParams=[], maddr=-1, moffset=0): if hasattr(channel, 'frequency'): frequency = channel.frequency else: @@ -49,7 +50,8 @@ def __new__(cls, label, channel, shapeParams, amp=1.0, phase=0, frameChange=0, i return super(cls, Pulse).__new__(cls, label, channel, shapeParams['length'], amp, phase, frequency, frameChange, shapeParams, - isTimeAmp, isZero, ignoredStrParams, maddr) + isTimeAmp, isZero, ignoredStrParams, + maddr, moffset) def __str__(self): kwvals = [] diff --git a/QGL/__init__.py b/QGL/__init__.py index ddeff94e..7dfb706f 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -9,3 +9,5 @@ from .PulseSequencePlotter import plot_pulse_files from .Tomography import state_tomo, process_tomo from .Scheduler import schedule + +from .ControlFlow import MajorityMask, MajorityVote, WriteAddr, Invalidate diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index bcdca79e..0d48e931 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -59,6 +59,11 @@ PREFETCH = 0xC NOP = 0XF +# APS3 prototype +INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode +WRITEADDR = 0xE +CUSTOM = 0xD + # WFM/MARKER op codes PLAY = 0x0 WAIT_TRIG = 0x1 @@ -392,6 +397,29 @@ def Prefetch(addr, label=None): def NoOp(): return Instruction.unflatten(0xffffffffffffffff) +# APS3 prototype instructions +def Invalidate(addr, mask, label=None): + return WriteAddr(addr, mask, set_valid=False, label=label) + +def WriteAddr(addr, value, set_valid=True, label=None): + header = (WRITEADDR << 4) | 1 if set_valid else 0 + # TODO: error checking here, to make sure that the value + # fits in 32 bits, and the addr fits in 16. + # TODO: the results are nonsensical if they don't fit. + payload = (value << 16) | addr + return Instruction(header, payload, label=label) # + +def Custom(in_addr, out_addr, custom_op, label=None): + header = CUSTOM << 4 + payload = (custom_op << 32) | (in_addr << 16) | out_addr + return Instruction(header, payload, label=label) + +def MajorityVote(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 0, label=label) + +def MajorityVoteMask(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 1, label=label) + def preprocess(seqs, shapeLib): seqs = PatternUtils.convert_lengths_to_samples( @@ -710,6 +738,44 @@ def create_seq_instructions(seqs, offsets): instructions.append(Cmp(cmpTable[entry.operator], entry.value, label=label)) + elif isinstance(entry, ControlFlow.CustomInstruction): + """ + Quick and dirty prototype of handling the 'APS3' extensions. + """ + + if entry.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (entry.args['in_addr'], + entry.args['out_addr'])) + instructions.append( + MajorityVote(entry.args['in_addr'], entry.args['out_addr'], + label=label)) + + elif entry.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (entry.args['in_addr'], entry.args['out_addr'])) + instructions.append( + MajorityMask(entry.args['in_addr'], entry.args['out_addr'], + label=label)) + + elif entry.instruction == 'INVALIDATE': + print('INVALIDATE(addr=%x, mask=%x)' % + (entry.args['addr'], entry.args['mask'])) + instructions.append( + Invalidate(entry.args['addr'], entry.args['mask'], + label=label)) + + elif entry.instruction == 'WRITEADDR': + print('WRITEADDR(channel=%s, addr=%x, value=%x)' % + (str(entry.args['channel']), + entry.args['addr'], + entry.args['value'])) + instructions.append( + WriteAddr(entry.args['addr'], entry.args['value'], 1, + label=label)) + else: + print('UNSUPPORTED CUSTOM: %s(%s)' % + (entry.instruction, str(entry.args))) continue @@ -720,8 +786,8 @@ def create_seq_instructions(seqs, offsets): warn("Dropping Waveform entry of length %s!" % entry.length) continue - if entry.label == 'MEAS' and entry.maddr != -1: - print('GOT MEAS WAVEFORM WITH MADDR %d' % entry.maddr) + if entry.label == 'MEAS' and entry.maddr != (-1, 0): + print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) instructions.append(Waveform(offsets[wf_sig(entry)], entry.length, From 1914f29fd0f556316c9dff81c067da97b3fa2b9d Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sat, 20 Jan 2018 12:08:52 -0500 Subject: [PATCH 06/67] cleaning up new operations; adding str method differentiating between CUSTOM and WRITEADDR still need to address changes to ordinary APS2 opcodes --- QGL/ControlFlow.py | 38 ++++++++----------- QGL/drivers/APS2Pattern.py | 78 ++++++++++++++++++++++---------------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index fbacfb53..babceb55 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -219,37 +219,31 @@ def __str__(self): class CustomInstruction(ControlInstruction): - def __init__(self, name, channels=None, **kwargs): + def __init__(self, name, in_addr, out_addr, **kwargs): super(CustomInstruction, self).__init__(name) + self.in_addr = in_addr + self.out_addr = out_addr self.args = kwargs def MajorityVote(in_addr, out_addr): - return CustomInstruction( - 'MAJORITY', in_addr=in_addr, out_addr=out_addr) + return CustomInstruction('MAJORITY', in_addr, out_addr) def MajorityMask(in_addr, out_addr): - return CustomInstruction( - 'MAJORITY', in_addr=in_addr, out_addr=out_addr) + return CustomInstruction('MAJORITYMASK', in_addr, out_addr) +class WriteAddrInstruction(ControlInstruction): -def Invalidate(channel, addr, mask): - """ - See comment for WriteAddr - """ - - return CustomInstruction('INVALIDATE', addr=addr, mask=mask) - -def WriteAddr(channel, addr, value): - """ - This isn't a custom instruction, per se, but is similar in - the sense that it has unusual behavior. For convenience, - I'm prototyping it as one. + def __init__(self, name, channel, invalid, addr, value, **kwargs): + super(WriteAddrInstruction, self).__init__(name) + self.xchannel = channel + self.invalid = invalid + self.addr = addr + self.value = value - The channel may be None, in which case it should be run on - the TDM instead of an APS. - """ +def Invalidate(addr, mask, channel=None): + return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask) - return CustomInstruction( - 'WRITEADDR', channel=channel, addr=addr, value=value) +def WriteAddr(addr, value, channel=None): + return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value) # TODO: the rest of the CUSTOM instructions diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 0d48e931..34b9f3f4 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -60,9 +60,9 @@ NOP = 0XF # APS3 prototype +CUSTOM = 0xD INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode WRITEADDR = 0xE -CUSTOM = 0xD # WFM/MARKER op codes PLAY = 0x0 @@ -211,7 +211,11 @@ def __str__(self): opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "NOP", "NOP", "NOP"] + "CUSTOM", "WRITEADDR", "NOP"] + + customOps = [ + "MajorityVote", "MajoritySetMask", # TODO there are others... + ] out = "{0} ".format(self.label) if self.label else "" @@ -276,6 +280,21 @@ def __str__(self): elif instrOpCode == LOAD: out += " | count = {}".format(self.payload) + elif instrOpCode == WRITEADDR: + if self.header & 1: + out += ' Invalidate(addr=0x%x, mask=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + else: + out += ' WriteAddr(addr=0x%x, value=0x%x' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + + elif instrOpCode == CUSTOM: + out += ' %s(src=0x%x, dst=0x%x)' % ( + customOps[(self.payload >> 32) & 0xff], + (self.payload >> 16) & 0xffff, + self.payload & 0xffff) return out def __eq__(self, other): @@ -399,15 +418,14 @@ def NoOp(): # APS3 prototype instructions def Invalidate(addr, mask, label=None): - return WriteAddr(addr, mask, set_valid=False, label=label) + header = WRITEADDR << 4 + payload = (mask << 16) | addr + return Instruction(header, payload, label=label) -def WriteAddr(addr, value, set_valid=True, label=None): - header = (WRITEADDR << 4) | 1 if set_valid else 0 - # TODO: error checking here, to make sure that the value - # fits in 32 bits, and the addr fits in 16. - # TODO: the results are nonsensical if they don't fit. +def WriteAddr(addr, value, label=None): + header = (WRITEADDR << 4) | 1 payload = (value << 16) | addr - return Instruction(header, payload, label=label) # + return Instruction(header, payload, label=label) def Custom(in_addr, out_addr, custom_op, label=None): header = CUSTOM << 4 @@ -738,44 +756,40 @@ def create_seq_instructions(seqs, offsets): instructions.append(Cmp(cmpTable[entry.operator], entry.value, label=label)) - elif isinstance(entry, ControlFlow.CustomInstruction): - """ - Quick and dirty prototype of handling the 'APS3' extensions. - """ + elif isinstance(entry, ControlFlow.CustomInstruction): if entry.instruction == 'MAJORITY': print('MAJORITY(in_addr=%x, out_addr=%x)' % - (entry.args['in_addr'], - entry.args['out_addr'])) + (entry.in_addr, entry.out_addr)) instructions.append( - MajorityVote(entry.args['in_addr'], entry.args['out_addr'], + MajorityVote( + entry.in_addr, entry.out_addr, label=label)) elif entry.instruction == 'MAJORITYMASK': print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (entry.args['in_addr'], entry.args['out_addr'])) + (entry.in_addr, entry.out_addr)) instructions.append( - MajorityMask(entry.args['in_addr'], entry.args['out_addr'], + MajorityVoteMask( + entry.in_addr, entry.out_addr, label=label)) - elif entry.instruction == 'INVALIDATE': - print('INVALIDATE(addr=%x, mask=%x)' % - (entry.args['addr'], entry.args['mask'])) + else: + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (entry.instruction, entry.in_addr, entry.out_addr)) + + elif isinstance(entry, ControlFlow.WriteAddrInstruction): + if entry.instruction == 'INVALIDATE': + print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(entry.xchannel), entry.addr, entry.value)) instructions.append( - Invalidate(entry.args['addr'], entry.args['mask'], - label=label)) + Invalidate(entry.addr, entry.value, label=label)) elif entry.instruction == 'WRITEADDR': - print('WRITEADDR(channel=%s, addr=%x, value=%x)' % - (str(entry.args['channel']), - entry.args['addr'], - entry.args['value'])) + print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(entry.xchannel), entry.addr, entry.value)) instructions.append( - WriteAddr(entry.args['addr'], entry.args['value'], 1, - label=label)) - else: - print('UNSUPPORTED CUSTOM: %s(%s)' % - (entry.instruction, str(entry.args))) + WriteAddr(entry.addr, entry.value, label=label)) continue From c24e23cf175181c99b7c243996e01699967339cb Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sat, 20 Jan 2018 12:46:09 -0500 Subject: [PATCH 07/67] pretty-print WFM to VRAM modifier --- QGL/drivers/APS2Pattern.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 34b9f3f4..0b09d789 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -244,6 +244,10 @@ def __str__(self): out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) out += ", addr = {}".format(self.payload & 2**24 - 1) + # APS3/TDM modifier to use VRAM output + if self.payload & (1 << 48): + out += ", use_vram" + elif instrOpCode == MARKER: mrkOpCode = (self.payload >> 46) & 0x3 mrkOpCodes = ["PLAY", "TRIG", "SYNC"] From ab57543498eea4f336e287df8ea589ef19d571f4 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sat, 20 Jan 2018 14:27:20 -0500 Subject: [PATCH 08/67] moving TDM-specific changes here, for neatness avoiding breaking the APS2 in order to support the TDM operations by creating a new driver for the "APS2TDM". --- QGL/drivers/APS2TDMPattern.py | 1256 +++++++++++++++++++++++++++++++++ 1 file changed, 1256 insertions(+) create mode 100644 QGL/drivers/APS2TDMPattern.py diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py new file mode 100644 index 00000000..69dcd3b8 --- /dev/null +++ b/QGL/drivers/APS2TDMPattern.py @@ -0,0 +1,1256 @@ +''' +Module for writing hdf5 APS2 files from sequences and patterns + +Copyright 2014 Raytheon BBN Technologies + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +import os +import logging +from warnings import warn +from copy import copy +from future.moves.itertools import zip_longest +import pickle + +import h5py +import numpy as np + +from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils +from QGL.PatternUtils import hash_pulse, flatten + +# Python 2/3 compatibility: use 'int' that subclasses 'long' +from builtins import int + +#Some constants +SAMPLING_RATE = 1.2e9 +ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps +MIN_ENTRY_LENGTH = 8 +MAX_WAVEFORM_PTS = 2**28 #maximum size of waveform memory +WAVEFORM_CACHE_SIZE = 2**17 +MAX_WAVEFORM_VALUE = 2**13 - 1 #maximum waveform value i.e. 14bit DAC +MAX_NUM_INSTRUCTIONS = 2**26 +MAX_REPEAT_COUNT = 2**16 - 1 +MAX_TRIGGER_COUNT = 2**32 - 1 + +# instruction encodings +WFM = 0x0 +MARKER = 0x1 +WAIT = 0x2 +LOAD = 0x3 +REPEAT = 0x4 +CMP = 0x5 +GOTO = 0x6 +CALL = 0x7 +RET = 0x8 +SYNC = 0x9 +MODULATION = 0xA +LOADCMP = 0xB +PREFETCH = 0xC +NOP = 0XF + +# APS3 prototype +CUSTOM = 0xD +INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode +WRITEADDR = 0xE + +# WFM/MARKER op codes +PLAY = 0x0 +WAIT_TRIG = 0x1 +WAIT_SYNC = 0x2 +WFM_PREFETCH = 0x3 +WFM_OP_OFFSET = 46 +TA_PAIR_BIT = 45 + +# CMP op encodings +EQUAL = 0x0 +NOTEQUAL = 0x1 +GREATERTHAN = 0x2 +LESSTHAN = 0x3 + +# Whether we use PHASE_OFFSET modulation commands or bake it into waveform +# Default to false as we usually don't have many variants +USE_PHASE_OFFSET_INSTRUCTION = False + +# Whether to save the waveform offsets for partial compilation +SAVE_WF_OFFSETS = False + +# Do we want a pulse file per instrument or per channel +SEQFILE_PER_CHANNEL = False + +def get_empty_channel_set(): + return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} + + +def get_seq_file_extension(): + return '.h5' + + +def is_compatible_file(filename): + with h5py.File(filename, 'r') as FID: + target = FID['/'].attrs['target hardware'] + if isinstance(target, str): + target = target.encode('utf-8') + if target == b'APS2': + return True + return False + +def create_wf_vector(wfLib, seqs): + ''' + Helper function to create the wf vector and offsets into it. + ''' + max_pts_needed = 0 + for wf in wfLib.values(): + if len(wf) == 1: + max_pts_needed += ADDRESS_UNIT + else: + max_pts_needed += len(wf) + + #If we have more than fits in cache we'll need to align and prefetch + need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE + + idx = 0 + + if not need_prefetch: + offsets = [{}] + cache_lines = [] + #if we can fit them all in just pack + wfVec = np.zeros(max_pts_needed, dtype=np.int16) + for key, wf in wfLib.items(): + #Clip the wf + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) + offsets[-1][key] = idx + idx += wf.size + + #Trim the waveform + wfVec.resize(idx) + + else: + #otherwise fill in one cache line at a time + CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 + wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) + offsets = [{}] + cache_lines = [] + for seq in seqs: + #go through sequence and see what we need to add + pts_to_add = 0 + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + pts_to_add += entry.length + + #If what we need to add spills over then add a line and start again + if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: + idx = int(CACHE_LINE_LENGTH * ( + (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) + wfVec = np.append(wfVec, + np.zeros(CACHE_LINE_LENGTH, + dtype=np.int16)) + offsets.append({}) + + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + wf = wfLib[sig] + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16( + MAX_WAVEFORM_VALUE * wf) + offsets[-1][sig] = idx + idx += wf.size + + cache_lines.append(int(idx // CACHE_LINE_LENGTH)) + + return wfVec, offsets, cache_lines + + +class Instruction(object): + def __init__(self, header, payload, label=None, target=None): + self.header = header + self.payload = int(payload) + self.label = label + self.target = target + + @classmethod + def unflatten(cls, instr): + return cls(header=(int(instr) >> 56) & 0xff, + payload=int(instr) & 0xffffffffffffff) + + def __repr__(self): + return self.__str__() + + def __str__(self): + + opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", + "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", + "CUSTOM", "WRITEADDR", "NOP"] + + customOps = [ + "MajorityVote", "MajoritySetMask", # TODO there are others... + ] + + out = "{0} ".format(self.label) if self.label else "" + + instrOpCode = (self.header >> 4) & 0xf + out += opCodes[instrOpCode] + + if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( + instrOpCode == MODULATION): + if (instrOpCode == MARKER) or (instrOpCode == WFM): + out += "; engine={}, ".format((self.header >> 2) & 0x3) + else: + out += "; " + if self.header & 0x1: + out += "write=1 | " + else: + out += "write=0 | " + + if self.target: + out += " {}".format(self.target) + + if instrOpCode == WFM: + wfOpCode = (self.payload >> 46) & 0x3 + wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] + out += wfOpCodes[wfOpCode] + out += "; TA bit={}".format((self.payload >> 45) & 0x1) + out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) + out += ", addr = {}".format(self.payload & 2**24 - 1) + + # APS3/TDM modifier to use VRAM output + if self.payload & (1 << 48): + out += ", use_vram" + + elif instrOpCode == MARKER: + mrkOpCode = (self.payload >> 46) & 0x3 + mrkOpCodes = ["PLAY", "TRIG", "SYNC"] + out += mrkOpCodes[mrkOpCode] + out += "; state={}".format((self.payload >> 32) & 0x1) + out += ", count = {}".format(self.payload & 2**32 - 1) + + elif instrOpCode == MODULATION: + modulatorOpCode = (self.payload >> 45) & 0x7 + modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", + "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] + out += modulatorOpCodes[modulatorOpCode] + out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) + if modulatorOpCode == 0x0: + out += ", count={:d}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x3: + out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x5: + out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x7: + out += ", frame_change=0x{:08x}".format(self.payload & + 0xffffffff) + + elif instrOpCode == CMP: + cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] + cmpCode = (self.payload >> 8) & 0x3 + out += " | " + cmpCodes[cmpCode] + out += ", value = {}".format(self.payload & 0xff) + + elif any( + [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): + out += " | target addr = {}".format(self.payload & 2**26 - 1) + + elif instrOpCode == LOAD: + out += " | count = {}".format(self.payload) + + elif instrOpCode == WRITEADDR: + if (self.header & 0xf) == 1: + out += ' Invalidate(addr=0x%x, mask=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + elif (self.header & 0xf) == 5: + out += ' StoreMeas(addr=0x%x, mapping=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + else: + out += ' WriteAddr(addr=0x%x, value=0x%x' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + + elif instrOpCode == CUSTOM: + out += ' %s(src=0x%x, dst=0x%x)' % ( + customOps[(self.payload >> 32) & 0xff], + (self.payload >> 16) & 0xffff, + self.payload & 0xffff) + return out + + def __eq__(self, other): + return self.header == other.header and self.payload == other.payload and self.label == other.label + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.header, self.payload, self.label)) + + @property + def address(self): + return self.payload & 0xffffffff # bottom 32-bits of payload + + @address.setter + def address(self, value): + self.payload |= value & 0xffffffff + + @property + def writeFlag(self): + return self.header & 0x1 + + @writeFlag.setter + def writeFlag(self, value): + self.header |= value & 0x1 + + @property + def opcode(self): + return self.header >> 4 + + def flatten(self): + return int((self.header << 56) | (self.payload & 0xffffffffffff)) + + +def Waveform(addr, count, isTA, write=False, label=None): + header = (WFM << 4) | (0x3 << 2) | (write & + 0x1) #broadcast to both engines + count = int(count) + count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count + addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr + payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) + << TA_PAIR_BIT) | (count << 24) | addr + return Instruction(header, payload, label) + + +def WaveformPrefetch(addr): + header = (WFM << 4) | (0x3 << 2) | (0x1) + payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr + return Instruction(header, payload, None) + + +def Marker(sel, state, count, write=False, label=None): + header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) + count = int(count) + four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count + count_rem = count % ADDRESS_UNIT + if state == 0: + transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} + transition = transitionWords[count_rem] + else: + transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} + transition = transitionWords[count_rem] + payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( + (state & 0x1) << 32) | four_count + return Instruction(header, payload, label) + + +def Command(cmd, payload, write=False, label=None): + header = (cmd << 4) + if isinstance(payload, int): + instr = Instruction(header, payload, label) + else: + instr = Instruction(header, 0, label, target=payload) + instr.writeFlag = write + return instr + + +def Sync(label=None): + return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) + + +def Wait(label=None): + return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) + + +def LoadCmp(label=None): + return Command(LOADCMP, 0, label=label) + + +def Cmp(op, value, label=None): + return Command(CMP, (op << 8) | (value & 0xff), label=label) + + +def Goto(addr, label=None): + return Command(GOTO, addr, label=label) + + +def Call(addr, label=None): + return Command(CALL, addr, label=label) + + +def Return(label=None): + return Command(RET, 0, label=label) + + +def Load(count, label=None): + return Command(LOAD, count, label=label) + + +def Repeat(addr, label=None): + return Command(REPEAT, addr, label=label) + + +def Prefetch(addr, label=None): + return Command(PREFETCH, addr) + + +def NoOp(): + return Instruction.unflatten(0xffffffffffffffff) + +# APS3 prototype instructions +def Invalidate(addr, mask, label=None): + header = WRITEADDR << 4 + payload = (mask << 16) | addr + return Instruction(header, payload, label=label) + +def WriteAddr(addr, value, label=None): + header = (WRITEADDR << 4) | 1 + payload = (value << 16) | addr + return Instruction(header, payload, label=label) + +def StoreMeas(addr, mapping, label=None): + header = (WRITEADDR << 4) | 5 + payload = (mapping << 16) | addr + return Instruction(header, payload, label=label) + +def Custom(in_addr, out_addr, custom_op, label=None): + header = CUSTOM << 4 + payload = (custom_op << 32) | (in_addr << 16) | out_addr + return Instruction(header, payload, label=label) + +def MajorityVote(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 0, label=label) + +def MajorityVoteMask(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 1, label=label) + + +def preprocess(seqs, shapeLib): + seqs = PatternUtils.convert_lengths_to_samples( + seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) + wfLib = build_waveforms(seqs, shapeLib) + inject_modulation_cmds(seqs) + return seqs, wfLib + + +def wf_sig(wf): + ''' + Compute a signature of a Compiler.Waveform that identifies the relevant properties for + two Waveforms to be considered "equal" in the waveform library. For example, we ignore + length of TA waveforms. + ''' + if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.amp) + else: + return (wf.amp, round(wf.phase * 2**13)) + else: + #TODO: why do we need the length? + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.key, wf.amp, wf.length) + else: + return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) + + +class ModulationCommand(object): + """docstring for ModulationCommand""" + + def __init__(self, + instruction, + nco_select, + frequency=0, + phase=0, + length=0): + super(ModulationCommand, self).__init__() + self.instruction = instruction + self.nco_select = nco_select + self.frequency = frequency + self.phase = phase + self.length = length + + def __str__(self): + out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, + self.nco_select) + if self.instruction == "MODULATE": + out += ", length={})".format(self.length) + elif self.instruction == "SET_FREQ": + out += ", frequency={})".format(self.frequency) + elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": + out += ", phase={})".format(self.phase) + else: + out += ")" + return out + + def _repr_pretty_(self, p, cycle): + p.text(str(self)) + + def __repr__(self): + return str(self) + + def to_instruction(self, write_flag=True, label=None): + #Modulator op codes + MODULATOR_OP_OFFSET = 44 + NCO_SELECT_OP_OFFSET = 40 + MODULATION_CLOCK = 300e6 + + op_code_map = {"MODULATE": 0x0, + "RESET_PHASE": 0x2, + "SET_FREQ": 0x6, + "SET_PHASE": 0xa, + "UPDATE_FRAME": 0xe} + payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( + self.nco_select << NCO_SELECT_OP_OFFSET) + if self.instruction == "MODULATE": + #zero-indexed quad count + payload |= np.uint32(self.length / ADDRESS_UNIT - 1) + elif self.instruction == "SET_FREQ": + # frequencies can span -2 to 2 or 0 to 4 in unsigned + payload |= np.uint32( + (self.frequency / MODULATION_CLOCK if self.frequency > 0 else + self.frequency / MODULATION_CLOCK + 4) * 2**28) + elif (self.instruction == "SET_PHASE") | ( + self.instruction == "UPDATE_FRAME"): + #phases can span -0.5 to 0.5 or 0 to 1 in unsigned + payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) + + instr = Instruction(MODULATION << 4, payload, label) + instr.writeFlag = write_flag + return instr + +def inject_modulation_cmds(seqs): + """ + Inject modulation commands from phase, frequency and frameChange of waveforms + in an IQ waveform sequence. Assume up to 2 NCOs for now. + """ + cur_freq = 0 + cur_phase = 0 + for ct,seq in enumerate(seqs): + #check whether we have modulation commands + freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) + if len(freqs) > 2: + raise Exception("Max 2 frequencies on the same channel allowed.") + no_freq_cmds = np.allclose(freqs, 0) + phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_phase_cmds = np.allclose(phases, 0) + frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_frame_cmds = np.allclose(frame_changes, 0) + no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds + + if no_modulation_cmds: + continue + + mod_seq = [] + pending_frame_update = False + + for entry in seq: + + #copies to avoid same object having different timestamps later + #copy through BlockLabel + if isinstance(entry, BlockLabel.BlockLabel): + mod_seq.append(copy(entry)) + #mostly copy through control-flow + elif isinstance(entry, ControlFlow.ControlInstruction): + #heuristic to insert phase reset before trigger if we have modulation commands + if isinstance(entry, ControlFlow.Wait): + if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): + mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) + for nco_ind, freq in enumerate(freqs): + mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) + elif isinstance(entry, ControlFlow.Return): + cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines + mod_seq.append(copy(entry)) + elif isinstance(entry, Compiler.Waveform): + if not no_modulation_cmds: + #select nco + nco_select = (list(freqs)).index(entry.frequency) + 1 + cur_freq = entry.frequency + if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): + mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) + cur_phase = entry.phase + #now apply modulation for count command and waveform command, if non-zero length + if entry.length > 0: + mod_seq.append(entry) + # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command + if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ + and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: + mod_seq[-2].length += entry.length + else: + mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) + pending_frame_update = False + #now apply non-zero frame changes after so it is applied at end + if entry.frameChange != 0: + pending_frame_update = True + #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible + if entry.length == 0: + #if the last is a frame change then we can add to the frame change + if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": + mod_seq[-1].phase += entry.frameChange + #if last entry was pulse without frame change we add frame change + elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #if this is the first entry with a wait for trigger then we can inject a frame change + #before the wait for trigger but after the RESET_PHASE + elif isinstance(mod_seq[-1], ControlFlow.Wait): + mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": + mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #otherwise drop and error if frame has been defined + else: + raise Exception("Unable to implement zero time Z pulse") + else: + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + + seqs[ct] = mod_seq + +def build_waveforms(seqs, shapeLib): + # apply amplitude (and optionally phase) and add the resulting waveforms to the library + wfLib = {} + for wf in flatten(seqs): + if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: + shape = wf.amp * shapeLib[wf.key] + if not USE_PHASE_OFFSET_INSTRUCTION: + shape *= np.exp(1j * wf.phase) + wfLib[wf_sig(wf)] = shape + return wfLib + + +def timestamp_entries(seq): + t = 0 + for ct in range(len(seq)): + seq[ct].startTime = t + t += seq[ct].length + + +def synchronize_clocks(seqs): + # Control-flow instructions (CFIs) must occur at the same time on all channels. + # Therefore, we need to "reset the clock" by synchronizing the accumulated + # time at each CFI to the largest value on any channel + syncInstructions = [list(filter( + lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) + for seq in seqs if seq] + + # Add length to control-flow instructions to make accumulated time match at end of CFI. + # Keep running tally of how much each channel has been shifted so far. + localShift = [0 for _ in syncInstructions] + for ct in range(len(syncInstructions[0])): + step = [seq[ct] for seq in syncInstructions] + endTime = max((s.startTime + shift + for s, shift in zip(step, localShift))) + for ct, s in enumerate(step): + s.length = endTime - (s.startTime + localShift[ct]) + # localShift[ct] += endTime - (s.startTime + localShift[ct]) + # the += and the last term cancel, therefore: + localShift[ct] = endTime - s.startTime + # re-timestamp to propagate changes across the sequences + for seq in seqs: + timestamp_entries(seq) + # then transfer the control flow "lengths" back into start times + for seq in syncInstructions: + for instr in seq: + instr.startTime += instr.length + instr.length = 0 + + +def create_seq_instructions(seqs, offsets): + ''' + Helper function to create instruction vector from an IR sequence and an offset dictionary + keyed on the wf keys. + + Seqs is a list of lists containing waveform and marker data, e.g. + [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] + + We take the strategy of greedily grabbing the next instruction that occurs in time, accross + all waveform and marker channels. + ''' + + print('SEQ %s' % str(seqs)) + + # timestamp all entries before filtering (where we lose time information on control flow) + for seq in seqs: + timestamp_entries(seq) + + synchronize_clocks(seqs) + + # create (seq, startTime) pairs over all sequences + timeTuples = [] + for ct, seq in enumerate(seqs): + timeTuples += [(entry.startTime, ct) for entry in seq] + timeTuples.sort() + + # keep track of where we are in each sequence + indexes = np.zeros(len(seqs), dtype=np.int64) + + cmpTable = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} + + # always start with SYNC (stealing label from beginning of sequence) + # unless it is a subroutine (using last entry as return as tell) + label = None + instructions = [] + for ct, seq in enumerate(seqs): + if len(seq): + first_non_empty = ct + break + if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): + if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): + label = seqs[first_non_empty][0] + timeTuples.pop(0) + indexes[first_non_empty] += 1 + instructions.append(Sync(label=label)) + label = None + + while len(timeTuples) > 0: + #pop off all entries that have the same time + entries = [] + start_time = 0 + while True: + start_time, seq_idx = timeTuples.pop(0) + entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) + indexes[seq_idx] += 1 + next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 + if start_time != next_start_time: + break + + write_flags = [True] * len(entries) + for ct, (entry, seq_idx) in enumerate(entries): + #use first non empty sequence for control flow + if seq_idx == first_non_empty and ( + isinstance(entry, ControlFlow.ControlInstruction) or + isinstance(entry, BlockLabel.BlockLabel)): + if isinstance(entry, BlockLabel.BlockLabel): + # carry label forward to next entry + label = entry + continue + # control flow instructions + elif isinstance(entry, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(entry, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + elif isinstance(entry, ControlFlow.Sync): + instructions.append(Sync(label=label)) + elif isinstance(entry, ControlFlow.Return): + instructions.append(Return(label=label)) + # target argument commands + elif isinstance(entry, ControlFlow.Goto): + instructions.append(Goto(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Call): + instructions.append(Call(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Repeat): + instructions.append(Repeat(entry.target, label=label)) + # value argument commands + elif isinstance(entry, ControlFlow.LoadRepeat): + instructions.append(Load(entry.value - 1, label=label)) + elif isinstance(entry, ControlFlow.ComparisonInstruction): + # TODO modify Cmp operator to load from specified address + instructions.append(Cmp(cmpTable[entry.operator], + entry.value, + label=label)) + + elif isinstance(entry, ControlFlow.CustomInstruction): + if entry.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (entry.in_addr, entry.out_addr)) + instructions.append( + MajorityVote( + entry.in_addr, entry.out_addr, + label=label)) + + elif entry.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (entry.in_addr, entry.out_addr)) + instructions.append( + MajorityVoteMask( + entry.in_addr, entry.out_addr, + label=label)) + + else: + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (entry.instruction, entry.in_addr, entry.out_addr)) + + elif isinstance(entry, ControlFlow.WriteAddrInstruction): + if entry.instruction == 'INVALIDATE': + print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(entry.xchannel), entry.addr, entry.value)) + instructions.append( + Invalidate(entry.addr, entry.value, label=label)) + + elif entry.instruction == 'WRITEADDR': + print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(entry.xchannel), entry.addr, entry.value)) + instructions.append( + WriteAddr(entry.addr, entry.value, label=label)) + + elif entry.instruction == 'STOREMEAS': + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(entry.xchannel), entry.addr, entry.value)) + instructions.append( + StoreMeas(entry.addr, entry.value, label=label)) + + continue + + if seq_idx == 0: + #analog - waveforms or modulation + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping Waveform entry of length %s!" % entry.length) + continue + + if entry.label == 'MEAS' and entry.maddr != (-1, 0): + print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) + + instructions.append(Waveform(offsets[wf_sig(entry)], + entry.length, + entry.isTimeAmp or + entry.isZero, + write=write_flags[ct], + label=label)) + elif isinstance(entry, ModulationCommand): + instructions.append(entry.to_instruction( + write_flag=write_flags[ct], + label=label)) + + else: # a marker engine + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping entry!") + continue + markerSel = seq_idx - 1 + state = not entry.isZero + instructions.append(Marker(markerSel, + state, + entry.length, + write=write_flags[ct], + label=label)) + + #clear label + label = None + + return instructions + + +def create_instr_data(seqs, offsets, cache_lines): + ''' + Constructs the complete instruction data vector, and does basic checks for validity. + + Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line + ''' + logger = logging.getLogger(__name__) + logger.debug('') + + seq_instrs = [] + need_prefetch = len(cache_lines) > 0 + num_cache_lines = len(set(cache_lines)) + cache_line_changes = np.concatenate( + ([0], np.where(np.diff(cache_lines))[0] + 1)) + for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): + seq_instrs.append(create_seq_instructions( + list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0])) + #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line + if need_prefetch and (ct in cache_line_changes): + next_cache_line = cache_lines[cache_line_changes[(np.where( + ct == cache_line_changes)[0][0] + 1) % len( + cache_line_changes)]] + seq_instrs[-1].insert(0, WaveformPrefetch(int( + next_cache_line * WAVEFORM_CACHE_SIZE / 2))) + #steal label + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None + + #concatenate instructions + instructions = [] + subroutines_start = -1 + for ct, seq in enumerate(seq_instrs): + #Use last instruction being return as mark of start of subroutines + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + instructions += seq + + #if we have any subroutines then group in cache lines + if subroutines_start >= 0: + subroutine_instrs = [] + subroutine_cache_line = {} + CACHE_LINE_LENGTH = 128 + offset = 0 + for sub in seq_instrs[subroutines_start:]: + #TODO for now we don't properly handle prefetching mulitple cache lines + if len(sub) > CACHE_LINE_LENGTH: + warnings.warn( + "Subroutines longer than {} instructions may not be prefetched correctly") + #Don't unecessarily split across a cache line + if (len(sub) + offset > CACHE_LINE_LENGTH) and ( + len(sub) < CACHE_LINE_LENGTH): + pad_instrs = 128 - ((offset + 128) % 128) + subroutine_instrs += [NoOp()] * pad_instrs + offset = 0 + if offset == 0: + line_label = sub[0].label + subroutine_cache_line[sub[0].label] = line_label + subroutine_instrs += sub + offset += len(sub) % CACHE_LINE_LENGTH + logger.debug("Placed {} subroutines into {} cache lines".format( + len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // + CACHE_LINE_LENGTH)) + #inject prefetch commands before waits + wait_idx = [idx for idx, instr in enumerate(instructions) + if (instr.header >> 4) == WAIT] + [len(instructions)] + instructions_with_prefetch = instructions[:wait_idx[0]] + last_prefetch = None + for start, stop in zip(wait_idx[:-1], wait_idx[1:]): + call_targets = [instr.target for instr in instructions[start:stop] + if (instr.header >> 4) == CALL] + needed_lines = set() + for target in call_targets: + needed_lines.add(subroutine_cache_line[target]) + if len(needed_lines) > 8: + raise RuntimeError( + "Unable to prefetch more than 8 cache lines") + for needed_line in needed_lines: + if needed_line != last_prefetch: + instructions_with_prefetch.append(Prefetch(needed_line)) + last_prefetch = needed_line + instructions_with_prefetch += instructions[start:stop] + + instructions = instructions_with_prefetch + #pad out instruction vector to ensure circular cache never loads a subroutine + pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) + instructions += [NoOp()] * pad_instrs + + instructions += subroutine_instrs + + #turn symbols into integers addresses + resolve_symbols(instructions) + + assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ + 'Oops! too many instructions: {0}'.format(len(instructions)) + + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) + + +def resolve_symbols(seq): + symbols = {} + # create symbol look-up table + for ct, entry in enumerate(seq): + if entry.label and entry.label not in symbols: + symbols[entry.label] = ct + # then update + for entry in seq: + if entry.target: + entry.address = symbols[entry.target] + + +def compress_marker(markerLL): + ''' + Compresses adjacent entries of the same state into single entries + ''' + for seq in markerLL: + idx = 0 + while idx + 1 < len(seq): + if (isinstance(seq[idx], Compiler.Waveform) and + isinstance(seq[idx + 1], Compiler.Waveform) and + seq[idx].isZero == seq[idx + 1].isZero): + + seq[idx].length += seq[idx + 1].length + del seq[idx + 1] + else: + idx += 1 + + +def write_sequence_file(awgData, fileName): + ''' + Main function to pack channel sequences into an APS2 h5 file. + ''' + # Convert QGL IR into a representation that is closer to the hardware. + awgData['ch12']['linkList'], wfLib = preprocess( + awgData['ch12']['linkList'], awgData['ch12']['wfLib']) + + # compress marker data + for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: + if 'linkList' in awgData[field].keys(): + PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], + SAMPLING_RATE, 1, + Compiler.Waveform) + compress_marker(awgData[field]['linkList']) + else: + awgData[field]['linkList'] = [] + + #Create the waveform vectors + wfInfo = [] + wfInfo.append(create_wf_vector({key: wf.real + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + wfInfo.append(create_wf_vector({key: wf.imag + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + + if SAVE_WF_OFFSETS: + #create a set of all waveform signatures in offset dictionaries + #we could have multiple offsets for the same pulse becuase it could + #be repeated in multiple cache lines + wf_sigs = set() + for offset_dict in wfInfo[0][1]: + wf_sigs |= set(offset_dict.keys()) + #create dictionary linking entry labels (that's what we'll have later) with offsets + offsets = {} + for seq in awgData['ch12']['linkList']: + for entry in seq: + if len(wf_sigs) == 0: + break + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig in wf_sigs: + #store offsets and wavefor lib length + #time ampltidue entries are clamped to ADDRESS_UNIT + wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length + offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], + wf_length) + wf_sigs.discard(sig) + + #break out of outer loop too + if len(wf_sigs) == 0: + break + + #now pickle the label=>offsets + with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: + pickle.dump(offsets, FID) + + # build instruction vector + seq_data = [awgData[s]['linkList'] + for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] + instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) + + #Open the HDF5 file + if os.path.isfile(fileName): + os.remove(fileName) + with h5py.File(fileName, 'w') as FID: + FID['/'].attrs['Version'] = 4.0 + FID['/'].attrs['target hardware'] = 'APS2' + FID['/'].attrs['minimum firmware version'] = 4.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) + + #Create the groups and datasets + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + #Write the waveformLib to file + FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) + + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', + data=instructions) + + +def read_sequence_file(fileName): + """ + Reads a HDF5 sequence file and returns a dictionary of lists. + Dictionary keys are channel strings such as ch1, ch12m1 + Lists are or tuples of time-amplitude pairs (time, output) + """ + chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', + 'mod_phase'] + seqs = {ch: [] for ch in chanStrs} + + def start_new_seq(): + for ct, ch in enumerate(chanStrs): + if (ct < 2) or (ct == 6): + #analog or modulation channel + seqs[ch].append([]) + else: + #marker channel + seqs[ch].append([]) + + with h5py.File(fileName, 'r') as FID: + file_version = FID["/"].attrs["Version"] + wf_lib = {} + wf_lib['ch1'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() + wf_lib['ch2'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() + instructions = FID['/chan_1/instructions'].value.flatten() + NUM_NCO = 2 + freq = np.zeros(NUM_NCO) #radians per timestep + phase = np.zeros(NUM_NCO) + frame = np.zeros(NUM_NCO) + next_freq = np.zeros(NUM_NCO) + next_phase = np.zeros(NUM_NCO) + next_frame = np.zeros(NUM_NCO) + accumulated_phase = np.zeros(NUM_NCO) + reset_flag = [False]*NUM_NCO + + for data in instructions: + instr = Instruction.unflatten(data) + + modulator_opcode = instr.payload >> 44 + + #update phases at these boundaries + if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( + (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): + for ct in range(NUM_NCO): + if reset_flag[ct]: + #would expect this to be zero but this is first non-zero point + accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT + reset_flag[ct] = False + freq[:] = next_freq[:] + phase[:] = next_phase[:] + frame[:] = next_frame[:] + + #Assume new sequence at every WAIT + if instr.opcode == WAIT: + start_new_seq() + + elif instr.opcode == WFM and (( + (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): + addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT + count = (instr.payload >> 24) & 0xfffff + count = (count + 1) * ADDRESS_UNIT + isTA = (instr.payload >> 45) & 0x1 + chan_select_bits = ((instr.header >> 2) & 0x1, + (instr.header >> 3) & 0x1) + #On older firmware we broadcast by default whereas on newer we respect the engine select + for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): + if (file_version < 4) or select_bit: + if isTA: + seqs[chan][-1].append((count, wf_lib[chan][addr])) + else: + for sample in wf_lib[chan][addr:addr + count]: + seqs[chan][-1].append((1, sample)) + + elif instr.opcode == MARKER: + chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) + count = instr.payload & 0xffffffff + count = (count + 1) * ADDRESS_UNIT + state = (instr.payload >> 32) & 0x1 + seqs[chan][-1].append((count, state)) + + elif instr.opcode == MODULATION: + # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} + nco_select_bits = (instr.payload >> 40) & 0xf + if modulator_opcode == 0x0: + #modulate + count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT + nco_select = {0b0001: 0, + 0b0010: 1, + 0b0100: 2, + 0b1000: 3}[nco_select_bits] + seqs['mod_phase'][-1] = np.append( + seqs['mod_phase'][-1], freq[nco_select] * + np.arange(count) + accumulated_phase[nco_select] + + phase[nco_select] + frame[nco_select]) + accumulated_phase += count * freq + else: + phase_rad = 2 * np.pi * (instr.payload & + 0xffffffff) / 2**28 + for ct in range(NUM_NCO): + if (nco_select_bits >> ct) & 0x1: + if modulator_opcode == 0x2: + #reset + next_phase[ct] = 0 + next_frame[ct] = 0 + reset_flag[ct] = True + elif modulator_opcode == 0x6: + #set frequency + next_freq[ct] = phase_rad / ADDRESS_UNIT + elif modulator_opcode == 0xa: + #set phase + next_phase[ct] = phase_rad + elif modulator_opcode == 0xe: + #update frame + next_frame[ct] += phase_rad + + #Apply modulation if we have any + for ct, ( + ch1, ch2, mod_phase + ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): + if len(mod_phase): + #only really works if ch1, ch2 are broadcast together + mod_ch1 = [] + mod_ch2 = [] + cum_time = 0 + for ((time_ch1, amp_ch1), + (time_ch2, amp_ch2)) in zip(ch1, ch2): + if (amp_ch1 != 0) or (amp_ch2 != 0): + assert time_ch1 == time_ch2 + if time_ch1 == 1: + #single timestep + modulated = np.exp(1j * mod_phase[cum_time]) * ( + amp_ch1 + 1j * amp_ch2) + mod_ch1.append((1, modulated.real)) + mod_ch2.append((1, modulated.imag)) + else: + #expand TA + modulated = np.exp( + 1j * + mod_phase[cum_time:cum_time + time_ch1]) * ( + amp_ch1 + 1j * amp_ch2) + for val in modulated: + mod_ch1.append((1, val.real)) + mod_ch2.append((1, val.imag)) + else: + mod_ch1.append((time_ch1, amp_ch1)) + mod_ch2.append((time_ch2, amp_ch2)) + + cum_time += time_ch1 + seqs['ch1'][ct] = mod_ch1 + seqs['ch2'][ct] = mod_ch2 + + del seqs['mod_phase'] + + return seqs + + +def update_wf_library(filename, pulses, offsets): + """ + Update a H5 waveform library in place give an iterable of (pulseName, pulse) + tuples and offsets into the waveform library. + """ + assert USE_PHASE_OFFSET_INSTRUCTION == False + #load the h5 file + with h5py.File(filename) as FID: + for label, pulse in pulses.items(): + #create a new waveform + if pulse.isTimeAmp: + shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) + else: + shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape + try: + length = offsets[label][1] + except KeyError: + print("\t{} not found in offsets so skipping".format(pulse)) + continue + for offset in offsets[label][0]: + print("\tUpdating {} at offset {}".format(pulse, offset)) + FID['/chan_1/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.real) + FID['/chan_2/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.imag) From fe0b91d3c0e8566393adda40c89054b9d1e3181e Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sat, 20 Jan 2018 14:44:17 -0500 Subject: [PATCH 09/67] revert all APS2/TDM changes this APS2 driver should be identical to master at this point --- QGL/drivers/APS2Pattern.py | 90 +------------------------------------- 1 file changed, 1 insertion(+), 89 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 0b09d789..dcee35ab 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -59,11 +59,6 @@ PREFETCH = 0xC NOP = 0XF -# APS3 prototype -CUSTOM = 0xD -INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode -WRITEADDR = 0xE - # WFM/MARKER op codes PLAY = 0x0 WAIT_TRIG = 0x1 @@ -211,11 +206,7 @@ def __str__(self): opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "CUSTOM", "WRITEADDR", "NOP"] - - customOps = [ - "MajorityVote", "MajoritySetMask", # TODO there are others... - ] + "NOP", "NOP", "NOP"] out = "{0} ".format(self.label) if self.label else "" @@ -244,10 +235,6 @@ def __str__(self): out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) out += ", addr = {}".format(self.payload & 2**24 - 1) - # APS3/TDM modifier to use VRAM output - if self.payload & (1 << 48): - out += ", use_vram" - elif instrOpCode == MARKER: mrkOpCode = (self.payload >> 46) & 0x3 mrkOpCodes = ["PLAY", "TRIG", "SYNC"] @@ -284,21 +271,6 @@ def __str__(self): elif instrOpCode == LOAD: out += " | count = {}".format(self.payload) - elif instrOpCode == WRITEADDR: - if self.header & 1: - out += ' Invalidate(addr=0x%x, mask=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - else: - out += ' WriteAddr(addr=0x%x, value=0x%x' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - - elif instrOpCode == CUSTOM: - out += ' %s(src=0x%x, dst=0x%x)' % ( - customOps[(self.payload >> 32) & 0xff], - (self.payload >> 16) & 0xffff, - self.payload & 0xffff) return out def __eq__(self, other): @@ -420,28 +392,6 @@ def Prefetch(addr, label=None): def NoOp(): return Instruction.unflatten(0xffffffffffffffff) -# APS3 prototype instructions -def Invalidate(addr, mask, label=None): - header = WRITEADDR << 4 - payload = (mask << 16) | addr - return Instruction(header, payload, label=label) - -def WriteAddr(addr, value, label=None): - header = (WRITEADDR << 4) | 1 - payload = (value << 16) | addr - return Instruction(header, payload, label=label) - -def Custom(in_addr, out_addr, custom_op, label=None): - header = CUSTOM << 4 - payload = (custom_op << 32) | (in_addr << 16) | out_addr - return Instruction(header, payload, label=label) - -def MajorityVote(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 0, label=label) - -def MajorityVoteMask(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 1, label=label) - def preprocess(seqs, shapeLib): seqs = PatternUtils.convert_lengths_to_samples( @@ -761,40 +711,6 @@ def create_seq_instructions(seqs, offsets): entry.value, label=label)) - elif isinstance(entry, ControlFlow.CustomInstruction): - if entry.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (entry.in_addr, entry.out_addr)) - instructions.append( - MajorityVote( - entry.in_addr, entry.out_addr, - label=label)) - - elif entry.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (entry.in_addr, entry.out_addr)) - instructions.append( - MajorityVoteMask( - entry.in_addr, entry.out_addr, - label=label)) - - else: - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (entry.instruction, entry.in_addr, entry.out_addr)) - - elif isinstance(entry, ControlFlow.WriteAddrInstruction): - if entry.instruction == 'INVALIDATE': - print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(entry.xchannel), entry.addr, entry.value)) - instructions.append( - Invalidate(entry.addr, entry.value, label=label)) - - elif entry.instruction == 'WRITEADDR': - print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(entry.xchannel), entry.addr, entry.value)) - instructions.append( - WriteAddr(entry.addr, entry.value, label=label)) - continue if seq_idx == 0: @@ -803,10 +719,6 @@ def create_seq_instructions(seqs, offsets): if entry.length < MIN_ENTRY_LENGTH: warn("Dropping Waveform entry of length %s!" % entry.length) continue - - if entry.label == 'MEAS' and entry.maddr != (-1, 0): - print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) - instructions.append(Waveform(offsets[wf_sig(entry)], entry.length, entry.isTimeAmp or From 28de8104dd48a5864d0c296846bc5911324527ad Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sat, 20 Jan 2018 16:00:50 -0500 Subject: [PATCH 10/67] separate TDM types from APS2 types in my initial sketch, the TDM instructions were subclasses from the ControlFlow instructions, which made them pretty easy to slip into the driver. Now that I've made them into a separate hierarchy, all the isinstance tests for ControlFlow fail for the TDM instructions, so new tests are needed. Also added something equivalent to aps2_reader, since this was linked to the APS2 driver, which I am trying to leave unaltered. (all dependencies on aps2_reader could be removed at this point) --- QGL/Compiler.py | 6 +++++- QGL/ControlFlow.py | 7 +++++-- QGL/PatternUtils.py | 7 +++++-- QGL/__init__.py | 2 +- QGL/drivers/APS2TDMPattern.py | 17 +++++++++++------ 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index c5c737a0..7d3bab17 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -35,6 +35,7 @@ from .PulseSequencer import Pulse, PulseBlock, CompositePulse from . import ControlFlow from . import BlockLabel +from . import TdmInstructions # only for APS2-TDM logger = logging.getLogger(__name__) @@ -532,7 +533,9 @@ def compile_sequence(seq, channels=None): wires[chan] += [copy(block)] continue # control flow broadcasts to all channels if channel attribute is None - if isinstance(block, ControlFlow.ControlInstruction): + if (isinstance(block, ControlFlow.ControlInstruction) or + isinstance(block, TdmInstructions.WriteAddrInstruction) or + isinstance(block, TdmInstructions.CustomInstruction)): # Need to deal with attaching measurements and edges to control # instruction. Until we have a proper solution for that, we will # always broadcast control instructions to all channels @@ -543,6 +546,7 @@ def compile_sequence(seq, channels=None): continue # propagate frame change from nodes to edges for chan in channels: + print('chan %s channels %s' % (str(chan), str(channels))) if block.pulses[chan].frameChange == 0: continue if chan in ChannelLibraries.channelLib.connectivityG.nodes(): diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index babceb55..4ced8c4f 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -233,10 +233,10 @@ def MajorityMask(in_addr, out_addr): class WriteAddrInstruction(ControlInstruction): - def __init__(self, name, channel, invalid, addr, value, **kwargs): + def __init__(self, name, channel, modifier, addr, value, **kwargs): super(WriteAddrInstruction, self).__init__(name) self.xchannel = channel - self.invalid = invalid + self.invalid = modifier self.addr = addr self.value = value @@ -246,4 +246,7 @@ def Invalidate(addr, mask, channel=None): def WriteAddr(addr, value, channel=None): return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value) +def StoreMeas(addr, value, channel=None): + return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value) + # TODO: the rest of the CUSTOM instructions diff --git a/QGL/PatternUtils.py b/QGL/PatternUtils.py index 42c6aba9..90fe67e4 100644 --- a/QGL/PatternUtils.py +++ b/QGL/PatternUtils.py @@ -24,6 +24,7 @@ from .PulsePrimitives import BLANK from . import ControlFlow from . import BlockLabel +from . import TdmInstructions import QGL.drivers def hash_pulse(shape): @@ -133,8 +134,10 @@ def apply_gating_constraints(chan, linkList): # first pass consolidates entries previousEntry = None for ct,entry in enumerate(miniLL): - if isinstance(entry, (ControlFlow.ControlInstruction, - BlockLabel.BlockLabel)): + if isinstance(entry, + (ControlFlow.ControlInstruction, BlockLabel.BlockLabel, + TdmInstructions.CustomInstruction, + TdmInstructions.WriteAddrInstruction)): if previousEntry: gateSeq.append(previousEntry) previousEntry = None diff --git a/QGL/__init__.py b/QGL/__init__.py index 7dfb706f..a8dd659c 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -10,4 +10,4 @@ from .Tomography import state_tomo, process_tomo from .Scheduler import schedule -from .ControlFlow import MajorityMask, MajorityVote, WriteAddr, Invalidate +from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 69dcd3b8..17489c82 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -28,6 +28,7 @@ from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils from QGL.PatternUtils import hash_pulse, flatten +from QGL import TdmInstructions # Python 2/3 compatibility: use 'int' that subclasses 'long' from builtins import int @@ -742,7 +743,9 @@ def create_seq_instructions(seqs, offsets): #use first non empty sequence for control flow if seq_idx == first_non_empty and ( isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel)): + isinstance(entry, BlockLabel.BlockLabel) or + isinstance(entry, TdmInstructions.CustomInstruction) or + isinstance(entry, TdmInstructions.WriteAddrInstruction)): if isinstance(entry, BlockLabel.BlockLabel): # carry label forward to next entry label = entry @@ -772,7 +775,8 @@ def create_seq_instructions(seqs, offsets): entry.value, label=label)) - elif isinstance(entry, ControlFlow.CustomInstruction): + elif isinstance(entry, TdmInstructions.CustomInstruction): + print('HEY custom') if entry.instruction == 'MAJORITY': print('MAJORITY(in_addr=%x, out_addr=%x)' % (entry.in_addr, entry.out_addr)) @@ -793,22 +797,23 @@ def create_seq_instructions(seqs, offsets): print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % (entry.instruction, entry.in_addr, entry.out_addr)) - elif isinstance(entry, ControlFlow.WriteAddrInstruction): + elif isinstance(entry, TdmInstructions.WriteAddrInstruction): + print('HEY writeAddr') if entry.instruction == 'INVALIDATE': print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(entry.xchannel), entry.addr, entry.value)) + (str(entry.channel), entry.addr, entry.value)) instructions.append( Invalidate(entry.addr, entry.value, label=label)) elif entry.instruction == 'WRITEADDR': print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(entry.xchannel), entry.addr, entry.value)) + (str(entry.channel), entry.addr, entry.value)) instructions.append( WriteAddr(entry.addr, entry.value, label=label)) elif entry.instruction == 'STOREMEAS': print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(entry.xchannel), entry.addr, entry.value)) + (str(entry.channel), entry.addr, entry.value)) instructions.append( StoreMeas(entry.addr, entry.value, label=label)) From d4c5fdc05f041e4d8833ac18e615738e83b41fd9 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sun, 21 Jan 2018 14:55:10 -0500 Subject: [PATCH 11/67] splitting between APS and TDM instruction streams there are still some problems with this, mostly me not knowing where things should go. There's also the problem of compiling for the measurement channel vs the qubit channel: how do we know which one the TDM should follow? --- QGL/drivers/APS2TDMPattern.py | 90 +++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 17489c82..c84f7a65 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -678,6 +678,25 @@ def synchronize_clocks(seqs): instr.startTime += instr.length instr.length = 0 +_TDM_INSTRUCTIONS = None + +def pad_with_nops(list1, list2): + """ + Given two lists of instructions, pad the shorter until they + are equal length + """ + + len1 = len(list1) + len2 = len(list2) + + if len1 == len2: + return + elif len1 > len2: + for _ in range(len1 - len2): + list2.append(NoOp()) + else: + for _ in range(len2 - len1): + list1.append(NoOp()) def create_seq_instructions(seqs, offsets): ''' @@ -691,7 +710,8 @@ def create_seq_instructions(seqs, offsets): all waveform and marker channels. ''' - print('SEQ %s' % str(seqs)) + global _TDM_INSTRUCTIONS + _TDM_INSTRUCTIONS = list() # timestamp all entries before filtering (where we lose time information on control flow) for seq in seqs: @@ -724,6 +744,7 @@ def create_seq_instructions(seqs, offsets): timeTuples.pop(0) indexes[first_non_empty] += 1 instructions.append(Sync(label=label)) + _TDM_INSTRUCTIONS.append(Sync(label=label)) label = None while len(timeTuples) > 0: @@ -740,6 +761,12 @@ def create_seq_instructions(seqs, offsets): write_flags = [True] * len(entries) for ct, (entry, seq_idx) in enumerate(entries): + + # If then instructions list isn't the same length as the + # TDM_INSTRUCTIONS list, then append NOP instructions to + # the shorter of the two until they are. + pad_with_nops(instructions, _TDM_INSTRUCTIONS) + #use first non empty sequence for control flow if seq_idx == first_non_empty and ( isinstance(entry, ControlFlow.ControlInstruction) or @@ -753,6 +780,7 @@ def create_seq_instructions(seqs, offsets): # control flow instructions elif isinstance(entry, ControlFlow.Wait): instructions.append(Wait(label=label)) + _TDM_INSTRUCTIONS.append(Wait(label=label)) elif isinstance(entry, ControlFlow.LoadCmp): instructions.append(LoadCmp(label=label)) elif isinstance(entry, ControlFlow.Sync): @@ -776,46 +804,50 @@ def create_seq_instructions(seqs, offsets): label=label)) elif isinstance(entry, TdmInstructions.CustomInstruction): - print('HEY custom') if entry.instruction == 'MAJORITY': print('MAJORITY(in_addr=%x, out_addr=%x)' % (entry.in_addr, entry.out_addr)) - instructions.append( + _TDM_INSTRUCTIONS.append( MajorityVote( - entry.in_addr, entry.out_addr, - label=label)) - + entry.in_addr, entry.out_addr, label=label)) elif entry.instruction == 'MAJORITYMASK': print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % (entry.in_addr, entry.out_addr)) - instructions.append( + _TDM_INSTRUCTIONS.append( MajorityVoteMask( - entry.in_addr, entry.out_addr, - label=label)) - + entry.in_addr, entry.out_addr, label=label)) else: print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % (entry.instruction, entry.in_addr, entry.out_addr)) elif isinstance(entry, TdmInstructions.WriteAddrInstruction): - print('HEY writeAddr') if entry.instruction == 'INVALIDATE': print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % (str(entry.channel), entry.addr, entry.value)) - instructions.append( - Invalidate(entry.addr, entry.value, label=label)) + instr = Invalidate(entry.addr, entry.value, label=label) + if entry.channel: + instructions.append(instr) + else: + _TDM_INSTRUCTIONS.append(instr) elif entry.instruction == 'WRITEADDR': print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % (str(entry.channel), entry.addr, entry.value)) - instructions.append( - WriteAddr(entry.addr, entry.value, label=label)) - + instr = WriteAddr(entry.addr, entry.value, label=label) + if entry.channel: + instructions.append(instr) + else: + _TDM_INSTRUCTIONS.append(instr) elif entry.instruction == 'STOREMEAS': + # TODO: STOREMEAS only happens on the TDM, right? print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % (str(entry.channel), entry.addr, entry.value)) - instructions.append( - StoreMeas(entry.addr, entry.value, label=label)) + _TDM_INSTRUCTIONS.append(StoreMeas(entry.addr, entry.value, label=label)) + else: + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (entry.instruction, str(entry.channel), + entry.addr, entry.value)) + continue continue @@ -826,15 +858,20 @@ def create_seq_instructions(seqs, offsets): warn("Dropping Waveform entry of length %s!" % entry.length) continue + wfm_instr = Waveform( + offsets[wf_sig(entry)], entry.length, + entry.isTimeAmp or entry.isZero, + write=write_flags[ct], label=label) + + # TODO: is this the right thing to do? if entry.label == 'MEAS' and entry.maddr != (-1, 0): print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) + _TDM_INSTRUCTIONS.append(LoadCmp(label=label)) + _TDM_INSTRUCTIONS.append( + StoreMeas(entry.maddr[0], 1 << entry.maddr[1])) + wfm_instr.payload |= (1 << 48) - instructions.append(Waveform(offsets[wf_sig(entry)], - entry.length, - entry.isTimeAmp or - entry.isZero, - write=write_flags[ct], - label=label)) + instructions.append(wfm_instr) elif isinstance(entry, ModulationCommand): instructions.append(entry.to_instruction( write_flag=write_flags[ct], @@ -856,8 +893,13 @@ def create_seq_instructions(seqs, offsets): #clear label label = None + pad_with_nops(instructions, _TDM_INSTRUCTIONS) + + pad_with_nops(instructions, _TDM_INSTRUCTIONS) return instructions +def get_tdm_instructions(): + return [instr.flatten() for instr in _TDM_INSTRUCTIONS] def create_instr_data(seqs, offsets, cache_lines): ''' From ceea4dffcc747ff055ae9058f2db9df920c4abac Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Sun, 21 Jan 2018 14:56:35 -0500 Subject: [PATCH 12/67] simple test driver for TDM functionality --- QGL/n.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 QGL/n.py diff --git a/QGL/n.py b/QGL/n.py new file mode 100644 index 00000000..905bb9d0 --- /dev/null +++ b/QGL/n.py @@ -0,0 +1,96 @@ + +import QGL.drivers +from QGL import * +import json +import numpy as np +import pprint +import time +import aps2_reader + +from QGL.drivers.APS2TDMPattern import Instruction +import QGL.drivers.APS2TDMPattern + +# import aps2 + +ChannelLibrary(blank=True) + +_q1 = Qubit(label='q1') +_q2 = Qubit(label='q2') + +def setUp(): + # Copied from the unittests (CompileUtils). + # Probably not valid, but OK for placeholders. + + """ + q1gate = Channels.LogicalMarkerChannel(label='q1-gate') + q1 = Qubit(label='q1', gate_chan=q1gate) + q1.pulse_params['length'] = 30e-9 + + q2gate = Channels.LogicalMarkerChannel(label='q2-gate') + q2 = Qubit(label='q2', gate_chan=q2gate) + q2.pulse_params['length'] = 30e-9 + + trigger = Channels.LogicalMarkerChannel(label='trigger') + measq1 = Channels.Measurement(label='M-q1', meas_type='autodyne') + measq1.trig_chan = trigger + + measq2 = Channels.Measurement(label='M-q2', meas_type='autodyne') + measq2.trig_chan = trigger + """ + + cl = ChannelLibrary(library_file="./meas.yml") + + # ChannelLibrary(blank=True) # Create a blank ChannelLibrary + """ + ChannelLibraries.channelLib.channelDict = { + 'q1': q1, + 'q2': q2, + 'M-q1': measq1, + 'M-q2': measq2 + } + """ + ChannelLibraries.channelLib.build_connectivity_graph() + +setUp() + + +q1 = QubitFactory('q1') +q2 = QubitFactory('q2') + +seq = [ + X90(q1), + # X90(q2), + MEASA(q1, maddr=(10, 3)), + WriteAddr(1, 7, channel=q1), + Invalidate(addr=4, mask=0xfff), + MajorityMask(1, 0), + MajorityVote(10, 9), + WriteAddr(12, 13, channel=q1) + ] + +aps_metafile = compile_to_hardware([seq], '/tmp/f') +tdm_instr = QGL.drivers.APS2TDMPattern.get_tdm_instructions() + +aps_metadata = json.loads(open(aps_metafile).read()) +print(aps_metadata) + +for key in aps_metadata['instruments']: + print('INSTRUMENT %s' % str(key)) + instructions = aps2_reader.raw_instructions(aps_metadata['instruments'][key]) + # print('TYPE %s' % str(type(instructions))) + # aps2_reader.display_decompiled_instructions(instructions) + + print('') + for i in range(len(instructions)): + instr_bits = instructions[i] + instr_txt = str(Instruction.unflatten(instr_bits)) + print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) + +print('INSTRUMENT tdm') +for i in range(len(tdm_instr)): + instr_bits = tdm_instr[i] + instr_txt = str(Instruction.unflatten(instr_bits)) + print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) + + + From fd595e3f1f9f3b17cc7c643da70c03c5958d0c59 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Mon, 22 Jan 2018 15:58:25 -0500 Subject: [PATCH 13/67] simplifying how the TDM sequence is built this removes the extra NOPs, but I'm not sure whether the labels are correct, and doesn't work for multiple qubits, and is an awful hack --- QGL/Compiler.py | 7 +- QGL/ControlFlow.py | 33 ------- QGL/drivers/APS2TDMPattern.py | 164 ++++++++++++++++++++-------------- QGL/n.py | 23 +++-- 4 files changed, 119 insertions(+), 108 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index 7d3bab17..3cd73c52 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -461,6 +461,12 @@ def compile_to_hardware(seqs, for wire in old_wire_instrs.keys(): wire.instrument = old_wire_instrs[wire] + # FIXME: a one-off hack for the TDM + for s in physWires.keys(): + if s.label == 'BBNAPS1-12': + # print('----- %s' % str(s)) + tdm_i = pattern_module.tdm_instructions(physWires[s][0]) + # Return the filenames we wrote return metafilepath @@ -546,7 +552,6 @@ def compile_sequence(seq, channels=None): continue # propagate frame change from nodes to edges for chan in channels: - print('chan %s channels %s' % (str(chan), str(channels))) if block.pulses[chan].frameChange == 0: continue if chan in ChannelLibraries.channelLib.connectivityG.nodes(): diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 4ced8c4f..54147a5d 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -217,36 +217,3 @@ def __str__(self): base = "BARRIER({0})".format(self.chanlist) return base -class CustomInstruction(ControlInstruction): - - def __init__(self, name, in_addr, out_addr, **kwargs): - super(CustomInstruction, self).__init__(name) - self.in_addr = in_addr - self.out_addr = out_addr - self.args = kwargs - -def MajorityVote(in_addr, out_addr): - return CustomInstruction('MAJORITY', in_addr, out_addr) - -def MajorityMask(in_addr, out_addr): - return CustomInstruction('MAJORITYMASK', in_addr, out_addr) - -class WriteAddrInstruction(ControlInstruction): - - def __init__(self, name, channel, modifier, addr, value, **kwargs): - super(WriteAddrInstruction, self).__init__(name) - self.xchannel = channel - self.invalid = modifier - self.addr = addr - self.value = value - -def Invalidate(addr, mask, channel=None): - return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask) - -def WriteAddr(addr, value, channel=None): - return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value) - -def StoreMeas(addr, value, channel=None): - return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value) - -# TODO: the rest of the CUSTOM instructions diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index c84f7a65..60bcdcb7 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -29,6 +29,7 @@ from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils from QGL.PatternUtils import hash_pulse, flatten from QGL import TdmInstructions +from QGL import PulseSequencer # Python 2/3 compatibility: use 'int' that subclasses 'long' from builtins import int @@ -295,7 +296,7 @@ def __str__(self): self.payload & 0xffff, (self.payload >> 16) & 0xffffffff) else: - out += ' WriteAddr(addr=0x%x, value=0x%x' % ( + out += ' WriteAddr(addr=0x%x, value=0x%x)' % ( self.payload & 0xffff, (self.payload >> 16) & 0xffffffff) @@ -678,8 +679,6 @@ def synchronize_clocks(seqs): instr.startTime += instr.length instr.length = 0 -_TDM_INSTRUCTIONS = None - def pad_with_nops(list1, list2): """ Given two lists of instructions, pad the shorter until they @@ -710,9 +709,6 @@ def create_seq_instructions(seqs, offsets): all waveform and marker channels. ''' - global _TDM_INSTRUCTIONS - _TDM_INSTRUCTIONS = list() - # timestamp all entries before filtering (where we lose time information on control flow) for seq in seqs: timestamp_entries(seq) @@ -744,7 +740,6 @@ def create_seq_instructions(seqs, offsets): timeTuples.pop(0) indexes[first_non_empty] += 1 instructions.append(Sync(label=label)) - _TDM_INSTRUCTIONS.append(Sync(label=label)) label = None while len(timeTuples) > 0: @@ -762,11 +757,6 @@ def create_seq_instructions(seqs, offsets): write_flags = [True] * len(entries) for ct, (entry, seq_idx) in enumerate(entries): - # If then instructions list isn't the same length as the - # TDM_INSTRUCTIONS list, then append NOP instructions to - # the shorter of the two until they are. - pad_with_nops(instructions, _TDM_INSTRUCTIONS) - #use first non empty sequence for control flow if seq_idx == first_non_empty and ( isinstance(entry, ControlFlow.ControlInstruction) or @@ -780,7 +770,6 @@ def create_seq_instructions(seqs, offsets): # control flow instructions elif isinstance(entry, ControlFlow.Wait): instructions.append(Wait(label=label)) - _TDM_INSTRUCTIONS.append(Wait(label=label)) elif isinstance(entry, ControlFlow.LoadCmp): instructions.append(LoadCmp(label=label)) elif isinstance(entry, ControlFlow.Sync): @@ -803,51 +792,11 @@ def create_seq_instructions(seqs, offsets): entry.value, label=label)) + # TDM instructions are ignored by the APS elif isinstance(entry, TdmInstructions.CustomInstruction): - if entry.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (entry.in_addr, entry.out_addr)) - _TDM_INSTRUCTIONS.append( - MajorityVote( - entry.in_addr, entry.out_addr, label=label)) - elif entry.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (entry.in_addr, entry.out_addr)) - _TDM_INSTRUCTIONS.append( - MajorityVoteMask( - entry.in_addr, entry.out_addr, label=label)) - else: - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (entry.instruction, entry.in_addr, entry.out_addr)) - + pass elif isinstance(entry, TdmInstructions.WriteAddrInstruction): - if entry.instruction == 'INVALIDATE': - print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(entry.channel), entry.addr, entry.value)) - instr = Invalidate(entry.addr, entry.value, label=label) - if entry.channel: - instructions.append(instr) - else: - _TDM_INSTRUCTIONS.append(instr) - - elif entry.instruction == 'WRITEADDR': - print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(entry.channel), entry.addr, entry.value)) - instr = WriteAddr(entry.addr, entry.value, label=label) - if entry.channel: - instructions.append(instr) - else: - _TDM_INSTRUCTIONS.append(instr) - elif entry.instruction == 'STOREMEAS': - # TODO: STOREMEAS only happens on the TDM, right? - print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(entry.channel), entry.addr, entry.value)) - _TDM_INSTRUCTIONS.append(StoreMeas(entry.addr, entry.value, label=label)) - else: - print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % - (entry.instruction, str(entry.channel), - entry.addr, entry.value)) - continue + pass continue @@ -866,9 +815,6 @@ def create_seq_instructions(seqs, offsets): # TODO: is this the right thing to do? if entry.label == 'MEAS' and entry.maddr != (-1, 0): print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) - _TDM_INSTRUCTIONS.append(LoadCmp(label=label)) - _TDM_INSTRUCTIONS.append( - StoreMeas(entry.maddr[0], 1 << entry.maddr[1])) wfm_instr.payload |= (1 << 48) instructions.append(wfm_instr) @@ -893,14 +839,8 @@ def create_seq_instructions(seqs, offsets): #clear label label = None - pad_with_nops(instructions, _TDM_INSTRUCTIONS) - - pad_with_nops(instructions, _TDM_INSTRUCTIONS) return instructions -def get_tdm_instructions(): - return [instr.flatten() for instr in _TDM_INSTRUCTIONS] - def create_instr_data(seqs, offsets, cache_lines): ''' Constructs the complete instruction data vector, and does basic checks for validity. @@ -1301,3 +1241,97 @@ def update_wf_library(filename, pulses, offsets): MAX_WAVEFORM_VALUE * shape.real) FID['/chan_2/waveforms'][offset:offset + length] = np.int16( MAX_WAVEFORM_VALUE * shape.imag) + + +def tdm_instructions(seq): + instructions = list() + + label = None + for s in seq: + if isinstance(s, BlockLabel.BlockLabel): + # carry label forward to next entry + label = s + continue + + # FIXME: not sure if this is right... + # Need to put a SYNC at the beginning + if len(instructions) == 0: + instructions.append(Sync(label=label)) + + elif isinstance(s, ControlFlow.Wait): + instructions.append(Wait(label=label)) + + elif isinstance(s, TdmInstructions.WriteAddrInstruction): + if s.instruction == 'INVALIDATE': + print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(Invalidate(s.addr, s.value, label=label)) + + elif s.instruction == 'WRITEADDR': + print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(WriteAddr(s.addr, s.value, label=label)) + + elif s.instruction == 'STOREMEAS': + # TODO: STOREMEAS only happens on the TDM, right? + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(StoreMeas(s.addr, s.value, label=label)) + else: + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (s.instruction, str(s.channel), + s.addr, s.value)) + continue + + elif isinstance(s, TdmInstructions.CustomInstruction): + + if s.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVote(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + else: + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (s.instruction, s.in_addr, s.out_addr)) + + elif isinstance(s, ControlFlow.Goto): + instructions.append(Goto(s.target, label=label)) + + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Repeat(s.target, label=label)) + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Load(s.value - 1, label=label)) + + elif isinstance(s, Compiler.Waveform): + if s.label == 'MEAS' and s.maddr != (-1, 0): + print('GOT MEAS WAVEFORM WITH MADDR %s' % str(s.maddr)) + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) + else: + # This isn't necessarily an error, because the TDM ignores a + # lot of instructions, but until this is debugged it's handy + # to see what's falling through. + # + print('OOPS: unhandled [%s]' % str(s)) + + # clear label + label = None + + # for i in range(len(instructions)): + # instr_bits = instructions[i].flatten() + # # instr_txt = str(Instruction.unflatten(instr_bits)) + # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) + + global _TDM_INSTRUCTIONS + _TDM_INSTRUCTIONS = [i.flatten() for i in instructions] + +def get_tdm_instructions(): + return _TDM_INSTRUCTIONS + + + diff --git a/QGL/n.py b/QGL/n.py index 905bb9d0..4ee45faf 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -55,17 +55,21 @@ def setUp(): q1 = QubitFactory('q1') -q2 = QubitFactory('q2') +# q2 = QubitFactory('q2') seq = [ - X90(q1), - # X90(q2), - MEASA(q1, maddr=(10, 3)), - WriteAddr(1, 7, channel=q1), - Invalidate(addr=4, mask=0xfff), + Id(q1), + + WriteAddr(1, 7), MajorityMask(1, 0), - MajorityVote(10, 9), - WriteAddr(12, 13, channel=q1) + + Invalidate(addr=4, mask=0xfff), + + MEASA(q1, maddr=(10, 0)), + MEASA(q1, maddr=(10, 1)), + MEASA(q1, maddr=(10, 2)), + + MajorityVote(10, 11), ] aps_metafile = compile_to_hardware([seq], '/tmp/f') @@ -75,17 +79,18 @@ def setUp(): print(aps_metadata) for key in aps_metadata['instruments']: + print('') print('INSTRUMENT %s' % str(key)) instructions = aps2_reader.raw_instructions(aps_metadata['instruments'][key]) # print('TYPE %s' % str(type(instructions))) # aps2_reader.display_decompiled_instructions(instructions) - print('') for i in range(len(instructions)): instr_bits = instructions[i] instr_txt = str(Instruction.unflatten(instr_bits)) print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) +print('') print('INSTRUMENT tdm') for i in range(len(tdm_instr)): instr_bits = tdm_instr[i] From 41f80e357432ff2e980ac2aa8fc3e07a472aa029 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Mon, 22 Jan 2018 19:08:35 -0500 Subject: [PATCH 14/67] add new LoadCmpTdm pseudo-op also fixed the mask of the payload in flatten, which was lopping off the top 8 bits. --- QGL/Compiler.py | 3 ++- QGL/PatternUtils.py | 4 +++- QGL/__init__.py | 2 +- QGL/drivers/APS2TDMPattern.py | 42 ++++++++++++++++++++++------------- QGL/n.py | 13 +++++++---- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index 3cd73c52..e5868026 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -541,7 +541,8 @@ def compile_sequence(seq, channels=None): # control flow broadcasts to all channels if channel attribute is None if (isinstance(block, ControlFlow.ControlInstruction) or isinstance(block, TdmInstructions.WriteAddrInstruction) or - isinstance(block, TdmInstructions.CustomInstruction)): + isinstance(block, TdmInstructions.CustomInstruction) or + isinstance(block, TdmInstructions.LoadCmpTdmInstruction)): # Need to deal with attaching measurements and edges to control # instruction. Until we have a proper solution for that, we will # always broadcast control instructions to all channels diff --git a/QGL/PatternUtils.py b/QGL/PatternUtils.py index 90fe67e4..9ab312e0 100644 --- a/QGL/PatternUtils.py +++ b/QGL/PatternUtils.py @@ -137,7 +137,9 @@ def apply_gating_constraints(chan, linkList): if isinstance(entry, (ControlFlow.ControlInstruction, BlockLabel.BlockLabel, TdmInstructions.CustomInstruction, - TdmInstructions.WriteAddrInstruction)): + TdmInstructions.WriteAddrInstruction, + TdmInstructions.LoadCmpTdmInstruction)): + if previousEntry: gateSeq.append(previousEntry) previousEntry = None diff --git a/QGL/__init__.py b/QGL/__init__.py index a8dd659c..f5cc7433 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -10,4 +10,4 @@ from .Tomography import state_tomo, process_tomo from .Scheduler import schedule -from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate +from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpTdm diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 60bcdcb7..358fd43d 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -29,7 +29,6 @@ from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils from QGL.PatternUtils import hash_pulse, flatten from QGL import TdmInstructions -from QGL import PulseSequencer # Python 2/3 compatibility: use 'int' that subclasses 'long' from builtins import int @@ -288,7 +287,7 @@ def __str__(self): elif instrOpCode == WRITEADDR: if (self.header & 0xf) == 1: - out += ' Invalidate(addr=0x%x, mask=0x%x)' % ( + out += ' WriteAddr(addr=0x%x, mask=0x%x)' % ( self.payload & 0xffff, (self.payload >> 16) & 0xffffffff) elif (self.header & 0xf) == 5: @@ -296,7 +295,7 @@ def __str__(self): self.payload & 0xffff, (self.payload >> 16) & 0xffffffff) else: - out += ' WriteAddr(addr=0x%x, value=0x%x)' % ( + out += ' Invalidate(addr=0x%x, value=0x%x)' % ( self.payload & 0xffff, (self.payload >> 16) & 0xffffffff) @@ -305,6 +304,13 @@ def __str__(self): customOps[(self.payload >> 32) & 0xff], (self.payload >> 16) & 0xffff, self.payload & 0xffff) + + elif (instrOpCode == LOADCMP) and (self.payload != 0): + if self.payload & (1 << 48): + out += ' LoadCmp vram(addr=0x%x, mask=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + return out def __eq__(self, other): @@ -337,7 +343,7 @@ def opcode(self): return self.header >> 4 def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffff)) + return int((self.header << 56) | (self.payload & 0xffffffffffffff)) def Waveform(addr, count, isTA, write=False, label=None): @@ -453,6 +459,11 @@ def MajorityVote(in_addr, out_addr, label=None): def MajorityVoteMask(in_addr, out_addr, label=None): return Custom(in_addr, out_addr, 1, label=label) +def LoadCmpTdm(addr, mask, label=None): + header = LOADCMP << 4 + payload = (1 << 48) | (mask << 16) | addr + return Instruction(header, payload, label=label) + def preprocess(seqs, shapeLib): seqs = PatternUtils.convert_lengths_to_samples( @@ -762,7 +773,8 @@ def create_seq_instructions(seqs, offsets): isinstance(entry, ControlFlow.ControlInstruction) or isinstance(entry, BlockLabel.BlockLabel) or isinstance(entry, TdmInstructions.CustomInstruction) or - isinstance(entry, TdmInstructions.WriteAddrInstruction)): + isinstance(entry, TdmInstructions.WriteAddrInstruction) or + isinstance(entry, TdmInstructions.LoadCmpTdmInstruction)): if isinstance(entry, BlockLabel.BlockLabel): # carry label forward to next entry label = entry @@ -807,17 +819,10 @@ def create_seq_instructions(seqs, offsets): warn("Dropping Waveform entry of length %s!" % entry.length) continue - wfm_instr = Waveform( + instructions.append(Waveform( offsets[wf_sig(entry)], entry.length, entry.isTimeAmp or entry.isZero, - write=write_flags[ct], label=label) - - # TODO: is this the right thing to do? - if entry.label == 'MEAS' and entry.maddr != (-1, 0): - print('GOT MEAS WAVEFORM WITH MADDR %s' % str(entry.maddr)) - wfm_instr.payload |= (1 << 48) - - instructions.append(wfm_instr) + write=write_flags[ct], label=label)) elif isinstance(entry, ModulationCommand): instructions.append(entry.to_instruction( write_flag=write_flags[ct], @@ -1260,6 +1265,8 @@ def tdm_instructions(seq): elif isinstance(s, ControlFlow.Wait): instructions.append(Wait(label=label)) + elif isinstance(s, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) elif isinstance(s, TdmInstructions.WriteAddrInstruction): if s.instruction == 'INVALIDATE': @@ -1307,9 +1314,14 @@ def tdm_instructions(seq): elif isinstance(s, ControlFlow.Repeat): instructions.append(Load(s.value - 1, label=label)) + elif isinstance(s, TdmInstructions.LoadCmpTdmInstruction): + if s.instruction == 'LOADCMPTDM': + instructions.append( + LoadCmpTdm(s.addr, s.mask, label=label)) + elif isinstance(s, Compiler.Waveform): if s.label == 'MEAS' and s.maddr != (-1, 0): - print('GOT MEAS WAVEFORM WITH MADDR %s' % str(s.maddr)) + print('TDM GOT MEAS WAVEFORM WITH MADDR %s' % str(s.maddr)) instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) else: diff --git a/QGL/n.py b/QGL/n.py index 4ee45faf..095de807 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -55,7 +55,7 @@ def setUp(): q1 = QubitFactory('q1') -# q2 = QubitFactory('q2') +q2 = QubitFactory('q2') seq = [ Id(q1), @@ -63,11 +63,14 @@ def setUp(): WriteAddr(1, 7), MajorityMask(1, 0), - Invalidate(addr=4, mask=0xfff), + Invalidate(addr=10, mask=0x7), MEASA(q1, maddr=(10, 0)), - MEASA(q1, maddr=(10, 1)), - MEASA(q1, maddr=(10, 2)), + # MEASA(q1, maddr=(10, 1)), + # MEASA(q1, maddr=(10, 2)), + MEASA(q1, maddr=(20, 0)), + + LoadCmpTdm(0xfedc, 0x1234678), MajorityVote(10, 11), ] @@ -97,5 +100,7 @@ def setUp(): instr_txt = str(Instruction.unflatten(instr_bits)) print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) +# TODO: insert TDM instructions into the output file + From 9f89dd65d69ba48855ef0255e145f1ad2069283f Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Tue, 23 Jan 2018 11:39:08 -0500 Subject: [PATCH 15/67] cleanup and complete (save output...) --- QGL/n.py | 89 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/QGL/n.py b/QGL/n.py index 095de807..a414e09a 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -1,21 +1,25 @@ +# Example program for creating TDM instruction files + +import copy +import json +import numpy +import os +import shutil import QGL.drivers from QGL import * -import json -import numpy as np -import pprint -import time import aps2_reader from QGL.drivers.APS2TDMPattern import Instruction import QGL.drivers.APS2TDMPattern -# import aps2 - -ChannelLibrary(blank=True) - -_q1 = Qubit(label='q1') -_q2 = Qubit(label='q2') +def pp_instructions(name, instructions): + print('') + print('INSTRUMENT: ' + name) + for i in range(len(instructions)): + instr_bits = instructions[i] + instr_txt = str(Instruction.unflatten(instr_bits)) + print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) def setUp(): # Copied from the unittests (CompileUtils). @@ -51,9 +55,9 @@ def setUp(): """ ChannelLibraries.channelLib.build_connectivity_graph() +ChannelLibrary(blank=True) setUp() - q1 = QubitFactory('q1') q2 = QubitFactory('q2') @@ -68,39 +72,50 @@ def setUp(): MEASA(q1, maddr=(10, 0)), # MEASA(q1, maddr=(10, 1)), # MEASA(q1, maddr=(10, 2)), - MEASA(q1, maddr=(20, 0)), + # MEASA(q2, maddr=(20, 0)), - LoadCmpTdm(0xfedc, 0x1234678), + # LoadCmpTdm(0xfedc, 0x1234678), MajorityVote(10, 11), ] -aps_metafile = compile_to_hardware([seq], '/tmp/f') +# First, compile for the APS units. As a side effect, +# this creates the TDM instructions, but does NOT +# put them into the APS output file. We retrieve +# the TDM instructions, and then recompile the instructions +# to create a template TDM file, and then insert the +# TDM instructions into the template. +# +# Note that the steps of compiling for the TDM and +# inserting the instructions into the file are FRAGILE +# because they require that there be an instrument named +# "BBNAPS1" in the machine config. This name has special +# meaning. + +# IMPORTANT: compilation is destructive: it modifies +# the input sequences (and possibly the instances in those +# sequences. So, running compiler_to_hardware on the +# same sequence twice can FAIL (or give weird results). +# So copy the input sequence each time we use it... + +aps_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/aps') tdm_instr = QGL.drivers.APS2TDMPattern.get_tdm_instructions() - aps_metadata = json.loads(open(aps_metafile).read()) -print(aps_metadata) - -for key in aps_metadata['instruments']: - print('') - print('INSTRUMENT %s' % str(key)) - instructions = aps2_reader.raw_instructions(aps_metadata['instruments'][key]) - # print('TYPE %s' % str(type(instructions))) - # aps2_reader.display_decompiled_instructions(instructions) - - for i in range(len(instructions)): - instr_bits = instructions[i] - instr_txt = str(Instruction.unflatten(instr_bits)) - print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) - -print('') -print('INSTRUMENT tdm') -for i in range(len(tdm_instr)): - instr_bits = tdm_instr[i] - instr_txt = str(Instruction.unflatten(instr_bits)) - print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) - -# TODO: insert TDM instructions into the output file +tdm_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/tdm') +tdm_metadata = json.loads(open(tdm_metafile).read()) +aps2_reader.replace_instructions( + tdm_metadata['instruments']['BBNAPS1'], + numpy.array(tdm_instr, dtype=np.uint64)) +for key in aps_metadata['instruments']: + instructions = aps2_reader.raw_instructions( + aps_metadata['instruments'][key]) + pp_instructions(str(key), instructions) + +# Read the TDM instructions from file, just to make sure +# +tdm_instr_from_file = aps2_reader.raw_instructions( + tdm_metadata['instruments']['BBNAPS1']) +pp_instructions('tdm', tdm_instr_from_file) From 08e4f7fcbd1403cca16bb5fd6aeb3ffa1272b7b5 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Tue, 23 Jan 2018 12:55:35 -0500 Subject: [PATCH 16/67] remove unreferenced chaff --- QGL/n.py | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/QGL/n.py b/QGL/n.py index a414e09a..53afdfeb 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -3,8 +3,6 @@ import copy import json import numpy -import os -import shutil import QGL.drivers from QGL import * @@ -22,40 +20,9 @@ def pp_instructions(name, instructions): print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) def setUp(): - # Copied from the unittests (CompileUtils). - # Probably not valid, but OK for placeholders. - - """ - q1gate = Channels.LogicalMarkerChannel(label='q1-gate') - q1 = Qubit(label='q1', gate_chan=q1gate) - q1.pulse_params['length'] = 30e-9 - - q2gate = Channels.LogicalMarkerChannel(label='q2-gate') - q2 = Qubit(label='q2', gate_chan=q2gate) - q2.pulse_params['length'] = 30e-9 - - trigger = Channels.LogicalMarkerChannel(label='trigger') - measq1 = Channels.Measurement(label='M-q1', meas_type='autodyne') - measq1.trig_chan = trigger - - measq2 = Channels.Measurement(label='M-q2', meas_type='autodyne') - measq2.trig_chan = trigger - """ - cl = ChannelLibrary(library_file="./meas.yml") - - # ChannelLibrary(blank=True) # Create a blank ChannelLibrary - """ - ChannelLibraries.channelLib.channelDict = { - 'q1': q1, - 'q2': q2, - 'M-q1': measq1, - 'M-q2': measq2 - } - """ ChannelLibraries.channelLib.build_connectivity_graph() -ChannelLibrary(blank=True) setUp() q1 = QubitFactory('q1') @@ -74,7 +41,7 @@ def setUp(): # MEASA(q1, maddr=(10, 2)), # MEASA(q2, maddr=(20, 0)), - # LoadCmpTdm(0xfedc, 0x1234678), + LoadCmpTdm(0xfedc, 0x1234678), MajorityVote(10, 11), ] From a98e2c9a16db8b8985ba75c5429679fe69fbd9b5 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Tue, 23 Jan 2018 16:30:05 -0500 Subject: [PATCH 17/67] remove pad_with_nops; no longer needed --- QGL/drivers/APS2TDMPattern.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 358fd43d..fd2edc08 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -690,23 +690,6 @@ def synchronize_clocks(seqs): instr.startTime += instr.length instr.length = 0 -def pad_with_nops(list1, list2): - """ - Given two lists of instructions, pad the shorter until they - are equal length - """ - - len1 = len(list1) - len2 = len(list2) - - if len1 == len2: - return - elif len1 > len2: - for _ in range(len1 - len2): - list2.append(NoOp()) - else: - for _ in range(len2 - len1): - list1.append(NoOp()) def create_seq_instructions(seqs, offsets): ''' From 0f8ad8d835d27547f13e6090032ff0c3deb9835c Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Tue, 23 Jan 2018 17:39:45 -0500 Subject: [PATCH 18/67] backpatch label references in TDM instruction stream --- QGL/drivers/APS2TDMPattern.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index fd2edc08..7e602a52 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -1234,9 +1234,14 @@ def update_wf_library(filename, pulses, offsets): def tdm_instructions(seq): instructions = list() + # the backpatch table for labels + label2addr = dict() + label = None for s in seq: if isinstance(s, BlockLabel.BlockLabel): + label2addr[s.label] = len(instructions) + # carry label forward to next entry label = s continue @@ -1291,10 +1296,9 @@ def tdm_instructions(seq): elif isinstance(s, ControlFlow.Goto): instructions.append(Goto(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): instructions.append(Repeat(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): + elif isinstance(s, ControlFlow.LoadRepeat): instructions.append(Load(s.value - 1, label=label)) elif isinstance(s, TdmInstructions.LoadCmpTdmInstruction): @@ -1322,6 +1326,12 @@ def tdm_instructions(seq): # # instr_txt = str(Instruction.unflatten(instr_bits)) # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) + # backpatch any instructions that have target fields + # + for i in instructions: + if i.target: + i.payload = label2addr[i.target.label] + global _TDM_INSTRUCTIONS _TDM_INSTRUCTIONS = [i.flatten() for i in instructions] From b1259f2d4a576a920e6f60343f56218abc561717 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 24 Jan 2018 11:24:25 -0500 Subject: [PATCH 19/67] TDM surface-level instructions --- QGL/TdmInstructions.py | 105 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 QGL/TdmInstructions.py diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py new file mode 100644 index 00000000..27b386a4 --- /dev/null +++ b/QGL/TdmInstructions.py @@ -0,0 +1,105 @@ +''' +Copyright 2018 Raytheon BBN Technologies + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +# Instructions for the the prototype APS2-TDM hardware. + +class CustomInstruction(object): + + def __init__(self, name, in_addr, out_addr, **kwargs): + self.instruction = name + self.in_addr = in_addr + self.out_addr = out_addr + self.kwargs = kwargs + self.length = 0 + + def promote(self, ptype): + return self + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other): + return not self == other + + +def MajorityVote(in_addr, out_addr): + return CustomInstruction('MAJORITY', in_addr, out_addr) + +def MajorityMask(in_addr, out_addr): + return CustomInstruction('MAJORITYMASK', in_addr, out_addr) + +# TODO: the rest of the CUSTOM instructions + +class WriteAddrInstruction(object): + + def __init__(self, name, channel, modifier, addr, value, **kwargs): + self.instruction = name + self.channel = channel + self.invalid = modifier + self.addr = addr + self.value = value + self.kwargs = kwargs + self.length = 0 + + def promote(self, ptype): + return self + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other): + return not self == other + +def WriteAddr(addr, value, channel=None): + return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value) + +def Invalidate(addr, mask, channel=None): + return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask) + +def StoreMeas(addr, value, channel=None): + return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value) + +class LoadCmpTdmInstruction(object): + + def __init__(self, name, use_vram, addr, mask): + # TODO: sanity checks on input values + self.instruction = name + self.use_vram = use_vram + self.mask = mask + self.addr = addr + self.length = 0 + + def promote(self, ptype): + return self + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other): + return not self == other + + +def LoadCmpTdm(addr, mask): + return LoadCmpTdmInstruction('LOADCMPTDM', 1, addr, mask) + +# TODO: are there other variants of WriteAddr? + From 494f00aa7797ff01cfc54c48642d7af21c02a69f Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Wed, 24 Jan 2018 13:19:24 -0500 Subject: [PATCH 20/67] make the tdm code conditional on the device (The TDM instructions are defined in the APS2TDM generator, not anywhere else) --- QGL/Compiler.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index e5868026..a72ab5f1 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -461,11 +461,11 @@ def compile_to_hardware(seqs, for wire in old_wire_instrs.keys(): wire.instrument = old_wire_instrs[wire] - # FIXME: a one-off hack for the TDM - for s in physWires.keys(): - if s.label == 'BBNAPS1-12': - # print('----- %s' % str(s)) - tdm_i = pattern_module.tdm_instructions(physWires[s][0]) + if hasattr(pattern_module, 'tdm_instructions'): + # FIXME: a one-off hack for the TDM + for s in physWires.keys(): + if s.label == 'BBNAPS1-12': + pattern_module.tdm_instructions(physWires[s][0]) # Return the filenames we wrote return metafilepath From 64d6857a7128c39b48f6ef04963df62d8dd45548 Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Mon, 29 Jan 2018 16:35:08 -0500 Subject: [PATCH 21/67] splitting out the TDM compilation stuff --- QGL/Compiler.py | 6 ----- QGL/drivers/APS2TDMPattern.py | 49 +++++++++++++++++++++++++---------- QGL/n.py | 18 ++++++++----- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index a72ab5f1..f6a76c10 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -461,12 +461,6 @@ def compile_to_hardware(seqs, for wire in old_wire_instrs.keys(): wire.instrument = old_wire_instrs[wire] - if hasattr(pattern_module, 'tdm_instructions'): - # FIXME: a one-off hack for the TDM - for s in physWires.keys(): - if s.label == 'BBNAPS1-12': - pattern_module.tdm_instructions(physWires[s][0]) - # Return the filenames we wrote return metafilepath diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 7e602a52..d28c4482 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -27,12 +27,15 @@ import numpy as np from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils +from QGL import PulseSequencer from QGL.PatternUtils import hash_pulse, flatten from QGL import TdmInstructions # Python 2/3 compatibility: use 'int' that subclasses 'long' from builtins import int +logger = logging.getLogger(__name__) + #Some constants SAMPLING_RATE = 1.2e9 ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps @@ -1232,6 +1235,20 @@ def update_wf_library(filename, pulses, offsets): def tdm_instructions(seq): + """ + Generate the TDM instructions for the given sequence. + + This assumes that there is one instruction sequence, not + a list of them (as is generally the case elsewhere) + """ + + seq = list(flatten(copy(seq))) + + # turn into a loop, by appending GOTO(0) at end of the sequence + if not isinstance(seq[-1], ControlFlow.Goto): + seq.append(ControlFlow.Goto(BlockLabel.label(seq))) + logger.debug("Appending a GOTO at end to loop") + instructions = list() # the backpatch table for labels @@ -1258,12 +1275,12 @@ def tdm_instructions(seq): elif isinstance(s, TdmInstructions.WriteAddrInstruction): if s.instruction == 'INVALIDATE': - print('INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % (str(s.channel), s.addr, s.value)) instructions.append(Invalidate(s.addr, s.value, label=label)) elif s.instruction == 'WRITEADDR': - print('WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % (str(s.channel), s.addr, s.value)) instructions.append(WriteAddr(s.addr, s.value, label=label)) @@ -1306,17 +1323,28 @@ def tdm_instructions(seq): instructions.append( LoadCmpTdm(s.addr, s.mask, label=label)) - elif isinstance(s, Compiler.Waveform): + elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): - print('TDM GOT MEAS WAVEFORM WITH MADDR %s' % str(s.maddr)) instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) + + elif isinstance(s, PulseSequencer.PulseBlock): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) + + elif isinstance(s, list): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT LIST: %s' % str(s)) + else: # This isn't necessarily an error, because the TDM ignores a # lot of instructions, but until this is debugged it's handy # to see what's falling through. - # - print('OOPS: unhandled [%s]' % str(s)) + + # FIXME: We're missing a lot of control-flow instructions + print('OOPS: unhandled [%s]' % str(type(s))) # clear label label = None @@ -1332,11 +1360,4 @@ def tdm_instructions(seq): if i.target: i.payload = label2addr[i.target.label] - global _TDM_INSTRUCTIONS - _TDM_INSTRUCTIONS = [i.flatten() for i in instructions] - -def get_tdm_instructions(): - return _TDM_INSTRUCTIONS - - - + return [i.flatten() for i in instructions] diff --git a/QGL/n.py b/QGL/n.py index 53afdfeb..54e6f39e 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -29,21 +29,24 @@ def setUp(): q2 = QubitFactory('q2') seq = [ - Id(q1), + X(q1), + X(q2), WriteAddr(1, 7), MajorityMask(1, 0), Invalidate(addr=10, mask=0x7), - MEASA(q1, maddr=(10, 0)), - # MEASA(q1, maddr=(10, 1)), + MEASA(q1, maddr=(10, 4)), + MEASA(q1, maddr=(10, 1)), # MEASA(q1, maddr=(10, 2)), - # MEASA(q2, maddr=(20, 0)), + MEASA(q2, maddr=(10, 2)), - LoadCmpTdm(0xfedc, 0x1234678), + LoadCmpTdm(10, 7), MajorityVote(10, 11), + qif(0, [X90(q1), Y(q1), X(q1)], [Y90(q2)]), + ] # First, compile for the APS units. As a side effect, @@ -66,11 +69,13 @@ def setUp(): # So copy the input sequence each time we use it... aps_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/aps') -tdm_instr = QGL.drivers.APS2TDMPattern.get_tdm_instructions() aps_metadata = json.loads(open(aps_metafile).read()) tdm_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/tdm') tdm_metadata = json.loads(open(tdm_metafile).read()) + +tdm_instr = QGL.drivers.APS2TDMPattern.tdm_instructions(seq) + aps2_reader.replace_instructions( tdm_metadata['instruments']['BBNAPS1'], numpy.array(tdm_instr, dtype=np.uint64)) @@ -85,4 +90,3 @@ def setUp(): tdm_instr_from_file = aps2_reader.raw_instructions( tdm_metadata['instruments']['BBNAPS1']) pp_instructions('tdm', tdm_instr_from_file) - From f466945398c6630830d6beb1c8364eeb0f43276a Mon Sep 17 00:00:00 2001 From: Daniel Ellard Date: Tue, 30 Jan 2018 10:45:09 -0500 Subject: [PATCH 22/67] add conditions to TDM; rename TdmLoadCmp instruction The "TdmLoadCmp" instruction is actually available to the AWG; it really refers to the Vram. There may still be instructions not handled correctly by the TDM generator, but there are enough to show the basic process --- QGL/Compiler.py | 2 +- QGL/PatternUtils.py | 2 +- QGL/TdmInstructions.py | 6 +++--- QGL/__init__.py | 2 +- QGL/drivers/APS2TDMPattern.py | 21 +++++++++++++-------- QGL/n.py | 5 +++-- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index f6a76c10..15e00b7a 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -536,7 +536,7 @@ def compile_sequence(seq, channels=None): if (isinstance(block, ControlFlow.ControlInstruction) or isinstance(block, TdmInstructions.WriteAddrInstruction) or isinstance(block, TdmInstructions.CustomInstruction) or - isinstance(block, TdmInstructions.LoadCmpTdmInstruction)): + isinstance(block, TdmInstructions.LoadCmpVramInstruction)): # Need to deal with attaching measurements and edges to control # instruction. Until we have a proper solution for that, we will # always broadcast control instructions to all channels diff --git a/QGL/PatternUtils.py b/QGL/PatternUtils.py index 9ab312e0..80f1fe50 100644 --- a/QGL/PatternUtils.py +++ b/QGL/PatternUtils.py @@ -138,7 +138,7 @@ def apply_gating_constraints(chan, linkList): (ControlFlow.ControlInstruction, BlockLabel.BlockLabel, TdmInstructions.CustomInstruction, TdmInstructions.WriteAddrInstruction, - TdmInstructions.LoadCmpTdmInstruction)): + TdmInstructions.LoadCmpVramInstruction)): if previousEntry: gateSeq.append(previousEntry) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index 27b386a4..bab8cc5e 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -76,7 +76,7 @@ def Invalidate(addr, mask, channel=None): def StoreMeas(addr, value, channel=None): return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value) -class LoadCmpTdmInstruction(object): +class LoadCmpVramInstruction(object): def __init__(self, name, use_vram, addr, mask): # TODO: sanity checks on input values @@ -98,8 +98,8 @@ def __ne__(self, other): return not self == other -def LoadCmpTdm(addr, mask): - return LoadCmpTdmInstruction('LOADCMPTDM', 1, addr, mask) +def LoadCmpVram(addr, mask): + return LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask) # TODO: are there other variants of WriteAddr? diff --git a/QGL/__init__.py b/QGL/__init__.py index f5cc7433..99f2b24f 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -10,4 +10,4 @@ from .Tomography import state_tomo, process_tomo from .Scheduler import schedule -from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpTdm +from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpVram diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index d28c4482..bd130097 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -82,6 +82,9 @@ GREATERTHAN = 0x2 LESSTHAN = 0x3 +CMPTABLE = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} + + # Whether we use PHASE_OFFSET modulation commands or bake it into waveform # Default to false as we usually don't have many variants USE_PHASE_OFFSET_INSTRUCTION = False @@ -462,7 +465,7 @@ def MajorityVote(in_addr, out_addr, label=None): def MajorityVoteMask(in_addr, out_addr, label=None): return Custom(in_addr, out_addr, 1, label=label) -def LoadCmpTdm(addr, mask, label=None): +def LoadCmpVram(addr, mask, label=None): header = LOADCMP << 4 payload = (1 << 48) | (mask << 16) | addr return Instruction(header, payload, label=label) @@ -721,8 +724,6 @@ def create_seq_instructions(seqs, offsets): # keep track of where we are in each sequence indexes = np.zeros(len(seqs), dtype=np.int64) - cmpTable = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} - # always start with SYNC (stealing label from beginning of sequence) # unless it is a subroutine (using last entry as return as tell) label = None @@ -760,7 +761,7 @@ def create_seq_instructions(seqs, offsets): isinstance(entry, BlockLabel.BlockLabel) or isinstance(entry, TdmInstructions.CustomInstruction) or isinstance(entry, TdmInstructions.WriteAddrInstruction) or - isinstance(entry, TdmInstructions.LoadCmpTdmInstruction)): + isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): if isinstance(entry, BlockLabel.BlockLabel): # carry label forward to next entry label = entry @@ -786,7 +787,7 @@ def create_seq_instructions(seqs, offsets): instructions.append(Load(entry.value - 1, label=label)) elif isinstance(entry, ControlFlow.ComparisonInstruction): # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(cmpTable[entry.operator], + instructions.append(Cmp(CMPTABLE[entry.operator], entry.value, label=label)) @@ -1318,10 +1319,10 @@ def tdm_instructions(seq): elif isinstance(s, ControlFlow.LoadRepeat): instructions.append(Load(s.value - 1, label=label)) - elif isinstance(s, TdmInstructions.LoadCmpTdmInstruction): - if s.instruction == 'LOADCMPTDM': + elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): + if s.instruction == 'LOADCMPVRAM': instructions.append( - LoadCmpTdm(s.addr, s.mask, label=label)) + LoadCmpVram(s.addr, s.mask, label=label)) elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): @@ -1338,6 +1339,10 @@ def tdm_instructions(seq): # If this happens, we are confused. print('FIXME: TDM GOT LIST: %s' % str(s)) + elif isinstance(s, ControlFlow.ComparisonInstruction): + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) + else: # This isn't necessarily an error, because the TDM ignores a # lot of instructions, but until this is debugged it's handy diff --git a/QGL/n.py b/QGL/n.py index 54e6f39e..bf72a5e9 100644 --- a/QGL/n.py +++ b/QGL/n.py @@ -42,10 +42,11 @@ def setUp(): # MEASA(q1, maddr=(10, 2)), MEASA(q2, maddr=(10, 2)), - LoadCmpTdm(10, 7), + LoadCmpVram(10, 7), MajorityVote(10, 11), - qif(0, [X90(q1), Y(q1), X(q1)], [Y90(q2)]), + LoadCmpVram(11, 1), + qif(0, [MEASA(q1, maddr=(12, 0)), X(q1)], [Y90(q2)]), ] From 3786480627c56c81bfac5d72fddbaadaca74dcf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 4 Jun 2018 17:59:08 -0400 Subject: [PATCH 23/67] Fix merge --- QGL/PulsePrimitives.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/QGL/PulsePrimitives.py b/QGL/PulsePrimitives.py index d9f1af6f..ef94fc98 100644 --- a/QGL/PulsePrimitives.py +++ b/QGL/PulsePrimitives.py @@ -745,14 +745,12 @@ def _MEAS(qubit, **kwargs): if 'amp' not in kwargs: ignoredStrParams.append('amp') meas_label = "MEAS_no_trig" if 'dig_trig' in kwargs and not kwargs['dig_trig'] else "MEAS" - return Pulse(meas_label, measChan, params, amp, 0.0, 0.0, ignoredStrParams) - if 'maddr' in kwargs: maddr = kwargs['maddr'] else: maddr = (-1, 0) - return Pulse("MEAS", measChan, params, + return Pulse(meas_label, measChan, params, amp, 0.0, 0.0, ignoredStrParams, maddr=maddr) ## Measurement operators From 37aba85f2f21ff13c464e378f7b69c915b407614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 4 Jun 2018 22:49:23 -0400 Subject: [PATCH 24/67] Add wait before first waveform --- QGL/drivers/APS2TDMPattern.py | 2087 +++++++++++++++++---------------- 1 file changed, 1045 insertions(+), 1042 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index bd130097..b83eca76 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -96,472 +96,472 @@ SEQFILE_PER_CHANNEL = False def get_empty_channel_set(): - return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} + return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} def get_seq_file_extension(): - return '.h5' + return '.h5' def is_compatible_file(filename): - with h5py.File(filename, 'r') as FID: - target = FID['/'].attrs['target hardware'] - if isinstance(target, str): - target = target.encode('utf-8') - if target == b'APS2': - return True - return False + with h5py.File(filename, 'r') as FID: + target = FID['/'].attrs['target hardware'] + if isinstance(target, str): + target = target.encode('utf-8') + if target == b'APS2': + return True + return False def create_wf_vector(wfLib, seqs): - ''' + ''' Helper function to create the wf vector and offsets into it. ''' - max_pts_needed = 0 - for wf in wfLib.values(): - if len(wf) == 1: - max_pts_needed += ADDRESS_UNIT - else: - max_pts_needed += len(wf) - - #If we have more than fits in cache we'll need to align and prefetch - need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE - - idx = 0 - - if not need_prefetch: - offsets = [{}] - cache_lines = [] - #if we can fit them all in just pack - wfVec = np.zeros(max_pts_needed, dtype=np.int16) - for key, wf in wfLib.items(): - #Clip the wf - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) - offsets[-1][key] = idx - idx += wf.size - - #Trim the waveform - wfVec.resize(idx) - - else: - #otherwise fill in one cache line at a time - CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 - wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) - offsets = [{}] - cache_lines = [] - for seq in seqs: - #go through sequence and see what we need to add - pts_to_add = 0 - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - pts_to_add += entry.length - - #If what we need to add spills over then add a line and start again - if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: - idx = int(CACHE_LINE_LENGTH * ( - (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) - wfVec = np.append(wfVec, - np.zeros(CACHE_LINE_LENGTH, - dtype=np.int16)) - offsets.append({}) - - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - wf = wfLib[sig] - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16( - MAX_WAVEFORM_VALUE * wf) - offsets[-1][sig] = idx - idx += wf.size - - cache_lines.append(int(idx // CACHE_LINE_LENGTH)) - - return wfVec, offsets, cache_lines + max_pts_needed = 0 + for wf in wfLib.values(): + if len(wf) == 1: + max_pts_needed += ADDRESS_UNIT + else: + max_pts_needed += len(wf) + + #If we have more than fits in cache we'll need to align and prefetch + need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE + + idx = 0 + + if not need_prefetch: + offsets = [{}] + cache_lines = [] + #if we can fit them all in just pack + wfVec = np.zeros(max_pts_needed, dtype=np.int16) + for key, wf in wfLib.items(): + #Clip the wf + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) + offsets[-1][key] = idx + idx += wf.size + + #Trim the waveform + wfVec.resize(idx) + + else: + #otherwise fill in one cache line at a time + CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 + wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) + offsets = [{}] + cache_lines = [] + for seq in seqs: + #go through sequence and see what we need to add + pts_to_add = 0 + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + pts_to_add += entry.length + + #If what we need to add spills over then add a line and start again + if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: + idx = int(CACHE_LINE_LENGTH * ( + (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) + wfVec = np.append(wfVec, + np.zeros(CACHE_LINE_LENGTH, + dtype=np.int16)) + offsets.append({}) + + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + wf = wfLib[sig] + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16( + MAX_WAVEFORM_VALUE * wf) + offsets[-1][sig] = idx + idx += wf.size + + cache_lines.append(int(idx // CACHE_LINE_LENGTH)) + + return wfVec, offsets, cache_lines class Instruction(object): - def __init__(self, header, payload, label=None, target=None): - self.header = header - self.payload = int(payload) - self.label = label - self.target = target - - @classmethod - def unflatten(cls, instr): - return cls(header=(int(instr) >> 56) & 0xff, - payload=int(instr) & 0xffffffffffffff) - - def __repr__(self): - return self.__str__() - - def __str__(self): - - opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", - "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "CUSTOM", "WRITEADDR", "NOP"] - - customOps = [ - "MajorityVote", "MajoritySetMask", # TODO there are others... - ] - - out = "{0} ".format(self.label) if self.label else "" - - instrOpCode = (self.header >> 4) & 0xf - out += opCodes[instrOpCode] - - if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( - instrOpCode == MODULATION): - if (instrOpCode == MARKER) or (instrOpCode == WFM): - out += "; engine={}, ".format((self.header >> 2) & 0x3) - else: - out += "; " - if self.header & 0x1: - out += "write=1 | " - else: - out += "write=0 | " - - if self.target: - out += " {}".format(self.target) - - if instrOpCode == WFM: - wfOpCode = (self.payload >> 46) & 0x3 - wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] - out += wfOpCodes[wfOpCode] - out += "; TA bit={}".format((self.payload >> 45) & 0x1) - out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) - out += ", addr = {}".format(self.payload & 2**24 - 1) - - # APS3/TDM modifier to use VRAM output - if self.payload & (1 << 48): - out += ", use_vram" - - elif instrOpCode == MARKER: - mrkOpCode = (self.payload >> 46) & 0x3 - mrkOpCodes = ["PLAY", "TRIG", "SYNC"] - out += mrkOpCodes[mrkOpCode] - out += "; state={}".format((self.payload >> 32) & 0x1) - out += ", count = {}".format(self.payload & 2**32 - 1) - - elif instrOpCode == MODULATION: - modulatorOpCode = (self.payload >> 45) & 0x7 - modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", - "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] - out += modulatorOpCodes[modulatorOpCode] - out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) - if modulatorOpCode == 0x0: - out += ", count={:d}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x3: - out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x5: - out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x7: - out += ", frame_change=0x{:08x}".format(self.payload & - 0xffffffff) - - elif instrOpCode == CMP: - cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] - cmpCode = (self.payload >> 8) & 0x3 - out += " | " + cmpCodes[cmpCode] - out += ", value = {}".format(self.payload & 0xff) - - elif any( - [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): - out += " | target addr = {}".format(self.payload & 2**26 - 1) - - elif instrOpCode == LOAD: - out += " | count = {}".format(self.payload) - - elif instrOpCode == WRITEADDR: - if (self.header & 0xf) == 1: - out += ' WriteAddr(addr=0x%x, mask=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - elif (self.header & 0xf) == 5: - out += ' StoreMeas(addr=0x%x, mapping=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - else: - out += ' Invalidate(addr=0x%x, value=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - - elif instrOpCode == CUSTOM: - out += ' %s(src=0x%x, dst=0x%x)' % ( - customOps[(self.payload >> 32) & 0xff], - (self.payload >> 16) & 0xffff, - self.payload & 0xffff) - - elif (instrOpCode == LOADCMP) and (self.payload != 0): - if self.payload & (1 << 48): - out += ' LoadCmp vram(addr=0x%x, mask=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - - return out - - def __eq__(self, other): - return self.header == other.header and self.payload == other.payload and self.label == other.label - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.header, self.payload, self.label)) - - @property - def address(self): - return self.payload & 0xffffffff # bottom 32-bits of payload - - @address.setter - def address(self, value): - self.payload |= value & 0xffffffff - - @property - def writeFlag(self): - return self.header & 0x1 - - @writeFlag.setter - def writeFlag(self, value): - self.header |= value & 0x1 - - @property - def opcode(self): - return self.header >> 4 - - def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffffff)) + def __init__(self, header, payload, label=None, target=None): + self.header = header + self.payload = int(payload) + self.label = label + self.target = target + + @classmethod + def unflatten(cls, instr): + return cls(header=(int(instr) >> 56) & 0xff, + payload=int(instr) & 0xffffffffffffff) + + def __repr__(self): + return self.__str__() + + def __str__(self): + + opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", + "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", + "CUSTOM", "WRITEADDR", "NOP"] + + customOps = [ + "MajorityVote", "MajoritySetMask", # TODO there are others... + ] + + out = "{0} ".format(self.label) if self.label else "" + + instrOpCode = (self.header >> 4) & 0xf + out += opCodes[instrOpCode] + + if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( + instrOpCode == MODULATION): + if (instrOpCode == MARKER) or (instrOpCode == WFM): + out += "; engine={}, ".format((self.header >> 2) & 0x3) + else: + out += "; " + if self.header & 0x1: + out += "write=1 | " + else: + out += "write=0 | " + + if self.target: + out += " {}".format(self.target) + + if instrOpCode == WFM: + wfOpCode = (self.payload >> 46) & 0x3 + wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] + out += wfOpCodes[wfOpCode] + out += "; TA bit={}".format((self.payload >> 45) & 0x1) + out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) + out += ", addr = {}".format(self.payload & 2**24 - 1) + + # APS3/TDM modifier to use VRAM output + if self.payload & (1 << 48): + out += ", use_vram" + + elif instrOpCode == MARKER: + mrkOpCode = (self.payload >> 46) & 0x3 + mrkOpCodes = ["PLAY", "TRIG", "SYNC"] + out += mrkOpCodes[mrkOpCode] + out += "; state={}".format((self.payload >> 32) & 0x1) + out += ", count = {}".format(self.payload & 2**32 - 1) + + elif instrOpCode == MODULATION: + modulatorOpCode = (self.payload >> 45) & 0x7 + modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", + "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] + out += modulatorOpCodes[modulatorOpCode] + out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) + if modulatorOpCode == 0x0: + out += ", count={:d}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x3: + out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x5: + out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x7: + out += ", frame_change=0x{:08x}".format(self.payload & + 0xffffffff) + + elif instrOpCode == CMP: + cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] + cmpCode = (self.payload >> 8) & 0x3 + out += " | " + cmpCodes[cmpCode] + out += ", value = {}".format(self.payload & 0xff) + + elif any( + [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): + out += " | target addr = {}".format(self.payload & 2**26 - 1) + + elif instrOpCode == LOAD: + out += " | count = {}".format(self.payload) + + elif instrOpCode == WRITEADDR: + if (self.header & 0xf) == 1: + out += ' WriteAddr(addr=0x%x, mask=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + elif (self.header & 0xf) == 5: + out += ' StoreMeas(addr=0x%x, mapping=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + else: + out += ' Invalidate(addr=0x%x, value=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + + elif instrOpCode == CUSTOM: + out += ' %s(src=0x%x, dst=0x%x)' % ( + customOps[(self.payload >> 32) & 0xff], + (self.payload >> 16) & 0xffff, + self.payload & 0xffff) + + elif (instrOpCode == LOADCMP) and (self.payload != 0): + if self.payload & (1 << 48): + out += ' LoadCmp vram(addr=0x%x, mask=0x%x)' % ( + self.payload & 0xffff, + (self.payload >> 16) & 0xffffffff) + + return out + + def __eq__(self, other): + return self.header == other.header and self.payload == other.payload and self.label == other.label + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.header, self.payload, self.label)) + + @property + def address(self): + return self.payload & 0xffffffff # bottom 32-bits of payload + + @address.setter + def address(self, value): + self.payload |= value & 0xffffffff + + @property + def writeFlag(self): + return self.header & 0x1 + + @writeFlag.setter + def writeFlag(self, value): + self.header |= value & 0x1 + + @property + def opcode(self): + return self.header >> 4 + + def flatten(self): + return int((self.header << 56) | (self.payload & 0xffffffffffffff)) def Waveform(addr, count, isTA, write=False, label=None): - header = (WFM << 4) | (0x3 << 2) | (write & - 0x1) #broadcast to both engines - count = int(count) - count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count - addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr - payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) - << TA_PAIR_BIT) | (count << 24) | addr - return Instruction(header, payload, label) + header = (WFM << 4) | (0x3 << 2) | (write & + 0x1) #broadcast to both engines + count = int(count) + count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count + addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr + payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) + << TA_PAIR_BIT) | (count << 24) | addr + return Instruction(header, payload, label) def WaveformPrefetch(addr): - header = (WFM << 4) | (0x3 << 2) | (0x1) - payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr - return Instruction(header, payload, None) + header = (WFM << 4) | (0x3 << 2) | (0x1) + payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr + return Instruction(header, payload, None) def Marker(sel, state, count, write=False, label=None): - header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) - count = int(count) - four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count - count_rem = count % ADDRESS_UNIT - if state == 0: - transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} - transition = transitionWords[count_rem] - else: - transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} - transition = transitionWords[count_rem] - payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( - (state & 0x1) << 32) | four_count - return Instruction(header, payload, label) + header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) + count = int(count) + four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count + count_rem = count % ADDRESS_UNIT + if state == 0: + transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} + transition = transitionWords[count_rem] + else: + transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} + transition = transitionWords[count_rem] + payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( + (state & 0x1) << 32) | four_count + return Instruction(header, payload, label) def Command(cmd, payload, write=False, label=None): - header = (cmd << 4) - if isinstance(payload, int): - instr = Instruction(header, payload, label) - else: - instr = Instruction(header, 0, label, target=payload) - instr.writeFlag = write - return instr + header = (cmd << 4) + if isinstance(payload, int): + instr = Instruction(header, payload, label) + else: + instr = Instruction(header, 0, label, target=payload) + instr.writeFlag = write + return instr def Sync(label=None): - return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) + return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) def Wait(label=None): - return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) + return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) def LoadCmp(label=None): - return Command(LOADCMP, 0, label=label) + return Command(LOADCMP, 0, label=label) def Cmp(op, value, label=None): - return Command(CMP, (op << 8) | (value & 0xff), label=label) + return Command(CMP, (op << 8) | (value & 0xff), label=label) def Goto(addr, label=None): - return Command(GOTO, addr, label=label) + return Command(GOTO, addr, label=label) def Call(addr, label=None): - return Command(CALL, addr, label=label) + return Command(CALL, addr, label=label) def Return(label=None): - return Command(RET, 0, label=label) + return Command(RET, 0, label=label) def Load(count, label=None): - return Command(LOAD, count, label=label) + return Command(LOAD, count, label=label) def Repeat(addr, label=None): - return Command(REPEAT, addr, label=label) + return Command(REPEAT, addr, label=label) def Prefetch(addr, label=None): - return Command(PREFETCH, addr) + return Command(PREFETCH, addr) def NoOp(): - return Instruction.unflatten(0xffffffffffffffff) + return Instruction.unflatten(0xffffffffffffffff) # APS3 prototype instructions def Invalidate(addr, mask, label=None): - header = WRITEADDR << 4 - payload = (mask << 16) | addr - return Instruction(header, payload, label=label) + header = WRITEADDR << 4 + payload = (mask << 16) | addr + return Instruction(header, payload, label=label) def WriteAddr(addr, value, label=None): - header = (WRITEADDR << 4) | 1 - payload = (value << 16) | addr - return Instruction(header, payload, label=label) + header = (WRITEADDR << 4) | 1 + payload = (value << 16) | addr + return Instruction(header, payload, label=label) def StoreMeas(addr, mapping, label=None): - header = (WRITEADDR << 4) | 5 - payload = (mapping << 16) | addr - return Instruction(header, payload, label=label) + header = (WRITEADDR << 4) | 5 + payload = (mapping << 16) | addr + return Instruction(header, payload, label=label) def Custom(in_addr, out_addr, custom_op, label=None): - header = CUSTOM << 4 - payload = (custom_op << 32) | (in_addr << 16) | out_addr - return Instruction(header, payload, label=label) + header = CUSTOM << 4 + payload = (custom_op << 32) | (in_addr << 16) | out_addr + return Instruction(header, payload, label=label) def MajorityVote(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 0, label=label) + return Custom(in_addr, out_addr, 0, label=label) def MajorityVoteMask(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 1, label=label) + return Custom(in_addr, out_addr, 1, label=label) def LoadCmpVram(addr, mask, label=None): - header = LOADCMP << 4 - payload = (1 << 48) | (mask << 16) | addr - return Instruction(header, payload, label=label) + header = LOADCMP << 4 + payload = (1 << 48) | (mask << 16) | addr + return Instruction(header, payload, label=label) def preprocess(seqs, shapeLib): - seqs = PatternUtils.convert_lengths_to_samples( - seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) - wfLib = build_waveforms(seqs, shapeLib) - inject_modulation_cmds(seqs) - return seqs, wfLib + seqs = PatternUtils.convert_lengths_to_samples( + seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) + wfLib = build_waveforms(seqs, shapeLib) + inject_modulation_cmds(seqs) + return seqs, wfLib def wf_sig(wf): - ''' + ''' Compute a signature of a Compiler.Waveform that identifies the relevant properties for two Waveforms to be considered "equal" in the waveform library. For example, we ignore length of TA waveforms. ''' - if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.amp) - else: - return (wf.amp, round(wf.phase * 2**13)) - else: - #TODO: why do we need the length? - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.key, wf.amp, wf.length) - else: - return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) + if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.amp) + else: + return (wf.amp, round(wf.phase * 2**13)) + else: + #TODO: why do we need the length? + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.key, wf.amp, wf.length) + else: + return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) class ModulationCommand(object): - """docstring for ModulationCommand""" - - def __init__(self, - instruction, - nco_select, - frequency=0, - phase=0, - length=0): - super(ModulationCommand, self).__init__() - self.instruction = instruction - self.nco_select = nco_select - self.frequency = frequency - self.phase = phase - self.length = length - - def __str__(self): - out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, - self.nco_select) - if self.instruction == "MODULATE": - out += ", length={})".format(self.length) - elif self.instruction == "SET_FREQ": - out += ", frequency={})".format(self.frequency) - elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": - out += ", phase={})".format(self.phase) - else: - out += ")" - return out - - def _repr_pretty_(self, p, cycle): - p.text(str(self)) - - def __repr__(self): - return str(self) - - def to_instruction(self, write_flag=True, label=None): - #Modulator op codes - MODULATOR_OP_OFFSET = 44 - NCO_SELECT_OP_OFFSET = 40 - MODULATION_CLOCK = 300e6 - - op_code_map = {"MODULATE": 0x0, - "RESET_PHASE": 0x2, - "SET_FREQ": 0x6, - "SET_PHASE": 0xa, - "UPDATE_FRAME": 0xe} - payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( - self.nco_select << NCO_SELECT_OP_OFFSET) - if self.instruction == "MODULATE": - #zero-indexed quad count - payload |= np.uint32(self.length / ADDRESS_UNIT - 1) - elif self.instruction == "SET_FREQ": - # frequencies can span -2 to 2 or 0 to 4 in unsigned - payload |= np.uint32( - (self.frequency / MODULATION_CLOCK if self.frequency > 0 else - self.frequency / MODULATION_CLOCK + 4) * 2**28) - elif (self.instruction == "SET_PHASE") | ( - self.instruction == "UPDATE_FRAME"): - #phases can span -0.5 to 0.5 or 0 to 1 in unsigned - payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) - - instr = Instruction(MODULATION << 4, payload, label) - instr.writeFlag = write_flag - return instr + """docstring for ModulationCommand""" + + def __init__(self, + instruction, + nco_select, + frequency=0, + phase=0, + length=0): + super(ModulationCommand, self).__init__() + self.instruction = instruction + self.nco_select = nco_select + self.frequency = frequency + self.phase = phase + self.length = length + + def __str__(self): + out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, + self.nco_select) + if self.instruction == "MODULATE": + out += ", length={})".format(self.length) + elif self.instruction == "SET_FREQ": + out += ", frequency={})".format(self.frequency) + elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": + out += ", phase={})".format(self.phase) + else: + out += ")" + return out + + def _repr_pretty_(self, p, cycle): + p.text(str(self)) + + def __repr__(self): + return str(self) + + def to_instruction(self, write_flag=True, label=None): + #Modulator op codes + MODULATOR_OP_OFFSET = 44 + NCO_SELECT_OP_OFFSET = 40 + MODULATION_CLOCK = 300e6 + + op_code_map = {"MODULATE": 0x0, + "RESET_PHASE": 0x2, + "SET_FREQ": 0x6, + "SET_PHASE": 0xa, + "UPDATE_FRAME": 0xe} + payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( + self.nco_select << NCO_SELECT_OP_OFFSET) + if self.instruction == "MODULATE": + #zero-indexed quad count + payload |= np.uint32(self.length / ADDRESS_UNIT - 1) + elif self.instruction == "SET_FREQ": + # frequencies can span -2 to 2 or 0 to 4 in unsigned + payload |= np.uint32( + (self.frequency / MODULATION_CLOCK if self.frequency > 0 else + self.frequency / MODULATION_CLOCK + 4) * 2**28) + elif (self.instruction == "SET_PHASE") | ( + self.instruction == "UPDATE_FRAME"): + #phases can span -0.5 to 0.5 or 0 to 1 in unsigned + payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) + + instr = Instruction(MODULATION << 4, payload, label) + instr.writeFlag = write_flag + return instr def inject_modulation_cmds(seqs): """ @@ -649,56 +649,56 @@ def inject_modulation_cmds(seqs): seqs[ct] = mod_seq def build_waveforms(seqs, shapeLib): - # apply amplitude (and optionally phase) and add the resulting waveforms to the library - wfLib = {} - for wf in flatten(seqs): - if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: - shape = wf.amp * shapeLib[wf.key] - if not USE_PHASE_OFFSET_INSTRUCTION: - shape *= np.exp(1j * wf.phase) - wfLib[wf_sig(wf)] = shape - return wfLib + # apply amplitude (and optionally phase) and add the resulting waveforms to the library + wfLib = {} + for wf in flatten(seqs): + if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: + shape = wf.amp * shapeLib[wf.key] + if not USE_PHASE_OFFSET_INSTRUCTION: + shape *= np.exp(1j * wf.phase) + wfLib[wf_sig(wf)] = shape + return wfLib def timestamp_entries(seq): - t = 0 - for ct in range(len(seq)): - seq[ct].startTime = t - t += seq[ct].length + t = 0 + for ct in range(len(seq)): + seq[ct].startTime = t + t += seq[ct].length def synchronize_clocks(seqs): - # Control-flow instructions (CFIs) must occur at the same time on all channels. - # Therefore, we need to "reset the clock" by synchronizing the accumulated - # time at each CFI to the largest value on any channel - syncInstructions = [list(filter( - lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) - for seq in seqs if seq] - - # Add length to control-flow instructions to make accumulated time match at end of CFI. - # Keep running tally of how much each channel has been shifted so far. - localShift = [0 for _ in syncInstructions] - for ct in range(len(syncInstructions[0])): - step = [seq[ct] for seq in syncInstructions] - endTime = max((s.startTime + shift - for s, shift in zip(step, localShift))) - for ct, s in enumerate(step): - s.length = endTime - (s.startTime + localShift[ct]) - # localShift[ct] += endTime - (s.startTime + localShift[ct]) - # the += and the last term cancel, therefore: - localShift[ct] = endTime - s.startTime - # re-timestamp to propagate changes across the sequences - for seq in seqs: - timestamp_entries(seq) - # then transfer the control flow "lengths" back into start times - for seq in syncInstructions: - for instr in seq: - instr.startTime += instr.length - instr.length = 0 + # Control-flow instructions (CFIs) must occur at the same time on all channels. + # Therefore, we need to "reset the clock" by synchronizing the accumulated + # time at each CFI to the largest value on any channel + syncInstructions = [list(filter( + lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) + for seq in seqs if seq] + + # Add length to control-flow instructions to make accumulated time match at end of CFI. + # Keep running tally of how much each channel has been shifted so far. + localShift = [0 for _ in syncInstructions] + for ct in range(len(syncInstructions[0])): + step = [seq[ct] for seq in syncInstructions] + endTime = max((s.startTime + shift + for s, shift in zip(step, localShift))) + for ct, s in enumerate(step): + s.length = endTime - (s.startTime + localShift[ct]) + # localShift[ct] += endTime - (s.startTime + localShift[ct]) + # the += and the last term cancel, therefore: + localShift[ct] = endTime - s.startTime + # re-timestamp to propagate changes across the sequences + for seq in seqs: + timestamp_entries(seq) + # then transfer the control flow "lengths" back into start times + for seq in syncInstructions: + for instr in seq: + instr.startTime += instr.length + instr.length = 0 def create_seq_instructions(seqs, offsets): - ''' + ''' Helper function to create instruction vector from an IR sequence and an offset dictionary keyed on the wf keys. @@ -709,660 +709,663 @@ def create_seq_instructions(seqs, offsets): all waveform and marker channels. ''' - # timestamp all entries before filtering (where we lose time information on control flow) - for seq in seqs: - timestamp_entries(seq) - - synchronize_clocks(seqs) - - # create (seq, startTime) pairs over all sequences - timeTuples = [] - for ct, seq in enumerate(seqs): - timeTuples += [(entry.startTime, ct) for entry in seq] - timeTuples.sort() - - # keep track of where we are in each sequence - indexes = np.zeros(len(seqs), dtype=np.int64) - - # always start with SYNC (stealing label from beginning of sequence) - # unless it is a subroutine (using last entry as return as tell) - label = None - instructions = [] - for ct, seq in enumerate(seqs): - if len(seq): - first_non_empty = ct - break - if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): - if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] - timeTuples.pop(0) - indexes[first_non_empty] += 1 - instructions.append(Sync(label=label)) - label = None - - while len(timeTuples) > 0: - #pop off all entries that have the same time - entries = [] - start_time = 0 - while True: - start_time, seq_idx = timeTuples.pop(0) - entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) - indexes[seq_idx] += 1 - next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 - if start_time != next_start_time: - break - - write_flags = [True] * len(entries) - for ct, (entry, seq_idx) in enumerate(entries): - - #use first non empty sequence for control flow - if seq_idx == first_non_empty and ( - isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel) or - isinstance(entry, TdmInstructions.CustomInstruction) or - isinstance(entry, TdmInstructions.WriteAddrInstruction) or - isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): - if isinstance(entry, BlockLabel.BlockLabel): - # carry label forward to next entry - label = entry - continue - # control flow instructions - elif isinstance(entry, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(entry, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - elif isinstance(entry, ControlFlow.Sync): - instructions.append(Sync(label=label)) - elif isinstance(entry, ControlFlow.Return): - instructions.append(Return(label=label)) - # target argument commands - elif isinstance(entry, ControlFlow.Goto): - instructions.append(Goto(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Call): - instructions.append(Call(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Repeat): - instructions.append(Repeat(entry.target, label=label)) - # value argument commands - elif isinstance(entry, ControlFlow.LoadRepeat): - instructions.append(Load(entry.value - 1, label=label)) - elif isinstance(entry, ControlFlow.ComparisonInstruction): - # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(CMPTABLE[entry.operator], - entry.value, - label=label)) - - # TDM instructions are ignored by the APS - elif isinstance(entry, TdmInstructions.CustomInstruction): - pass - elif isinstance(entry, TdmInstructions.WriteAddrInstruction): - pass - - continue - - if seq_idx == 0: - #analog - waveforms or modulation - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping Waveform entry of length %s!" % entry.length) - continue - - instructions.append(Waveform( - offsets[wf_sig(entry)], entry.length, - entry.isTimeAmp or entry.isZero, - write=write_flags[ct], label=label)) - elif isinstance(entry, ModulationCommand): - instructions.append(entry.to_instruction( - write_flag=write_flags[ct], - label=label)) - - else: # a marker engine - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping entry!") - continue - markerSel = seq_idx - 1 - state = not entry.isZero - instructions.append(Marker(markerSel, - state, - entry.length, - write=write_flags[ct], - label=label)) - - #clear label - label = None - - return instructions + # timestamp all entries before filtering (where we lose time information on control flow) + for seq in seqs: + timestamp_entries(seq) + + synchronize_clocks(seqs) + + # create (seq, startTime) pairs over all sequences + timeTuples = [] + for ct, seq in enumerate(seqs): + timeTuples += [(entry.startTime, ct) for entry in seq] + timeTuples.sort() + + # keep track of where we are in each sequence + indexes = np.zeros(len(seqs), dtype=np.int64) + + # always start with SYNC (stealing label from beginning of sequence) + # unless it is a subroutine (using last entry as return as tell) + label = None + instructions = [] + for ct, seq in enumerate(seqs): + if len(seq): + first_non_empty = ct + break + if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): + if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): + label = seqs[first_non_empty][0] + timeTuples.pop(0) + indexes[first_non_empty] += 1 + instructions.append(Sync(label=label)) + label = None + + while len(timeTuples) > 0: + #pop off all entries that have the same time + entries = [] + start_time = 0 + while True: + start_time, seq_idx = timeTuples.pop(0) + entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) + indexes[seq_idx] += 1 + next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 + if start_time != next_start_time: + break + + write_flags = [True] * len(entries) + for ct, (entry, seq_idx) in enumerate(entries): + + #use first non empty sequence for control flow + if seq_idx == first_non_empty and ( + isinstance(entry, ControlFlow.ControlInstruction) or + isinstance(entry, BlockLabel.BlockLabel) or + isinstance(entry, TdmInstructions.CustomInstruction) or + isinstance(entry, TdmInstructions.WriteAddrInstruction) or + isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): + if isinstance(entry, BlockLabel.BlockLabel): + # carry label forward to next entry + label = entry + continue + # control flow instructions + elif isinstance(entry, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(entry, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + elif isinstance(entry, ControlFlow.Sync): + instructions.append(Sync(label=label)) + elif isinstance(entry, ControlFlow.Return): + instructions.append(Return(label=label)) + # target argument commands + elif isinstance(entry, ControlFlow.Goto): + instructions.append(Goto(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Call): + instructions.append(Call(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Repeat): + instructions.append(Repeat(entry.target, label=label)) + # value argument commands + elif isinstance(entry, ControlFlow.LoadRepeat): + instructions.append(Load(entry.value - 1, label=label)) + elif isinstance(entry, ControlFlow.ComparisonInstruction): + # TODO modify Cmp operator to load from specified address + instructions.append(Cmp(CMPTABLE[entry.operator], + entry.value, + label=label)) + + # TDM instructions are ignored by the APS + elif isinstance(entry, TdmInstructions.CustomInstruction): + pass + elif isinstance(entry, TdmInstructions.WriteAddrInstruction): + pass + + continue + + if seq_idx == 0: + #analog - waveforms or modulation + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping Waveform entry of length %s!" % entry.length) + continue + + instructions.append(Waveform( + offsets[wf_sig(entry)], entry.length, + entry.isTimeAmp or entry.isZero, + write=write_flags[ct], label=label)) + elif isinstance(entry, ModulationCommand): + instructions.append(entry.to_instruction( + write_flag=write_flags[ct], + label=label)) + + else: # a marker engine + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping entry!") + continue + markerSel = seq_idx - 1 + state = not entry.isZero + instructions.append(Marker(markerSel, + state, + entry.length, + write=write_flags[ct], + label=label)) + + #clear label + label = None + + return instructions def create_instr_data(seqs, offsets, cache_lines): - ''' + ''' Constructs the complete instruction data vector, and does basic checks for validity. Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line ''' - logger = logging.getLogger(__name__) - logger.debug('') - - seq_instrs = [] - need_prefetch = len(cache_lines) > 0 - num_cache_lines = len(set(cache_lines)) - cache_line_changes = np.concatenate( - ([0], np.where(np.diff(cache_lines))[0] + 1)) - for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) - #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line - if need_prefetch and (ct in cache_line_changes): - next_cache_line = cache_lines[cache_line_changes[(np.where( - ct == cache_line_changes)[0][0] + 1) % len( - cache_line_changes)]] - seq_instrs[-1].insert(0, WaveformPrefetch(int( - next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - - #concatenate instructions - instructions = [] - subroutines_start = -1 - for ct, seq in enumerate(seq_instrs): - #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break - instructions += seq - - #if we have any subroutines then group in cache lines - if subroutines_start >= 0: - subroutine_instrs = [] - subroutine_cache_line = {} - CACHE_LINE_LENGTH = 128 - offset = 0 - for sub in seq_instrs[subroutines_start:]: - #TODO for now we don't properly handle prefetching mulitple cache lines - if len(sub) > CACHE_LINE_LENGTH: - warnings.warn( - "Subroutines longer than {} instructions may not be prefetched correctly") - #Don't unecessarily split across a cache line - if (len(sub) + offset > CACHE_LINE_LENGTH) and ( - len(sub) < CACHE_LINE_LENGTH): - pad_instrs = 128 - ((offset + 128) % 128) - subroutine_instrs += [NoOp()] * pad_instrs - offset = 0 - if offset == 0: - line_label = sub[0].label - subroutine_cache_line[sub[0].label] = line_label - subroutine_instrs += sub - offset += len(sub) % CACHE_LINE_LENGTH - logger.debug("Placed {} subroutines into {} cache lines".format( - len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // - CACHE_LINE_LENGTH)) - #inject prefetch commands before waits - wait_idx = [idx for idx, instr in enumerate(instructions) - if (instr.header >> 4) == WAIT] + [len(instructions)] - instructions_with_prefetch = instructions[:wait_idx[0]] - last_prefetch = None - for start, stop in zip(wait_idx[:-1], wait_idx[1:]): - call_targets = [instr.target for instr in instructions[start:stop] - if (instr.header >> 4) == CALL] - needed_lines = set() - for target in call_targets: - needed_lines.add(subroutine_cache_line[target]) - if len(needed_lines) > 8: - raise RuntimeError( - "Unable to prefetch more than 8 cache lines") - for needed_line in needed_lines: - if needed_line != last_prefetch: - instructions_with_prefetch.append(Prefetch(needed_line)) - last_prefetch = needed_line - instructions_with_prefetch += instructions[start:stop] - - instructions = instructions_with_prefetch - #pad out instruction vector to ensure circular cache never loads a subroutine - pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) - instructions += [NoOp()] * pad_instrs - - instructions += subroutine_instrs - - #turn symbols into integers addresses - resolve_symbols(instructions) - - assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ - 'Oops! too many instructions: {0}'.format(len(instructions)) - - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) + logger = logging.getLogger(__name__) + logger.debug('') + + seq_instrs = [] + need_prefetch = len(cache_lines) > 0 + num_cache_lines = len(set(cache_lines)) + cache_line_changes = np.concatenate( + ([0], np.where(np.diff(cache_lines))[0] + 1)) + for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): + seq_instrs.append(create_seq_instructions( + list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0])) + #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line + if need_prefetch and (ct in cache_line_changes): + next_cache_line = cache_lines[cache_line_changes[(np.where( + ct == cache_line_changes)[0][0] + 1) % len( + cache_line_changes)]] + seq_instrs[-1].insert(0, WaveformPrefetch(int( + next_cache_line * WAVEFORM_CACHE_SIZE / 2))) + #steal label + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None + + #concatenate instructions + instructions = [] + subroutines_start = -1 + for ct, seq in enumerate(seq_instrs): + #Use last instruction being return as mark of start of subroutines + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + instructions += seq + + #if we have any subroutines then group in cache lines + if subroutines_start >= 0: + subroutine_instrs = [] + subroutine_cache_line = {} + CACHE_LINE_LENGTH = 128 + offset = 0 + for sub in seq_instrs[subroutines_start:]: + #TODO for now we don't properly handle prefetching mulitple cache lines + if len(sub) > CACHE_LINE_LENGTH: + warnings.warn( + "Subroutines longer than {} instructions may not be prefetched correctly") + #Don't unecessarily split across a cache line + if (len(sub) + offset > CACHE_LINE_LENGTH) and ( + len(sub) < CACHE_LINE_LENGTH): + pad_instrs = 128 - ((offset + 128) % 128) + subroutine_instrs += [NoOp()] * pad_instrs + offset = 0 + if offset == 0: + line_label = sub[0].label + subroutine_cache_line[sub[0].label] = line_label + subroutine_instrs += sub + offset += len(sub) % CACHE_LINE_LENGTH + logger.debug("Placed {} subroutines into {} cache lines".format( + len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // + CACHE_LINE_LENGTH)) + #inject prefetch commands before waits + wait_idx = [idx for idx, instr in enumerate(instructions) + if (instr.header >> 4) == WAIT] + [len(instructions)] + instructions_with_prefetch = instructions[:wait_idx[0]] + last_prefetch = None + for start, stop in zip(wait_idx[:-1], wait_idx[1:]): + call_targets = [instr.target for instr in instructions[start:stop] + if (instr.header >> 4) == CALL] + needed_lines = set() + for target in call_targets: + needed_lines.add(subroutine_cache_line[target]) + if len(needed_lines) > 8: + raise RuntimeError( + "Unable to prefetch more than 8 cache lines") + for needed_line in needed_lines: + if needed_line != last_prefetch: + instructions_with_prefetch.append(Prefetch(needed_line)) + last_prefetch = needed_line + instructions_with_prefetch += instructions[start:stop] + + instructions = instructions_with_prefetch + #pad out instruction vector to ensure circular cache never loads a subroutine + pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) + instructions += [NoOp()] * pad_instrs + + instructions += subroutine_instrs + + #turn symbols into integers addresses + resolve_symbols(instructions) + + assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ + 'Oops! too many instructions: {0}'.format(len(instructions)) + + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) def resolve_symbols(seq): - symbols = {} - # create symbol look-up table - for ct, entry in enumerate(seq): - if entry.label and entry.label not in symbols: - symbols[entry.label] = ct - # then update - for entry in seq: - if entry.target: - entry.address = symbols[entry.target] + symbols = {} + # create symbol look-up table + for ct, entry in enumerate(seq): + if entry.label and entry.label not in symbols: + symbols[entry.label] = ct + # then update + for entry in seq: + if entry.target: + entry.address = symbols[entry.target] def compress_marker(markerLL): - ''' + ''' Compresses adjacent entries of the same state into single entries ''' - for seq in markerLL: - idx = 0 - while idx + 1 < len(seq): - if (isinstance(seq[idx], Compiler.Waveform) and - isinstance(seq[idx + 1], Compiler.Waveform) and - seq[idx].isZero == seq[idx + 1].isZero): + for seq in markerLL: + idx = 0 + while idx + 1 < len(seq): + if (isinstance(seq[idx], Compiler.Waveform) and + isinstance(seq[idx + 1], Compiler.Waveform) and + seq[idx].isZero == seq[idx + 1].isZero): - seq[idx].length += seq[idx + 1].length - del seq[idx + 1] - else: - idx += 1 + seq[idx].length += seq[idx + 1].length + del seq[idx + 1] + else: + idx += 1 def write_sequence_file(awgData, fileName): - ''' + ''' Main function to pack channel sequences into an APS2 h5 file. ''' - # Convert QGL IR into a representation that is closer to the hardware. - awgData['ch12']['linkList'], wfLib = preprocess( - awgData['ch12']['linkList'], awgData['ch12']['wfLib']) - - # compress marker data - for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: - if 'linkList' in awgData[field].keys(): - PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], - SAMPLING_RATE, 1, - Compiler.Waveform) - compress_marker(awgData[field]['linkList']) - else: - awgData[field]['linkList'] = [] - - #Create the waveform vectors - wfInfo = [] - wfInfo.append(create_wf_vector({key: wf.real - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - wfInfo.append(create_wf_vector({key: wf.imag - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - - if SAVE_WF_OFFSETS: - #create a set of all waveform signatures in offset dictionaries - #we could have multiple offsets for the same pulse becuase it could - #be repeated in multiple cache lines - wf_sigs = set() - for offset_dict in wfInfo[0][1]: - wf_sigs |= set(offset_dict.keys()) - #create dictionary linking entry labels (that's what we'll have later) with offsets - offsets = {} - for seq in awgData['ch12']['linkList']: - for entry in seq: - if len(wf_sigs) == 0: - break - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig in wf_sigs: - #store offsets and wavefor lib length - #time ampltidue entries are clamped to ADDRESS_UNIT - wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length - offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], - wf_length) - wf_sigs.discard(sig) - - #break out of outer loop too - if len(wf_sigs) == 0: - break - - #now pickle the label=>offsets - with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: - pickle.dump(offsets, FID) - - # build instruction vector - seq_data = [awgData[s]['linkList'] - for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] - instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) - - #Open the HDF5 file - if os.path.isfile(fileName): - os.remove(fileName) - with h5py.File(fileName, 'w') as FID: - FID['/'].attrs['Version'] = 4.0 - FID['/'].attrs['target hardware'] = 'APS2' - FID['/'].attrs['minimum firmware version'] = 4.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) - - #Create the groups and datasets - for chanct in range(2): - chanStr = '/chan_{0}'.format(chanct + 1) - chanGroup = FID.create_group(chanStr) - #Write the waveformLib to file - FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) - - #Write the instructions to channel 1 - if np.mod(chanct, 2) == 0: - FID.create_dataset(chanStr + '/instructions', - data=instructions) + # Convert QGL IR into a representation that is closer to the hardware. + awgData['ch12']['linkList'], wfLib = preprocess( + awgData['ch12']['linkList'], awgData['ch12']['wfLib']) + + # compress marker data + for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: + if 'linkList' in awgData[field].keys(): + PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], + SAMPLING_RATE, 1, + Compiler.Waveform) + compress_marker(awgData[field]['linkList']) + else: + awgData[field]['linkList'] = [] + + #Create the waveform vectors + wfInfo = [] + wfInfo.append(create_wf_vector({key: wf.real + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + wfInfo.append(create_wf_vector({key: wf.imag + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + + if SAVE_WF_OFFSETS: + #create a set of all waveform signatures in offset dictionaries + #we could have multiple offsets for the same pulse becuase it could + #be repeated in multiple cache lines + wf_sigs = set() + for offset_dict in wfInfo[0][1]: + wf_sigs |= set(offset_dict.keys()) + #create dictionary linking entry labels (that's what we'll have later) with offsets + offsets = {} + for seq in awgData['ch12']['linkList']: + for entry in seq: + if len(wf_sigs) == 0: + break + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig in wf_sigs: + #store offsets and wavefor lib length + #time ampltidue entries are clamped to ADDRESS_UNIT + wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length + offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], + wf_length) + wf_sigs.discard(sig) + + #break out of outer loop too + if len(wf_sigs) == 0: + break + + #now pickle the label=>offsets + with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: + pickle.dump(offsets, FID) + + # build instruction vector + seq_data = [awgData[s]['linkList'] + for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] + instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) + + #Open the HDF5 file + if os.path.isfile(fileName): + os.remove(fileName) + with h5py.File(fileName, 'w') as FID: + FID['/'].attrs['Version'] = 4.0 + FID['/'].attrs['target hardware'] = 'APS2' + FID['/'].attrs['minimum firmware version'] = 4.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) + + #Create the groups and datasets + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + #Write the waveformLib to file + FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) + + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', + data=instructions) def read_sequence_file(fileName): - """ + """ Reads a HDF5 sequence file and returns a dictionary of lists. Dictionary keys are channel strings such as ch1, ch12m1 Lists are or tuples of time-amplitude pairs (time, output) """ - chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', - 'mod_phase'] - seqs = {ch: [] for ch in chanStrs} - - def start_new_seq(): - for ct, ch in enumerate(chanStrs): - if (ct < 2) or (ct == 6): - #analog or modulation channel - seqs[ch].append([]) - else: - #marker channel - seqs[ch].append([]) - - with h5py.File(fileName, 'r') as FID: - file_version = FID["/"].attrs["Version"] - wf_lib = {} - wf_lib['ch1'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() - wf_lib['ch2'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() - instructions = FID['/chan_1/instructions'].value.flatten() - NUM_NCO = 2 - freq = np.zeros(NUM_NCO) #radians per timestep - phase = np.zeros(NUM_NCO) - frame = np.zeros(NUM_NCO) - next_freq = np.zeros(NUM_NCO) - next_phase = np.zeros(NUM_NCO) - next_frame = np.zeros(NUM_NCO) - accumulated_phase = np.zeros(NUM_NCO) - reset_flag = [False]*NUM_NCO - - for data in instructions: - instr = Instruction.unflatten(data) - - modulator_opcode = instr.payload >> 44 - - #update phases at these boundaries - if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( - (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): - for ct in range(NUM_NCO): - if reset_flag[ct]: - #would expect this to be zero but this is first non-zero point - accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT - reset_flag[ct] = False - freq[:] = next_freq[:] - phase[:] = next_phase[:] - frame[:] = next_frame[:] - - #Assume new sequence at every WAIT - if instr.opcode == WAIT: - start_new_seq() - - elif instr.opcode == WFM and (( - (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): - addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT - count = (instr.payload >> 24) & 0xfffff - count = (count + 1) * ADDRESS_UNIT - isTA = (instr.payload >> 45) & 0x1 - chan_select_bits = ((instr.header >> 2) & 0x1, - (instr.header >> 3) & 0x1) - #On older firmware we broadcast by default whereas on newer we respect the engine select - for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): - if (file_version < 4) or select_bit: - if isTA: - seqs[chan][-1].append((count, wf_lib[chan][addr])) - else: - for sample in wf_lib[chan][addr:addr + count]: - seqs[chan][-1].append((1, sample)) - - elif instr.opcode == MARKER: - chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) - count = instr.payload & 0xffffffff - count = (count + 1) * ADDRESS_UNIT - state = (instr.payload >> 32) & 0x1 - seqs[chan][-1].append((count, state)) - - elif instr.opcode == MODULATION: - # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} - nco_select_bits = (instr.payload >> 40) & 0xf - if modulator_opcode == 0x0: - #modulate - count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT - nco_select = {0b0001: 0, - 0b0010: 1, - 0b0100: 2, - 0b1000: 3}[nco_select_bits] - seqs['mod_phase'][-1] = np.append( - seqs['mod_phase'][-1], freq[nco_select] * - np.arange(count) + accumulated_phase[nco_select] + - phase[nco_select] + frame[nco_select]) - accumulated_phase += count * freq - else: - phase_rad = 2 * np.pi * (instr.payload & - 0xffffffff) / 2**28 - for ct in range(NUM_NCO): - if (nco_select_bits >> ct) & 0x1: - if modulator_opcode == 0x2: - #reset - next_phase[ct] = 0 - next_frame[ct] = 0 - reset_flag[ct] = True - elif modulator_opcode == 0x6: - #set frequency - next_freq[ct] = phase_rad / ADDRESS_UNIT - elif modulator_opcode == 0xa: - #set phase - next_phase[ct] = phase_rad - elif modulator_opcode == 0xe: - #update frame - next_frame[ct] += phase_rad - - #Apply modulation if we have any - for ct, ( - ch1, ch2, mod_phase - ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): - if len(mod_phase): - #only really works if ch1, ch2 are broadcast together - mod_ch1 = [] - mod_ch2 = [] - cum_time = 0 - for ((time_ch1, amp_ch1), - (time_ch2, amp_ch2)) in zip(ch1, ch2): - if (amp_ch1 != 0) or (amp_ch2 != 0): - assert time_ch1 == time_ch2 - if time_ch1 == 1: - #single timestep - modulated = np.exp(1j * mod_phase[cum_time]) * ( - amp_ch1 + 1j * amp_ch2) - mod_ch1.append((1, modulated.real)) - mod_ch2.append((1, modulated.imag)) - else: - #expand TA - modulated = np.exp( - 1j * - mod_phase[cum_time:cum_time + time_ch1]) * ( - amp_ch1 + 1j * amp_ch2) - for val in modulated: - mod_ch1.append((1, val.real)) - mod_ch2.append((1, val.imag)) - else: - mod_ch1.append((time_ch1, amp_ch1)) - mod_ch2.append((time_ch2, amp_ch2)) - - cum_time += time_ch1 - seqs['ch1'][ct] = mod_ch1 - seqs['ch2'][ct] = mod_ch2 - - del seqs['mod_phase'] - - return seqs + chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', + 'mod_phase'] + seqs = {ch: [] for ch in chanStrs} + + def start_new_seq(): + for ct, ch in enumerate(chanStrs): + if (ct < 2) or (ct == 6): + #analog or modulation channel + seqs[ch].append([]) + else: + #marker channel + seqs[ch].append([]) + + with h5py.File(fileName, 'r') as FID: + file_version = FID["/"].attrs["Version"] + wf_lib = {} + wf_lib['ch1'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() + wf_lib['ch2'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() + instructions = FID['/chan_1/instructions'].value.flatten() + NUM_NCO = 2 + freq = np.zeros(NUM_NCO) #radians per timestep + phase = np.zeros(NUM_NCO) + frame = np.zeros(NUM_NCO) + next_freq = np.zeros(NUM_NCO) + next_phase = np.zeros(NUM_NCO) + next_frame = np.zeros(NUM_NCO) + accumulated_phase = np.zeros(NUM_NCO) + reset_flag = [False]*NUM_NCO + + for data in instructions: + instr = Instruction.unflatten(data) + + modulator_opcode = instr.payload >> 44 + + #update phases at these boundaries + if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( + (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): + for ct in range(NUM_NCO): + if reset_flag[ct]: + #would expect this to be zero but this is first non-zero point + accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT + reset_flag[ct] = False + freq[:] = next_freq[:] + phase[:] = next_phase[:] + frame[:] = next_frame[:] + + #Assume new sequence at every WAIT + if instr.opcode == WAIT: + start_new_seq() + + elif instr.opcode == WFM and (( + (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): + addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT + count = (instr.payload >> 24) & 0xfffff + count = (count + 1) * ADDRESS_UNIT + isTA = (instr.payload >> 45) & 0x1 + chan_select_bits = ((instr.header >> 2) & 0x1, + (instr.header >> 3) & 0x1) + #On older firmware we broadcast by default whereas on newer we respect the engine select + for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): + if (file_version < 4) or select_bit: + if isTA: + seqs[chan][-1].append((count, wf_lib[chan][addr])) + else: + for sample in wf_lib[chan][addr:addr + count]: + seqs[chan][-1].append((1, sample)) + + elif instr.opcode == MARKER: + chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) + count = instr.payload & 0xffffffff + count = (count + 1) * ADDRESS_UNIT + state = (instr.payload >> 32) & 0x1 + seqs[chan][-1].append((count, state)) + + elif instr.opcode == MODULATION: + # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} + nco_select_bits = (instr.payload >> 40) & 0xf + if modulator_opcode == 0x0: + #modulate + count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT + nco_select = {0b0001: 0, + 0b0010: 1, + 0b0100: 2, + 0b1000: 3}[nco_select_bits] + seqs['mod_phase'][-1] = np.append( + seqs['mod_phase'][-1], freq[nco_select] * + np.arange(count) + accumulated_phase[nco_select] + + phase[nco_select] + frame[nco_select]) + accumulated_phase += count * freq + else: + phase_rad = 2 * np.pi * (instr.payload & + 0xffffffff) / 2**28 + for ct in range(NUM_NCO): + if (nco_select_bits >> ct) & 0x1: + if modulator_opcode == 0x2: + #reset + next_phase[ct] = 0 + next_frame[ct] = 0 + reset_flag[ct] = True + elif modulator_opcode == 0x6: + #set frequency + next_freq[ct] = phase_rad / ADDRESS_UNIT + elif modulator_opcode == 0xa: + #set phase + next_phase[ct] = phase_rad + elif modulator_opcode == 0xe: + #update frame + next_frame[ct] += phase_rad + + #Apply modulation if we have any + for ct, ( + ch1, ch2, mod_phase + ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): + if len(mod_phase): + #only really works if ch1, ch2 are broadcast together + mod_ch1 = [] + mod_ch2 = [] + cum_time = 0 + for ((time_ch1, amp_ch1), + (time_ch2, amp_ch2)) in zip(ch1, ch2): + if (amp_ch1 != 0) or (amp_ch2 != 0): + assert time_ch1 == time_ch2 + if time_ch1 == 1: + #single timestep + modulated = np.exp(1j * mod_phase[cum_time]) * ( + amp_ch1 + 1j * amp_ch2) + mod_ch1.append((1, modulated.real)) + mod_ch2.append((1, modulated.imag)) + else: + #expand TA + modulated = np.exp( + 1j * + mod_phase[cum_time:cum_time + time_ch1]) * ( + amp_ch1 + 1j * amp_ch2) + for val in modulated: + mod_ch1.append((1, val.real)) + mod_ch2.append((1, val.imag)) + else: + mod_ch1.append((time_ch1, amp_ch1)) + mod_ch2.append((time_ch2, amp_ch2)) + + cum_time += time_ch1 + seqs['ch1'][ct] = mod_ch1 + seqs['ch2'][ct] = mod_ch2 + + del seqs['mod_phase'] + + return seqs def update_wf_library(filename, pulses, offsets): - """ + """ Update a H5 waveform library in place give an iterable of (pulseName, pulse) tuples and offsets into the waveform library. """ - assert USE_PHASE_OFFSET_INSTRUCTION == False - #load the h5 file - with h5py.File(filename) as FID: - for label, pulse in pulses.items(): - #create a new waveform - if pulse.isTimeAmp: - shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) - else: - shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape - try: - length = offsets[label][1] - except KeyError: - print("\t{} not found in offsets so skipping".format(pulse)) - continue - for offset in offsets[label][0]: - print("\tUpdating {} at offset {}".format(pulse, offset)) - FID['/chan_1/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.real) - FID['/chan_2/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.imag) + assert USE_PHASE_OFFSET_INSTRUCTION == False + #load the h5 file + with h5py.File(filename) as FID: + for label, pulse in pulses.items(): + #create a new waveform + if pulse.isTimeAmp: + shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) + else: + shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape + try: + length = offsets[label][1] + except KeyError: + print("\t{} not found in offsets so skipping".format(pulse)) + continue + for offset in offsets[label][0]: + print("\tUpdating {} at offset {}".format(pulse, offset)) + FID['/chan_1/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.real) + FID['/chan_2/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.imag) def tdm_instructions(seq): - """ - Generate the TDM instructions for the given sequence. - - This assumes that there is one instruction sequence, not - a list of them (as is generally the case elsewhere) - """ - - seq = list(flatten(copy(seq))) - - # turn into a loop, by appending GOTO(0) at end of the sequence - if not isinstance(seq[-1], ControlFlow.Goto): - seq.append(ControlFlow.Goto(BlockLabel.label(seq))) - logger.debug("Appending a GOTO at end to loop") - - instructions = list() - - # the backpatch table for labels - label2addr = dict() - - label = None - for s in seq: - if isinstance(s, BlockLabel.BlockLabel): - label2addr[s.label] = len(instructions) - - # carry label forward to next entry - label = s - continue - - # FIXME: not sure if this is right... - # Need to put a SYNC at the beginning - if len(instructions) == 0: - instructions.append(Sync(label=label)) - - elif isinstance(s, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(s, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - - elif isinstance(s, TdmInstructions.WriteAddrInstruction): - if s.instruction == 'INVALIDATE': - print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(Invalidate(s.addr, s.value, label=label)) - - elif s.instruction == 'WRITEADDR': - print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(WriteAddr(s.addr, s.value, label=label)) - - elif s.instruction == 'STOREMEAS': - # TODO: STOREMEAS only happens on the TDM, right? - print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(StoreMeas(s.addr, s.value, label=label)) - else: - print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % - (s.instruction, str(s.channel), - s.addr, s.value)) - continue - - elif isinstance(s, TdmInstructions.CustomInstruction): - - if s.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVote(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVoteMask(s.in_addr, s.out_addr, label=label)) - else: - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (s.instruction, s.in_addr, s.out_addr)) - - elif isinstance(s, ControlFlow.Goto): - instructions.append(Goto(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): - instructions.append(Repeat(s.target, label=label)) - elif isinstance(s, ControlFlow.LoadRepeat): - instructions.append(Load(s.value - 1, label=label)) - - elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): - if s.instruction == 'LOADCMPVRAM': - instructions.append( - LoadCmpVram(s.addr, s.mask, label=label)) - - elif isinstance(s, PulseSequencer.Pulse): - if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) - - elif isinstance(s, PulseSequencer.PulseBlock): - # FIXME: - # If this happens, we are confused. - print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) - - elif isinstance(s, list): - # FIXME: - # If this happens, we are confused. - print('FIXME: TDM GOT LIST: %s' % str(s)) - - elif isinstance(s, ControlFlow.ComparisonInstruction): - instructions.append( - Cmp(CMPTABLE[s.operator], s.value, label=label)) - - else: - # This isn't necessarily an error, because the TDM ignores a - # lot of instructions, but until this is debugged it's handy - # to see what's falling through. - - # FIXME: We're missing a lot of control-flow instructions - print('OOPS: unhandled [%s]' % str(type(s))) - - # clear label - label = None - - # for i in range(len(instructions)): - # instr_bits = instructions[i].flatten() - # # instr_txt = str(Instruction.unflatten(instr_bits)) - # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) - - # backpatch any instructions that have target fields - # - for i in instructions: - if i.target: - i.payload = label2addr[i.target.label] - - return [i.flatten() for i in instructions] + """ + Generate the TDM instructions for the given sequence. + + This assumes that there is one instruction sequence, not + a list of them (as is generally the case elsewhere). FIXME + """ + + seq = list(flatten(copy(seq))) + + # turn into a loop, by appending GOTO(0) at end of the sequence + if not isinstance(seq[-1], ControlFlow.Goto): + seq.append(ControlFlow.Goto(BlockLabel.label(seq))) + logger.debug("Appending a GOTO at end to loop") + # add a WAIT before the first waveform FIXME: there must be a more efficient way + if ControlFlow.Wait not in seq: + ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) + seq.insert(ind_wait, ControlFlow.Wait()) + + instructions = list() + + # the backpatch table for labels + label2addr = dict() + + label = None + for s in seq: + if isinstance(s, BlockLabel.BlockLabel): + label2addr[s.label] = len(instructions) + + # carry label forward to next entry + label = s + continue + + # FIXME: not sure if this is right... + # Need to put a SYNC at the beginning + if len(instructions) == 0: + instructions.append(Sync(label=label)) + + elif isinstance(s, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(s, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + + elif isinstance(s, TdmInstructions.WriteAddrInstruction): + if s.instruction == 'INVALIDATE': + print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(Invalidate(s.addr, s.value, label=label)) + + elif s.instruction == 'WRITEADDR': + print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(WriteAddr(s.addr, s.value, label=label)) + + elif s.instruction == 'STOREMEAS': + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(StoreMeas(s.addr, s.value, label=label)) + else: + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (s.instruction, str(s.channel), + s.addr, s.value)) + continue + + elif isinstance(s, TdmInstructions.CustomInstruction): + + if s.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVote(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + else: #TODO: add decoder + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (s.instruction, s.in_addr, s.out_addr)) + + elif isinstance(s, ControlFlow.Goto): + instructions.append(Goto(s.target, label=label)) + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Repeat(s.target, label=label)) + elif isinstance(s, ControlFlow.LoadRepeat): + instructions.append(Load(s.value - 1, label=label)) + + elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): + if s.instruction == 'LOADCMPVRAM': + instructions.append( + LoadCmpVram(s.addr, s.mask, label=label)) + + elif isinstance(s, PulseSequencer.Pulse): + if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) + + elif isinstance(s, PulseSequencer.PulseBlock): + # FIXME: + # If this happens, we are confused. This could be a block containing multiple measurements, FIXME! + print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) + + elif isinstance(s, list): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT LIST: %s' % str(s)) + + elif isinstance(s, ControlFlow.ComparisonInstruction): + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) + + else: + # This isn't typically an error, because the TDM ignores a + # lot of instructions, but until this is debugged it's handy + # to see what's falling through. + + # FIXME: We're missing a lot of control-flow instructions + print('OOPS: unhandled [%s]' % str(type(s))) + + # clear label + label = None + + # for i in range(len(instructions)): + # instr_bits = instructions[i].flatten() + # # instr_txt = str(Instruction.unflatten(instr_bits)) + # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) + + # backpatch any instructions that have target fields + # + for i in instructions: + if i.target: + i.payload = label2addr[i.target.label] + + return [i.flatten() for i in instructions] From ddd85b58651416f2aa6c0f329cc9c280287b647e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 4 Jun 2018 23:08:38 -0400 Subject: [PATCH 25/67] :bug: Sync should not replace the first instruction --- QGL/drivers/APS2TDMPattern.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index b83eca76..5de50229 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -1244,7 +1244,10 @@ def tdm_instructions(seq): """ seq = list(flatten(copy(seq))) + instructions = list() + # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label + instructions.append(Sync(label=seq[0])) # turn into a loop, by appending GOTO(0) at end of the sequence if not isinstance(seq[-1], ControlFlow.Goto): seq.append(ControlFlow.Goto(BlockLabel.label(seq))) @@ -1254,7 +1257,6 @@ def tdm_instructions(seq): ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) seq.insert(ind_wait, ControlFlow.Wait()) - instructions = list() # the backpatch table for labels label2addr = dict() @@ -1268,12 +1270,7 @@ def tdm_instructions(seq): label = s continue - # FIXME: not sure if this is right... - # Need to put a SYNC at the beginning - if len(instructions) == 0: - instructions.append(Sync(label=label)) - - elif isinstance(s, ControlFlow.Wait): + if isinstance(s, ControlFlow.Wait): instructions.append(Wait(label=label)) elif isinstance(s, ControlFlow.LoadCmp): instructions.append(LoadCmp(label=label)) From 0bc62c1aa38a1ead8b8997618a83e2f3e840b8d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 5 Jun 2018 11:42:05 -0400 Subject: [PATCH 26/67] Merge BD instructions --- QGL/drivers/APS2TDMPattern.py | 90 +++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 30 deletions(-) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py index 5de50229..8e52ac4e 100644 --- a/QGL/drivers/APS2TDMPattern.py +++ b/QGL/drivers/APS2TDMPattern.py @@ -63,7 +63,7 @@ PREFETCH = 0xC NOP = 0XF -# APS3 prototype +# # APS3 prototype CUSTOM = 0xD INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode WRITEADDR = 0xE @@ -84,6 +84,13 @@ CMPTABLE = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} +# custom OP_CODES +TDM_MAJORITY_VOTE = 0 +TDM_MAJORITY_VOTE_SET_MASK = 1 +TDM_TSM_SET_ROUNDS = 2 +TDM_TSM = 3 + +TDM_CUSTOM_DECODE = ["TDM_MAJORITY_VOTE", "TDM_MAJORITY_VOTE_SET_MASK", "TDM_TSM_SET_ROUNDS", "TDM_TSM"] # Whether we use PHASE_OFFSET modulation commands or bake it into waveform # Default to false as we usually don't have many variants @@ -220,6 +227,9 @@ def __str__(self): "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", "CUSTOM", "WRITEADDR", "NOP"] + # list of opCodes where the reprenstation will change + excludeList = ["WRITEADDR", "LOADCMP"] + customOps = [ "MajorityVote", "MajoritySetMask", # TODO there are others... ] @@ -227,7 +237,11 @@ def __str__(self): out = "{0} ".format(self.label) if self.label else "" instrOpCode = (self.header >> 4) & 0xf - out += opCodes[instrOpCode] + + opCodeStr = opCodes[instrOpCode] + + if opCodeStr not in excludeList: + out += opCodeStr if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( instrOpCode == MODULATION): @@ -251,9 +265,9 @@ def __str__(self): out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) out += ", addr = {}".format(self.payload & 2**24 - 1) - # APS3/TDM modifier to use VRAM output - if self.payload & (1 << 48): - out += ", use_vram" + # # APS3/TDM modifier to use VRAM output + # if self.payload & (1 << 48): + # out += ", use_vram" elif instrOpCode == MARKER: mrkOpCode = (self.payload >> 46) & 0x3 @@ -291,32 +305,48 @@ def __str__(self): elif instrOpCode == LOAD: out += " | count = {}".format(self.payload) - elif instrOpCode == WRITEADDR: - if (self.header & 0xf) == 1: - out += ' WriteAddr(addr=0x%x, mask=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - elif (self.header & 0xf) == 5: - out += ' StoreMeas(addr=0x%x, mapping=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - else: - out += ' Invalidate(addr=0x%x, value=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) - elif instrOpCode == CUSTOM: - out += ' %s(src=0x%x, dst=0x%x)' % ( - customOps[(self.payload >> 32) & 0xff], - (self.payload >> 16) & 0xffff, - self.payload & 0xffff) - - elif (instrOpCode == LOADCMP) and (self.payload != 0): - if self.payload & (1 << 48): - out += ' LoadCmp vram(addr=0x%x, mask=0x%x)' % ( - self.payload & 0xffff, - (self.payload >> 16) & 0xffffffff) + store_addr = self.payload & 0xFFFF + load_addr = (self.payload >> 16) & 0xFFFF + instruction = (self.payload >> 32) & 0xFF + instructionAPS = TDM_CUSTOM_DECODE[instruction] + out += " | instruction = {0} ({1}), load_addr = 0x{2:0x}, store_addr = 0x{3:0x}".format(instruction, instructionAPS, load_addr, store_addr) + elif instrOpCode == WRITEADDR: + addr = self.payload & 0xFFFF + value = (self.payload >> 16) & 0xFFFFFFFF + invalidate = not (self.header & 0x1) + mapTrigger = (self.header >> 2) & 0x1 + writeCrossbar = (self.header >> 1) & 0x1 + + instrStr = "WRITEADDR " + valueType = "value" + + if invalidate: + instrStr = "INVALIDATE" + valueType = "valid_mask" + + if mapTrigger: + instrStr = "STOREMEAS" + valueType = "mapping" + + if writeCrossbar: + instrStr = "WRITECB" + valuetype = "mapping" + addr = (self.payload >> 16) & 0xFFFF + value = (self.payload >> 32) & 0xFFFF + + out += "{0} | addr = 0x{1:0x}, {2} = 0x{3:0x}".format(instrStr, addr, valueType, value) + + elif instrOpCode == LOADCMP: + addr = self.payload & 0xFFFF + mask = (self.payload >> 16) & 0xFFFF + use_ram = (self.payload >> 48) & 0x1 + if not use_ram: + out += "WAITMEAS" + else: + src = "RAM" + out += "LOADCMP | source = {0}, addr = 0x{1:0x}, read_mask = 0x{2:0x}".format(src, addr, mask) return out def __eq__(self, other): @@ -438,7 +468,7 @@ def Prefetch(addr, label=None): def NoOp(): return Instruction.unflatten(0xffffffffffffffff) -# APS3 prototype instructions +# QGL instructions def Invalidate(addr, mask, label=None): header = WRITEADDR << 4 payload = (mask << 16) | addr From db0ee4ce7495179b9c95e12c222772407cf0c771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 5 Jun 2018 22:01:06 -0400 Subject: [PATCH 27/67] Consolidate code in APS2Pattern Try to combine again drivers for APS2 and TDM. The compiler functions are separate. Some instructions (like Invalidate and LoadCmpVRam) work differently for APS2 and TDM. Explicit calls to LoadVRam can only be made on the APS2 for now. --- QGL/TdmInstructions.py | 26 +- QGL/drivers/APS2Pattern.py | 2011 ++++++++++++++++++-------------- QGL/drivers/APS2Pattern_bck.py | 1151 ++++++++++++++++++ QGL/drivers/APS2TDMPattern.py | 1398 ---------------------- 4 files changed, 2322 insertions(+), 2264 deletions(-) create mode 100644 QGL/drivers/APS2Pattern_bck.py delete mode 100644 QGL/drivers/APS2TDMPattern.py diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index bab8cc5e..f0af4633 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -47,13 +47,14 @@ def MajorityMask(in_addr, out_addr): class WriteAddrInstruction(object): - def __init__(self, name, channel, modifier, addr, value, **kwargs): + def __init__(self, name, channel, modifier, addr, value, tdm, **kwargs): self.instruction = name self.channel = channel self.invalid = modifier self.addr = addr self.value = value self.kwargs = kwargs + self.tdm = tdm self.length = 0 def promote(self, ptype): @@ -67,23 +68,24 @@ def __eq__(self, other): def __ne__(self, other): return not self == other -def WriteAddr(addr, value, channel=None): - return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value) +def WriteAddr(addr, value, channel=None, tdm=False): + return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value, tdm) -def Invalidate(addr, mask, channel=None): - return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask) +def Invalidate(addr, mask, channel=None, tdm=False): + return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm) -def StoreMeas(addr, value, channel=None): - return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value) +def StoreMeas(addr, value, channel=None, tdm=False): + return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value, tdm) class LoadCmpVramInstruction(object): - def __init__(self, name, use_vram, addr, mask): + def __init__(self, name, use_vram, addr, mask, tdm): # TODO: sanity checks on input values self.instruction = name self.use_vram = use_vram self.mask = mask self.addr = addr + self.tdm = tdm self.length = 0 def promote(self, ptype): @@ -98,8 +100,10 @@ def __ne__(self, other): return not self == other -def LoadCmpVram(addr, mask): - return LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask) +# def LoadCmpVram(addr, mask): +# return LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask) -# TODO: are there other variants of WriteAddr? +def LoadCmpVram(addr, mask, channel=None, tdm=False): # this should be for APS2 only + return [WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask, tdm)] +# TODO: are there other variants of WriteAddr? diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index dcee35ab..ba795f69 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -27,11 +27,15 @@ import numpy as np from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils +from QGL import PulseSequencer from QGL.PatternUtils import hash_pulse, flatten +from QGL import TdmInstructions # Python 2/3 compatibility: use 'int' that subclasses 'long' from builtins import int +logger = logging.getLogger(__name__) + #Some constants SAMPLING_RATE = 1.2e9 ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps @@ -59,6 +63,11 @@ PREFETCH = 0xC NOP = 0XF +# # APS3 prototype +CUSTOM = 0xD +INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode +WRITEADDR = 0xE + # WFM/MARKER op codes PLAY = 0x0 WAIT_TRIG = 0x1 @@ -73,6 +82,19 @@ GREATERTHAN = 0x2 LESSTHAN = 0x3 +CMPTABLE = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} + +# custom OP_CODES +TDM_MAJORITY_VOTE = 0 +TDM_MAJORITY_VOTE_SET_MASK = 1 +TDM_TSM_SET_ROUNDS = 2 +TDM_TSM = 3 + +APS_CUSTOM_DECODE = ["APS_RAND", "APS_CLIFFORD_RAND","APS_CLIFFORD_SET_SEED" ,"APS_CLIFFORD_SET_OFFSET" +, "APS_CLIFFORD_SET_SPACING"] + +TDM_CUSTOM_DECODE = ["TDM_MAJORITY_VOTE", "TDM_MAJORITY_VOTE_SET_MASK", "TDM_TSM_SET_ROUNDS", "TDM_TSM"] + # Whether we use PHASE_OFFSET modulation commands or bake it into waveform # Default to false as we usually don't have many variants USE_PHASE_OFFSET_INSTRUCTION = False @@ -84,406 +106,499 @@ SEQFILE_PER_CHANNEL = False def get_empty_channel_set(): - return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} + return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} def get_seq_file_extension(): - return '.h5' + return '.h5' def is_compatible_file(filename): - with h5py.File(filename, 'r') as FID: - target = FID['/'].attrs['target hardware'] - if isinstance(target, str): - target = target.encode('utf-8') - if target == b'APS2': - return True - return False + with h5py.File(filename, 'r') as FID: + target = FID['/'].attrs['target hardware'] + if isinstance(target, str): + target = target.encode('utf-8') + if target == b'APS2': + return True + return False def create_wf_vector(wfLib, seqs): - ''' + ''' Helper function to create the wf vector and offsets into it. ''' - max_pts_needed = 0 - for wf in wfLib.values(): - if len(wf) == 1: - max_pts_needed += ADDRESS_UNIT - else: - max_pts_needed += len(wf) - - #If we have more than fits in cache we'll need to align and prefetch - need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE - - idx = 0 - - if not need_prefetch: - offsets = [{}] - cache_lines = [] - #if we can fit them all in just pack - wfVec = np.zeros(max_pts_needed, dtype=np.int16) - for key, wf in wfLib.items(): - #Clip the wf - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) - offsets[-1][key] = idx - idx += wf.size - - #Trim the waveform - wfVec.resize(idx) - - else: - #otherwise fill in one cache line at a time - CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 - wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) - offsets = [{}] - cache_lines = [] - for seq in seqs: - #go through sequence and see what we need to add - pts_to_add = 0 - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - pts_to_add += entry.length - - #If what we need to add spills over then add a line and start again - if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: - idx = int(CACHE_LINE_LENGTH * ( - (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) - wfVec = np.append(wfVec, - np.zeros(CACHE_LINE_LENGTH, - dtype=np.int16)) - offsets.append({}) - - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - wf = wfLib[sig] - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16( - MAX_WAVEFORM_VALUE * wf) - offsets[-1][sig] = idx - idx += wf.size - - cache_lines.append(int(idx // CACHE_LINE_LENGTH)) - - return wfVec, offsets, cache_lines + max_pts_needed = 0 + for wf in wfLib.values(): + if len(wf) == 1: + max_pts_needed += ADDRESS_UNIT + else: + max_pts_needed += len(wf) + + #If we have more than fits in cache we'll need to align and prefetch + need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE + + idx = 0 + + if not need_prefetch: + offsets = [{}] + cache_lines = [] + #if we can fit them all in just pack + wfVec = np.zeros(max_pts_needed, dtype=np.int16) + for key, wf in wfLib.items(): + #Clip the wf + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) + offsets[-1][key] = idx + idx += wf.size + + #Trim the waveform + wfVec.resize(idx) + + else: + #otherwise fill in one cache line at a time + CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 + wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) + offsets = [{}] + cache_lines = [] + for seq in seqs: + #go through sequence and see what we need to add + pts_to_add = 0 + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + pts_to_add += entry.length + + #If what we need to add spills over then add a line and start again + if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: + idx = int(CACHE_LINE_LENGTH * ( + (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) + wfVec = np.append(wfVec, + np.zeros(CACHE_LINE_LENGTH, + dtype=np.int16)) + offsets.append({}) + + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + wf = wfLib[sig] + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16( + MAX_WAVEFORM_VALUE * wf) + offsets[-1][sig] = idx + idx += wf.size + + cache_lines.append(int(idx // CACHE_LINE_LENGTH)) + + return wfVec, offsets, cache_lines class Instruction(object): - def __init__(self, header, payload, label=None, target=None): - self.header = header - self.payload = int(payload) - self.label = label - self.target = target - - @classmethod - def unflatten(cls, instr): - return cls(header=(int(instr) >> 56) & 0xff, - payload=int(instr) & 0xffffffffffffff) - - def __repr__(self): - return self.__str__() - - def __str__(self): - - opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", - "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "NOP", "NOP", "NOP"] - - out = "{0} ".format(self.label) if self.label else "" - - instrOpCode = (self.header >> 4) & 0xf - out += opCodes[instrOpCode] - - if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( - instrOpCode == MODULATION): - if (instrOpCode == MARKER) or (instrOpCode == WFM): - out += "; engine={}, ".format((self.header >> 2) & 0x3) - else: - out += "; " - if self.header & 0x1: - out += "write=1 | " - else: - out += "write=0 | " - - if self.target: - out += " {}".format(self.target) - - if instrOpCode == WFM: - wfOpCode = (self.payload >> 46) & 0x3 - wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] - out += wfOpCodes[wfOpCode] - out += "; TA bit={}".format((self.payload >> 45) & 0x1) - out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) - out += ", addr = {}".format(self.payload & 2**24 - 1) - - elif instrOpCode == MARKER: - mrkOpCode = (self.payload >> 46) & 0x3 - mrkOpCodes = ["PLAY", "TRIG", "SYNC"] - out += mrkOpCodes[mrkOpCode] - out += "; state={}".format((self.payload >> 32) & 0x1) - out += ", count = {}".format(self.payload & 2**32 - 1) - - elif instrOpCode == MODULATION: - modulatorOpCode = (self.payload >> 45) & 0x7 - modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", - "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] - out += modulatorOpCodes[modulatorOpCode] - out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) - if modulatorOpCode == 0x0: - out += ", count={:d}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x3: - out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x5: - out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x7: - out += ", frame_change=0x{:08x}".format(self.payload & - 0xffffffff) - - elif instrOpCode == CMP: - cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] - cmpCode = (self.payload >> 8) & 0x3 - out += " | " + cmpCodes[cmpCode] - out += ", value = {}".format(self.payload & 0xff) - - elif any( - [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): - out += " | target addr = {}".format(self.payload & 2**26 - 1) - - elif instrOpCode == LOAD: - out += " | count = {}".format(self.payload) - - return out - - def __eq__(self, other): - return self.header == other.header and self.payload == other.payload and self.label == other.label - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.header, self.payload, self.label)) - - @property - def address(self): - return self.payload & 0xffffffff # bottom 32-bits of payload - - @address.setter - def address(self, value): - self.payload |= value & 0xffffffff - - @property - def writeFlag(self): - return self.header & 0x1 - - @writeFlag.setter - def writeFlag(self, value): - self.header |= value & 0x1 - - @property - def opcode(self): - return self.header >> 4 - - def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffff)) + def __init__(self, header, payload, label=None, target=None, decode_as_tdm = False): + self.header = header + self.payload = int(payload) + self.label = label + self.target = target + self.decode_as_tdm = decode_as_tdm + + @classmethod + def unflatten(cls, instr, decode_as_tdm = False): + return cls(header=(int(instr) >> 56) & 0xff, + payload=int(instr) & 0xffffffffffffff, + decode_as_tdm = decode_as_tdm) + + def __repr__(self): + return self.__str__() + + def __str__(self): + + opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", + "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", + "CUSTOM", "WRITEADDR", "NOP"] + + # list of opCodes where the reprenstation will change + excludeList = ["WRITEADDR", "LOADCMP"] + + customOps = [ + "MajorityVote", "MajoritySetMask", # TODO there are others... + ] + + out = "{0} ".format(self.label) if self.label else "" + + instrOpCode = (self.header >> 4) & 0xf + + opCodeStr = opCodes[instrOpCode] + + if opCodeStr not in excludeList: + out += opCodeStr + + if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( + instrOpCode == MODULATION): + if (instrOpCode == MARKER) or (instrOpCode == WFM): + out += "; engine={}, ".format((self.header >> 2) & 0x3) + else: + out += "; " + if self.header & 0x1: + out += "write=1 | " + else: + out += "write=0 | " + + if self.target: + out += " {}".format(self.target) + + if instrOpCode == WFM: + wfOpCode = (self.payload >> 46) & 0x3 + wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] + out += wfOpCodes[wfOpCode] + out += "; TA bit={}".format((self.payload >> 45) & 0x1) + out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) + out += ", addr = {}".format(self.payload & 2**24 - 1) + + # # APS3/TDM modifier to use VRAM output + # if self.payload & (1 << 48): + # out += ", use_vram" + + elif instrOpCode == MARKER: + mrkOpCode = (self.payload >> 46) & 0x3 + mrkOpCodes = ["PLAY", "TRIG", "SYNC"] + out += mrkOpCodes[mrkOpCode] + out += "; state={}".format((self.payload >> 32) & 0x1) + out += ", count = {}".format(self.payload & 2**32 - 1) + + elif instrOpCode == MODULATION: + modulatorOpCode = (self.payload >> 45) & 0x7 + modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", + "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] + out += modulatorOpCodes[modulatorOpCode] + out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) + if modulatorOpCode == 0x0: + out += ", count={:d}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x3: + out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x5: + out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x7: + out += ", frame_change=0x{:08x}".format(self.payload & + 0xffffffff) + + elif instrOpCode == CMP: + cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] + cmpCode = (self.payload >> 8) & 0x3 + out += " | " + cmpCodes[cmpCode] + out += ", value = {}".format(self.payload & 0xff) + + elif any( + [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): + out += " | target addr = {}".format(self.payload & 2**26 - 1) + + elif instrOpCode == LOAD: + out += " | count = {}".format(self.payload) + + elif instrOpCode == CUSTOM: + store_addr = self.payload & 0xFFFF + load_addr = (self.payload >> 16) & 0xFFFF + instruction = (self.payload >> 32) & 0xFF + instructionAPS = TDM_CUSTOM_DECODE[instruction] + out += " | instruction = {0} ({1}), load_addr = 0x{2:0x}, store_addr = 0x{3:0x}".format(instruction, instructionAPS, load_addr, store_addr) + + elif instrOpCode == WRITEADDR: + addr = self.payload & 0xFFFF + value = (self.payload >> 16) & 0xFFFFFFFF + invalidate = not (self.header & 0x1) + mapTrigger = (self.header >> 2) & 0x1 + writeCrossbar = (self.header >> 1) & 0x1 + + instrStr = "WRITEADDR " + valueType = "value" + + if invalidate: + instrStr = "INVALIDATE" + valueType = "valid_mask" + + if mapTrigger: + instrStr = "STOREMEAS" + valueType = "mapping" + + if writeCrossbar: + instrStr = "WRITECB" + valuetype = "mapping" + addr = (self.payload >> 16) & 0xFFFF + value = (self.payload >> 32) & 0xFFFF + + out += "{0} | addr = 0x{1:0x}, {2} = 0x{3:0x}".format(instrStr, addr, valueType, value) + + elif instrOpCode == LOADCMP: + addr = self.payload & 0xFFFF + mask = (self.payload >> 16) & 0xFFFF + use_ram = (self.payload >> 48) & 0x1 + if self.decode_as_tdm and not use_ram: + out += "WAITMEAS" + else: + src = "EXT" + if use_ram: + src = "RAM" + out += "LOADCMP | source = {0}, addr = 0x{1:0x}, read_mask = 0x{2:0x}".format(src, addr, mask) + return out + + def __eq__(self, other): + return self.header == other.header and self.payload == other.payload and self.label == other.label + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.header, self.payload, self.label)) + + @property + def address(self): + return self.payload & 0xffffffff # bottom 32-bits of payload + + @address.setter + def address(self, value): + self.payload |= value & 0xffffffff + + @property + def writeFlag(self): + return self.header & 0x1 + + @writeFlag.setter + def writeFlag(self, value): + self.header |= value & 0x1 + + @property + def opcode(self): + return self.header >> 4 + + def flatten(self): + return int((self.header << 56) | (self.payload & 0xffffffffffffff)) def Waveform(addr, count, isTA, write=False, label=None): - header = (WFM << 4) | (0x3 << 2) | (write & - 0x1) #broadcast to both engines - count = int(count) - count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count - addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr - payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) - << TA_PAIR_BIT) | (count << 24) | addr - return Instruction(header, payload, label) + header = (WFM << 4) | (0x3 << 2) | (write & + 0x1) #broadcast to both engines + count = int(count) + count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count + addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr + payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) + << TA_PAIR_BIT) | (count << 24) | addr + return Instruction(header, payload, label) def WaveformPrefetch(addr): - header = (WFM << 4) | (0x3 << 2) | (0x1) - payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr - return Instruction(header, payload, None) + header = (WFM << 4) | (0x3 << 2) | (0x1) + payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr + return Instruction(header, payload, None) def Marker(sel, state, count, write=False, label=None): - header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) - count = int(count) - four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count - count_rem = count % ADDRESS_UNIT - if state == 0: - transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} - transition = transitionWords[count_rem] - else: - transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} - transition = transitionWords[count_rem] - payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( - (state & 0x1) << 32) | four_count - return Instruction(header, payload, label) + header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) + count = int(count) + four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count + count_rem = count % ADDRESS_UNIT + if state == 0: + transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} + transition = transitionWords[count_rem] + else: + transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} + transition = transitionWords[count_rem] + payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( + (state & 0x1) << 32) | four_count + return Instruction(header, payload, label) def Command(cmd, payload, write=False, label=None): - header = (cmd << 4) - if isinstance(payload, int): - instr = Instruction(header, payload, label) - else: - instr = Instruction(header, 0, label, target=payload) - instr.writeFlag = write - return instr + header = (cmd << 4) + if isinstance(payload, int): + instr = Instruction(header, payload, label) + else: + instr = Instruction(header, 0, label, target=payload) + instr.writeFlag = write + return instr def Sync(label=None): - return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) + return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) def Wait(label=None): - return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) + return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) def LoadCmp(label=None): - return Command(LOADCMP, 0, label=label) + return Command(LOADCMP, 0, label=label) def Cmp(op, value, label=None): - return Command(CMP, (op << 8) | (value & 0xff), label=label) + return Command(CMP, (op << 8) | (value & 0xff), label=label) def Goto(addr, label=None): - return Command(GOTO, addr, label=label) + return Command(GOTO, addr, label=label) def Call(addr, label=None): - return Command(CALL, addr, label=label) + return Command(CALL, addr, label=label) def Return(label=None): - return Command(RET, 0, label=label) + return Command(RET, 0, label=label) def Load(count, label=None): - return Command(LOAD, count, label=label) + return Command(LOAD, count, label=label) def Repeat(addr, label=None): - return Command(REPEAT, addr, label=label) + return Command(REPEAT, addr, label=label) def Prefetch(addr, label=None): - return Command(PREFETCH, addr) + return Command(PREFETCH, addr) def NoOp(): - return Instruction.unflatten(0xffffffffffffffff) + return Instruction.unflatten(0xffffffffffffffff) + +# QGL instructions +def Invalidate(addr, mask, label=None): + header = WRITEADDR << 4 + payload = (mask << 16) | addr + return Instruction(header, payload, label=label) +# +# def WriteAddr(addr, value, label=None): +# header = (WRITEADDR << 4) | 1 +# payload = (value << 16) | addr +# return Instruction(header, payload, label=label) +# +def StoreMeas(addr, mapping, label=None): + header = (WRITEADDR << 4) | 5 + payload = (mapping << 16) | addr + return Instruction(header, payload, label=label) +# +# def Custom(in_addr, out_addr, custom_op, label=None): +# header = CUSTOM << 4 +# payload = (custom_op << 32) | (in_addr << 16) | out_addr +# return Instruction(header, payload, label=label) +# +# def MajorityVote(in_addr, out_addr, label=None): +# return Custom(in_addr, out_addr, 0, label=label) +# +# def MajorityVoteMask(in_addr, out_addr, label=None): +# return Custom(in_addr, out_addr, 1, label=label) +# +def LoadCmpVram(addr, mask, label=None): + header = LOADCMP << 4 + payload = (1 << 48) | (mask << 16) | addr + return Instruction(header, payload, label=label) def preprocess(seqs, shapeLib): - seqs = PatternUtils.convert_lengths_to_samples( - seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) - wfLib = build_waveforms(seqs, shapeLib) - inject_modulation_cmds(seqs) - return seqs, wfLib + seqs = PatternUtils.convert_lengths_to_samples( + seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) + wfLib = build_waveforms(seqs, shapeLib) + inject_modulation_cmds(seqs) + return seqs, wfLib def wf_sig(wf): - ''' + ''' Compute a signature of a Compiler.Waveform that identifies the relevant properties for two Waveforms to be considered "equal" in the waveform library. For example, we ignore length of TA waveforms. ''' - if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.amp) - else: - return (wf.amp, round(wf.phase * 2**13)) - else: - #TODO: why do we need the length? - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.key, wf.amp, wf.length) - else: - return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) + if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.amp) + else: + return (wf.amp, round(wf.phase * 2**13)) + else: + #TODO: why do we need the length? + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.key, wf.amp, wf.length) + else: + return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) class ModulationCommand(object): - """docstring for ModulationCommand""" - - def __init__(self, - instruction, - nco_select, - frequency=0, - phase=0, - length=0): - super(ModulationCommand, self).__init__() - self.instruction = instruction - self.nco_select = nco_select - self.frequency = frequency - self.phase = phase - self.length = length - - def __str__(self): - out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, - self.nco_select) - if self.instruction == "MODULATE": - out += ", length={})".format(self.length) - elif self.instruction == "SET_FREQ": - out += ", frequency={})".format(self.frequency) - elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": - out += ", phase={})".format(self.phase) - else: - out += ")" - return out - - def _repr_pretty_(self, p, cycle): - p.text(str(self)) - - def __repr__(self): - return str(self) - - def to_instruction(self, write_flag=True, label=None): - #Modulator op codes - MODULATOR_OP_OFFSET = 44 - NCO_SELECT_OP_OFFSET = 40 - MODULATION_CLOCK = 300e6 - - op_code_map = {"MODULATE": 0x0, - "RESET_PHASE": 0x2, - "SET_FREQ": 0x6, - "SET_PHASE": 0xa, - "UPDATE_FRAME": 0xe} - payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( - self.nco_select << NCO_SELECT_OP_OFFSET) - if self.instruction == "MODULATE": - #zero-indexed quad count - payload |= np.uint32(self.length / ADDRESS_UNIT - 1) - elif self.instruction == "SET_FREQ": - # frequencies can span -2 to 2 or 0 to 4 in unsigned - payload |= np.uint32( - (self.frequency / MODULATION_CLOCK if self.frequency > 0 else - self.frequency / MODULATION_CLOCK + 4) * 2**28) - elif (self.instruction == "SET_PHASE") | ( - self.instruction == "UPDATE_FRAME"): - #phases can span -0.5 to 0.5 or 0 to 1 in unsigned - payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) - - instr = Instruction(MODULATION << 4, payload, label) - instr.writeFlag = write_flag - return instr + """docstring for ModulationCommand""" + + def __init__(self, + instruction, + nco_select, + frequency=0, + phase=0, + length=0): + super(ModulationCommand, self).__init__() + self.instruction = instruction + self.nco_select = nco_select + self.frequency = frequency + self.phase = phase + self.length = length + + def __str__(self): + out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, + self.nco_select) + if self.instruction == "MODULATE": + out += ", length={})".format(self.length) + elif self.instruction == "SET_FREQ": + out += ", frequency={})".format(self.frequency) + elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": + out += ", phase={})".format(self.phase) + else: + out += ")" + return out + + def _repr_pretty_(self, p, cycle): + p.text(str(self)) + + def __repr__(self): + return str(self) + + def to_instruction(self, write_flag=True, label=None): + #Modulator op codes + MODULATOR_OP_OFFSET = 44 + NCO_SELECT_OP_OFFSET = 40 + MODULATION_CLOCK = 300e6 + + op_code_map = {"MODULATE": 0x0, + "RESET_PHASE": 0x2, + "SET_FREQ": 0x6, + "SET_PHASE": 0xa, + "UPDATE_FRAME": 0xe} + payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( + self.nco_select << NCO_SELECT_OP_OFFSET) + if self.instruction == "MODULATE": + #zero-indexed quad count + payload |= np.uint32(self.length / ADDRESS_UNIT - 1) + elif self.instruction == "SET_FREQ": + # frequencies can span -2 to 2 or 0 to 4 in unsigned + payload |= np.uint32( + (self.frequency / MODULATION_CLOCK if self.frequency > 0 else + self.frequency / MODULATION_CLOCK + 4) * 2**28) + elif (self.instruction == "SET_PHASE") | ( + self.instruction == "UPDATE_FRAME"): + #phases can span -0.5 to 0.5 or 0 to 1 in unsigned + payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) + + instr = Instruction(MODULATION << 4, payload, label) + instr.writeFlag = write_flag + return instr def inject_modulation_cmds(seqs): """ @@ -571,56 +686,56 @@ def inject_modulation_cmds(seqs): seqs[ct] = mod_seq def build_waveforms(seqs, shapeLib): - # apply amplitude (and optionally phase) and add the resulting waveforms to the library - wfLib = {} - for wf in flatten(seqs): - if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: - shape = wf.amp * shapeLib[wf.key] - if not USE_PHASE_OFFSET_INSTRUCTION: - shape *= np.exp(1j * wf.phase) - wfLib[wf_sig(wf)] = shape - return wfLib + # apply amplitude (and optionally phase) and add the resulting waveforms to the library + wfLib = {} + for wf in flatten(seqs): + if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: + shape = wf.amp * shapeLib[wf.key] + if not USE_PHASE_OFFSET_INSTRUCTION: + shape *= np.exp(1j * wf.phase) + wfLib[wf_sig(wf)] = shape + return wfLib def timestamp_entries(seq): - t = 0 - for ct in range(len(seq)): - seq[ct].startTime = t - t += seq[ct].length + t = 0 + for ct in range(len(seq)): + seq[ct].startTime = t + t += seq[ct].length def synchronize_clocks(seqs): - # Control-flow instructions (CFIs) must occur at the same time on all channels. - # Therefore, we need to "reset the clock" by synchronizing the accumulated - # time at each CFI to the largest value on any channel - syncInstructions = [list(filter( - lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) - for seq in seqs if seq] - - # Add length to control-flow instructions to make accumulated time match at end of CFI. - # Keep running tally of how much each channel has been shifted so far. - localShift = [0 for _ in syncInstructions] - for ct in range(len(syncInstructions[0])): - step = [seq[ct] for seq in syncInstructions] - endTime = max((s.startTime + shift - for s, shift in zip(step, localShift))) - for ct, s in enumerate(step): - s.length = endTime - (s.startTime + localShift[ct]) - # localShift[ct] += endTime - (s.startTime + localShift[ct]) - # the += and the last term cancel, therefore: - localShift[ct] = endTime - s.startTime - # re-timestamp to propagate changes across the sequences - for seq in seqs: - timestamp_entries(seq) - # then transfer the control flow "lengths" back into start times - for seq in syncInstructions: - for instr in seq: - instr.startTime += instr.length - instr.length = 0 + # Control-flow instructions (CFIs) must occur at the same time on all channels. + # Therefore, we need to "reset the clock" by synchronizing the accumulated + # time at each CFI to the largest value on any channel + syncInstructions = [list(filter( + lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) + for seq in seqs if seq] + + # Add length to control-flow instructions to make accumulated time match at end of CFI. + # Keep running tally of how much each channel has been shifted so far. + localShift = [0 for _ in syncInstructions] + for ct in range(len(syncInstructions[0])): + step = [seq[ct] for seq in syncInstructions] + endTime = max((s.startTime + shift + for s, shift in zip(step, localShift))) + for ct, s in enumerate(step): + s.length = endTime - (s.startTime + localShift[ct]) + # localShift[ct] += endTime - (s.startTime + localShift[ct]) + # the += and the last term cancel, therefore: + localShift[ct] = endTime - s.startTime + # re-timestamp to propagate changes across the sequences + for seq in seqs: + timestamp_entries(seq) + # then transfer the control flow "lengths" back into start times + for seq in syncInstructions: + for instr in seq: + instr.startTime += instr.length + instr.length = 0 def create_seq_instructions(seqs, offsets): - ''' + ''' Helper function to create instruction vector from an IR sequence and an offset dictionary keyed on the wf keys. @@ -631,521 +746,707 @@ def create_seq_instructions(seqs, offsets): all waveform and marker channels. ''' - # timestamp all entries before filtering (where we lose time information on control flow) - for seq in seqs: - timestamp_entries(seq) - - synchronize_clocks(seqs) - - # create (seq, startTime) pairs over all sequences - timeTuples = [] - for ct, seq in enumerate(seqs): - timeTuples += [(entry.startTime, ct) for entry in seq] - timeTuples.sort() - - # keep track of where we are in each sequence - indexes = np.zeros(len(seqs), dtype=np.int64) - - cmpTable = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} - - # always start with SYNC (stealing label from beginning of sequence) - # unless it is a subroutine (using last entry as return as tell) - label = None - instructions = [] - for ct, seq in enumerate(seqs): - if len(seq): - first_non_empty = ct - break - if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): - if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] - timeTuples.pop(0) - indexes[first_non_empty] += 1 - instructions.append(Sync(label=label)) - label = None - - while len(timeTuples) > 0: - #pop off all entries that have the same time - entries = [] - start_time = 0 - while True: - start_time, seq_idx = timeTuples.pop(0) - entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) - indexes[seq_idx] += 1 - next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 - if start_time != next_start_time: - break - - write_flags = [True] * len(entries) - for ct, (entry, seq_idx) in enumerate(entries): - #use first non empty sequence for control flow - if seq_idx == first_non_empty and ( - isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel)): - if isinstance(entry, BlockLabel.BlockLabel): - # carry label forward to next entry - label = entry - continue - # control flow instructions - elif isinstance(entry, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(entry, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - elif isinstance(entry, ControlFlow.Sync): - instructions.append(Sync(label=label)) - elif isinstance(entry, ControlFlow.Return): - instructions.append(Return(label=label)) - # target argument commands - elif isinstance(entry, ControlFlow.Goto): - instructions.append(Goto(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Call): - instructions.append(Call(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Repeat): - instructions.append(Repeat(entry.target, label=label)) - # value argument commands - elif isinstance(entry, ControlFlow.LoadRepeat): - instructions.append(Load(entry.value - 1, label=label)) - elif isinstance(entry, ControlFlow.ComparisonInstruction): - # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(cmpTable[entry.operator], - entry.value, - label=label)) - - continue - - if seq_idx == 0: - #analog - waveforms or modulation - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping Waveform entry of length %s!" % entry.length) - continue - instructions.append(Waveform(offsets[wf_sig(entry)], - entry.length, - entry.isTimeAmp or - entry.isZero, - write=write_flags[ct], - label=label)) - elif isinstance(entry, ModulationCommand): - instructions.append(entry.to_instruction( - write_flag=write_flags[ct], - label=label)) - - else: # a marker engine - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping entry!") - continue - markerSel = seq_idx - 1 - state = not entry.isZero - instructions.append(Marker(markerSel, - state, - entry.length, - write=write_flags[ct], - label=label)) - - #clear label - label = None - - return instructions - + # timestamp all entries before filtering (where we lose time information on control flow) + for seq in seqs: + timestamp_entries(seq) + + synchronize_clocks(seqs) + + # create (seq, startTime) pairs over all sequences + timeTuples = [] + for ct, seq in enumerate(seqs): + timeTuples += [(entry.startTime, ct) for entry in seq] + timeTuples.sort() + + # keep track of where we are in each sequence + indexes = np.zeros(len(seqs), dtype=np.int64) + + # always start with SYNC (stealing label from beginning of sequence) + # unless it is a subroutine (using last entry as return as tell) + label = None + instructions = [] + for ct, seq in enumerate(seqs): + if len(seq): + first_non_empty = ct + break + if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): + if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): + label = seqs[first_non_empty][0] + timeTuples.pop(0) + indexes[first_non_empty] += 1 + instructions.append(Sync(label=label)) + label = None + + while len(timeTuples) > 0: + #pop off all entries that have the same time + entries = [] + start_time = 0 + while True: + start_time, seq_idx = timeTuples.pop(0) + entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) + indexes[seq_idx] += 1 + next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 + if start_time != next_start_time: + break + + write_flags = [True] * len(entries) + for ct, (entry, seq_idx) in enumerate(entries): + + #use first non empty sequence for control flow + if seq_idx == first_non_empty and ( + isinstance(entry, ControlFlow.ControlInstruction) or + isinstance(entry, BlockLabel.BlockLabel) or + isinstance(entry, TdmInstructions.CustomInstruction) or + isinstance(entry, TdmInstructions.WriteAddrInstruction) or + isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): + if isinstance(entry, BlockLabel.BlockLabel): + # carry label forward to next entry + label = entry + continue + # control flow instructions + elif isinstance(entry, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(entry, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + elif isinstance(entry, ControlFlow.Sync): + instructions.append(Sync(label=label)) + elif isinstance(entry, ControlFlow.Return): + instructions.append(Return(label=label)) + # target argument commands + elif isinstance(entry, ControlFlow.Goto): + instructions.append(Goto(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Call): + instructions.append(Call(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Repeat): + instructions.append(Repeat(entry.target, label=label)) + # value argument commands + elif isinstance(entry, ControlFlow.LoadRepeat): + instructions.append(Load(entry.value - 1, label=label)) + elif isinstance(entry, ControlFlow.ComparisonInstruction): + # TODO modify Cmp operator to load from specified address + instructions.append(Cmp(CMPTABLE[entry.operator], + entry.value, + label=label)) + elif isinstance(entry, TdmInstructions.LoadCmpVramInstruction) and entry.tdm == False: + instructions.append(LoadCmpVram(entry.addr, entry.mask, label=label)) + # some TDM instructions are ignored by the APS + elif isinstance(entry, TdmInstructions.CustomInstruction): + pass + elif isinstance(entry, TdmInstructions.WriteAddrInstruction): + if entry.instruction == 'INVALIDATE' and entry.tdm == False: + instructions.append(Invalidate(entry.addr, entry.value, label=label)) + + continue + + if seq_idx == 0: + #analog - waveforms or modulation + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping Waveform entry of length %s!" % entry.length) + continue + + instructions.append(Waveform( + offsets[wf_sig(entry)], entry.length, + entry.isTimeAmp or entry.isZero, + write=write_flags[ct], label=label)) + elif isinstance(entry, ModulationCommand): + instructions.append(entry.to_instruction( + write_flag=write_flags[ct], + label=label)) + + else: # a marker engine + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping entry!") + continue + markerSel = seq_idx - 1 + state = not entry.isZero + instructions.append(Marker(markerSel, + state, + entry.length, + write=write_flags[ct], + label=label)) + + #clear label + label = None + + return instructions def create_instr_data(seqs, offsets, cache_lines): - ''' + ''' Constructs the complete instruction data vector, and does basic checks for validity. Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line ''' - logger = logging.getLogger(__name__) - logger.debug('') - - seq_instrs = [] - need_prefetch = len(cache_lines) > 0 - num_cache_lines = len(set(cache_lines)) - cache_line_changes = np.concatenate( - ([0], np.where(np.diff(cache_lines))[0] + 1)) - for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) - #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line - if need_prefetch and (ct in cache_line_changes): - next_cache_line = cache_lines[cache_line_changes[(np.where( - ct == cache_line_changes)[0][0] + 1) % len( - cache_line_changes)]] - seq_instrs[-1].insert(0, WaveformPrefetch(int( - next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - - #concatenate instructions - instructions = [] - subroutines_start = -1 - for ct, seq in enumerate(seq_instrs): - #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break - instructions += seq - - #if we have any subroutines then group in cache lines - if subroutines_start >= 0: - subroutine_instrs = [] - subroutine_cache_line = {} - CACHE_LINE_LENGTH = 128 - offset = 0 - for sub in seq_instrs[subroutines_start:]: - #TODO for now we don't properly handle prefetching mulitple cache lines - if len(sub) > CACHE_LINE_LENGTH: - warnings.warn( - "Subroutines longer than {} instructions may not be prefetched correctly") - #Don't unecessarily split across a cache line - if (len(sub) + offset > CACHE_LINE_LENGTH) and ( - len(sub) < CACHE_LINE_LENGTH): - pad_instrs = 128 - ((offset + 128) % 128) - subroutine_instrs += [NoOp()] * pad_instrs - offset = 0 - if offset == 0: - line_label = sub[0].label - subroutine_cache_line[sub[0].label] = line_label - subroutine_instrs += sub - offset += len(sub) % CACHE_LINE_LENGTH - logger.debug("Placed {} subroutines into {} cache lines".format( - len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // - CACHE_LINE_LENGTH)) - #inject prefetch commands before waits - wait_idx = [idx for idx, instr in enumerate(instructions) - if (instr.header >> 4) == WAIT] + [len(instructions)] - instructions_with_prefetch = instructions[:wait_idx[0]] - last_prefetch = None - for start, stop in zip(wait_idx[:-1], wait_idx[1:]): - call_targets = [instr.target for instr in instructions[start:stop] - if (instr.header >> 4) == CALL] - needed_lines = set() - for target in call_targets: - needed_lines.add(subroutine_cache_line[target]) - if len(needed_lines) > 8: - raise RuntimeError( - "Unable to prefetch more than 8 cache lines") - for needed_line in needed_lines: - if needed_line != last_prefetch: - instructions_with_prefetch.append(Prefetch(needed_line)) - last_prefetch = needed_line - instructions_with_prefetch += instructions[start:stop] - - instructions = instructions_with_prefetch - #pad out instruction vector to ensure circular cache never loads a subroutine - pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) - instructions += [NoOp()] * pad_instrs - - instructions += subroutine_instrs - - #turn symbols into integers addresses - resolve_symbols(instructions) - - assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ - 'Oops! too many instructions: {0}'.format(len(instructions)) - - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) + logger = logging.getLogger(__name__) + logger.debug('') + + seq_instrs = [] + need_prefetch = len(cache_lines) > 0 + num_cache_lines = len(set(cache_lines)) + cache_line_changes = np.concatenate( + ([0], np.where(np.diff(cache_lines))[0] + 1)) + for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): + seq_instrs.append(create_seq_instructions( + list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0])) + #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line + if need_prefetch and (ct in cache_line_changes): + next_cache_line = cache_lines[cache_line_changes[(np.where( + ct == cache_line_changes)[0][0] + 1) % len( + cache_line_changes)]] + seq_instrs[-1].insert(0, WaveformPrefetch(int( + next_cache_line * WAVEFORM_CACHE_SIZE / 2))) + #steal label + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None + + #concatenate instructions + instructions = [] + subroutines_start = -1 + for ct, seq in enumerate(seq_instrs): + #Use last instruction being return as mark of start of subroutines + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + instructions += seq + + #if we have any subroutines then group in cache lines + if subroutines_start >= 0: + subroutine_instrs = [] + subroutine_cache_line = {} + CACHE_LINE_LENGTH = 128 + offset = 0 + for sub in seq_instrs[subroutines_start:]: + #TODO for now we don't properly handle prefetching mulitple cache lines + if len(sub) > CACHE_LINE_LENGTH: + warnings.warn( + "Subroutines longer than {} instructions may not be prefetched correctly") + #Don't unecessarily split across a cache line + if (len(sub) + offset > CACHE_LINE_LENGTH) and ( + len(sub) < CACHE_LINE_LENGTH): + pad_instrs = 128 - ((offset + 128) % 128) + subroutine_instrs += [NoOp()] * pad_instrs + offset = 0 + if offset == 0: + line_label = sub[0].label + subroutine_cache_line[sub[0].label] = line_label + subroutine_instrs += sub + offset += len(sub) % CACHE_LINE_LENGTH + logger.debug("Placed {} subroutines into {} cache lines".format( + len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // + CACHE_LINE_LENGTH)) + #inject prefetch commands before waits + wait_idx = [idx for idx, instr in enumerate(instructions) + if (instr.header >> 4) == WAIT] + [len(instructions)] + instructions_with_prefetch = instructions[:wait_idx[0]] + last_prefetch = None + for start, stop in zip(wait_idx[:-1], wait_idx[1:]): + call_targets = [instr.target for instr in instructions[start:stop] + if (instr.header >> 4) == CALL] + needed_lines = set() + for target in call_targets: + needed_lines.add(subroutine_cache_line[target]) + if len(needed_lines) > 8: + raise RuntimeError( + "Unable to prefetch more than 8 cache lines") + for needed_line in needed_lines: + if needed_line != last_prefetch: + instructions_with_prefetch.append(Prefetch(needed_line)) + last_prefetch = needed_line + instructions_with_prefetch += instructions[start:stop] + + instructions = instructions_with_prefetch + #pad out instruction vector to ensure circular cache never loads a subroutine + pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) + instructions += [NoOp()] * pad_instrs + + instructions += subroutine_instrs + + #turn symbols into integers addresses + resolve_symbols(instructions) + + assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ + 'Oops! too many instructions: {0}'.format(len(instructions)) + + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) def resolve_symbols(seq): - symbols = {} - # create symbol look-up table - for ct, entry in enumerate(seq): - if entry.label and entry.label not in symbols: - symbols[entry.label] = ct - # then update - for entry in seq: - if entry.target: - entry.address = symbols[entry.target] + symbols = {} + # create symbol look-up table + for ct, entry in enumerate(seq): + if entry.label and entry.label not in symbols: + symbols[entry.label] = ct + # then update + for entry in seq: + if entry.target: + entry.address = symbols[entry.target] def compress_marker(markerLL): - ''' + ''' Compresses adjacent entries of the same state into single entries ''' - for seq in markerLL: - idx = 0 - while idx + 1 < len(seq): - if (isinstance(seq[idx], Compiler.Waveform) and - isinstance(seq[idx + 1], Compiler.Waveform) and - seq[idx].isZero == seq[idx + 1].isZero): + for seq in markerLL: + idx = 0 + while idx + 1 < len(seq): + if (isinstance(seq[idx], Compiler.Waveform) and + isinstance(seq[idx + 1], Compiler.Waveform) and + seq[idx].isZero == seq[idx + 1].isZero): - seq[idx].length += seq[idx + 1].length - del seq[idx + 1] - else: - idx += 1 + seq[idx].length += seq[idx + 1].length + del seq[idx + 1] + else: + idx += 1 def write_sequence_file(awgData, fileName): - ''' + ''' Main function to pack channel sequences into an APS2 h5 file. ''' - # Convert QGL IR into a representation that is closer to the hardware. - awgData['ch12']['linkList'], wfLib = preprocess( - awgData['ch12']['linkList'], awgData['ch12']['wfLib']) - - # compress marker data - for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: - if 'linkList' in awgData[field].keys(): - PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], - SAMPLING_RATE, 1, - Compiler.Waveform) - compress_marker(awgData[field]['linkList']) - else: - awgData[field]['linkList'] = [] - - #Create the waveform vectors - wfInfo = [] - wfInfo.append(create_wf_vector({key: wf.real - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - wfInfo.append(create_wf_vector({key: wf.imag - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - - if SAVE_WF_OFFSETS: - #create a set of all waveform signatures in offset dictionaries - #we could have multiple offsets for the same pulse becuase it could - #be repeated in multiple cache lines - wf_sigs = set() - for offset_dict in wfInfo[0][1]: - wf_sigs |= set(offset_dict.keys()) - #create dictionary linking entry labels (that's what we'll have later) with offsets - offsets = {} - for seq in awgData['ch12']['linkList']: - for entry in seq: - if len(wf_sigs) == 0: - break - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig in wf_sigs: - #store offsets and wavefor lib length - #time ampltidue entries are clamped to ADDRESS_UNIT - wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length - offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], - wf_length) - wf_sigs.discard(sig) - - #break out of outer loop too - if len(wf_sigs) == 0: - break - - #now pickle the label=>offsets - with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: - pickle.dump(offsets, FID) - - # build instruction vector - seq_data = [awgData[s]['linkList'] - for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] - instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) - - #Open the HDF5 file - if os.path.isfile(fileName): - os.remove(fileName) - with h5py.File(fileName, 'w') as FID: - FID['/'].attrs['Version'] = 4.0 - FID['/'].attrs['target hardware'] = 'APS2' - FID['/'].attrs['minimum firmware version'] = 4.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) - - #Create the groups and datasets - for chanct in range(2): - chanStr = '/chan_{0}'.format(chanct + 1) - chanGroup = FID.create_group(chanStr) - #Write the waveformLib to file - FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) - - #Write the instructions to channel 1 - if np.mod(chanct, 2) == 0: - FID.create_dataset(chanStr + '/instructions', - data=instructions) + # Convert QGL IR into a representation that is closer to the hardware. + awgData['ch12']['linkList'], wfLib = preprocess( + awgData['ch12']['linkList'], awgData['ch12']['wfLib']) + + # compress marker data + for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: + if 'linkList' in awgData[field].keys(): + PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], + SAMPLING_RATE, 1, + Compiler.Waveform) + compress_marker(awgData[field]['linkList']) + else: + awgData[field]['linkList'] = [] + + #Create the waveform vectors + wfInfo = [] + wfInfo.append(create_wf_vector({key: wf.real + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + wfInfo.append(create_wf_vector({key: wf.imag + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + + if SAVE_WF_OFFSETS: + #create a set of all waveform signatures in offset dictionaries + #we could have multiple offsets for the same pulse becuase it could + #be repeated in multiple cache lines + wf_sigs = set() + for offset_dict in wfInfo[0][1]: + wf_sigs |= set(offset_dict.keys()) + #create dictionary linking entry labels (that's what we'll have later) with offsets + offsets = {} + for seq in awgData['ch12']['linkList']: + for entry in seq: + if len(wf_sigs) == 0: + break + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig in wf_sigs: + #store offsets and wavefor lib length + #time ampltidue entries are clamped to ADDRESS_UNIT + wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length + offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], + wf_length) + wf_sigs.discard(sig) + + #break out of outer loop too + if len(wf_sigs) == 0: + break + + #now pickle the label=>offsets + with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: + pickle.dump(offsets, FID) + + # build instruction vector + seq_data = [awgData[s]['linkList'] + for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] + instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) + + #Open the HDF5 file + if os.path.isfile(fileName): + os.remove(fileName) + with h5py.File(fileName, 'w') as FID: + FID['/'].attrs['Version'] = 4.0 + FID['/'].attrs['target hardware'] = 'APS2' + FID['/'].attrs['minimum firmware version'] = 4.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) + + #Create the groups and datasets + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + #Write the waveformLib to file + FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) + + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', + data=instructions) def read_sequence_file(fileName): - """ + """ Reads a HDF5 sequence file and returns a dictionary of lists. Dictionary keys are channel strings such as ch1, ch12m1 Lists are or tuples of time-amplitude pairs (time, output) """ - chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', - 'mod_phase'] - seqs = {ch: [] for ch in chanStrs} - - def start_new_seq(): - for ct, ch in enumerate(chanStrs): - if (ct < 2) or (ct == 6): - #analog or modulation channel - seqs[ch].append([]) - else: - #marker channel - seqs[ch].append([]) - - with h5py.File(fileName, 'r') as FID: - file_version = FID["/"].attrs["Version"] - wf_lib = {} - wf_lib['ch1'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() - wf_lib['ch2'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() - instructions = FID['/chan_1/instructions'].value.flatten() - NUM_NCO = 2 - freq = np.zeros(NUM_NCO) #radians per timestep - phase = np.zeros(NUM_NCO) - frame = np.zeros(NUM_NCO) - next_freq = np.zeros(NUM_NCO) - next_phase = np.zeros(NUM_NCO) - next_frame = np.zeros(NUM_NCO) - accumulated_phase = np.zeros(NUM_NCO) - reset_flag = [False]*NUM_NCO - - for data in instructions: - instr = Instruction.unflatten(data) - - modulator_opcode = instr.payload >> 44 - - #update phases at these boundaries - if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( - (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): - for ct in range(NUM_NCO): - if reset_flag[ct]: - #would expect this to be zero but this is first non-zero point - accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT - reset_flag[ct] = False - freq[:] = next_freq[:] - phase[:] = next_phase[:] - frame[:] = next_frame[:] - - #Assume new sequence at every WAIT - if instr.opcode == WAIT: - start_new_seq() - - elif instr.opcode == WFM and (( - (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): - addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT - count = (instr.payload >> 24) & 0xfffff - count = (count + 1) * ADDRESS_UNIT - isTA = (instr.payload >> 45) & 0x1 - chan_select_bits = ((instr.header >> 2) & 0x1, - (instr.header >> 3) & 0x1) - #On older firmware we broadcast by default whereas on newer we respect the engine select - for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): - if (file_version < 4) or select_bit: - if isTA: - seqs[chan][-1].append((count, wf_lib[chan][addr])) - else: - for sample in wf_lib[chan][addr:addr + count]: - seqs[chan][-1].append((1, sample)) - - elif instr.opcode == MARKER: - chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) - count = instr.payload & 0xffffffff - count = (count + 1) * ADDRESS_UNIT - state = (instr.payload >> 32) & 0x1 - seqs[chan][-1].append((count, state)) - - elif instr.opcode == MODULATION: - # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} - nco_select_bits = (instr.payload >> 40) & 0xf - if modulator_opcode == 0x0: - #modulate - count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT - nco_select = {0b0001: 0, - 0b0010: 1, - 0b0100: 2, - 0b1000: 3}[nco_select_bits] - seqs['mod_phase'][-1] = np.append( - seqs['mod_phase'][-1], freq[nco_select] * - np.arange(count) + accumulated_phase[nco_select] + - phase[nco_select] + frame[nco_select]) - accumulated_phase += count * freq - else: - phase_rad = 2 * np.pi * (instr.payload & - 0xffffffff) / 2**28 - for ct in range(NUM_NCO): - if (nco_select_bits >> ct) & 0x1: - if modulator_opcode == 0x2: - #reset - next_phase[ct] = 0 - next_frame[ct] = 0 - reset_flag[ct] = True - elif modulator_opcode == 0x6: - #set frequency - next_freq[ct] = phase_rad / ADDRESS_UNIT - elif modulator_opcode == 0xa: - #set phase - next_phase[ct] = phase_rad - elif modulator_opcode == 0xe: - #update frame - next_frame[ct] += phase_rad - - #Apply modulation if we have any - for ct, ( - ch1, ch2, mod_phase - ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): - if len(mod_phase): - #only really works if ch1, ch2 are broadcast together - mod_ch1 = [] - mod_ch2 = [] - cum_time = 0 - for ((time_ch1, amp_ch1), - (time_ch2, amp_ch2)) in zip(ch1, ch2): - if (amp_ch1 != 0) or (amp_ch2 != 0): - assert time_ch1 == time_ch2 - if time_ch1 == 1: - #single timestep - modulated = np.exp(1j * mod_phase[cum_time]) * ( - amp_ch1 + 1j * amp_ch2) - mod_ch1.append((1, modulated.real)) - mod_ch2.append((1, modulated.imag)) - else: - #expand TA - modulated = np.exp( - 1j * - mod_phase[cum_time:cum_time + time_ch1]) * ( - amp_ch1 + 1j * amp_ch2) - for val in modulated: - mod_ch1.append((1, val.real)) - mod_ch2.append((1, val.imag)) - else: - mod_ch1.append((time_ch1, amp_ch1)) - mod_ch2.append((time_ch2, amp_ch2)) - - cum_time += time_ch1 - seqs['ch1'][ct] = mod_ch1 - seqs['ch2'][ct] = mod_ch2 - - del seqs['mod_phase'] - - return seqs + chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', + 'mod_phase'] + seqs = {ch: [] for ch in chanStrs} + + def start_new_seq(): + for ct, ch in enumerate(chanStrs): + if (ct < 2) or (ct == 6): + #analog or modulation channel + seqs[ch].append([]) + else: + #marker channel + seqs[ch].append([]) + + with h5py.File(fileName, 'r') as FID: + file_version = FID["/"].attrs["Version"] + wf_lib = {} + wf_lib['ch1'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() + wf_lib['ch2'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() + instructions = FID['/chan_1/instructions'].value.flatten() + NUM_NCO = 2 + freq = np.zeros(NUM_NCO) #radians per timestep + phase = np.zeros(NUM_NCO) + frame = np.zeros(NUM_NCO) + next_freq = np.zeros(NUM_NCO) + next_phase = np.zeros(NUM_NCO) + next_frame = np.zeros(NUM_NCO) + accumulated_phase = np.zeros(NUM_NCO) + reset_flag = [False]*NUM_NCO + + for data in instructions: + instr = Instruction.unflatten(data) + + modulator_opcode = instr.payload >> 44 + + #update phases at these boundaries + if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( + (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): + for ct in range(NUM_NCO): + if reset_flag[ct]: + #would expect this to be zero but this is first non-zero point + accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT + reset_flag[ct] = False + freq[:] = next_freq[:] + phase[:] = next_phase[:] + frame[:] = next_frame[:] + + #Assume new sequence at every WAIT + if instr.opcode == WAIT: + start_new_seq() + + elif instr.opcode == WFM and (( + (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): + addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT + count = (instr.payload >> 24) & 0xfffff + count = (count + 1) * ADDRESS_UNIT + isTA = (instr.payload >> 45) & 0x1 + chan_select_bits = ((instr.header >> 2) & 0x1, + (instr.header >> 3) & 0x1) + #On older firmware we broadcast by default whereas on newer we respect the engine select + for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): + if (file_version < 4) or select_bit: + if isTA: + seqs[chan][-1].append((count, wf_lib[chan][addr])) + else: + for sample in wf_lib[chan][addr:addr + count]: + seqs[chan][-1].append((1, sample)) + + elif instr.opcode == MARKER: + chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) + count = instr.payload & 0xffffffff + count = (count + 1) * ADDRESS_UNIT + state = (instr.payload >> 32) & 0x1 + seqs[chan][-1].append((count, state)) + + elif instr.opcode == MODULATION: + # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} + nco_select_bits = (instr.payload >> 40) & 0xf + if modulator_opcode == 0x0: + #modulate + count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT + nco_select = {0b0001: 0, + 0b0010: 1, + 0b0100: 2, + 0b1000: 3}[nco_select_bits] + seqs['mod_phase'][-1] = np.append( + seqs['mod_phase'][-1], freq[nco_select] * + np.arange(count) + accumulated_phase[nco_select] + + phase[nco_select] + frame[nco_select]) + accumulated_phase += count * freq + else: + phase_rad = 2 * np.pi * (instr.payload & + 0xffffffff) / 2**28 + for ct in range(NUM_NCO): + if (nco_select_bits >> ct) & 0x1: + if modulator_opcode == 0x2: + #reset + next_phase[ct] = 0 + next_frame[ct] = 0 + reset_flag[ct] = True + elif modulator_opcode == 0x6: + #set frequency + next_freq[ct] = phase_rad / ADDRESS_UNIT + elif modulator_opcode == 0xa: + #set phase + next_phase[ct] = phase_rad + elif modulator_opcode == 0xe: + #update frame + next_frame[ct] += phase_rad + + #Apply modulation if we have any + for ct, ( + ch1, ch2, mod_phase + ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): + if len(mod_phase): + #only really works if ch1, ch2 are broadcast together + mod_ch1 = [] + mod_ch2 = [] + cum_time = 0 + for ((time_ch1, amp_ch1), + (time_ch2, amp_ch2)) in zip(ch1, ch2): + if (amp_ch1 != 0) or (amp_ch2 != 0): + assert time_ch1 == time_ch2 + if time_ch1 == 1: + #single timestep + modulated = np.exp(1j * mod_phase[cum_time]) * ( + amp_ch1 + 1j * amp_ch2) + mod_ch1.append((1, modulated.real)) + mod_ch2.append((1, modulated.imag)) + else: + #expand TA + modulated = np.exp( + 1j * + mod_phase[cum_time:cum_time + time_ch1]) * ( + amp_ch1 + 1j * amp_ch2) + for val in modulated: + mod_ch1.append((1, val.real)) + mod_ch2.append((1, val.imag)) + else: + mod_ch1.append((time_ch1, amp_ch1)) + mod_ch2.append((time_ch2, amp_ch2)) + + cum_time += time_ch1 + seqs['ch1'][ct] = mod_ch1 + seqs['ch2'][ct] = mod_ch2 + + del seqs['mod_phase'] + + return seqs def update_wf_library(filename, pulses, offsets): - """ + """ Update a H5 waveform library in place give an iterable of (pulseName, pulse) tuples and offsets into the waveform library. """ - assert USE_PHASE_OFFSET_INSTRUCTION == False - #load the h5 file - with h5py.File(filename) as FID: - for label, pulse in pulses.items(): - #create a new waveform - if pulse.isTimeAmp: - shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) - else: - shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape - try: - length = offsets[label][1] - except KeyError: - print("\t{} not found in offsets so skipping".format(pulse)) - continue - for offset in offsets[label][0]: - print("\tUpdating {} at offset {}".format(pulse, offset)) - FID['/chan_1/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.real) - FID['/chan_2/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.imag) + assert USE_PHASE_OFFSET_INSTRUCTION == False + #load the h5 file + with h5py.File(filename) as FID: + for label, pulse in pulses.items(): + #create a new waveform + if pulse.isTimeAmp: + shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) + else: + shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape + try: + length = offsets[label][1] + except KeyError: + print("\t{} not found in offsets so skipping".format(pulse)) + continue + for offset in offsets[label][0]: + print("\tUpdating {} at offset {}".format(pulse, offset)) + FID['/chan_1/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.real) + FID['/chan_2/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.imag) + + +def tdm_instructions(seq): + """ + Generate the TDM instructions for the given sequence. + + This assumes that there is one instruction sequence, not + a list of them (as is generally the case elsewhere). FIXME + """ + + seq = list(flatten(copy(seq))) + instructions = list() + + # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label + instructions.append(Sync(label=seq[0])) + # turn into a loop, by appending GOTO(0) at end of the sequence + if not isinstance(seq[-1], ControlFlow.Goto): + seq.append(ControlFlow.Goto(BlockLabel.label(seq))) + logger.debug("Appending a GOTO at end to loop") + # add a WAIT before the first waveform FIXME: there must be a more efficient way + if ControlFlow.Wait not in seq: + ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) + seq.insert(ind_wait, ControlFlow.Wait()) + + + # the backpatch table for labels + label2addr = dict() + + label = None + for s in seq: + if isinstance(s, BlockLabel.BlockLabel): + label2addr[s.label] = len(instructions) + + # carry label forward to next entry + label = s + continue + + if isinstance(s, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(s, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + + elif isinstance(s, TdmInstructions.WriteAddrInstruction) and s.tdm == True: + if s.instruction == 'INVALIDATE': + print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(Invalidate(s.addr, s.value, label=label)) + + elif s.instruction == 'WRITEADDR': + print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(WriteAddr(s.addr, s.value, label=label)) + + elif s.instruction == 'STOREMEAS': + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(StoreMeas(s.addr, s.value, label=label)) + else: + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (s.instruction, str(s.channel), + s.addr, s.value)) + continue + + elif isinstance(s, TdmInstructions.CustomInstruction): + + if s.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVote(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + else: #TODO: add decoder + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (s.instruction, s.in_addr, s.out_addr)) + + elif isinstance(s, ControlFlow.Goto): + instructions.append(Goto(s.target, label=label)) + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Repeat(s.target, label=label)) + elif isinstance(s, ControlFlow.LoadRepeat): + instructions.append(Load(s.value - 1, label=label)) + + elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): + if s.instruction == 'LOADCMPVRAM' and s.tdm == True: + instructions.append( + LoadCmpVram(s.addr, s.mask, label=label)) + + elif isinstance(s, PulseSequencer.Pulse): + if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) + + elif isinstance(s, PulseSequencer.PulseBlock): + # FIXME: + # If this happens, we are confused. This could be a block containing multiple measurements, FIXME! + print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) + + elif isinstance(s, list): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT LIST: %s' % str(s)) + + elif isinstance(s, ControlFlow.ComparisonInstruction): + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) + + else: + # This isn't typically an error, because the TDM ignores a + # lot of instructions, but until this is debugged it's handy + # to see what's falling through. + + # FIXME: We're missing a lot of control-flow instructions + print('OOPS: unhandled [%s]' % str(type(s))) + + # clear label + label = None + + # for i in range(len(instructions)): + # instr_bits = instructions[i].flatten() + # # instr_txt = str(Instruction.unflatten(instr_bits)) + # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) + + # backpatch any instructions that have target fields + # + for i in instructions: + if i.target: + i.payload = label2addr[i.target.label] + + return [i.flatten() for i in instructions] + +# Utility Functions for displaying programs + +def get_channel_instructions_string(channel): + return '/chan_{}/instructions'.format(channel) + +def raw_instructions(filename, channel = 1): + channelStr = get_channel_instructions_string(channel) + with h5py.File(filename, 'r') as fid: + raw_instrs = fid[channelStr].value.flatten() + return raw_instrs + +def decompile_instructions(instructions, tdm = False): + return [Instruction.unflatten(x, decode_as_tdm = tdm) for x in instructions] + +def read_instructions(filename): + raw_instrs = raw_instructions(filename) + return decompile_instructions(raw_instrs) + +def replace_instructions(filename, instructions, channel = 1): + channelStr = get_channel_instructions_string(channel) + with h5py.File(filename, 'r+') as fid: + del fid[channelStr] + fid.create_dataset(channelStr, data=instructions) + +def display_decompiled_file(filename, tdm = False): + raw = raw_instructions(filename) + display_decompiled_instructions(raw, tdm) + +def display_decompiled_instructions(raw, tdm = False, display_op_codes = True): + cmds = decompile_instructions(raw, tdm) + opcodeStr = '' + for i,a in enumerate(zip(raw,cmds)): + x,y = a + if display_op_codes: + opcodeStr = "0x{:016x} - ".format(x) + print("{:5}: {}{}".format(i, opcodeStr,y)) + +def display_raw_instructions(raw): + for x in raw: + print("0x{:016x}".format(x)) + +def display_raw_file(filename): + raw = raw_instructions(filename) + display_raw_instructions(raw) diff --git a/QGL/drivers/APS2Pattern_bck.py b/QGL/drivers/APS2Pattern_bck.py new file mode 100644 index 00000000..dcee35ab --- /dev/null +++ b/QGL/drivers/APS2Pattern_bck.py @@ -0,0 +1,1151 @@ +''' +Module for writing hdf5 APS2 files from sequences and patterns + +Copyright 2014 Raytheon BBN Technologies + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +''' + +import os +import logging +from warnings import warn +from copy import copy +from future.moves.itertools import zip_longest +import pickle + +import h5py +import numpy as np + +from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils +from QGL.PatternUtils import hash_pulse, flatten + +# Python 2/3 compatibility: use 'int' that subclasses 'long' +from builtins import int + +#Some constants +SAMPLING_RATE = 1.2e9 +ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps +MIN_ENTRY_LENGTH = 8 +MAX_WAVEFORM_PTS = 2**28 #maximum size of waveform memory +WAVEFORM_CACHE_SIZE = 2**17 +MAX_WAVEFORM_VALUE = 2**13 - 1 #maximum waveform value i.e. 14bit DAC +MAX_NUM_INSTRUCTIONS = 2**26 +MAX_REPEAT_COUNT = 2**16 - 1 +MAX_TRIGGER_COUNT = 2**32 - 1 + +# instruction encodings +WFM = 0x0 +MARKER = 0x1 +WAIT = 0x2 +LOAD = 0x3 +REPEAT = 0x4 +CMP = 0x5 +GOTO = 0x6 +CALL = 0x7 +RET = 0x8 +SYNC = 0x9 +MODULATION = 0xA +LOADCMP = 0xB +PREFETCH = 0xC +NOP = 0XF + +# WFM/MARKER op codes +PLAY = 0x0 +WAIT_TRIG = 0x1 +WAIT_SYNC = 0x2 +WFM_PREFETCH = 0x3 +WFM_OP_OFFSET = 46 +TA_PAIR_BIT = 45 + +# CMP op encodings +EQUAL = 0x0 +NOTEQUAL = 0x1 +GREATERTHAN = 0x2 +LESSTHAN = 0x3 + +# Whether we use PHASE_OFFSET modulation commands or bake it into waveform +# Default to false as we usually don't have many variants +USE_PHASE_OFFSET_INSTRUCTION = False + +# Whether to save the waveform offsets for partial compilation +SAVE_WF_OFFSETS = False + +# Do we want a pulse file per instrument or per channel +SEQFILE_PER_CHANNEL = False + +def get_empty_channel_set(): + return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} + + +def get_seq_file_extension(): + return '.h5' + + +def is_compatible_file(filename): + with h5py.File(filename, 'r') as FID: + target = FID['/'].attrs['target hardware'] + if isinstance(target, str): + target = target.encode('utf-8') + if target == b'APS2': + return True + return False + +def create_wf_vector(wfLib, seqs): + ''' + Helper function to create the wf vector and offsets into it. + ''' + max_pts_needed = 0 + for wf in wfLib.values(): + if len(wf) == 1: + max_pts_needed += ADDRESS_UNIT + else: + max_pts_needed += len(wf) + + #If we have more than fits in cache we'll need to align and prefetch + need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE + + idx = 0 + + if not need_prefetch: + offsets = [{}] + cache_lines = [] + #if we can fit them all in just pack + wfVec = np.zeros(max_pts_needed, dtype=np.int16) + for key, wf in wfLib.items(): + #Clip the wf + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) + offsets[-1][key] = idx + idx += wf.size + + #Trim the waveform + wfVec.resize(idx) + + else: + #otherwise fill in one cache line at a time + CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 + wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) + offsets = [{}] + cache_lines = [] + for seq in seqs: + #go through sequence and see what we need to add + pts_to_add = 0 + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + pts_to_add += entry.length + + #If what we need to add spills over then add a line and start again + if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: + idx = int(CACHE_LINE_LENGTH * ( + (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) + wfVec = np.append(wfVec, + np.zeros(CACHE_LINE_LENGTH, + dtype=np.int16)) + offsets.append({}) + + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + wf = wfLib[sig] + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16( + MAX_WAVEFORM_VALUE * wf) + offsets[-1][sig] = idx + idx += wf.size + + cache_lines.append(int(idx // CACHE_LINE_LENGTH)) + + return wfVec, offsets, cache_lines + + +class Instruction(object): + def __init__(self, header, payload, label=None, target=None): + self.header = header + self.payload = int(payload) + self.label = label + self.target = target + + @classmethod + def unflatten(cls, instr): + return cls(header=(int(instr) >> 56) & 0xff, + payload=int(instr) & 0xffffffffffffff) + + def __repr__(self): + return self.__str__() + + def __str__(self): + + opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", + "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", + "NOP", "NOP", "NOP"] + + out = "{0} ".format(self.label) if self.label else "" + + instrOpCode = (self.header >> 4) & 0xf + out += opCodes[instrOpCode] + + if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( + instrOpCode == MODULATION): + if (instrOpCode == MARKER) or (instrOpCode == WFM): + out += "; engine={}, ".format((self.header >> 2) & 0x3) + else: + out += "; " + if self.header & 0x1: + out += "write=1 | " + else: + out += "write=0 | " + + if self.target: + out += " {}".format(self.target) + + if instrOpCode == WFM: + wfOpCode = (self.payload >> 46) & 0x3 + wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] + out += wfOpCodes[wfOpCode] + out += "; TA bit={}".format((self.payload >> 45) & 0x1) + out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) + out += ", addr = {}".format(self.payload & 2**24 - 1) + + elif instrOpCode == MARKER: + mrkOpCode = (self.payload >> 46) & 0x3 + mrkOpCodes = ["PLAY", "TRIG", "SYNC"] + out += mrkOpCodes[mrkOpCode] + out += "; state={}".format((self.payload >> 32) & 0x1) + out += ", count = {}".format(self.payload & 2**32 - 1) + + elif instrOpCode == MODULATION: + modulatorOpCode = (self.payload >> 45) & 0x7 + modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", + "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] + out += modulatorOpCodes[modulatorOpCode] + out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) + if modulatorOpCode == 0x0: + out += ", count={:d}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x3: + out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x5: + out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x7: + out += ", frame_change=0x{:08x}".format(self.payload & + 0xffffffff) + + elif instrOpCode == CMP: + cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] + cmpCode = (self.payload >> 8) & 0x3 + out += " | " + cmpCodes[cmpCode] + out += ", value = {}".format(self.payload & 0xff) + + elif any( + [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): + out += " | target addr = {}".format(self.payload & 2**26 - 1) + + elif instrOpCode == LOAD: + out += " | count = {}".format(self.payload) + + return out + + def __eq__(self, other): + return self.header == other.header and self.payload == other.payload and self.label == other.label + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.header, self.payload, self.label)) + + @property + def address(self): + return self.payload & 0xffffffff # bottom 32-bits of payload + + @address.setter + def address(self, value): + self.payload |= value & 0xffffffff + + @property + def writeFlag(self): + return self.header & 0x1 + + @writeFlag.setter + def writeFlag(self, value): + self.header |= value & 0x1 + + @property + def opcode(self): + return self.header >> 4 + + def flatten(self): + return int((self.header << 56) | (self.payload & 0xffffffffffff)) + + +def Waveform(addr, count, isTA, write=False, label=None): + header = (WFM << 4) | (0x3 << 2) | (write & + 0x1) #broadcast to both engines + count = int(count) + count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count + addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr + payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) + << TA_PAIR_BIT) | (count << 24) | addr + return Instruction(header, payload, label) + + +def WaveformPrefetch(addr): + header = (WFM << 4) | (0x3 << 2) | (0x1) + payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr + return Instruction(header, payload, None) + + +def Marker(sel, state, count, write=False, label=None): + header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) + count = int(count) + four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count + count_rem = count % ADDRESS_UNIT + if state == 0: + transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} + transition = transitionWords[count_rem] + else: + transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} + transition = transitionWords[count_rem] + payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( + (state & 0x1) << 32) | four_count + return Instruction(header, payload, label) + + +def Command(cmd, payload, write=False, label=None): + header = (cmd << 4) + if isinstance(payload, int): + instr = Instruction(header, payload, label) + else: + instr = Instruction(header, 0, label, target=payload) + instr.writeFlag = write + return instr + + +def Sync(label=None): + return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) + + +def Wait(label=None): + return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) + + +def LoadCmp(label=None): + return Command(LOADCMP, 0, label=label) + + +def Cmp(op, value, label=None): + return Command(CMP, (op << 8) | (value & 0xff), label=label) + + +def Goto(addr, label=None): + return Command(GOTO, addr, label=label) + + +def Call(addr, label=None): + return Command(CALL, addr, label=label) + + +def Return(label=None): + return Command(RET, 0, label=label) + + +def Load(count, label=None): + return Command(LOAD, count, label=label) + + +def Repeat(addr, label=None): + return Command(REPEAT, addr, label=label) + + +def Prefetch(addr, label=None): + return Command(PREFETCH, addr) + + +def NoOp(): + return Instruction.unflatten(0xffffffffffffffff) + + +def preprocess(seqs, shapeLib): + seqs = PatternUtils.convert_lengths_to_samples( + seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) + wfLib = build_waveforms(seqs, shapeLib) + inject_modulation_cmds(seqs) + return seqs, wfLib + + +def wf_sig(wf): + ''' + Compute a signature of a Compiler.Waveform that identifies the relevant properties for + two Waveforms to be considered "equal" in the waveform library. For example, we ignore + length of TA waveforms. + ''' + if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.amp) + else: + return (wf.amp, round(wf.phase * 2**13)) + else: + #TODO: why do we need the length? + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.key, wf.amp, wf.length) + else: + return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) + + +class ModulationCommand(object): + """docstring for ModulationCommand""" + + def __init__(self, + instruction, + nco_select, + frequency=0, + phase=0, + length=0): + super(ModulationCommand, self).__init__() + self.instruction = instruction + self.nco_select = nco_select + self.frequency = frequency + self.phase = phase + self.length = length + + def __str__(self): + out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, + self.nco_select) + if self.instruction == "MODULATE": + out += ", length={})".format(self.length) + elif self.instruction == "SET_FREQ": + out += ", frequency={})".format(self.frequency) + elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": + out += ", phase={})".format(self.phase) + else: + out += ")" + return out + + def _repr_pretty_(self, p, cycle): + p.text(str(self)) + + def __repr__(self): + return str(self) + + def to_instruction(self, write_flag=True, label=None): + #Modulator op codes + MODULATOR_OP_OFFSET = 44 + NCO_SELECT_OP_OFFSET = 40 + MODULATION_CLOCK = 300e6 + + op_code_map = {"MODULATE": 0x0, + "RESET_PHASE": 0x2, + "SET_FREQ": 0x6, + "SET_PHASE": 0xa, + "UPDATE_FRAME": 0xe} + payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( + self.nco_select << NCO_SELECT_OP_OFFSET) + if self.instruction == "MODULATE": + #zero-indexed quad count + payload |= np.uint32(self.length / ADDRESS_UNIT - 1) + elif self.instruction == "SET_FREQ": + # frequencies can span -2 to 2 or 0 to 4 in unsigned + payload |= np.uint32( + (self.frequency / MODULATION_CLOCK if self.frequency > 0 else + self.frequency / MODULATION_CLOCK + 4) * 2**28) + elif (self.instruction == "SET_PHASE") | ( + self.instruction == "UPDATE_FRAME"): + #phases can span -0.5 to 0.5 or 0 to 1 in unsigned + payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) + + instr = Instruction(MODULATION << 4, payload, label) + instr.writeFlag = write_flag + return instr + +def inject_modulation_cmds(seqs): + """ + Inject modulation commands from phase, frequency and frameChange of waveforms + in an IQ waveform sequence. Assume up to 2 NCOs for now. + """ + cur_freq = 0 + cur_phase = 0 + for ct,seq in enumerate(seqs): + #check whether we have modulation commands + freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) + if len(freqs) > 2: + raise Exception("Max 2 frequencies on the same channel allowed.") + no_freq_cmds = np.allclose(freqs, 0) + phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_phase_cmds = np.allclose(phases, 0) + frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_frame_cmds = np.allclose(frame_changes, 0) + no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds + + if no_modulation_cmds: + continue + + mod_seq = [] + pending_frame_update = False + + for entry in seq: + + #copies to avoid same object having different timestamps later + #copy through BlockLabel + if isinstance(entry, BlockLabel.BlockLabel): + mod_seq.append(copy(entry)) + #mostly copy through control-flow + elif isinstance(entry, ControlFlow.ControlInstruction): + #heuristic to insert phase reset before trigger if we have modulation commands + if isinstance(entry, ControlFlow.Wait): + if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): + mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) + for nco_ind, freq in enumerate(freqs): + mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) + elif isinstance(entry, ControlFlow.Return): + cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines + mod_seq.append(copy(entry)) + elif isinstance(entry, Compiler.Waveform): + if not no_modulation_cmds: + #select nco + nco_select = (list(freqs)).index(entry.frequency) + 1 + cur_freq = entry.frequency + if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): + mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) + cur_phase = entry.phase + #now apply modulation for count command and waveform command, if non-zero length + if entry.length > 0: + mod_seq.append(entry) + # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command + if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ + and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: + mod_seq[-2].length += entry.length + else: + mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) + pending_frame_update = False + #now apply non-zero frame changes after so it is applied at end + if entry.frameChange != 0: + pending_frame_update = True + #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible + if entry.length == 0: + #if the last is a frame change then we can add to the frame change + if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": + mod_seq[-1].phase += entry.frameChange + #if last entry was pulse without frame change we add frame change + elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #if this is the first entry with a wait for trigger then we can inject a frame change + #before the wait for trigger but after the RESET_PHASE + elif isinstance(mod_seq[-1], ControlFlow.Wait): + mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": + mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #otherwise drop and error if frame has been defined + else: + raise Exception("Unable to implement zero time Z pulse") + else: + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + + seqs[ct] = mod_seq + +def build_waveforms(seqs, shapeLib): + # apply amplitude (and optionally phase) and add the resulting waveforms to the library + wfLib = {} + for wf in flatten(seqs): + if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: + shape = wf.amp * shapeLib[wf.key] + if not USE_PHASE_OFFSET_INSTRUCTION: + shape *= np.exp(1j * wf.phase) + wfLib[wf_sig(wf)] = shape + return wfLib + + +def timestamp_entries(seq): + t = 0 + for ct in range(len(seq)): + seq[ct].startTime = t + t += seq[ct].length + + +def synchronize_clocks(seqs): + # Control-flow instructions (CFIs) must occur at the same time on all channels. + # Therefore, we need to "reset the clock" by synchronizing the accumulated + # time at each CFI to the largest value on any channel + syncInstructions = [list(filter( + lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) + for seq in seqs if seq] + + # Add length to control-flow instructions to make accumulated time match at end of CFI. + # Keep running tally of how much each channel has been shifted so far. + localShift = [0 for _ in syncInstructions] + for ct in range(len(syncInstructions[0])): + step = [seq[ct] for seq in syncInstructions] + endTime = max((s.startTime + shift + for s, shift in zip(step, localShift))) + for ct, s in enumerate(step): + s.length = endTime - (s.startTime + localShift[ct]) + # localShift[ct] += endTime - (s.startTime + localShift[ct]) + # the += and the last term cancel, therefore: + localShift[ct] = endTime - s.startTime + # re-timestamp to propagate changes across the sequences + for seq in seqs: + timestamp_entries(seq) + # then transfer the control flow "lengths" back into start times + for seq in syncInstructions: + for instr in seq: + instr.startTime += instr.length + instr.length = 0 + + +def create_seq_instructions(seqs, offsets): + ''' + Helper function to create instruction vector from an IR sequence and an offset dictionary + keyed on the wf keys. + + Seqs is a list of lists containing waveform and marker data, e.g. + [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] + + We take the strategy of greedily grabbing the next instruction that occurs in time, accross + all waveform and marker channels. + ''' + + # timestamp all entries before filtering (where we lose time information on control flow) + for seq in seqs: + timestamp_entries(seq) + + synchronize_clocks(seqs) + + # create (seq, startTime) pairs over all sequences + timeTuples = [] + for ct, seq in enumerate(seqs): + timeTuples += [(entry.startTime, ct) for entry in seq] + timeTuples.sort() + + # keep track of where we are in each sequence + indexes = np.zeros(len(seqs), dtype=np.int64) + + cmpTable = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} + + # always start with SYNC (stealing label from beginning of sequence) + # unless it is a subroutine (using last entry as return as tell) + label = None + instructions = [] + for ct, seq in enumerate(seqs): + if len(seq): + first_non_empty = ct + break + if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): + if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): + label = seqs[first_non_empty][0] + timeTuples.pop(0) + indexes[first_non_empty] += 1 + instructions.append(Sync(label=label)) + label = None + + while len(timeTuples) > 0: + #pop off all entries that have the same time + entries = [] + start_time = 0 + while True: + start_time, seq_idx = timeTuples.pop(0) + entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) + indexes[seq_idx] += 1 + next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 + if start_time != next_start_time: + break + + write_flags = [True] * len(entries) + for ct, (entry, seq_idx) in enumerate(entries): + #use first non empty sequence for control flow + if seq_idx == first_non_empty and ( + isinstance(entry, ControlFlow.ControlInstruction) or + isinstance(entry, BlockLabel.BlockLabel)): + if isinstance(entry, BlockLabel.BlockLabel): + # carry label forward to next entry + label = entry + continue + # control flow instructions + elif isinstance(entry, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(entry, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + elif isinstance(entry, ControlFlow.Sync): + instructions.append(Sync(label=label)) + elif isinstance(entry, ControlFlow.Return): + instructions.append(Return(label=label)) + # target argument commands + elif isinstance(entry, ControlFlow.Goto): + instructions.append(Goto(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Call): + instructions.append(Call(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Repeat): + instructions.append(Repeat(entry.target, label=label)) + # value argument commands + elif isinstance(entry, ControlFlow.LoadRepeat): + instructions.append(Load(entry.value - 1, label=label)) + elif isinstance(entry, ControlFlow.ComparisonInstruction): + # TODO modify Cmp operator to load from specified address + instructions.append(Cmp(cmpTable[entry.operator], + entry.value, + label=label)) + + continue + + if seq_idx == 0: + #analog - waveforms or modulation + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping Waveform entry of length %s!" % entry.length) + continue + instructions.append(Waveform(offsets[wf_sig(entry)], + entry.length, + entry.isTimeAmp or + entry.isZero, + write=write_flags[ct], + label=label)) + elif isinstance(entry, ModulationCommand): + instructions.append(entry.to_instruction( + write_flag=write_flags[ct], + label=label)) + + else: # a marker engine + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping entry!") + continue + markerSel = seq_idx - 1 + state = not entry.isZero + instructions.append(Marker(markerSel, + state, + entry.length, + write=write_flags[ct], + label=label)) + + #clear label + label = None + + return instructions + + +def create_instr_data(seqs, offsets, cache_lines): + ''' + Constructs the complete instruction data vector, and does basic checks for validity. + + Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line + ''' + logger = logging.getLogger(__name__) + logger.debug('') + + seq_instrs = [] + need_prefetch = len(cache_lines) > 0 + num_cache_lines = len(set(cache_lines)) + cache_line_changes = np.concatenate( + ([0], np.where(np.diff(cache_lines))[0] + 1)) + for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): + seq_instrs.append(create_seq_instructions( + list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0])) + #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line + if need_prefetch and (ct in cache_line_changes): + next_cache_line = cache_lines[cache_line_changes[(np.where( + ct == cache_line_changes)[0][0] + 1) % len( + cache_line_changes)]] + seq_instrs[-1].insert(0, WaveformPrefetch(int( + next_cache_line * WAVEFORM_CACHE_SIZE / 2))) + #steal label + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None + + #concatenate instructions + instructions = [] + subroutines_start = -1 + for ct, seq in enumerate(seq_instrs): + #Use last instruction being return as mark of start of subroutines + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + instructions += seq + + #if we have any subroutines then group in cache lines + if subroutines_start >= 0: + subroutine_instrs = [] + subroutine_cache_line = {} + CACHE_LINE_LENGTH = 128 + offset = 0 + for sub in seq_instrs[subroutines_start:]: + #TODO for now we don't properly handle prefetching mulitple cache lines + if len(sub) > CACHE_LINE_LENGTH: + warnings.warn( + "Subroutines longer than {} instructions may not be prefetched correctly") + #Don't unecessarily split across a cache line + if (len(sub) + offset > CACHE_LINE_LENGTH) and ( + len(sub) < CACHE_LINE_LENGTH): + pad_instrs = 128 - ((offset + 128) % 128) + subroutine_instrs += [NoOp()] * pad_instrs + offset = 0 + if offset == 0: + line_label = sub[0].label + subroutine_cache_line[sub[0].label] = line_label + subroutine_instrs += sub + offset += len(sub) % CACHE_LINE_LENGTH + logger.debug("Placed {} subroutines into {} cache lines".format( + len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // + CACHE_LINE_LENGTH)) + #inject prefetch commands before waits + wait_idx = [idx for idx, instr in enumerate(instructions) + if (instr.header >> 4) == WAIT] + [len(instructions)] + instructions_with_prefetch = instructions[:wait_idx[0]] + last_prefetch = None + for start, stop in zip(wait_idx[:-1], wait_idx[1:]): + call_targets = [instr.target for instr in instructions[start:stop] + if (instr.header >> 4) == CALL] + needed_lines = set() + for target in call_targets: + needed_lines.add(subroutine_cache_line[target]) + if len(needed_lines) > 8: + raise RuntimeError( + "Unable to prefetch more than 8 cache lines") + for needed_line in needed_lines: + if needed_line != last_prefetch: + instructions_with_prefetch.append(Prefetch(needed_line)) + last_prefetch = needed_line + instructions_with_prefetch += instructions[start:stop] + + instructions = instructions_with_prefetch + #pad out instruction vector to ensure circular cache never loads a subroutine + pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) + instructions += [NoOp()] * pad_instrs + + instructions += subroutine_instrs + + #turn symbols into integers addresses + resolve_symbols(instructions) + + assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ + 'Oops! too many instructions: {0}'.format(len(instructions)) + + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) + + +def resolve_symbols(seq): + symbols = {} + # create symbol look-up table + for ct, entry in enumerate(seq): + if entry.label and entry.label not in symbols: + symbols[entry.label] = ct + # then update + for entry in seq: + if entry.target: + entry.address = symbols[entry.target] + + +def compress_marker(markerLL): + ''' + Compresses adjacent entries of the same state into single entries + ''' + for seq in markerLL: + idx = 0 + while idx + 1 < len(seq): + if (isinstance(seq[idx], Compiler.Waveform) and + isinstance(seq[idx + 1], Compiler.Waveform) and + seq[idx].isZero == seq[idx + 1].isZero): + + seq[idx].length += seq[idx + 1].length + del seq[idx + 1] + else: + idx += 1 + + +def write_sequence_file(awgData, fileName): + ''' + Main function to pack channel sequences into an APS2 h5 file. + ''' + # Convert QGL IR into a representation that is closer to the hardware. + awgData['ch12']['linkList'], wfLib = preprocess( + awgData['ch12']['linkList'], awgData['ch12']['wfLib']) + + # compress marker data + for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: + if 'linkList' in awgData[field].keys(): + PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], + SAMPLING_RATE, 1, + Compiler.Waveform) + compress_marker(awgData[field]['linkList']) + else: + awgData[field]['linkList'] = [] + + #Create the waveform vectors + wfInfo = [] + wfInfo.append(create_wf_vector({key: wf.real + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + wfInfo.append(create_wf_vector({key: wf.imag + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + + if SAVE_WF_OFFSETS: + #create a set of all waveform signatures in offset dictionaries + #we could have multiple offsets for the same pulse becuase it could + #be repeated in multiple cache lines + wf_sigs = set() + for offset_dict in wfInfo[0][1]: + wf_sigs |= set(offset_dict.keys()) + #create dictionary linking entry labels (that's what we'll have later) with offsets + offsets = {} + for seq in awgData['ch12']['linkList']: + for entry in seq: + if len(wf_sigs) == 0: + break + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig in wf_sigs: + #store offsets and wavefor lib length + #time ampltidue entries are clamped to ADDRESS_UNIT + wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length + offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], + wf_length) + wf_sigs.discard(sig) + + #break out of outer loop too + if len(wf_sigs) == 0: + break + + #now pickle the label=>offsets + with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: + pickle.dump(offsets, FID) + + # build instruction vector + seq_data = [awgData[s]['linkList'] + for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] + instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) + + #Open the HDF5 file + if os.path.isfile(fileName): + os.remove(fileName) + with h5py.File(fileName, 'w') as FID: + FID['/'].attrs['Version'] = 4.0 + FID['/'].attrs['target hardware'] = 'APS2' + FID['/'].attrs['minimum firmware version'] = 4.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) + + #Create the groups and datasets + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + #Write the waveformLib to file + FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) + + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', + data=instructions) + + +def read_sequence_file(fileName): + """ + Reads a HDF5 sequence file and returns a dictionary of lists. + Dictionary keys are channel strings such as ch1, ch12m1 + Lists are or tuples of time-amplitude pairs (time, output) + """ + chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', + 'mod_phase'] + seqs = {ch: [] for ch in chanStrs} + + def start_new_seq(): + for ct, ch in enumerate(chanStrs): + if (ct < 2) or (ct == 6): + #analog or modulation channel + seqs[ch].append([]) + else: + #marker channel + seqs[ch].append([]) + + with h5py.File(fileName, 'r') as FID: + file_version = FID["/"].attrs["Version"] + wf_lib = {} + wf_lib['ch1'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() + wf_lib['ch2'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() + instructions = FID['/chan_1/instructions'].value.flatten() + NUM_NCO = 2 + freq = np.zeros(NUM_NCO) #radians per timestep + phase = np.zeros(NUM_NCO) + frame = np.zeros(NUM_NCO) + next_freq = np.zeros(NUM_NCO) + next_phase = np.zeros(NUM_NCO) + next_frame = np.zeros(NUM_NCO) + accumulated_phase = np.zeros(NUM_NCO) + reset_flag = [False]*NUM_NCO + + for data in instructions: + instr = Instruction.unflatten(data) + + modulator_opcode = instr.payload >> 44 + + #update phases at these boundaries + if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( + (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): + for ct in range(NUM_NCO): + if reset_flag[ct]: + #would expect this to be zero but this is first non-zero point + accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT + reset_flag[ct] = False + freq[:] = next_freq[:] + phase[:] = next_phase[:] + frame[:] = next_frame[:] + + #Assume new sequence at every WAIT + if instr.opcode == WAIT: + start_new_seq() + + elif instr.opcode == WFM and (( + (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): + addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT + count = (instr.payload >> 24) & 0xfffff + count = (count + 1) * ADDRESS_UNIT + isTA = (instr.payload >> 45) & 0x1 + chan_select_bits = ((instr.header >> 2) & 0x1, + (instr.header >> 3) & 0x1) + #On older firmware we broadcast by default whereas on newer we respect the engine select + for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): + if (file_version < 4) or select_bit: + if isTA: + seqs[chan][-1].append((count, wf_lib[chan][addr])) + else: + for sample in wf_lib[chan][addr:addr + count]: + seqs[chan][-1].append((1, sample)) + + elif instr.opcode == MARKER: + chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) + count = instr.payload & 0xffffffff + count = (count + 1) * ADDRESS_UNIT + state = (instr.payload >> 32) & 0x1 + seqs[chan][-1].append((count, state)) + + elif instr.opcode == MODULATION: + # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} + nco_select_bits = (instr.payload >> 40) & 0xf + if modulator_opcode == 0x0: + #modulate + count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT + nco_select = {0b0001: 0, + 0b0010: 1, + 0b0100: 2, + 0b1000: 3}[nco_select_bits] + seqs['mod_phase'][-1] = np.append( + seqs['mod_phase'][-1], freq[nco_select] * + np.arange(count) + accumulated_phase[nco_select] + + phase[nco_select] + frame[nco_select]) + accumulated_phase += count * freq + else: + phase_rad = 2 * np.pi * (instr.payload & + 0xffffffff) / 2**28 + for ct in range(NUM_NCO): + if (nco_select_bits >> ct) & 0x1: + if modulator_opcode == 0x2: + #reset + next_phase[ct] = 0 + next_frame[ct] = 0 + reset_flag[ct] = True + elif modulator_opcode == 0x6: + #set frequency + next_freq[ct] = phase_rad / ADDRESS_UNIT + elif modulator_opcode == 0xa: + #set phase + next_phase[ct] = phase_rad + elif modulator_opcode == 0xe: + #update frame + next_frame[ct] += phase_rad + + #Apply modulation if we have any + for ct, ( + ch1, ch2, mod_phase + ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): + if len(mod_phase): + #only really works if ch1, ch2 are broadcast together + mod_ch1 = [] + mod_ch2 = [] + cum_time = 0 + for ((time_ch1, amp_ch1), + (time_ch2, amp_ch2)) in zip(ch1, ch2): + if (amp_ch1 != 0) or (amp_ch2 != 0): + assert time_ch1 == time_ch2 + if time_ch1 == 1: + #single timestep + modulated = np.exp(1j * mod_phase[cum_time]) * ( + amp_ch1 + 1j * amp_ch2) + mod_ch1.append((1, modulated.real)) + mod_ch2.append((1, modulated.imag)) + else: + #expand TA + modulated = np.exp( + 1j * + mod_phase[cum_time:cum_time + time_ch1]) * ( + amp_ch1 + 1j * amp_ch2) + for val in modulated: + mod_ch1.append((1, val.real)) + mod_ch2.append((1, val.imag)) + else: + mod_ch1.append((time_ch1, amp_ch1)) + mod_ch2.append((time_ch2, amp_ch2)) + + cum_time += time_ch1 + seqs['ch1'][ct] = mod_ch1 + seqs['ch2'][ct] = mod_ch2 + + del seqs['mod_phase'] + + return seqs + + +def update_wf_library(filename, pulses, offsets): + """ + Update a H5 waveform library in place give an iterable of (pulseName, pulse) + tuples and offsets into the waveform library. + """ + assert USE_PHASE_OFFSET_INSTRUCTION == False + #load the h5 file + with h5py.File(filename) as FID: + for label, pulse in pulses.items(): + #create a new waveform + if pulse.isTimeAmp: + shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) + else: + shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape + try: + length = offsets[label][1] + except KeyError: + print("\t{} not found in offsets so skipping".format(pulse)) + continue + for offset in offsets[label][0]: + print("\tUpdating {} at offset {}".format(pulse, offset)) + FID['/chan_1/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.real) + FID['/chan_2/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.imag) diff --git a/QGL/drivers/APS2TDMPattern.py b/QGL/drivers/APS2TDMPattern.py deleted file mode 100644 index 8e52ac4e..00000000 --- a/QGL/drivers/APS2TDMPattern.py +++ /dev/null @@ -1,1398 +0,0 @@ -''' -Module for writing hdf5 APS2 files from sequences and patterns - -Copyright 2014 Raytheon BBN Technologies - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -''' - -import os -import logging -from warnings import warn -from copy import copy -from future.moves.itertools import zip_longest -import pickle - -import h5py -import numpy as np - -from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils -from QGL import PulseSequencer -from QGL.PatternUtils import hash_pulse, flatten -from QGL import TdmInstructions - -# Python 2/3 compatibility: use 'int' that subclasses 'long' -from builtins import int - -logger = logging.getLogger(__name__) - -#Some constants -SAMPLING_RATE = 1.2e9 -ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps -MIN_ENTRY_LENGTH = 8 -MAX_WAVEFORM_PTS = 2**28 #maximum size of waveform memory -WAVEFORM_CACHE_SIZE = 2**17 -MAX_WAVEFORM_VALUE = 2**13 - 1 #maximum waveform value i.e. 14bit DAC -MAX_NUM_INSTRUCTIONS = 2**26 -MAX_REPEAT_COUNT = 2**16 - 1 -MAX_TRIGGER_COUNT = 2**32 - 1 - -# instruction encodings -WFM = 0x0 -MARKER = 0x1 -WAIT = 0x2 -LOAD = 0x3 -REPEAT = 0x4 -CMP = 0x5 -GOTO = 0x6 -CALL = 0x7 -RET = 0x8 -SYNC = 0x9 -MODULATION = 0xA -LOADCMP = 0xB -PREFETCH = 0xC -NOP = 0XF - -# # APS3 prototype -CUSTOM = 0xD -INVALIDATE = 0xE # Invalidate and WriteAddr use the same opcode -WRITEADDR = 0xE - -# WFM/MARKER op codes -PLAY = 0x0 -WAIT_TRIG = 0x1 -WAIT_SYNC = 0x2 -WFM_PREFETCH = 0x3 -WFM_OP_OFFSET = 46 -TA_PAIR_BIT = 45 - -# CMP op encodings -EQUAL = 0x0 -NOTEQUAL = 0x1 -GREATERTHAN = 0x2 -LESSTHAN = 0x3 - -CMPTABLE = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} - -# custom OP_CODES -TDM_MAJORITY_VOTE = 0 -TDM_MAJORITY_VOTE_SET_MASK = 1 -TDM_TSM_SET_ROUNDS = 2 -TDM_TSM = 3 - -TDM_CUSTOM_DECODE = ["TDM_MAJORITY_VOTE", "TDM_MAJORITY_VOTE_SET_MASK", "TDM_TSM_SET_ROUNDS", "TDM_TSM"] - -# Whether we use PHASE_OFFSET modulation commands or bake it into waveform -# Default to false as we usually don't have many variants -USE_PHASE_OFFSET_INSTRUCTION = False - -# Whether to save the waveform offsets for partial compilation -SAVE_WF_OFFSETS = False - -# Do we want a pulse file per instrument or per channel -SEQFILE_PER_CHANNEL = False - -def get_empty_channel_set(): - return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} - - -def get_seq_file_extension(): - return '.h5' - - -def is_compatible_file(filename): - with h5py.File(filename, 'r') as FID: - target = FID['/'].attrs['target hardware'] - if isinstance(target, str): - target = target.encode('utf-8') - if target == b'APS2': - return True - return False - -def create_wf_vector(wfLib, seqs): - ''' - Helper function to create the wf vector and offsets into it. - ''' - max_pts_needed = 0 - for wf in wfLib.values(): - if len(wf) == 1: - max_pts_needed += ADDRESS_UNIT - else: - max_pts_needed += len(wf) - - #If we have more than fits in cache we'll need to align and prefetch - need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE - - idx = 0 - - if not need_prefetch: - offsets = [{}] - cache_lines = [] - #if we can fit them all in just pack - wfVec = np.zeros(max_pts_needed, dtype=np.int16) - for key, wf in wfLib.items(): - #Clip the wf - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) - offsets[-1][key] = idx - idx += wf.size - - #Trim the waveform - wfVec.resize(idx) - - else: - #otherwise fill in one cache line at a time - CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 - wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) - offsets = [{}] - cache_lines = [] - for seq in seqs: - #go through sequence and see what we need to add - pts_to_add = 0 - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - pts_to_add += entry.length - - #If what we need to add spills over then add a line and start again - if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: - idx = int(CACHE_LINE_LENGTH * ( - (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) - wfVec = np.append(wfVec, - np.zeros(CACHE_LINE_LENGTH, - dtype=np.int16)) - offsets.append({}) - - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - wf = wfLib[sig] - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16( - MAX_WAVEFORM_VALUE * wf) - offsets[-1][sig] = idx - idx += wf.size - - cache_lines.append(int(idx // CACHE_LINE_LENGTH)) - - return wfVec, offsets, cache_lines - - -class Instruction(object): - def __init__(self, header, payload, label=None, target=None): - self.header = header - self.payload = int(payload) - self.label = label - self.target = target - - @classmethod - def unflatten(cls, instr): - return cls(header=(int(instr) >> 56) & 0xff, - payload=int(instr) & 0xffffffffffffff) - - def __repr__(self): - return self.__str__() - - def __str__(self): - - opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", - "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "CUSTOM", "WRITEADDR", "NOP"] - - # list of opCodes where the reprenstation will change - excludeList = ["WRITEADDR", "LOADCMP"] - - customOps = [ - "MajorityVote", "MajoritySetMask", # TODO there are others... - ] - - out = "{0} ".format(self.label) if self.label else "" - - instrOpCode = (self.header >> 4) & 0xf - - opCodeStr = opCodes[instrOpCode] - - if opCodeStr not in excludeList: - out += opCodeStr - - if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( - instrOpCode == MODULATION): - if (instrOpCode == MARKER) or (instrOpCode == WFM): - out += "; engine={}, ".format((self.header >> 2) & 0x3) - else: - out += "; " - if self.header & 0x1: - out += "write=1 | " - else: - out += "write=0 | " - - if self.target: - out += " {}".format(self.target) - - if instrOpCode == WFM: - wfOpCode = (self.payload >> 46) & 0x3 - wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] - out += wfOpCodes[wfOpCode] - out += "; TA bit={}".format((self.payload >> 45) & 0x1) - out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) - out += ", addr = {}".format(self.payload & 2**24 - 1) - - # # APS3/TDM modifier to use VRAM output - # if self.payload & (1 << 48): - # out += ", use_vram" - - elif instrOpCode == MARKER: - mrkOpCode = (self.payload >> 46) & 0x3 - mrkOpCodes = ["PLAY", "TRIG", "SYNC"] - out += mrkOpCodes[mrkOpCode] - out += "; state={}".format((self.payload >> 32) & 0x1) - out += ", count = {}".format(self.payload & 2**32 - 1) - - elif instrOpCode == MODULATION: - modulatorOpCode = (self.payload >> 45) & 0x7 - modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", - "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] - out += modulatorOpCodes[modulatorOpCode] - out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) - if modulatorOpCode == 0x0: - out += ", count={:d}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x3: - out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x5: - out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x7: - out += ", frame_change=0x{:08x}".format(self.payload & - 0xffffffff) - - elif instrOpCode == CMP: - cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] - cmpCode = (self.payload >> 8) & 0x3 - out += " | " + cmpCodes[cmpCode] - out += ", value = {}".format(self.payload & 0xff) - - elif any( - [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): - out += " | target addr = {}".format(self.payload & 2**26 - 1) - - elif instrOpCode == LOAD: - out += " | count = {}".format(self.payload) - - elif instrOpCode == CUSTOM: - store_addr = self.payload & 0xFFFF - load_addr = (self.payload >> 16) & 0xFFFF - instruction = (self.payload >> 32) & 0xFF - instructionAPS = TDM_CUSTOM_DECODE[instruction] - out += " | instruction = {0} ({1}), load_addr = 0x{2:0x}, store_addr = 0x{3:0x}".format(instruction, instructionAPS, load_addr, store_addr) - - elif instrOpCode == WRITEADDR: - addr = self.payload & 0xFFFF - value = (self.payload >> 16) & 0xFFFFFFFF - invalidate = not (self.header & 0x1) - mapTrigger = (self.header >> 2) & 0x1 - writeCrossbar = (self.header >> 1) & 0x1 - - instrStr = "WRITEADDR " - valueType = "value" - - if invalidate: - instrStr = "INVALIDATE" - valueType = "valid_mask" - - if mapTrigger: - instrStr = "STOREMEAS" - valueType = "mapping" - - if writeCrossbar: - instrStr = "WRITECB" - valuetype = "mapping" - addr = (self.payload >> 16) & 0xFFFF - value = (self.payload >> 32) & 0xFFFF - - out += "{0} | addr = 0x{1:0x}, {2} = 0x{3:0x}".format(instrStr, addr, valueType, value) - - elif instrOpCode == LOADCMP: - addr = self.payload & 0xFFFF - mask = (self.payload >> 16) & 0xFFFF - use_ram = (self.payload >> 48) & 0x1 - if not use_ram: - out += "WAITMEAS" - else: - src = "RAM" - out += "LOADCMP | source = {0}, addr = 0x{1:0x}, read_mask = 0x{2:0x}".format(src, addr, mask) - return out - - def __eq__(self, other): - return self.header == other.header and self.payload == other.payload and self.label == other.label - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.header, self.payload, self.label)) - - @property - def address(self): - return self.payload & 0xffffffff # bottom 32-bits of payload - - @address.setter - def address(self, value): - self.payload |= value & 0xffffffff - - @property - def writeFlag(self): - return self.header & 0x1 - - @writeFlag.setter - def writeFlag(self, value): - self.header |= value & 0x1 - - @property - def opcode(self): - return self.header >> 4 - - def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffffff)) - - -def Waveform(addr, count, isTA, write=False, label=None): - header = (WFM << 4) | (0x3 << 2) | (write & - 0x1) #broadcast to both engines - count = int(count) - count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count - addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr - payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) - << TA_PAIR_BIT) | (count << 24) | addr - return Instruction(header, payload, label) - - -def WaveformPrefetch(addr): - header = (WFM << 4) | (0x3 << 2) | (0x1) - payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr - return Instruction(header, payload, None) - - -def Marker(sel, state, count, write=False, label=None): - header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) - count = int(count) - four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count - count_rem = count % ADDRESS_UNIT - if state == 0: - transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} - transition = transitionWords[count_rem] - else: - transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} - transition = transitionWords[count_rem] - payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( - (state & 0x1) << 32) | four_count - return Instruction(header, payload, label) - - -def Command(cmd, payload, write=False, label=None): - header = (cmd << 4) - if isinstance(payload, int): - instr = Instruction(header, payload, label) - else: - instr = Instruction(header, 0, label, target=payload) - instr.writeFlag = write - return instr - - -def Sync(label=None): - return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) - - -def Wait(label=None): - return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) - - -def LoadCmp(label=None): - return Command(LOADCMP, 0, label=label) - - -def Cmp(op, value, label=None): - return Command(CMP, (op << 8) | (value & 0xff), label=label) - - -def Goto(addr, label=None): - return Command(GOTO, addr, label=label) - - -def Call(addr, label=None): - return Command(CALL, addr, label=label) - - -def Return(label=None): - return Command(RET, 0, label=label) - - -def Load(count, label=None): - return Command(LOAD, count, label=label) - - -def Repeat(addr, label=None): - return Command(REPEAT, addr, label=label) - - -def Prefetch(addr, label=None): - return Command(PREFETCH, addr) - - -def NoOp(): - return Instruction.unflatten(0xffffffffffffffff) - -# QGL instructions -def Invalidate(addr, mask, label=None): - header = WRITEADDR << 4 - payload = (mask << 16) | addr - return Instruction(header, payload, label=label) - -def WriteAddr(addr, value, label=None): - header = (WRITEADDR << 4) | 1 - payload = (value << 16) | addr - return Instruction(header, payload, label=label) - -def StoreMeas(addr, mapping, label=None): - header = (WRITEADDR << 4) | 5 - payload = (mapping << 16) | addr - return Instruction(header, payload, label=label) - -def Custom(in_addr, out_addr, custom_op, label=None): - header = CUSTOM << 4 - payload = (custom_op << 32) | (in_addr << 16) | out_addr - return Instruction(header, payload, label=label) - -def MajorityVote(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 0, label=label) - -def MajorityVoteMask(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 1, label=label) - -def LoadCmpVram(addr, mask, label=None): - header = LOADCMP << 4 - payload = (1 << 48) | (mask << 16) | addr - return Instruction(header, payload, label=label) - - -def preprocess(seqs, shapeLib): - seqs = PatternUtils.convert_lengths_to_samples( - seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) - wfLib = build_waveforms(seqs, shapeLib) - inject_modulation_cmds(seqs) - return seqs, wfLib - - -def wf_sig(wf): - ''' - Compute a signature of a Compiler.Waveform that identifies the relevant properties for - two Waveforms to be considered "equal" in the waveform library. For example, we ignore - length of TA waveforms. - ''' - if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.amp) - else: - return (wf.amp, round(wf.phase * 2**13)) - else: - #TODO: why do we need the length? - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.key, wf.amp, wf.length) - else: - return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) - - -class ModulationCommand(object): - """docstring for ModulationCommand""" - - def __init__(self, - instruction, - nco_select, - frequency=0, - phase=0, - length=0): - super(ModulationCommand, self).__init__() - self.instruction = instruction - self.nco_select = nco_select - self.frequency = frequency - self.phase = phase - self.length = length - - def __str__(self): - out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, - self.nco_select) - if self.instruction == "MODULATE": - out += ", length={})".format(self.length) - elif self.instruction == "SET_FREQ": - out += ", frequency={})".format(self.frequency) - elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": - out += ", phase={})".format(self.phase) - else: - out += ")" - return out - - def _repr_pretty_(self, p, cycle): - p.text(str(self)) - - def __repr__(self): - return str(self) - - def to_instruction(self, write_flag=True, label=None): - #Modulator op codes - MODULATOR_OP_OFFSET = 44 - NCO_SELECT_OP_OFFSET = 40 - MODULATION_CLOCK = 300e6 - - op_code_map = {"MODULATE": 0x0, - "RESET_PHASE": 0x2, - "SET_FREQ": 0x6, - "SET_PHASE": 0xa, - "UPDATE_FRAME": 0xe} - payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( - self.nco_select << NCO_SELECT_OP_OFFSET) - if self.instruction == "MODULATE": - #zero-indexed quad count - payload |= np.uint32(self.length / ADDRESS_UNIT - 1) - elif self.instruction == "SET_FREQ": - # frequencies can span -2 to 2 or 0 to 4 in unsigned - payload |= np.uint32( - (self.frequency / MODULATION_CLOCK if self.frequency > 0 else - self.frequency / MODULATION_CLOCK + 4) * 2**28) - elif (self.instruction == "SET_PHASE") | ( - self.instruction == "UPDATE_FRAME"): - #phases can span -0.5 to 0.5 or 0 to 1 in unsigned - payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) - - instr = Instruction(MODULATION << 4, payload, label) - instr.writeFlag = write_flag - return instr - -def inject_modulation_cmds(seqs): - """ - Inject modulation commands from phase, frequency and frameChange of waveforms - in an IQ waveform sequence. Assume up to 2 NCOs for now. - """ - cur_freq = 0 - cur_phase = 0 - for ct,seq in enumerate(seqs): - #check whether we have modulation commands - freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) - if len(freqs) > 2: - raise Exception("Max 2 frequencies on the same channel allowed.") - no_freq_cmds = np.allclose(freqs, 0) - phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_phase_cmds = np.allclose(phases, 0) - frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_frame_cmds = np.allclose(frame_changes, 0) - no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds - - if no_modulation_cmds: - continue - - mod_seq = [] - pending_frame_update = False - - for entry in seq: - - #copies to avoid same object having different timestamps later - #copy through BlockLabel - if isinstance(entry, BlockLabel.BlockLabel): - mod_seq.append(copy(entry)) - #mostly copy through control-flow - elif isinstance(entry, ControlFlow.ControlInstruction): - #heuristic to insert phase reset before trigger if we have modulation commands - if isinstance(entry, ControlFlow.Wait): - if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): - mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) - for nco_ind, freq in enumerate(freqs): - mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) - elif isinstance(entry, ControlFlow.Return): - cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines - mod_seq.append(copy(entry)) - elif isinstance(entry, Compiler.Waveform): - if not no_modulation_cmds: - #select nco - nco_select = (list(freqs)).index(entry.frequency) + 1 - cur_freq = entry.frequency - if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): - mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) - cur_phase = entry.phase - #now apply modulation for count command and waveform command, if non-zero length - if entry.length > 0: - mod_seq.append(entry) - # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command - if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ - and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: - mod_seq[-2].length += entry.length - else: - mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) - pending_frame_update = False - #now apply non-zero frame changes after so it is applied at end - if entry.frameChange != 0: - pending_frame_update = True - #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible - if entry.length == 0: - #if the last is a frame change then we can add to the frame change - if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": - mod_seq[-1].phase += entry.frameChange - #if last entry was pulse without frame change we add frame change - elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #if this is the first entry with a wait for trigger then we can inject a frame change - #before the wait for trigger but after the RESET_PHASE - elif isinstance(mod_seq[-1], ControlFlow.Wait): - mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": - mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #otherwise drop and error if frame has been defined - else: - raise Exception("Unable to implement zero time Z pulse") - else: - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - - seqs[ct] = mod_seq - -def build_waveforms(seqs, shapeLib): - # apply amplitude (and optionally phase) and add the resulting waveforms to the library - wfLib = {} - for wf in flatten(seqs): - if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: - shape = wf.amp * shapeLib[wf.key] - if not USE_PHASE_OFFSET_INSTRUCTION: - shape *= np.exp(1j * wf.phase) - wfLib[wf_sig(wf)] = shape - return wfLib - - -def timestamp_entries(seq): - t = 0 - for ct in range(len(seq)): - seq[ct].startTime = t - t += seq[ct].length - - -def synchronize_clocks(seqs): - # Control-flow instructions (CFIs) must occur at the same time on all channels. - # Therefore, we need to "reset the clock" by synchronizing the accumulated - # time at each CFI to the largest value on any channel - syncInstructions = [list(filter( - lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) - for seq in seqs if seq] - - # Add length to control-flow instructions to make accumulated time match at end of CFI. - # Keep running tally of how much each channel has been shifted so far. - localShift = [0 for _ in syncInstructions] - for ct in range(len(syncInstructions[0])): - step = [seq[ct] for seq in syncInstructions] - endTime = max((s.startTime + shift - for s, shift in zip(step, localShift))) - for ct, s in enumerate(step): - s.length = endTime - (s.startTime + localShift[ct]) - # localShift[ct] += endTime - (s.startTime + localShift[ct]) - # the += and the last term cancel, therefore: - localShift[ct] = endTime - s.startTime - # re-timestamp to propagate changes across the sequences - for seq in seqs: - timestamp_entries(seq) - # then transfer the control flow "lengths" back into start times - for seq in syncInstructions: - for instr in seq: - instr.startTime += instr.length - instr.length = 0 - - -def create_seq_instructions(seqs, offsets): - ''' - Helper function to create instruction vector from an IR sequence and an offset dictionary - keyed on the wf keys. - - Seqs is a list of lists containing waveform and marker data, e.g. - [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] - - We take the strategy of greedily grabbing the next instruction that occurs in time, accross - all waveform and marker channels. - ''' - - # timestamp all entries before filtering (where we lose time information on control flow) - for seq in seqs: - timestamp_entries(seq) - - synchronize_clocks(seqs) - - # create (seq, startTime) pairs over all sequences - timeTuples = [] - for ct, seq in enumerate(seqs): - timeTuples += [(entry.startTime, ct) for entry in seq] - timeTuples.sort() - - # keep track of where we are in each sequence - indexes = np.zeros(len(seqs), dtype=np.int64) - - # always start with SYNC (stealing label from beginning of sequence) - # unless it is a subroutine (using last entry as return as tell) - label = None - instructions = [] - for ct, seq in enumerate(seqs): - if len(seq): - first_non_empty = ct - break - if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): - if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] - timeTuples.pop(0) - indexes[first_non_empty] += 1 - instructions.append(Sync(label=label)) - label = None - - while len(timeTuples) > 0: - #pop off all entries that have the same time - entries = [] - start_time = 0 - while True: - start_time, seq_idx = timeTuples.pop(0) - entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) - indexes[seq_idx] += 1 - next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 - if start_time != next_start_time: - break - - write_flags = [True] * len(entries) - for ct, (entry, seq_idx) in enumerate(entries): - - #use first non empty sequence for control flow - if seq_idx == first_non_empty and ( - isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel) or - isinstance(entry, TdmInstructions.CustomInstruction) or - isinstance(entry, TdmInstructions.WriteAddrInstruction) or - isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): - if isinstance(entry, BlockLabel.BlockLabel): - # carry label forward to next entry - label = entry - continue - # control flow instructions - elif isinstance(entry, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(entry, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - elif isinstance(entry, ControlFlow.Sync): - instructions.append(Sync(label=label)) - elif isinstance(entry, ControlFlow.Return): - instructions.append(Return(label=label)) - # target argument commands - elif isinstance(entry, ControlFlow.Goto): - instructions.append(Goto(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Call): - instructions.append(Call(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Repeat): - instructions.append(Repeat(entry.target, label=label)) - # value argument commands - elif isinstance(entry, ControlFlow.LoadRepeat): - instructions.append(Load(entry.value - 1, label=label)) - elif isinstance(entry, ControlFlow.ComparisonInstruction): - # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(CMPTABLE[entry.operator], - entry.value, - label=label)) - - # TDM instructions are ignored by the APS - elif isinstance(entry, TdmInstructions.CustomInstruction): - pass - elif isinstance(entry, TdmInstructions.WriteAddrInstruction): - pass - - continue - - if seq_idx == 0: - #analog - waveforms or modulation - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping Waveform entry of length %s!" % entry.length) - continue - - instructions.append(Waveform( - offsets[wf_sig(entry)], entry.length, - entry.isTimeAmp or entry.isZero, - write=write_flags[ct], label=label)) - elif isinstance(entry, ModulationCommand): - instructions.append(entry.to_instruction( - write_flag=write_flags[ct], - label=label)) - - else: # a marker engine - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping entry!") - continue - markerSel = seq_idx - 1 - state = not entry.isZero - instructions.append(Marker(markerSel, - state, - entry.length, - write=write_flags[ct], - label=label)) - - #clear label - label = None - - return instructions - -def create_instr_data(seqs, offsets, cache_lines): - ''' - Constructs the complete instruction data vector, and does basic checks for validity. - - Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line - ''' - logger = logging.getLogger(__name__) - logger.debug('') - - seq_instrs = [] - need_prefetch = len(cache_lines) > 0 - num_cache_lines = len(set(cache_lines)) - cache_line_changes = np.concatenate( - ([0], np.where(np.diff(cache_lines))[0] + 1)) - for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) - #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line - if need_prefetch and (ct in cache_line_changes): - next_cache_line = cache_lines[cache_line_changes[(np.where( - ct == cache_line_changes)[0][0] + 1) % len( - cache_line_changes)]] - seq_instrs[-1].insert(0, WaveformPrefetch(int( - next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - - #concatenate instructions - instructions = [] - subroutines_start = -1 - for ct, seq in enumerate(seq_instrs): - #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break - instructions += seq - - #if we have any subroutines then group in cache lines - if subroutines_start >= 0: - subroutine_instrs = [] - subroutine_cache_line = {} - CACHE_LINE_LENGTH = 128 - offset = 0 - for sub in seq_instrs[subroutines_start:]: - #TODO for now we don't properly handle prefetching mulitple cache lines - if len(sub) > CACHE_LINE_LENGTH: - warnings.warn( - "Subroutines longer than {} instructions may not be prefetched correctly") - #Don't unecessarily split across a cache line - if (len(sub) + offset > CACHE_LINE_LENGTH) and ( - len(sub) < CACHE_LINE_LENGTH): - pad_instrs = 128 - ((offset + 128) % 128) - subroutine_instrs += [NoOp()] * pad_instrs - offset = 0 - if offset == 0: - line_label = sub[0].label - subroutine_cache_line[sub[0].label] = line_label - subroutine_instrs += sub - offset += len(sub) % CACHE_LINE_LENGTH - logger.debug("Placed {} subroutines into {} cache lines".format( - len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // - CACHE_LINE_LENGTH)) - #inject prefetch commands before waits - wait_idx = [idx for idx, instr in enumerate(instructions) - if (instr.header >> 4) == WAIT] + [len(instructions)] - instructions_with_prefetch = instructions[:wait_idx[0]] - last_prefetch = None - for start, stop in zip(wait_idx[:-1], wait_idx[1:]): - call_targets = [instr.target for instr in instructions[start:stop] - if (instr.header >> 4) == CALL] - needed_lines = set() - for target in call_targets: - needed_lines.add(subroutine_cache_line[target]) - if len(needed_lines) > 8: - raise RuntimeError( - "Unable to prefetch more than 8 cache lines") - for needed_line in needed_lines: - if needed_line != last_prefetch: - instructions_with_prefetch.append(Prefetch(needed_line)) - last_prefetch = needed_line - instructions_with_prefetch += instructions[start:stop] - - instructions = instructions_with_prefetch - #pad out instruction vector to ensure circular cache never loads a subroutine - pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) - instructions += [NoOp()] * pad_instrs - - instructions += subroutine_instrs - - #turn symbols into integers addresses - resolve_symbols(instructions) - - assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ - 'Oops! too many instructions: {0}'.format(len(instructions)) - - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) - - -def resolve_symbols(seq): - symbols = {} - # create symbol look-up table - for ct, entry in enumerate(seq): - if entry.label and entry.label not in symbols: - symbols[entry.label] = ct - # then update - for entry in seq: - if entry.target: - entry.address = symbols[entry.target] - - -def compress_marker(markerLL): - ''' - Compresses adjacent entries of the same state into single entries - ''' - for seq in markerLL: - idx = 0 - while idx + 1 < len(seq): - if (isinstance(seq[idx], Compiler.Waveform) and - isinstance(seq[idx + 1], Compiler.Waveform) and - seq[idx].isZero == seq[idx + 1].isZero): - - seq[idx].length += seq[idx + 1].length - del seq[idx + 1] - else: - idx += 1 - - -def write_sequence_file(awgData, fileName): - ''' - Main function to pack channel sequences into an APS2 h5 file. - ''' - # Convert QGL IR into a representation that is closer to the hardware. - awgData['ch12']['linkList'], wfLib = preprocess( - awgData['ch12']['linkList'], awgData['ch12']['wfLib']) - - # compress marker data - for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: - if 'linkList' in awgData[field].keys(): - PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], - SAMPLING_RATE, 1, - Compiler.Waveform) - compress_marker(awgData[field]['linkList']) - else: - awgData[field]['linkList'] = [] - - #Create the waveform vectors - wfInfo = [] - wfInfo.append(create_wf_vector({key: wf.real - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - wfInfo.append(create_wf_vector({key: wf.imag - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - - if SAVE_WF_OFFSETS: - #create a set of all waveform signatures in offset dictionaries - #we could have multiple offsets for the same pulse becuase it could - #be repeated in multiple cache lines - wf_sigs = set() - for offset_dict in wfInfo[0][1]: - wf_sigs |= set(offset_dict.keys()) - #create dictionary linking entry labels (that's what we'll have later) with offsets - offsets = {} - for seq in awgData['ch12']['linkList']: - for entry in seq: - if len(wf_sigs) == 0: - break - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig in wf_sigs: - #store offsets and wavefor lib length - #time ampltidue entries are clamped to ADDRESS_UNIT - wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length - offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], - wf_length) - wf_sigs.discard(sig) - - #break out of outer loop too - if len(wf_sigs) == 0: - break - - #now pickle the label=>offsets - with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: - pickle.dump(offsets, FID) - - # build instruction vector - seq_data = [awgData[s]['linkList'] - for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] - instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) - - #Open the HDF5 file - if os.path.isfile(fileName): - os.remove(fileName) - with h5py.File(fileName, 'w') as FID: - FID['/'].attrs['Version'] = 4.0 - FID['/'].attrs['target hardware'] = 'APS2' - FID['/'].attrs['minimum firmware version'] = 4.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) - - #Create the groups and datasets - for chanct in range(2): - chanStr = '/chan_{0}'.format(chanct + 1) - chanGroup = FID.create_group(chanStr) - #Write the waveformLib to file - FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) - - #Write the instructions to channel 1 - if np.mod(chanct, 2) == 0: - FID.create_dataset(chanStr + '/instructions', - data=instructions) - - -def read_sequence_file(fileName): - """ - Reads a HDF5 sequence file and returns a dictionary of lists. - Dictionary keys are channel strings such as ch1, ch12m1 - Lists are or tuples of time-amplitude pairs (time, output) - """ - chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', - 'mod_phase'] - seqs = {ch: [] for ch in chanStrs} - - def start_new_seq(): - for ct, ch in enumerate(chanStrs): - if (ct < 2) or (ct == 6): - #analog or modulation channel - seqs[ch].append([]) - else: - #marker channel - seqs[ch].append([]) - - with h5py.File(fileName, 'r') as FID: - file_version = FID["/"].attrs["Version"] - wf_lib = {} - wf_lib['ch1'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() - wf_lib['ch2'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() - instructions = FID['/chan_1/instructions'].value.flatten() - NUM_NCO = 2 - freq = np.zeros(NUM_NCO) #radians per timestep - phase = np.zeros(NUM_NCO) - frame = np.zeros(NUM_NCO) - next_freq = np.zeros(NUM_NCO) - next_phase = np.zeros(NUM_NCO) - next_frame = np.zeros(NUM_NCO) - accumulated_phase = np.zeros(NUM_NCO) - reset_flag = [False]*NUM_NCO - - for data in instructions: - instr = Instruction.unflatten(data) - - modulator_opcode = instr.payload >> 44 - - #update phases at these boundaries - if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( - (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): - for ct in range(NUM_NCO): - if reset_flag[ct]: - #would expect this to be zero but this is first non-zero point - accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT - reset_flag[ct] = False - freq[:] = next_freq[:] - phase[:] = next_phase[:] - frame[:] = next_frame[:] - - #Assume new sequence at every WAIT - if instr.opcode == WAIT: - start_new_seq() - - elif instr.opcode == WFM and (( - (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): - addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT - count = (instr.payload >> 24) & 0xfffff - count = (count + 1) * ADDRESS_UNIT - isTA = (instr.payload >> 45) & 0x1 - chan_select_bits = ((instr.header >> 2) & 0x1, - (instr.header >> 3) & 0x1) - #On older firmware we broadcast by default whereas on newer we respect the engine select - for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): - if (file_version < 4) or select_bit: - if isTA: - seqs[chan][-1].append((count, wf_lib[chan][addr])) - else: - for sample in wf_lib[chan][addr:addr + count]: - seqs[chan][-1].append((1, sample)) - - elif instr.opcode == MARKER: - chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) - count = instr.payload & 0xffffffff - count = (count + 1) * ADDRESS_UNIT - state = (instr.payload >> 32) & 0x1 - seqs[chan][-1].append((count, state)) - - elif instr.opcode == MODULATION: - # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} - nco_select_bits = (instr.payload >> 40) & 0xf - if modulator_opcode == 0x0: - #modulate - count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT - nco_select = {0b0001: 0, - 0b0010: 1, - 0b0100: 2, - 0b1000: 3}[nco_select_bits] - seqs['mod_phase'][-1] = np.append( - seqs['mod_phase'][-1], freq[nco_select] * - np.arange(count) + accumulated_phase[nco_select] + - phase[nco_select] + frame[nco_select]) - accumulated_phase += count * freq - else: - phase_rad = 2 * np.pi * (instr.payload & - 0xffffffff) / 2**28 - for ct in range(NUM_NCO): - if (nco_select_bits >> ct) & 0x1: - if modulator_opcode == 0x2: - #reset - next_phase[ct] = 0 - next_frame[ct] = 0 - reset_flag[ct] = True - elif modulator_opcode == 0x6: - #set frequency - next_freq[ct] = phase_rad / ADDRESS_UNIT - elif modulator_opcode == 0xa: - #set phase - next_phase[ct] = phase_rad - elif modulator_opcode == 0xe: - #update frame - next_frame[ct] += phase_rad - - #Apply modulation if we have any - for ct, ( - ch1, ch2, mod_phase - ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): - if len(mod_phase): - #only really works if ch1, ch2 are broadcast together - mod_ch1 = [] - mod_ch2 = [] - cum_time = 0 - for ((time_ch1, amp_ch1), - (time_ch2, amp_ch2)) in zip(ch1, ch2): - if (amp_ch1 != 0) or (amp_ch2 != 0): - assert time_ch1 == time_ch2 - if time_ch1 == 1: - #single timestep - modulated = np.exp(1j * mod_phase[cum_time]) * ( - amp_ch1 + 1j * amp_ch2) - mod_ch1.append((1, modulated.real)) - mod_ch2.append((1, modulated.imag)) - else: - #expand TA - modulated = np.exp( - 1j * - mod_phase[cum_time:cum_time + time_ch1]) * ( - amp_ch1 + 1j * amp_ch2) - for val in modulated: - mod_ch1.append((1, val.real)) - mod_ch2.append((1, val.imag)) - else: - mod_ch1.append((time_ch1, amp_ch1)) - mod_ch2.append((time_ch2, amp_ch2)) - - cum_time += time_ch1 - seqs['ch1'][ct] = mod_ch1 - seqs['ch2'][ct] = mod_ch2 - - del seqs['mod_phase'] - - return seqs - - -def update_wf_library(filename, pulses, offsets): - """ - Update a H5 waveform library in place give an iterable of (pulseName, pulse) - tuples and offsets into the waveform library. - """ - assert USE_PHASE_OFFSET_INSTRUCTION == False - #load the h5 file - with h5py.File(filename) as FID: - for label, pulse in pulses.items(): - #create a new waveform - if pulse.isTimeAmp: - shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) - else: - shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape - try: - length = offsets[label][1] - except KeyError: - print("\t{} not found in offsets so skipping".format(pulse)) - continue - for offset in offsets[label][0]: - print("\tUpdating {} at offset {}".format(pulse, offset)) - FID['/chan_1/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.real) - FID['/chan_2/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.imag) - - -def tdm_instructions(seq): - """ - Generate the TDM instructions for the given sequence. - - This assumes that there is one instruction sequence, not - a list of them (as is generally the case elsewhere). FIXME - """ - - seq = list(flatten(copy(seq))) - instructions = list() - - # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label - instructions.append(Sync(label=seq[0])) - # turn into a loop, by appending GOTO(0) at end of the sequence - if not isinstance(seq[-1], ControlFlow.Goto): - seq.append(ControlFlow.Goto(BlockLabel.label(seq))) - logger.debug("Appending a GOTO at end to loop") - # add a WAIT before the first waveform FIXME: there must be a more efficient way - if ControlFlow.Wait not in seq: - ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) - seq.insert(ind_wait, ControlFlow.Wait()) - - - # the backpatch table for labels - label2addr = dict() - - label = None - for s in seq: - if isinstance(s, BlockLabel.BlockLabel): - label2addr[s.label] = len(instructions) - - # carry label forward to next entry - label = s - continue - - if isinstance(s, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(s, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - - elif isinstance(s, TdmInstructions.WriteAddrInstruction): - if s.instruction == 'INVALIDATE': - print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(Invalidate(s.addr, s.value, label=label)) - - elif s.instruction == 'WRITEADDR': - print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(WriteAddr(s.addr, s.value, label=label)) - - elif s.instruction == 'STOREMEAS': - print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(StoreMeas(s.addr, s.value, label=label)) - else: - print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % - (s.instruction, str(s.channel), - s.addr, s.value)) - continue - - elif isinstance(s, TdmInstructions.CustomInstruction): - - if s.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVote(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVoteMask(s.in_addr, s.out_addr, label=label)) - else: #TODO: add decoder - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (s.instruction, s.in_addr, s.out_addr)) - - elif isinstance(s, ControlFlow.Goto): - instructions.append(Goto(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): - instructions.append(Repeat(s.target, label=label)) - elif isinstance(s, ControlFlow.LoadRepeat): - instructions.append(Load(s.value - 1, label=label)) - - elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): - if s.instruction == 'LOADCMPVRAM': - instructions.append( - LoadCmpVram(s.addr, s.mask, label=label)) - - elif isinstance(s, PulseSequencer.Pulse): - if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) - - elif isinstance(s, PulseSequencer.PulseBlock): - # FIXME: - # If this happens, we are confused. This could be a block containing multiple measurements, FIXME! - print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) - - elif isinstance(s, list): - # FIXME: - # If this happens, we are confused. - print('FIXME: TDM GOT LIST: %s' % str(s)) - - elif isinstance(s, ControlFlow.ComparisonInstruction): - instructions.append( - Cmp(CMPTABLE[s.operator], s.value, label=label)) - - else: - # This isn't typically an error, because the TDM ignores a - # lot of instructions, but until this is debugged it's handy - # to see what's falling through. - - # FIXME: We're missing a lot of control-flow instructions - print('OOPS: unhandled [%s]' % str(type(s))) - - # clear label - label = None - - # for i in range(len(instructions)): - # instr_bits = instructions[i].flatten() - # # instr_txt = str(Instruction.unflatten(instr_bits)) - # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) - - # backpatch any instructions that have target fields - # - for i in instructions: - if i.target: - i.payload = label2addr[i.target.label] - - return [i.flatten() for i in instructions] From 83517a84be8c10bbfbf6239c0736575340fd7d29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 5 Jun 2018 23:05:16 -0400 Subject: [PATCH 28/67] Add majorityvote Todo: add crossbar, fix StoreMeas --- QGL/TdmInstructions.py | 10 +++++----- QGL/drivers/APS2Pattern.py | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index f0af4633..0ee33546 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -37,8 +37,8 @@ def __ne__(self, other): return not self == other -def MajorityVote(in_addr, out_addr): - return CustomInstruction('MAJORITY', in_addr, out_addr) +def MajorityVote(in_addr, out_addr, mask): + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('MAJORITY', in_addr, out_addr)] def MajorityMask(in_addr, out_addr): return CustomInstruction('MAJORITYMASK', in_addr, out_addr) @@ -68,13 +68,13 @@ def __eq__(self, other): def __ne__(self, other): return not self == other -def WriteAddr(addr, value, channel=None, tdm=False): +def WriteAddr(addr, value, channel=None, tdm=True): return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value, tdm) -def Invalidate(addr, mask, channel=None, tdm=False): +def Invalidate(addr, mask, channel=None, tdm=True): return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm) -def StoreMeas(addr, value, channel=None, tdm=False): +def StoreMeas(addr, value, channel=None, tdm=True): return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value, tdm) class LoadCmpVramInstruction(object): diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index ba795f69..d7d4f81a 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -490,18 +490,18 @@ def StoreMeas(addr, mapping, label=None): header = (WRITEADDR << 4) | 5 payload = (mapping << 16) | addr return Instruction(header, payload, label=label) -# -# def Custom(in_addr, out_addr, custom_op, label=None): -# header = CUSTOM << 4 -# payload = (custom_op << 32) | (in_addr << 16) | out_addr -# return Instruction(header, payload, label=label) -# -# def MajorityVote(in_addr, out_addr, label=None): -# return Custom(in_addr, out_addr, 0, label=label) -# -# def MajorityVoteMask(in_addr, out_addr, label=None): -# return Custom(in_addr, out_addr, 1, label=label) -# + +def Custom(in_addr, out_addr, custom_op, label=None): + header = CUSTOM << 4 + payload = (custom_op << 32) | (in_addr << 16) | out_addr + return Instruction(header, payload, label=label) + +def MajorityVote(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 0, label=label) + +def MajorityVoteMask(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 1, label=label) + def LoadCmpVram(addr, mask, label=None): header = LOADCMP << 4 payload = (1 << 48) | (mask << 16) | addr From 7f1aed106f161f2a4be908d8f1cb6a4c6708a4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 13:46:20 -0400 Subject: [PATCH 29/67] Add WriteAddr and CrossBar Changed to reflect build_tdm_program_write_crossbar_time. Need to clarify the relation between CrossBar and StoreMeas --- QGL/TdmInstructions.py | 7 +++++-- QGL/drivers/APS2Pattern.py | 37 +++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index 0ee33546..7f02dad6 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -37,11 +37,11 @@ def __ne__(self, other): return not self == other -def MajorityVote(in_addr, out_addr, mask): +def MajorityVote(in_addr, out_addr, mask): # alternatively, append the loadcmpvram instruction when compiling (see STOREMEAS) return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('MAJORITY', in_addr, out_addr)] def MajorityMask(in_addr, out_addr): - return CustomInstruction('MAJORITYMASK', in_addr, out_addr) + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] # TODO: the rest of the CUSTOM instructions @@ -74,6 +74,9 @@ def WriteAddr(addr, value, channel=None, tdm=True): def Invalidate(addr, mask, channel=None, tdm=True): return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm) +def CrossBar(addr, value, channel=None, tdm=True): # should this be a high-level instruction though? + return WriteAddrInstruction('CROSSBAR', channel, 3, addr, value, tdm) + def StoreMeas(addr, value, channel=None, tdm=True): return WriteAddrInstruction('STOREMEAS', channel, 5, addr, value, tdm) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index d7d4f81a..16f95570 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -480,17 +480,22 @@ def Invalidate(addr, mask, label=None): header = WRITEADDR << 4 payload = (mask << 16) | addr return Instruction(header, payload, label=label) -# -# def WriteAddr(addr, value, label=None): -# header = (WRITEADDR << 4) | 1 -# payload = (value << 16) | addr -# return Instruction(header, payload, label=label) -# + +def WriteAddr(addr, value, label=None): + header = (WRITEADDR << 4) | 1 + payload = (value << 16) | addr + return Instruction(header, payload, label=label) + def StoreMeas(addr, mapping, label=None): header = (WRITEADDR << 4) | 5 payload = (mapping << 16) | addr return Instruction(header, payload, label=label) +def CrossBar(addr, value, label=None): + header = (WRITEADDR << 4) | 3 + payload = (value << 32) | (addr << 16) + return Instruction(header, payload, label=label) + def Custom(in_addr, out_addr, custom_op, label=None): header = CUSTOM << 4 payload = (custom_op << 32) | (in_addr << 16) | out_addr @@ -1285,12 +1290,13 @@ def tdm_instructions(seq): seq = list(flatten(copy(seq))) instructions = list() + # turn into a loop, by appending GOTO(0) at end of the sequence + seq.append(ControlFlow.Goto(BlockLabel.label(seq))) + logger.debug("Appending a GOTO at end to loop") + # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label instructions.append(Sync(label=seq[0])) - # turn into a loop, by appending GOTO(0) at end of the sequence - if not isinstance(seq[-1], ControlFlow.Goto): - seq.append(ControlFlow.Goto(BlockLabel.label(seq))) - logger.debug("Appending a GOTO at end to loop") + # add a WAIT before the first waveform FIXME: there must be a more efficient way if ControlFlow.Wait not in seq: ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) @@ -1303,7 +1309,7 @@ def tdm_instructions(seq): label = None for s in seq: if isinstance(s, BlockLabel.BlockLabel): - label2addr[s.label] = len(instructions) + label2addr[s.label] = len(instructions) - 1 # because of SYNC. FIXME # carry label forward to next entry label = s @@ -1365,8 +1371,9 @@ def tdm_instructions(seq): elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(CrossBar(s.maddr[1], 0x1)) # this has to change for sim. msmt's instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << s.maddr[1])) + instructions.append(StoreMeas(s.maddr[0], 1 << 16)) #1 << s.maddr[1])) elif isinstance(s, PulseSequencer.PulseBlock): # FIXME: @@ -1379,8 +1386,10 @@ def tdm_instructions(seq): print('FIXME: TDM GOT LIST: %s' % str(s)) elif isinstance(s, ControlFlow.ComparisonInstruction): - instructions.append( - Cmp(CMPTABLE[s.operator], s.value, label=label)) + # branching is currently reserved to APS + pass + #instructions.append( + # Cmp(CMPTABLE[s.operator], s.value, label=label)) else: # This isn't typically an error, because the TDM ignores a From 5241b2ca5f4667c83ddbd36556b8177bd4b60640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 16:07:03 -0400 Subject: [PATCH 30/67] Msmt's in PulseBlocks Support majority vote in space for simultaneous measurements --- QGL/drivers/APS2Pattern.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 16f95570..94a59e06 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1291,7 +1291,7 @@ def tdm_instructions(seq): instructions = list() # turn into a loop, by appending GOTO(0) at end of the sequence - seq.append(ControlFlow.Goto(BlockLabel.label(seq))) + seq.append(ControlFlow.Goto(BlockLabel.label(seq))) # BlockLabel.label adds a label at the beginning of the sequence logger.debug("Appending a GOTO at end to loop") # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label @@ -1309,7 +1309,7 @@ def tdm_instructions(seq): label = None for s in seq: if isinstance(s, BlockLabel.BlockLabel): - label2addr[s.label] = len(instructions) - 1 # because of SYNC. FIXME + label2addr[s.label] = len(instructions) #FIXME this convert a label (A, B, ...) to the instruction number # carry label forward to next entry label = s @@ -1335,7 +1335,7 @@ def tdm_instructions(seq): print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % (str(s.channel), s.addr, s.value)) instructions.append(StoreMeas(s.addr, s.value, label=label)) - else: + else: # TODO: add CrossBar (no need for explicit QGL call for TDM) print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % (s.instruction, str(s.channel), s.addr, s.value)) @@ -1358,6 +1358,8 @@ def tdm_instructions(seq): (s.instruction, s.in_addr, s.out_addr)) elif isinstance(s, ControlFlow.Goto): + #if s.target == 0: + entry = s instructions.append(Goto(s.target, label=label)) elif isinstance(s, ControlFlow.Repeat): instructions.append(Repeat(s.target, label=label)) @@ -1376,9 +1378,18 @@ def tdm_instructions(seq): instructions.append(StoreMeas(s.maddr[0], 1 << 16)) #1 << s.maddr[1])) elif isinstance(s, PulseSequencer.PulseBlock): - # FIXME: - # If this happens, we are confused. This could be a block containing multiple measurements, FIXME! - print('FIXME: TDM GOT MEAS PULSEBLOCK: %s' % str(s)) + sim_meas = [] + for k in s.pulses: + if s.pulses[k].label == 'MEAS' and s.pulses[k] != (-1, 0): + sim_meas.append(s.pulses[k]) + if sim_meas: + maddr = [m.maddr[0] for m in sim_meas] + if len(set(maddr))>1: + raise Exception('Storing simultaneous measurements on different addresses not supported.') + for n,m in enumerate(sim_meas): + instructions.append(CrossBar(2**n, 2**n)) + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(maddr[0], 1 << 16)) elif isinstance(s, list): # FIXME: @@ -1386,10 +1397,8 @@ def tdm_instructions(seq): print('FIXME: TDM GOT LIST: %s' % str(s)) elif isinstance(s, ControlFlow.ComparisonInstruction): - # branching is currently reserved to APS - pass - #instructions.append( - # Cmp(CMPTABLE[s.operator], s.value, label=label)) + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) else: # This isn't typically an error, because the TDM ignores a From b82f7cdccab6cc06830da85658a6b5b4bd972dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 16:08:21 -0400 Subject: [PATCH 31/67] Fix final Goto in a seq. Insert sync at the end to maintain address = number of instructions --- QGL/drivers/APS2Pattern.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 94a59e06..562c94b5 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1294,9 +1294,6 @@ def tdm_instructions(seq): seq.append(ControlFlow.Goto(BlockLabel.label(seq))) # BlockLabel.label adds a label at the beginning of the sequence logger.debug("Appending a GOTO at end to loop") - # start with sync. FIXME: for now, ignore subroutines. Assume that the first entry is a label - instructions.append(Sync(label=seq[0])) - # add a WAIT before the first waveform FIXME: there must be a more efficient way if ControlFlow.Wait not in seq: ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) @@ -1411,10 +1408,8 @@ def tdm_instructions(seq): # clear label label = None - # for i in range(len(instructions)): - # instr_bits = instructions[i].flatten() - # # instr_txt = str(Instruction.unflatten(instr_bits)) - # print('%5d: 0x%.16x - %s' % (i, instr_bits, str(instructions[i]))) + #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label + instructions.insert(0,Sync(label=seq[0])) # backpatch any instructions that have target fields # From 277bd8013505589810a8101639eef89238523491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 17:31:12 -0400 Subject: [PATCH 32/67] Allow list of seqs for TDM Also, progress towards fixing the labeling issue for instructions that do not exist in the TDM (e.g. pulses) --- QGL/drivers/APS2Pattern.py | 222 ++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 113 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 562c94b5..057b8183 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -986,8 +986,13 @@ def resolve_symbols(seq): symbols[entry.label] = ct # then update for entry in seq: + for (ct, entry) in enumerate(seq): if entry.target: - entry.address = symbols[entry.target] + # find next available label. The TDM may miss some labels if branches only contain waveforms (which are ignored) + for k in range(len(seq)-ct): + temp = seq[ct+k] + if temp.target in symbols: + entry.address = symbols[temp.target] def compress_marker(markerLL): @@ -1279,144 +1284,135 @@ def update_wf_library(filename, pulses, offsets): MAX_WAVEFORM_VALUE * shape.imag) -def tdm_instructions(seq): +def tdm_instructions(seqs): """ Generate the TDM instructions for the given sequence. This assumes that there is one instruction sequence, not a list of them (as is generally the case elsewhere). FIXME """ - - seq = list(flatten(copy(seq))) instructions = list() - + label2addr = dict() # the backpatch table for labels # turn into a loop, by appending GOTO(0) at end of the sequence - seq.append(ControlFlow.Goto(BlockLabel.label(seq))) # BlockLabel.label adds a label at the beginning of the sequence + seqs[-1].append(ControlFlow.Goto(BlockLabel.label(seqs[0]))) # BlockLabel.label adds a label at the beginning of the sequence logger.debug("Appending a GOTO at end to loop") + for seq in seqs: + seq = list(flatten(copy(seq))) - # add a WAIT before the first waveform FIXME: there must be a more efficient way - if ControlFlow.Wait not in seq: - ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) - seq.insert(ind_wait, ControlFlow.Wait()) + #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label + instructions.append(Sync(label=seq[0])) - # the backpatch table for labels - label2addr = dict() + # add a WAIT before the first waveform FIXME: there must be a more efficient way + if ControlFlow.Wait not in seq: + ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) + seq.insert(ind_wait, ControlFlow.Wait()) - label = None - for s in seq: - if isinstance(s, BlockLabel.BlockLabel): - label2addr[s.label] = len(instructions) #FIXME this convert a label (A, B, ...) to the instruction number - # carry label forward to next entry - label = s - continue + label = None + for s in seq: + if isinstance(s, BlockLabel.BlockLabel): + #label2addr[s.label] = len(instructions) #FIXME this convert a label (A, B, ...) to the instruction number, i.e. the address (typically) - if isinstance(s, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(s, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - - elif isinstance(s, TdmInstructions.WriteAddrInstruction) and s.tdm == True: - if s.instruction == 'INVALIDATE': - print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(Invalidate(s.addr, s.value, label=label)) - - elif s.instruction == 'WRITEADDR': - print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(WriteAddr(s.addr, s.value, label=label)) - - elif s.instruction == 'STOREMEAS': - print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(StoreMeas(s.addr, s.value, label=label)) - else: # TODO: add CrossBar (no need for explicit QGL call for TDM) - print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % - (s.instruction, str(s.channel), - s.addr, s.value)) + # carry label forward to next entry + label = s continue - elif isinstance(s, TdmInstructions.CustomInstruction): - - if s.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVote(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVoteMask(s.in_addr, s.out_addr, label=label)) - else: #TODO: add decoder - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (s.instruction, s.in_addr, s.out_addr)) - - elif isinstance(s, ControlFlow.Goto): - #if s.target == 0: - entry = s - instructions.append(Goto(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): - instructions.append(Repeat(s.target, label=label)) - elif isinstance(s, ControlFlow.LoadRepeat): - instructions.append(Load(s.value - 1, label=label)) - - elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): - if s.instruction == 'LOADCMPVRAM' and s.tdm == True: - instructions.append( - LoadCmpVram(s.addr, s.mask, label=label)) - - elif isinstance(s, PulseSequencer.Pulse): - if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(CrossBar(s.maddr[1], 0x1)) # this has to change for sim. msmt's + if isinstance(s, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(s, ControlFlow.LoadCmp): instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << 16)) #1 << s.maddr[1])) - - elif isinstance(s, PulseSequencer.PulseBlock): - sim_meas = [] - for k in s.pulses: - if s.pulses[k].label == 'MEAS' and s.pulses[k] != (-1, 0): - sim_meas.append(s.pulses[k]) - if sim_meas: - maddr = [m.maddr[0] for m in sim_meas] - if len(set(maddr))>1: - raise Exception('Storing simultaneous measurements on different addresses not supported.') - for n,m in enumerate(sim_meas): - instructions.append(CrossBar(2**n, 2**n)) - instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(maddr[0], 1 << 16)) - elif isinstance(s, list): - # FIXME: - # If this happens, we are confused. - print('FIXME: TDM GOT LIST: %s' % str(s)) + elif isinstance(s, TdmInstructions.WriteAddrInstruction) and s.tdm == True: + if s.instruction == 'INVALIDATE': + print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(Invalidate(s.addr, s.value, label=label)) + + elif s.instruction == 'WRITEADDR': + print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(WriteAddr(s.addr, s.value, label=label)) + + elif s.instruction == 'STOREMEAS': + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(StoreMeas(s.addr, s.value, label=label)) + else: # TODO: add CrossBar (no need for explicit QGL call for TDM) + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (s.instruction, str(s.channel), + s.addr, s.value)) + continue - elif isinstance(s, ControlFlow.ComparisonInstruction): - instructions.append( - Cmp(CMPTABLE[s.operator], s.value, label=label)) + elif isinstance(s, TdmInstructions.CustomInstruction): + + if s.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVote(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + else: #TODO: add decoder + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (s.instruction, s.in_addr, s.out_addr)) + + elif isinstance(s, ControlFlow.Goto): + instructions.append(Goto(s.target, label=label)) + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Repeat(s.target, label=label)) + elif isinstance(s, ControlFlow.LoadRepeat): + instructions.append(Load(s.value - 1, label=label)) + + elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): + if s.instruction == 'LOADCMPVRAM' and s.tdm == True: + instructions.append( + LoadCmpVram(s.addr, s.mask, label=label)) + + elif isinstance(s, PulseSequencer.Pulse): + if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(CrossBar(s.maddr[1], 0x1)) # this has to change for sim. msmt's + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(s.maddr[0], 1 << 16)) #1 << s.maddr[1])) + + elif isinstance(s, PulseSequencer.PulseBlock): + sim_meas = [] + for k in s.pulses: + if s.pulses[k].label == 'MEAS' and s.pulses[k] != (-1, 0): + sim_meas.append(s.pulses[k]) + if sim_meas: + maddr = [m.maddr[0] for m in sim_meas] + if len(set(maddr))>1: + raise Exception('Storing simultaneous measurements on different addresses not supported.') + for n,m in enumerate(sim_meas): + instructions.append(CrossBar(2**n, 2**n)) + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(maddr[0], 1 << 16)) - else: - # This isn't typically an error, because the TDM ignores a - # lot of instructions, but until this is debugged it's handy - # to see what's falling through. + elif isinstance(s, list): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT LIST: %s' % str(s)) - # FIXME: We're missing a lot of control-flow instructions - print('OOPS: unhandled [%s]' % str(type(s))) + elif isinstance(s, ControlFlow.ComparisonInstruction): + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) - # clear label - label = None + else: + # This isn't typically an error, because the TDM ignores a + # lot of instructions, but until this is debugged it's handy + # to see what's falling through. - #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label - instructions.insert(0,Sync(label=seq[0])) + # FIXME: We're missing a lot of control-flow instructions + print('OOPS: unhandled [%s]' % str(type(s))) - # backpatch any instructions that have target fields - # - for i in instructions: - if i.target: - i.payload = label2addr[i.target.label] + # clear label + label = None + resolve_symbols(instructions) return [i.flatten() for i in instructions] # Utility Functions for displaying programs From 345b9148a9299cc39bec42b4bf9e81c5fbfe2883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 17:31:32 -0400 Subject: [PATCH 33/67] Leftover from last commit --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 057b8183..96de8db2 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -985,13 +985,13 @@ def resolve_symbols(seq): if entry.label and entry.label not in symbols: symbols[entry.label] = ct # then update - for entry in seq: for (ct, entry) in enumerate(seq): if entry.target: # find next available label. The TDM may miss some labels if branches only contain waveforms (which are ignored) for k in range(len(seq)-ct): temp = seq[ct+k] if temp.target in symbols: + break entry.address = symbols[temp.target] From 7690a73ef5513f9787d4c587b5d7a0699620634f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 17:57:33 -0400 Subject: [PATCH 34/67] Fix label for group of instructions --- QGL/drivers/APS2Pattern.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 96de8db2..88cbd7d6 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1374,9 +1374,9 @@ def tdm_instructions(seqs): elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(CrossBar(s.maddr[1], 0x1)) # this has to change for sim. msmt's + instructions.append(CrossBar(s.maddr[1], 0x1, label=label)) # this has to change for sim. msmt's instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << 16)) #1 << s.maddr[1])) + instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) #1 << s.maddr[1])) elif isinstance(s, PulseSequencer.PulseBlock): sim_meas = [] From 3b3fbc8fc71f914bbb534841f090336570213550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 6 Jun 2018 18:27:43 -0400 Subject: [PATCH 35/67] Instructions for decoder --- QGL/TdmInstructions.py | 6 ++++++ QGL/__init__.py | 2 +- QGL/drivers/APS2Pattern.py | 20 ++++++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index 7f02dad6..e11ab1e0 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -43,6 +43,12 @@ def MajorityVote(in_addr, out_addr, mask): # alternatively, append the loadcmpvr def MajorityMask(in_addr, out_addr): return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] +def Decode(in_addr, out_addr, mask): + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('TSM', in_addr, out_addr)] + +def DecodeSetRounds(in_addr, out_addr): + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] + # TODO: the rest of the CUSTOM instructions class WriteAddrInstruction(object): diff --git a/QGL/__init__.py b/QGL/__init__.py index 99f2b24f..e1dcce54 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -10,4 +10,4 @@ from .Tomography import state_tomo, process_tomo from .Scheduler import schedule -from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpVram +from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpVram, Decode, DecodeSetRounds diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 88cbd7d6..7ebc59fc 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -235,10 +235,6 @@ def __str__(self): # list of opCodes where the reprenstation will change excludeList = ["WRITEADDR", "LOADCMP"] - customOps = [ - "MajorityVote", "MajoritySetMask", # TODO there are others... - ] - out = "{0} ".format(self.label) if self.label else "" instrOpCode = (self.header >> 4) & 0xf @@ -507,6 +503,12 @@ def MajorityVote(in_addr, out_addr, label=None): def MajorityVoteMask(in_addr, out_addr, label=None): return Custom(in_addr, out_addr, 1, label=label) +def DecodeSetRounds(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 2, label=label) + +def Decode(in_addr, out_addr, label=None): + return Custom(in_addr, out_addr, 3, label=label) + def LoadCmpVram(addr, mask, label=None): header = LOADCMP << 4 payload = (1 << 48) | (mask << 16) | addr @@ -1356,6 +1358,16 @@ def tdm_instructions(seqs): (s.in_addr, s.out_addr)) instructions.append( MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'TSM': + print('DECODE(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + Decode(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'TSM': + print('DECODESETROUNDS(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + DecodeSetRounds(s.in_addr, s.out_addr, label=label)) else: #TODO: add decoder print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % (s.instruction, s.in_addr, s.out_addr)) From 50bbbfe86afc62f2d9a39d7cd9915baf242148c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 11:14:49 -0400 Subject: [PATCH 36/67] :bug: Check for MEASA vs MEAS --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 7ebc59fc..5b50a36c 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1393,7 +1393,7 @@ def tdm_instructions(seqs): elif isinstance(s, PulseSequencer.PulseBlock): sim_meas = [] for k in s.pulses: - if s.pulses[k].label == 'MEAS' and s.pulses[k] != (-1, 0): + if s.pulses[k].label == 'MEAS' and s.pulses[k].maddr != (-1, 0): sim_meas.append(s.pulses[k]) if sim_meas: maddr = [m.maddr[0] for m in sim_meas] From 137764e9eed0fadb93105ff7a1fc66398f7fae73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 14:33:16 -0400 Subject: [PATCH 37/67] Other fixes for custom instructions Tofix: Goto target for final instruction in a sequence (list in a list). The default is 0 --- QGL/Compiler.py | 4 +++- QGL/TdmInstructions.py | 10 ++++------ QGL/drivers/APS2Pattern.py | 7 +++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index bbdb3b9e..2eb20569 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -77,7 +77,9 @@ def merge_channels(wires, channels): break # control flow on any channel should pass thru if any(isinstance(e, (ControlFlow.ControlInstruction, - BlockLabel.BlockLabel)) for e in entries): + BlockLabel.BlockLabel, TdmInstructions.WriteAddrInstruction, + TdmInstructions.CustomInstruction, + TdmInstructions.LoadCmpVramInstruction)) for e in entries): # for the moment require uniform control flow so that we # can pull from the first channel assert all(e == entries[0] diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index e11ab1e0..6f88f3ba 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -40,14 +40,14 @@ def __ne__(self, other): def MajorityVote(in_addr, out_addr, mask): # alternatively, append the loadcmpvram instruction when compiling (see STOREMEAS) return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('MAJORITY', in_addr, out_addr)] -def MajorityMask(in_addr, out_addr): - return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] +def MajorityMask(in_addr, out_addr, value): + return [WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] def Decode(in_addr, out_addr, mask): return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('TSM', in_addr, out_addr)] -def DecodeSetRounds(in_addr, out_addr): - return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] +def DecodeSetRounds(in_addr, out_addr, value): + return [WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] # TODO: the rest of the CUSTOM instructions @@ -114,5 +114,3 @@ def __ne__(self, other): def LoadCmpVram(addr, mask, channel=None, tdm=False): # this should be for APS2 only return [WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask, tdm)] - -# TODO: are there other variants of WriteAddr? diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 5b50a36c..503fbc59 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1298,13 +1298,12 @@ def tdm_instructions(seqs): # turn into a loop, by appending GOTO(0) at end of the sequence seqs[-1].append(ControlFlow.Goto(BlockLabel.label(seqs[0]))) # BlockLabel.label adds a label at the beginning of the sequence logger.debug("Appending a GOTO at end to loop") + label = seqs[0][0] for seq in seqs: seq = list(flatten(copy(seq))) #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label - instructions.append(Sync(label=seq[0])) - - + instructions.append(Sync(label=label)) # add a WAIT before the first waveform FIXME: there must be a more efficient way if ControlFlow.Wait not in seq: ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) @@ -1363,7 +1362,7 @@ def tdm_instructions(seqs): (s.in_addr, s.out_addr)) instructions.append( Decode(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'TSM': + elif s.instruction == 'TSM_SET_ROUNDS': print('DECODESETROUNDS(in_addr=%x, out_addr=%x)' % (s.in_addr, s.out_addr)) instructions.append( From d1c30308926deb9aab92e78ab3cf67d3cbfef3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 14:37:03 -0400 Subject: [PATCH 38/67] Preface masks with Invalidate as necessary --- QGL/TdmInstructions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index 6f88f3ba..bf9bfb47 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -41,13 +41,13 @@ def MajorityVote(in_addr, out_addr, mask): # alternatively, append the loadcmpvr return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('MAJORITY', in_addr, out_addr)] def MajorityMask(in_addr, out_addr, value): - return [WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] + return [WriteAddrInstruction('INVALIDATE', None, 1, in_addr, 0x0, True), WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] def Decode(in_addr, out_addr, mask): return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('TSM', in_addr, out_addr)] def DecodeSetRounds(in_addr, out_addr, value): - return [WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] + return [WriteAddrInstruction('INVALIDATE', None, 1, in_addr, 0x0, True), WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] # TODO: the rest of the CUSTOM instructions From dcefa1de047940547f4c4ecd51ba85712d4191ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 15:35:02 -0400 Subject: [PATCH 39/67] Carry label over next sequence If the label hasn't been used (for example because TDM instructions are missing), carry it over the next sequence --- QGL/drivers/APS2Pattern.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 503fbc59..ab9f7406 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1420,9 +1420,6 @@ def tdm_instructions(seqs): # FIXME: We're missing a lot of control-flow instructions print('OOPS: unhandled [%s]' % str(type(s))) - # clear label - label = None - resolve_symbols(instructions) return [i.flatten() for i in instructions] From a75e27382c3f4e6540f2331558655b6f6f93ed6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:13:08 -0400 Subject: [PATCH 40/67] Write TDM sequence --- QGL/Compiler.py | 9 +++++++++ QGL/drivers/APS2Pattern.py | 27 ++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index 2eb20569..b953b61c 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -435,6 +435,15 @@ def compile_to_hardware(seqs, else: files[awgName] = fullFileName + # generate TDM sequences FIXME: what's the best way to identify the need for a TDM seq.? Support for single TDM + if 'APS2Pattern' in [wire.translator for wire in physWires]: + aps2tdm_module = import_module('QGL.drivers.APS2Pattern') # this is redundant with above + tdm_instr = aps2tdm_module.tdm_instructions(seqs) + files['TDM'] = os.path.normpath(os.path.join( + config.AWGDir, fileName + '-' + 'TDM' + suffix + data[ + 'seqFileExt'])) + aps2tdm_module.write_tdm_seq(files['TDM']) + # create meta output if not axis_descriptor: axis_descriptor = [{ diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index ab9f7406..6ba4e009 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1413,15 +1413,28 @@ def tdm_instructions(seqs): Cmp(CMPTABLE[s.operator], s.value, label=label)) else: - # This isn't typically an error, because the TDM ignores a - # lot of instructions, but until this is debugged it's handy - # to see what's falling through. - - # FIXME: We're missing a lot of control-flow instructions - print('OOPS: unhandled [%s]' % str(type(s))) + pass + # use this for debugging purposes + #print('OOPS: unhandled [%s]' % str(type(s))) resolve_symbols(instructions) - return [i.flatten() for i in instructions] + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) + +def write_tdm_seq(seq, tdm_filename): + #Open the HDF5 file + if os.path.isfile(tdm_fileName): + os.remove(tdm_fileName) + with h5py.File(tdm_fileName, 'w') as FID: + FID['/'].attrs['Version'] = 5.0 + FID['/'].attrs['target hardware'] = 'TDM' + FID['/'].attrs['minimum firmware version'] = 5.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1]) + + #Create the groups and datasets + chanStr = '/chan_{0}'.format(1) + chanGroup = FID.create_group(chanStr) + FID.create_dataset(chanStr + '/instructions', data=tdm_instr) # Utility Functions for displaying programs From 5ff25921a856d8a2c9308086e0d71e90d49a04fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:13:48 -0400 Subject: [PATCH 41/67] Swithch back tabs -> spaces --- QGL/drivers/APS2Pattern.py | 2442 ++++++++++++++++++------------------ 1 file changed, 1221 insertions(+), 1221 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 6ba4e009..94cf6ce6 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -7,7 +7,7 @@ you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -106,1320 +106,1320 @@ SEQFILE_PER_CHANNEL = False def get_empty_channel_set(): - return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} + return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} def get_seq_file_extension(): - return '.h5' + return '.h5' def is_compatible_file(filename): - with h5py.File(filename, 'r') as FID: - target = FID['/'].attrs['target hardware'] - if isinstance(target, str): - target = target.encode('utf-8') - if target == b'APS2': - return True - return False + with h5py.File(filename, 'r') as FID: + target = FID['/'].attrs['target hardware'] + if isinstance(target, str): + target = target.encode('utf-8') + if target == b'APS2': + return True + return False def create_wf_vector(wfLib, seqs): - ''' - Helper function to create the wf vector and offsets into it. - ''' - max_pts_needed = 0 - for wf in wfLib.values(): - if len(wf) == 1: - max_pts_needed += ADDRESS_UNIT - else: - max_pts_needed += len(wf) - - #If we have more than fits in cache we'll need to align and prefetch - need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE - - idx = 0 - - if not need_prefetch: - offsets = [{}] - cache_lines = [] - #if we can fit them all in just pack - wfVec = np.zeros(max_pts_needed, dtype=np.int16) - for key, wf in wfLib.items(): - #Clip the wf - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) - offsets[-1][key] = idx - idx += wf.size - - #Trim the waveform - wfVec.resize(idx) - - else: - #otherwise fill in one cache line at a time - CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 - wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) - offsets = [{}] - cache_lines = [] - for seq in seqs: - #go through sequence and see what we need to add - pts_to_add = 0 - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - pts_to_add += entry.length - - #If what we need to add spills over then add a line and start again - if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: - idx = int(CACHE_LINE_LENGTH * ( - (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) - wfVec = np.append(wfVec, - np.zeros(CACHE_LINE_LENGTH, - dtype=np.int16)) - offsets.append({}) - - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - wf = wfLib[sig] - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16( - MAX_WAVEFORM_VALUE * wf) - offsets[-1][sig] = idx - idx += wf.size - - cache_lines.append(int(idx // CACHE_LINE_LENGTH)) - - return wfVec, offsets, cache_lines + ''' + Helper function to create the wf vector and offsets into it. + ''' + max_pts_needed = 0 + for wf in wfLib.values(): + if len(wf) == 1: + max_pts_needed += ADDRESS_UNIT + else: + max_pts_needed += len(wf) + + #If we have more than fits in cache we'll need to align and prefetch + need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE + + idx = 0 + + if not need_prefetch: + offsets = [{}] + cache_lines = [] + #if we can fit them all in just pack + wfVec = np.zeros(max_pts_needed, dtype=np.int16) + for key, wf in wfLib.items(): + #Clip the wf + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) + offsets[-1][key] = idx + idx += wf.size + + #Trim the waveform + wfVec.resize(idx) + + else: + #otherwise fill in one cache line at a time + CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 + wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) + offsets = [{}] + cache_lines = [] + for seq in seqs: + #go through sequence and see what we need to add + pts_to_add = 0 + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + pts_to_add += entry.length + + #If what we need to add spills over then add a line and start again + if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: + idx = int(CACHE_LINE_LENGTH * ( + (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) + wfVec = np.append(wfVec, + np.zeros(CACHE_LINE_LENGTH, + dtype=np.int16)) + offsets.append({}) + + for entry in seq: + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig not in offsets[-1]: + wf = wfLib[sig] + wf[wf > 1] = 1.0 + wf[wf < -1] = -1.0 + #TA pairs need to be repeated ADDRESS_UNIT times + if wf.size == 1: + wf = wf.repeat(ADDRESS_UNIT) + #Ensure the wf is an integer number of ADDRESS_UNIT's + trim = wf.size % ADDRESS_UNIT + if trim: + wf = wf[:-trim] + wfVec[idx:idx + wf.size] = np.int16( + MAX_WAVEFORM_VALUE * wf) + offsets[-1][sig] = idx + idx += wf.size + + cache_lines.append(int(idx // CACHE_LINE_LENGTH)) + + return wfVec, offsets, cache_lines class Instruction(object): - def __init__(self, header, payload, label=None, target=None, decode_as_tdm = False): - self.header = header - self.payload = int(payload) - self.label = label - self.target = target - self.decode_as_tdm = decode_as_tdm - - @classmethod - def unflatten(cls, instr, decode_as_tdm = False): - return cls(header=(int(instr) >> 56) & 0xff, - payload=int(instr) & 0xffffffffffffff, - decode_as_tdm = decode_as_tdm) - - def __repr__(self): - return self.__str__() - - def __str__(self): - - opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", - "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "CUSTOM", "WRITEADDR", "NOP"] - - # list of opCodes where the reprenstation will change - excludeList = ["WRITEADDR", "LOADCMP"] - - out = "{0} ".format(self.label) if self.label else "" - - instrOpCode = (self.header >> 4) & 0xf - - opCodeStr = opCodes[instrOpCode] - - if opCodeStr not in excludeList: - out += opCodeStr - - if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( - instrOpCode == MODULATION): - if (instrOpCode == MARKER) or (instrOpCode == WFM): - out += "; engine={}, ".format((self.header >> 2) & 0x3) - else: - out += "; " - if self.header & 0x1: - out += "write=1 | " - else: - out += "write=0 | " - - if self.target: - out += " {}".format(self.target) - - if instrOpCode == WFM: - wfOpCode = (self.payload >> 46) & 0x3 - wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] - out += wfOpCodes[wfOpCode] - out += "; TA bit={}".format((self.payload >> 45) & 0x1) - out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) - out += ", addr = {}".format(self.payload & 2**24 - 1) - - # # APS3/TDM modifier to use VRAM output - # if self.payload & (1 << 48): - # out += ", use_vram" - - elif instrOpCode == MARKER: - mrkOpCode = (self.payload >> 46) & 0x3 - mrkOpCodes = ["PLAY", "TRIG", "SYNC"] - out += mrkOpCodes[mrkOpCode] - out += "; state={}".format((self.payload >> 32) & 0x1) - out += ", count = {}".format(self.payload & 2**32 - 1) - - elif instrOpCode == MODULATION: - modulatorOpCode = (self.payload >> 45) & 0x7 - modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", - "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] - out += modulatorOpCodes[modulatorOpCode] - out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) - if modulatorOpCode == 0x0: - out += ", count={:d}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x3: - out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x5: - out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x7: - out += ", frame_change=0x{:08x}".format(self.payload & - 0xffffffff) - - elif instrOpCode == CMP: - cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] - cmpCode = (self.payload >> 8) & 0x3 - out += " | " + cmpCodes[cmpCode] - out += ", value = {}".format(self.payload & 0xff) - - elif any( - [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): - out += " | target addr = {}".format(self.payload & 2**26 - 1) - - elif instrOpCode == LOAD: - out += " | count = {}".format(self.payload) - - elif instrOpCode == CUSTOM: - store_addr = self.payload & 0xFFFF - load_addr = (self.payload >> 16) & 0xFFFF - instruction = (self.payload >> 32) & 0xFF - instructionAPS = TDM_CUSTOM_DECODE[instruction] - out += " | instruction = {0} ({1}), load_addr = 0x{2:0x}, store_addr = 0x{3:0x}".format(instruction, instructionAPS, load_addr, store_addr) - - elif instrOpCode == WRITEADDR: - addr = self.payload & 0xFFFF - value = (self.payload >> 16) & 0xFFFFFFFF - invalidate = not (self.header & 0x1) - mapTrigger = (self.header >> 2) & 0x1 - writeCrossbar = (self.header >> 1) & 0x1 - - instrStr = "WRITEADDR " - valueType = "value" - - if invalidate: - instrStr = "INVALIDATE" - valueType = "valid_mask" - - if mapTrigger: - instrStr = "STOREMEAS" - valueType = "mapping" - - if writeCrossbar: - instrStr = "WRITECB" - valuetype = "mapping" - addr = (self.payload >> 16) & 0xFFFF - value = (self.payload >> 32) & 0xFFFF - - out += "{0} | addr = 0x{1:0x}, {2} = 0x{3:0x}".format(instrStr, addr, valueType, value) - - elif instrOpCode == LOADCMP: - addr = self.payload & 0xFFFF - mask = (self.payload >> 16) & 0xFFFF - use_ram = (self.payload >> 48) & 0x1 - if self.decode_as_tdm and not use_ram: - out += "WAITMEAS" - else: - src = "EXT" - if use_ram: - src = "RAM" - out += "LOADCMP | source = {0}, addr = 0x{1:0x}, read_mask = 0x{2:0x}".format(src, addr, mask) - return out - - def __eq__(self, other): - return self.header == other.header and self.payload == other.payload and self.label == other.label - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.header, self.payload, self.label)) - - @property - def address(self): - return self.payload & 0xffffffff # bottom 32-bits of payload - - @address.setter - def address(self, value): - self.payload |= value & 0xffffffff - - @property - def writeFlag(self): - return self.header & 0x1 - - @writeFlag.setter - def writeFlag(self, value): - self.header |= value & 0x1 - - @property - def opcode(self): - return self.header >> 4 - - def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffffff)) + def __init__(self, header, payload, label=None, target=None, decode_as_tdm = False): + self.header = header + self.payload = int(payload) + self.label = label + self.target = target + self.decode_as_tdm = decode_as_tdm + + @classmethod + def unflatten(cls, instr, decode_as_tdm = False): + return cls(header=(int(instr) >> 56) & 0xff, + payload=int(instr) & 0xffffffffffffff, + decode_as_tdm = decode_as_tdm) + + def __repr__(self): + return self.__str__() + + def __str__(self): + + opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", + "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", + "CUSTOM", "WRITEADDR", "NOP"] + + # list of opCodes where the reprenstation will change + excludeList = ["WRITEADDR", "LOADCMP"] + + out = "{0} ".format(self.label) if self.label else "" + + instrOpCode = (self.header >> 4) & 0xf + + opCodeStr = opCodes[instrOpCode] + + if opCodeStr not in excludeList: + out += opCodeStr + + if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( + instrOpCode == MODULATION): + if (instrOpCode == MARKER) or (instrOpCode == WFM): + out += "; engine={}, ".format((self.header >> 2) & 0x3) + else: + out += "; " + if self.header & 0x1: + out += "write=1 | " + else: + out += "write=0 | " + + if self.target: + out += " {}".format(self.target) + + if instrOpCode == WFM: + wfOpCode = (self.payload >> 46) & 0x3 + wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] + out += wfOpCodes[wfOpCode] + out += "; TA bit={}".format((self.payload >> 45) & 0x1) + out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) + out += ", addr = {}".format(self.payload & 2**24 - 1) + + # # APS3/TDM modifier to use VRAM output + # if self.payload & (1 << 48): + # out += ", use_vram" + + elif instrOpCode == MARKER: + mrkOpCode = (self.payload >> 46) & 0x3 + mrkOpCodes = ["PLAY", "TRIG", "SYNC"] + out += mrkOpCodes[mrkOpCode] + out += "; state={}".format((self.payload >> 32) & 0x1) + out += ", count = {}".format(self.payload & 2**32 - 1) + + elif instrOpCode == MODULATION: + modulatorOpCode = (self.payload >> 45) & 0x7 + modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", + "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] + out += modulatorOpCodes[modulatorOpCode] + out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) + if modulatorOpCode == 0x0: + out += ", count={:d}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x3: + out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x5: + out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) + elif modulatorOpCode == 0x7: + out += ", frame_change=0x{:08x}".format(self.payload & + 0xffffffff) + + elif instrOpCode == CMP: + cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] + cmpCode = (self.payload >> 8) & 0x3 + out += " | " + cmpCodes[cmpCode] + out += ", value = {}".format(self.payload & 0xff) + + elif any( + [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): + out += " | target addr = {}".format(self.payload & 2**26 - 1) + + elif instrOpCode == LOAD: + out += " | count = {}".format(self.payload) + + elif instrOpCode == CUSTOM: + store_addr = self.payload & 0xFFFF + load_addr = (self.payload >> 16) & 0xFFFF + instruction = (self.payload >> 32) & 0xFF + instructionAPS = TDM_CUSTOM_DECODE[instruction] + out += " | instruction = {0} ({1}), load_addr = 0x{2:0x}, store_addr = 0x{3:0x}".format(instruction, instructionAPS, load_addr, store_addr) + + elif instrOpCode == WRITEADDR: + addr = self.payload & 0xFFFF + value = (self.payload >> 16) & 0xFFFFFFFF + invalidate = not (self.header & 0x1) + mapTrigger = (self.header >> 2) & 0x1 + writeCrossbar = (self.header >> 1) & 0x1 + + instrStr = "WRITEADDR " + valueType = "value" + + if invalidate: + instrStr = "INVALIDATE" + valueType = "valid_mask" + + if mapTrigger: + instrStr = "STOREMEAS" + valueType = "mapping" + + if writeCrossbar: + instrStr = "WRITECB" + valuetype = "mapping" + addr = (self.payload >> 16) & 0xFFFF + value = (self.payload >> 32) & 0xFFFF + + out += "{0} | addr = 0x{1:0x}, {2} = 0x{3:0x}".format(instrStr, addr, valueType, value) + + elif instrOpCode == LOADCMP: + addr = self.payload & 0xFFFF + mask = (self.payload >> 16) & 0xFFFF + use_ram = (self.payload >> 48) & 0x1 + if self.decode_as_tdm and not use_ram: + out += "WAITMEAS" + else: + src = "EXT" + if use_ram: + src = "RAM" + out += "LOADCMP | source = {0}, addr = 0x{1:0x}, read_mask = 0x{2:0x}".format(src, addr, mask) + return out + + def __eq__(self, other): + return self.header == other.header and self.payload == other.payload and self.label == other.label + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.header, self.payload, self.label)) + + @property + def address(self): + return self.payload & 0xffffffff # bottom 32-bits of payload + + @address.setter + def address(self, value): + self.payload |= value & 0xffffffff + + @property + def writeFlag(self): + return self.header & 0x1 + + @writeFlag.setter + def writeFlag(self, value): + self.header |= value & 0x1 + + @property + def opcode(self): + return self.header >> 4 + + def flatten(self): + return int((self.header << 56) | (self.payload & 0xffffffffffffff)) def Waveform(addr, count, isTA, write=False, label=None): - header = (WFM << 4) | (0x3 << 2) | (write & - 0x1) #broadcast to both engines - count = int(count) - count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count - addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr - payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) - << TA_PAIR_BIT) | (count << 24) | addr - return Instruction(header, payload, label) + header = (WFM << 4) | (0x3 << 2) | (write & + 0x1) #broadcast to both engines + count = int(count) + count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count + addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr + payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) + << TA_PAIR_BIT) | (count << 24) | addr + return Instruction(header, payload, label) def WaveformPrefetch(addr): - header = (WFM << 4) | (0x3 << 2) | (0x1) - payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr - return Instruction(header, payload, None) + header = (WFM << 4) | (0x3 << 2) | (0x1) + payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr + return Instruction(header, payload, None) def Marker(sel, state, count, write=False, label=None): - header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) - count = int(count) - four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count - count_rem = count % ADDRESS_UNIT - if state == 0: - transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} - transition = transitionWords[count_rem] - else: - transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} - transition = transitionWords[count_rem] - payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( - (state & 0x1) << 32) | four_count - return Instruction(header, payload, label) + header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) + count = int(count) + four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count + count_rem = count % ADDRESS_UNIT + if state == 0: + transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} + transition = transitionWords[count_rem] + else: + transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} + transition = transitionWords[count_rem] + payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( + (state & 0x1) << 32) | four_count + return Instruction(header, payload, label) def Command(cmd, payload, write=False, label=None): - header = (cmd << 4) - if isinstance(payload, int): - instr = Instruction(header, payload, label) - else: - instr = Instruction(header, 0, label, target=payload) - instr.writeFlag = write - return instr + header = (cmd << 4) + if isinstance(payload, int): + instr = Instruction(header, payload, label) + else: + instr = Instruction(header, 0, label, target=payload) + instr.writeFlag = write + return instr def Sync(label=None): - return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) + return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) def Wait(label=None): - return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) + return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) def LoadCmp(label=None): - return Command(LOADCMP, 0, label=label) + return Command(LOADCMP, 0, label=label) def Cmp(op, value, label=None): - return Command(CMP, (op << 8) | (value & 0xff), label=label) + return Command(CMP, (op << 8) | (value & 0xff), label=label) def Goto(addr, label=None): - return Command(GOTO, addr, label=label) + return Command(GOTO, addr, label=label) def Call(addr, label=None): - return Command(CALL, addr, label=label) + return Command(CALL, addr, label=label) def Return(label=None): - return Command(RET, 0, label=label) + return Command(RET, 0, label=label) def Load(count, label=None): - return Command(LOAD, count, label=label) + return Command(LOAD, count, label=label) def Repeat(addr, label=None): - return Command(REPEAT, addr, label=label) + return Command(REPEAT, addr, label=label) def Prefetch(addr, label=None): - return Command(PREFETCH, addr) + return Command(PREFETCH, addr) def NoOp(): - return Instruction.unflatten(0xffffffffffffffff) + return Instruction.unflatten(0xffffffffffffffff) # QGL instructions def Invalidate(addr, mask, label=None): - header = WRITEADDR << 4 - payload = (mask << 16) | addr - return Instruction(header, payload, label=label) + header = WRITEADDR << 4 + payload = (mask << 16) | addr + return Instruction(header, payload, label=label) def WriteAddr(addr, value, label=None): - header = (WRITEADDR << 4) | 1 - payload = (value << 16) | addr - return Instruction(header, payload, label=label) + header = (WRITEADDR << 4) | 1 + payload = (value << 16) | addr + return Instruction(header, payload, label=label) def StoreMeas(addr, mapping, label=None): - header = (WRITEADDR << 4) | 5 - payload = (mapping << 16) | addr - return Instruction(header, payload, label=label) + header = (WRITEADDR << 4) | 5 + payload = (mapping << 16) | addr + return Instruction(header, payload, label=label) def CrossBar(addr, value, label=None): - header = (WRITEADDR << 4) | 3 - payload = (value << 32) | (addr << 16) - return Instruction(header, payload, label=label) + header = (WRITEADDR << 4) | 3 + payload = (value << 32) | (addr << 16) + return Instruction(header, payload, label=label) def Custom(in_addr, out_addr, custom_op, label=None): - header = CUSTOM << 4 - payload = (custom_op << 32) | (in_addr << 16) | out_addr - return Instruction(header, payload, label=label) + header = CUSTOM << 4 + payload = (custom_op << 32) | (in_addr << 16) | out_addr + return Instruction(header, payload, label=label) def MajorityVote(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 0, label=label) + return Custom(in_addr, out_addr, 0, label=label) def MajorityVoteMask(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 1, label=label) + return Custom(in_addr, out_addr, 1, label=label) def DecodeSetRounds(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 2, label=label) + return Custom(in_addr, out_addr, 2, label=label) def Decode(in_addr, out_addr, label=None): - return Custom(in_addr, out_addr, 3, label=label) + return Custom(in_addr, out_addr, 3, label=label) def LoadCmpVram(addr, mask, label=None): - header = LOADCMP << 4 - payload = (1 << 48) | (mask << 16) | addr - return Instruction(header, payload, label=label) + header = LOADCMP << 4 + payload = (1 << 48) | (mask << 16) | addr + return Instruction(header, payload, label=label) def preprocess(seqs, shapeLib): - seqs = PatternUtils.convert_lengths_to_samples( - seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) - wfLib = build_waveforms(seqs, shapeLib) - inject_modulation_cmds(seqs) - return seqs, wfLib + seqs = PatternUtils.convert_lengths_to_samples( + seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) + wfLib = build_waveforms(seqs, shapeLib) + inject_modulation_cmds(seqs) + return seqs, wfLib def wf_sig(wf): - ''' - Compute a signature of a Compiler.Waveform that identifies the relevant properties for - two Waveforms to be considered "equal" in the waveform library. For example, we ignore - length of TA waveforms. - ''' - if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.amp) - else: - return (wf.amp, round(wf.phase * 2**13)) - else: - #TODO: why do we need the length? - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.key, wf.amp, wf.length) - else: - return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) + ''' + Compute a signature of a Compiler.Waveform that identifies the relevant properties for + two Waveforms to be considered "equal" in the waveform library. For example, we ignore + length of TA waveforms. + ''' + if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.amp) + else: + return (wf.amp, round(wf.phase * 2**13)) + else: + #TODO: why do we need the length? + if USE_PHASE_OFFSET_INSTRUCTION: + return (wf.key, wf.amp, wf.length) + else: + return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) class ModulationCommand(object): - """docstring for ModulationCommand""" - - def __init__(self, - instruction, - nco_select, - frequency=0, - phase=0, - length=0): - super(ModulationCommand, self).__init__() - self.instruction = instruction - self.nco_select = nco_select - self.frequency = frequency - self.phase = phase - self.length = length - - def __str__(self): - out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, - self.nco_select) - if self.instruction == "MODULATE": - out += ", length={})".format(self.length) - elif self.instruction == "SET_FREQ": - out += ", frequency={})".format(self.frequency) - elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": - out += ", phase={})".format(self.phase) - else: - out += ")" - return out - - def _repr_pretty_(self, p, cycle): - p.text(str(self)) - - def __repr__(self): - return str(self) - - def to_instruction(self, write_flag=True, label=None): - #Modulator op codes - MODULATOR_OP_OFFSET = 44 - NCO_SELECT_OP_OFFSET = 40 - MODULATION_CLOCK = 300e6 - - op_code_map = {"MODULATE": 0x0, - "RESET_PHASE": 0x2, - "SET_FREQ": 0x6, - "SET_PHASE": 0xa, - "UPDATE_FRAME": 0xe} - payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( - self.nco_select << NCO_SELECT_OP_OFFSET) - if self.instruction == "MODULATE": - #zero-indexed quad count - payload |= np.uint32(self.length / ADDRESS_UNIT - 1) - elif self.instruction == "SET_FREQ": - # frequencies can span -2 to 2 or 0 to 4 in unsigned - payload |= np.uint32( - (self.frequency / MODULATION_CLOCK if self.frequency > 0 else - self.frequency / MODULATION_CLOCK + 4) * 2**28) - elif (self.instruction == "SET_PHASE") | ( - self.instruction == "UPDATE_FRAME"): - #phases can span -0.5 to 0.5 or 0 to 1 in unsigned - payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) - - instr = Instruction(MODULATION << 4, payload, label) - instr.writeFlag = write_flag - return instr + """docstring for ModulationCommand""" + + def __init__(self, + instruction, + nco_select, + frequency=0, + phase=0, + length=0): + super(ModulationCommand, self).__init__() + self.instruction = instruction + self.nco_select = nco_select + self.frequency = frequency + self.phase = phase + self.length = length + + def __str__(self): + out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, + self.nco_select) + if self.instruction == "MODULATE": + out += ", length={})".format(self.length) + elif self.instruction == "SET_FREQ": + out += ", frequency={})".format(self.frequency) + elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": + out += ", phase={})".format(self.phase) + else: + out += ")" + return out + + def _repr_pretty_(self, p, cycle): + p.text(str(self)) + + def __repr__(self): + return str(self) + + def to_instruction(self, write_flag=True, label=None): + #Modulator op codes + MODULATOR_OP_OFFSET = 44 + NCO_SELECT_OP_OFFSET = 40 + MODULATION_CLOCK = 300e6 + + op_code_map = {"MODULATE": 0x0, + "RESET_PHASE": 0x2, + "SET_FREQ": 0x6, + "SET_PHASE": 0xa, + "UPDATE_FRAME": 0xe} + payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( + self.nco_select << NCO_SELECT_OP_OFFSET) + if self.instruction == "MODULATE": + #zero-indexed quad count + payload |= np.uint32(self.length / ADDRESS_UNIT - 1) + elif self.instruction == "SET_FREQ": + # frequencies can span -2 to 2 or 0 to 4 in unsigned + payload |= np.uint32( + (self.frequency / MODULATION_CLOCK if self.frequency > 0 else + self.frequency / MODULATION_CLOCK + 4) * 2**28) + elif (self.instruction == "SET_PHASE") | ( + self.instruction == "UPDATE_FRAME"): + #phases can span -0.5 to 0.5 or 0 to 1 in unsigned + payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) + + instr = Instruction(MODULATION << 4, payload, label) + instr.writeFlag = write_flag + return instr def inject_modulation_cmds(seqs): - """ - Inject modulation commands from phase, frequency and frameChange of waveforms - in an IQ waveform sequence. Assume up to 2 NCOs for now. - """ - cur_freq = 0 - cur_phase = 0 - for ct,seq in enumerate(seqs): - #check whether we have modulation commands - freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) - if len(freqs) > 2: - raise Exception("Max 2 frequencies on the same channel allowed.") - no_freq_cmds = np.allclose(freqs, 0) - phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_phase_cmds = np.allclose(phases, 0) - frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_frame_cmds = np.allclose(frame_changes, 0) - no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds - - if no_modulation_cmds: - continue - - mod_seq = [] - pending_frame_update = False - - for entry in seq: - - #copies to avoid same object having different timestamps later - #copy through BlockLabel - if isinstance(entry, BlockLabel.BlockLabel): - mod_seq.append(copy(entry)) - #mostly copy through control-flow - elif isinstance(entry, ControlFlow.ControlInstruction): - #heuristic to insert phase reset before trigger if we have modulation commands - if isinstance(entry, ControlFlow.Wait): - if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): - mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) - for nco_ind, freq in enumerate(freqs): - mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) - elif isinstance(entry, ControlFlow.Return): - cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines - mod_seq.append(copy(entry)) - elif isinstance(entry, Compiler.Waveform): - if not no_modulation_cmds: - #select nco - nco_select = (list(freqs)).index(entry.frequency) + 1 - cur_freq = entry.frequency - if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): - mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) - cur_phase = entry.phase - #now apply modulation for count command and waveform command, if non-zero length - if entry.length > 0: - mod_seq.append(entry) - # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command - if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ - and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: - mod_seq[-2].length += entry.length - else: - mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) - pending_frame_update = False - #now apply non-zero frame changes after so it is applied at end - if entry.frameChange != 0: - pending_frame_update = True - #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible - if entry.length == 0: - #if the last is a frame change then we can add to the frame change - if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": - mod_seq[-1].phase += entry.frameChange - #if last entry was pulse without frame change we add frame change - elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #if this is the first entry with a wait for trigger then we can inject a frame change - #before the wait for trigger but after the RESET_PHASE - elif isinstance(mod_seq[-1], ControlFlow.Wait): - mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": - mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #otherwise drop and error if frame has been defined - else: - raise Exception("Unable to implement zero time Z pulse") - else: - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - - seqs[ct] = mod_seq + """ + Inject modulation commands from phase, frequency and frameChange of waveforms + in an IQ waveform sequence. Assume up to 2 NCOs for now. + """ + cur_freq = 0 + cur_phase = 0 + for ct,seq in enumerate(seqs): + #check whether we have modulation commands + freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) + if len(freqs) > 2: + raise Exception("Max 2 frequencies on the same channel allowed.") + no_freq_cmds = np.allclose(freqs, 0) + phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_phase_cmds = np.allclose(phases, 0) + frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] + no_frame_cmds = np.allclose(frame_changes, 0) + no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds + + if no_modulation_cmds: + continue + + mod_seq = [] + pending_frame_update = False + + for entry in seq: + + #copies to avoid same object having different timestamps later + #copy through BlockLabel + if isinstance(entry, BlockLabel.BlockLabel): + mod_seq.append(copy(entry)) + #mostly copy through control-flow + elif isinstance(entry, ControlFlow.ControlInstruction): + #heuristic to insert phase reset before trigger if we have modulation commands + if isinstance(entry, ControlFlow.Wait): + if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): + mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) + for nco_ind, freq in enumerate(freqs): + mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) + elif isinstance(entry, ControlFlow.Return): + cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines + mod_seq.append(copy(entry)) + elif isinstance(entry, Compiler.Waveform): + if not no_modulation_cmds: + #select nco + nco_select = (list(freqs)).index(entry.frequency) + 1 + cur_freq = entry.frequency + if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): + mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) + cur_phase = entry.phase + #now apply modulation for count command and waveform command, if non-zero length + if entry.length > 0: + mod_seq.append(entry) + # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command + if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ + and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: + mod_seq[-2].length += entry.length + else: + mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) + pending_frame_update = False + #now apply non-zero frame changes after so it is applied at end + if entry.frameChange != 0: + pending_frame_update = True + #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible + if entry.length == 0: + #if the last is a frame change then we can add to the frame change + if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": + mod_seq[-1].phase += entry.frameChange + #if last entry was pulse without frame change we add frame change + elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #if this is the first entry with a wait for trigger then we can inject a frame change + #before the wait for trigger but after the RESET_PHASE + elif isinstance(mod_seq[-1], ControlFlow.Wait): + mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": + mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + #otherwise drop and error if frame has been defined + else: + raise Exception("Unable to implement zero time Z pulse") + else: + mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) + + seqs[ct] = mod_seq def build_waveforms(seqs, shapeLib): - # apply amplitude (and optionally phase) and add the resulting waveforms to the library - wfLib = {} - for wf in flatten(seqs): - if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: - shape = wf.amp * shapeLib[wf.key] - if not USE_PHASE_OFFSET_INSTRUCTION: - shape *= np.exp(1j * wf.phase) - wfLib[wf_sig(wf)] = shape - return wfLib + # apply amplitude (and optionally phase) and add the resulting waveforms to the library + wfLib = {} + for wf in flatten(seqs): + if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: + shape = wf.amp * shapeLib[wf.key] + if not USE_PHASE_OFFSET_INSTRUCTION: + shape *= np.exp(1j * wf.phase) + wfLib[wf_sig(wf)] = shape + return wfLib def timestamp_entries(seq): - t = 0 - for ct in range(len(seq)): - seq[ct].startTime = t - t += seq[ct].length + t = 0 + for ct in range(len(seq)): + seq[ct].startTime = t + t += seq[ct].length def synchronize_clocks(seqs): - # Control-flow instructions (CFIs) must occur at the same time on all channels. - # Therefore, we need to "reset the clock" by synchronizing the accumulated - # time at each CFI to the largest value on any channel - syncInstructions = [list(filter( - lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) - for seq in seqs if seq] - - # Add length to control-flow instructions to make accumulated time match at end of CFI. - # Keep running tally of how much each channel has been shifted so far. - localShift = [0 for _ in syncInstructions] - for ct in range(len(syncInstructions[0])): - step = [seq[ct] for seq in syncInstructions] - endTime = max((s.startTime + shift - for s, shift in zip(step, localShift))) - for ct, s in enumerate(step): - s.length = endTime - (s.startTime + localShift[ct]) - # localShift[ct] += endTime - (s.startTime + localShift[ct]) - # the += and the last term cancel, therefore: - localShift[ct] = endTime - s.startTime - # re-timestamp to propagate changes across the sequences - for seq in seqs: - timestamp_entries(seq) - # then transfer the control flow "lengths" back into start times - for seq in syncInstructions: - for instr in seq: - instr.startTime += instr.length - instr.length = 0 + # Control-flow instructions (CFIs) must occur at the same time on all channels. + # Therefore, we need to "reset the clock" by synchronizing the accumulated + # time at each CFI to the largest value on any channel + syncInstructions = [list(filter( + lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) + for seq in seqs if seq] + + # Add length to control-flow instructions to make accumulated time match at end of CFI. + # Keep running tally of how much each channel has been shifted so far. + localShift = [0 for _ in syncInstructions] + for ct in range(len(syncInstructions[0])): + step = [seq[ct] for seq in syncInstructions] + endTime = max((s.startTime + shift + for s, shift in zip(step, localShift))) + for ct, s in enumerate(step): + s.length = endTime - (s.startTime + localShift[ct]) + # localShift[ct] += endTime - (s.startTime + localShift[ct]) + # the += and the last term cancel, therefore: + localShift[ct] = endTime - s.startTime + # re-timestamp to propagate changes across the sequences + for seq in seqs: + timestamp_entries(seq) + # then transfer the control flow "lengths" back into start times + for seq in syncInstructions: + for instr in seq: + instr.startTime += instr.length + instr.length = 0 def create_seq_instructions(seqs, offsets): - ''' - Helper function to create instruction vector from an IR sequence and an offset dictionary - keyed on the wf keys. - - Seqs is a list of lists containing waveform and marker data, e.g. - [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] - - We take the strategy of greedily grabbing the next instruction that occurs in time, accross - all waveform and marker channels. - ''' - - # timestamp all entries before filtering (where we lose time information on control flow) - for seq in seqs: - timestamp_entries(seq) - - synchronize_clocks(seqs) - - # create (seq, startTime) pairs over all sequences - timeTuples = [] - for ct, seq in enumerate(seqs): - timeTuples += [(entry.startTime, ct) for entry in seq] - timeTuples.sort() - - # keep track of where we are in each sequence - indexes = np.zeros(len(seqs), dtype=np.int64) - - # always start with SYNC (stealing label from beginning of sequence) - # unless it is a subroutine (using last entry as return as tell) - label = None - instructions = [] - for ct, seq in enumerate(seqs): - if len(seq): - first_non_empty = ct - break - if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): - if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] - timeTuples.pop(0) - indexes[first_non_empty] += 1 - instructions.append(Sync(label=label)) - label = None - - while len(timeTuples) > 0: - #pop off all entries that have the same time - entries = [] - start_time = 0 - while True: - start_time, seq_idx = timeTuples.pop(0) - entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) - indexes[seq_idx] += 1 - next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 - if start_time != next_start_time: - break - - write_flags = [True] * len(entries) - for ct, (entry, seq_idx) in enumerate(entries): - - #use first non empty sequence for control flow - if seq_idx == first_non_empty and ( - isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel) or - isinstance(entry, TdmInstructions.CustomInstruction) or - isinstance(entry, TdmInstructions.WriteAddrInstruction) or - isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): - if isinstance(entry, BlockLabel.BlockLabel): - # carry label forward to next entry - label = entry - continue - # control flow instructions - elif isinstance(entry, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(entry, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - elif isinstance(entry, ControlFlow.Sync): - instructions.append(Sync(label=label)) - elif isinstance(entry, ControlFlow.Return): - instructions.append(Return(label=label)) - # target argument commands - elif isinstance(entry, ControlFlow.Goto): - instructions.append(Goto(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Call): - instructions.append(Call(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Repeat): - instructions.append(Repeat(entry.target, label=label)) - # value argument commands - elif isinstance(entry, ControlFlow.LoadRepeat): - instructions.append(Load(entry.value - 1, label=label)) - elif isinstance(entry, ControlFlow.ComparisonInstruction): - # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(CMPTABLE[entry.operator], - entry.value, - label=label)) - elif isinstance(entry, TdmInstructions.LoadCmpVramInstruction) and entry.tdm == False: - instructions.append(LoadCmpVram(entry.addr, entry.mask, label=label)) - # some TDM instructions are ignored by the APS - elif isinstance(entry, TdmInstructions.CustomInstruction): - pass - elif isinstance(entry, TdmInstructions.WriteAddrInstruction): - if entry.instruction == 'INVALIDATE' and entry.tdm == False: - instructions.append(Invalidate(entry.addr, entry.value, label=label)) - - continue - - if seq_idx == 0: - #analog - waveforms or modulation - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping Waveform entry of length %s!" % entry.length) - continue - - instructions.append(Waveform( - offsets[wf_sig(entry)], entry.length, - entry.isTimeAmp or entry.isZero, - write=write_flags[ct], label=label)) - elif isinstance(entry, ModulationCommand): - instructions.append(entry.to_instruction( - write_flag=write_flags[ct], - label=label)) - - else: # a marker engine - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping entry!") - continue - markerSel = seq_idx - 1 - state = not entry.isZero - instructions.append(Marker(markerSel, - state, - entry.length, - write=write_flags[ct], - label=label)) - - #clear label - label = None - - return instructions + ''' + Helper function to create instruction vector from an IR sequence and an offset dictionary + keyed on the wf keys. + + Seqs is a list of lists containing waveform and marker data, e.g. + [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] + + We take the strategy of greedily grabbing the next instruction that occurs in time, accross + all waveform and marker channels. + ''' + + # timestamp all entries before filtering (where we lose time information on control flow) + for seq in seqs: + timestamp_entries(seq) + + synchronize_clocks(seqs) + + # create (seq, startTime) pairs over all sequences + timeTuples = [] + for ct, seq in enumerate(seqs): + timeTuples += [(entry.startTime, ct) for entry in seq] + timeTuples.sort() + + # keep track of where we are in each sequence + indexes = np.zeros(len(seqs), dtype=np.int64) + + # always start with SYNC (stealing label from beginning of sequence) + # unless it is a subroutine (using last entry as return as tell) + label = None + instructions = [] + for ct, seq in enumerate(seqs): + if len(seq): + first_non_empty = ct + break + if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): + if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): + label = seqs[first_non_empty][0] + timeTuples.pop(0) + indexes[first_non_empty] += 1 + instructions.append(Sync(label=label)) + label = None + + while len(timeTuples) > 0: + #pop off all entries that have the same time + entries = [] + start_time = 0 + while True: + start_time, seq_idx = timeTuples.pop(0) + entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) + indexes[seq_idx] += 1 + next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 + if start_time != next_start_time: + break + + write_flags = [True] * len(entries) + for ct, (entry, seq_idx) in enumerate(entries): + + #use first non empty sequence for control flow + if seq_idx == first_non_empty and ( + isinstance(entry, ControlFlow.ControlInstruction) or + isinstance(entry, BlockLabel.BlockLabel) or + isinstance(entry, TdmInstructions.CustomInstruction) or + isinstance(entry, TdmInstructions.WriteAddrInstruction) or + isinstance(entry, TdmInstructions.LoadCmpVramInstruction)): + if isinstance(entry, BlockLabel.BlockLabel): + # carry label forward to next entry + label = entry + continue + # control flow instructions + elif isinstance(entry, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(entry, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + elif isinstance(entry, ControlFlow.Sync): + instructions.append(Sync(label=label)) + elif isinstance(entry, ControlFlow.Return): + instructions.append(Return(label=label)) + # target argument commands + elif isinstance(entry, ControlFlow.Goto): + instructions.append(Goto(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Call): + instructions.append(Call(entry.target, label=label)) + elif isinstance(entry, ControlFlow.Repeat): + instructions.append(Repeat(entry.target, label=label)) + # value argument commands + elif isinstance(entry, ControlFlow.LoadRepeat): + instructions.append(Load(entry.value - 1, label=label)) + elif isinstance(entry, ControlFlow.ComparisonInstruction): + # TODO modify Cmp operator to load from specified address + instructions.append(Cmp(CMPTABLE[entry.operator], + entry.value, + label=label)) + elif isinstance(entry, TdmInstructions.LoadCmpVramInstruction) and entry.tdm == False: + instructions.append(LoadCmpVram(entry.addr, entry.mask, label=label)) + # some TDM instructions are ignored by the APS + elif isinstance(entry, TdmInstructions.CustomInstruction): + pass + elif isinstance(entry, TdmInstructions.WriteAddrInstruction): + if entry.instruction == 'INVALIDATE' and entry.tdm == False: + instructions.append(Invalidate(entry.addr, entry.value, label=label)) + + continue + + if seq_idx == 0: + #analog - waveforms or modulation + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping Waveform entry of length %s!" % entry.length) + continue + + instructions.append(Waveform( + offsets[wf_sig(entry)], entry.length, + entry.isTimeAmp or entry.isZero, + write=write_flags[ct], label=label)) + elif isinstance(entry, ModulationCommand): + instructions.append(entry.to_instruction( + write_flag=write_flags[ct], + label=label)) + + else: # a marker engine + if isinstance(entry, Compiler.Waveform): + if entry.length < MIN_ENTRY_LENGTH: + warn("Dropping entry!") + continue + markerSel = seq_idx - 1 + state = not entry.isZero + instructions.append(Marker(markerSel, + state, + entry.length, + write=write_flags[ct], + label=label)) + + #clear label + label = None + + return instructions def create_instr_data(seqs, offsets, cache_lines): - ''' - Constructs the complete instruction data vector, and does basic checks for validity. - - Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line - ''' - logger = logging.getLogger(__name__) - logger.debug('') - - seq_instrs = [] - need_prefetch = len(cache_lines) > 0 - num_cache_lines = len(set(cache_lines)) - cache_line_changes = np.concatenate( - ([0], np.where(np.diff(cache_lines))[0] + 1)) - for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) - #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line - if need_prefetch and (ct in cache_line_changes): - next_cache_line = cache_lines[cache_line_changes[(np.where( - ct == cache_line_changes)[0][0] + 1) % len( - cache_line_changes)]] - seq_instrs[-1].insert(0, WaveformPrefetch(int( - next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - - #concatenate instructions - instructions = [] - subroutines_start = -1 - for ct, seq in enumerate(seq_instrs): - #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break - instructions += seq - - #if we have any subroutines then group in cache lines - if subroutines_start >= 0: - subroutine_instrs = [] - subroutine_cache_line = {} - CACHE_LINE_LENGTH = 128 - offset = 0 - for sub in seq_instrs[subroutines_start:]: - #TODO for now we don't properly handle prefetching mulitple cache lines - if len(sub) > CACHE_LINE_LENGTH: - warnings.warn( - "Subroutines longer than {} instructions may not be prefetched correctly") - #Don't unecessarily split across a cache line - if (len(sub) + offset > CACHE_LINE_LENGTH) and ( - len(sub) < CACHE_LINE_LENGTH): - pad_instrs = 128 - ((offset + 128) % 128) - subroutine_instrs += [NoOp()] * pad_instrs - offset = 0 - if offset == 0: - line_label = sub[0].label - subroutine_cache_line[sub[0].label] = line_label - subroutine_instrs += sub - offset += len(sub) % CACHE_LINE_LENGTH - logger.debug("Placed {} subroutines into {} cache lines".format( - len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // - CACHE_LINE_LENGTH)) - #inject prefetch commands before waits - wait_idx = [idx for idx, instr in enumerate(instructions) - if (instr.header >> 4) == WAIT] + [len(instructions)] - instructions_with_prefetch = instructions[:wait_idx[0]] - last_prefetch = None - for start, stop in zip(wait_idx[:-1], wait_idx[1:]): - call_targets = [instr.target for instr in instructions[start:stop] - if (instr.header >> 4) == CALL] - needed_lines = set() - for target in call_targets: - needed_lines.add(subroutine_cache_line[target]) - if len(needed_lines) > 8: - raise RuntimeError( - "Unable to prefetch more than 8 cache lines") - for needed_line in needed_lines: - if needed_line != last_prefetch: - instructions_with_prefetch.append(Prefetch(needed_line)) - last_prefetch = needed_line - instructions_with_prefetch += instructions[start:stop] - - instructions = instructions_with_prefetch - #pad out instruction vector to ensure circular cache never loads a subroutine - pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) - instructions += [NoOp()] * pad_instrs - - instructions += subroutine_instrs - - #turn symbols into integers addresses - resolve_symbols(instructions) - - assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ - 'Oops! too many instructions: {0}'.format(len(instructions)) - - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) + ''' + Constructs the complete instruction data vector, and does basic checks for validity. + + Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line + ''' + logger = logging.getLogger(__name__) + logger.debug('') + + seq_instrs = [] + need_prefetch = len(cache_lines) > 0 + num_cache_lines = len(set(cache_lines)) + cache_line_changes = np.concatenate( + ([0], np.where(np.diff(cache_lines))[0] + 1)) + for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): + seq_instrs.append(create_seq_instructions( + list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0])) + #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line + if need_prefetch and (ct in cache_line_changes): + next_cache_line = cache_lines[cache_line_changes[(np.where( + ct == cache_line_changes)[0][0] + 1) % len( + cache_line_changes)]] + seq_instrs[-1].insert(0, WaveformPrefetch(int( + next_cache_line * WAVEFORM_CACHE_SIZE / 2))) + #steal label + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None + + #concatenate instructions + instructions = [] + subroutines_start = -1 + for ct, seq in enumerate(seq_instrs): + #Use last instruction being return as mark of start of subroutines + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + instructions += seq + + #if we have any subroutines then group in cache lines + if subroutines_start >= 0: + subroutine_instrs = [] + subroutine_cache_line = {} + CACHE_LINE_LENGTH = 128 + offset = 0 + for sub in seq_instrs[subroutines_start:]: + #TODO for now we don't properly handle prefetching mulitple cache lines + if len(sub) > CACHE_LINE_LENGTH: + warnings.warn( + "Subroutines longer than {} instructions may not be prefetched correctly") + #Don't unecessarily split across a cache line + if (len(sub) + offset > CACHE_LINE_LENGTH) and ( + len(sub) < CACHE_LINE_LENGTH): + pad_instrs = 128 - ((offset + 128) % 128) + subroutine_instrs += [NoOp()] * pad_instrs + offset = 0 + if offset == 0: + line_label = sub[0].label + subroutine_cache_line[sub[0].label] = line_label + subroutine_instrs += sub + offset += len(sub) % CACHE_LINE_LENGTH + logger.debug("Placed {} subroutines into {} cache lines".format( + len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // + CACHE_LINE_LENGTH)) + #inject prefetch commands before waits + wait_idx = [idx for idx, instr in enumerate(instructions) + if (instr.header >> 4) == WAIT] + [len(instructions)] + instructions_with_prefetch = instructions[:wait_idx[0]] + last_prefetch = None + for start, stop in zip(wait_idx[:-1], wait_idx[1:]): + call_targets = [instr.target for instr in instructions[start:stop] + if (instr.header >> 4) == CALL] + needed_lines = set() + for target in call_targets: + needed_lines.add(subroutine_cache_line[target]) + if len(needed_lines) > 8: + raise RuntimeError( + "Unable to prefetch more than 8 cache lines") + for needed_line in needed_lines: + if needed_line != last_prefetch: + instructions_with_prefetch.append(Prefetch(needed_line)) + last_prefetch = needed_line + instructions_with_prefetch += instructions[start:stop] + + instructions = instructions_with_prefetch + #pad out instruction vector to ensure circular cache never loads a subroutine + pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) + instructions += [NoOp()] * pad_instrs + + instructions += subroutine_instrs + + #turn symbols into integers addresses + resolve_symbols(instructions) + + assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ + 'Oops! too many instructions: {0}'.format(len(instructions)) + + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) def resolve_symbols(seq): - symbols = {} - # create symbol look-up table - for ct, entry in enumerate(seq): - if entry.label and entry.label not in symbols: - symbols[entry.label] = ct - # then update - for (ct, entry) in enumerate(seq): - if entry.target: - # find next available label. The TDM may miss some labels if branches only contain waveforms (which are ignored) - for k in range(len(seq)-ct): - temp = seq[ct+k] - if temp.target in symbols: - break - entry.address = symbols[temp.target] + symbols = {} + # create symbol look-up table + for ct, entry in enumerate(seq): + if entry.label and entry.label not in symbols: + symbols[entry.label] = ct + # then update + for (ct, entry) in enumerate(seq): + if entry.target: + # find next available label. The TDM may miss some labels if branches only contain waveforms (which are ignored) + for k in range(len(seq)-ct): + temp = seq[ct+k] + if temp.target in symbols: + break + entry.address = symbols[temp.target] def compress_marker(markerLL): - ''' - Compresses adjacent entries of the same state into single entries - ''' - for seq in markerLL: - idx = 0 - while idx + 1 < len(seq): - if (isinstance(seq[idx], Compiler.Waveform) and - isinstance(seq[idx + 1], Compiler.Waveform) and - seq[idx].isZero == seq[idx + 1].isZero): - - seq[idx].length += seq[idx + 1].length - del seq[idx + 1] - else: - idx += 1 + ''' + Compresses adjacent entries of the same state into single entries + ''' + for seq in markerLL: + idx = 0 + while idx + 1 < len(seq): + if (isinstance(seq[idx], Compiler.Waveform) and + isinstance(seq[idx + 1], Compiler.Waveform) and + seq[idx].isZero == seq[idx + 1].isZero): + + seq[idx].length += seq[idx + 1].length + del seq[idx + 1] + else: + idx += 1 def write_sequence_file(awgData, fileName): - ''' - Main function to pack channel sequences into an APS2 h5 file. - ''' - # Convert QGL IR into a representation that is closer to the hardware. - awgData['ch12']['linkList'], wfLib = preprocess( - awgData['ch12']['linkList'], awgData['ch12']['wfLib']) - - # compress marker data - for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: - if 'linkList' in awgData[field].keys(): - PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], - SAMPLING_RATE, 1, - Compiler.Waveform) - compress_marker(awgData[field]['linkList']) - else: - awgData[field]['linkList'] = [] - - #Create the waveform vectors - wfInfo = [] - wfInfo.append(create_wf_vector({key: wf.real - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - wfInfo.append(create_wf_vector({key: wf.imag - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - - if SAVE_WF_OFFSETS: - #create a set of all waveform signatures in offset dictionaries - #we could have multiple offsets for the same pulse becuase it could - #be repeated in multiple cache lines - wf_sigs = set() - for offset_dict in wfInfo[0][1]: - wf_sigs |= set(offset_dict.keys()) - #create dictionary linking entry labels (that's what we'll have later) with offsets - offsets = {} - for seq in awgData['ch12']['linkList']: - for entry in seq: - if len(wf_sigs) == 0: - break - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig in wf_sigs: - #store offsets and wavefor lib length - #time ampltidue entries are clamped to ADDRESS_UNIT - wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length - offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], - wf_length) - wf_sigs.discard(sig) - - #break out of outer loop too - if len(wf_sigs) == 0: - break - - #now pickle the label=>offsets - with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: - pickle.dump(offsets, FID) - - # build instruction vector - seq_data = [awgData[s]['linkList'] - for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] - instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) - - #Open the HDF5 file - if os.path.isfile(fileName): - os.remove(fileName) - with h5py.File(fileName, 'w') as FID: - FID['/'].attrs['Version'] = 4.0 - FID['/'].attrs['target hardware'] = 'APS2' - FID['/'].attrs['minimum firmware version'] = 4.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) - - #Create the groups and datasets - for chanct in range(2): - chanStr = '/chan_{0}'.format(chanct + 1) - chanGroup = FID.create_group(chanStr) - #Write the waveformLib to file - FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) - - #Write the instructions to channel 1 - if np.mod(chanct, 2) == 0: - FID.create_dataset(chanStr + '/instructions', - data=instructions) + ''' + Main function to pack channel sequences into an APS2 h5 file. + ''' + # Convert QGL IR into a representation that is closer to the hardware. + awgData['ch12']['linkList'], wfLib = preprocess( + awgData['ch12']['linkList'], awgData['ch12']['wfLib']) + + # compress marker data + for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: + if 'linkList' in awgData[field].keys(): + PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], + SAMPLING_RATE, 1, + Compiler.Waveform) + compress_marker(awgData[field]['linkList']) + else: + awgData[field]['linkList'] = [] + + #Create the waveform vectors + wfInfo = [] + wfInfo.append(create_wf_vector({key: wf.real + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + wfInfo.append(create_wf_vector({key: wf.imag + for key, wf in wfLib.items()}, awgData[ + 'ch12']['linkList'])) + + if SAVE_WF_OFFSETS: + #create a set of all waveform signatures in offset dictionaries + #we could have multiple offsets for the same pulse becuase it could + #be repeated in multiple cache lines + wf_sigs = set() + for offset_dict in wfInfo[0][1]: + wf_sigs |= set(offset_dict.keys()) + #create dictionary linking entry labels (that's what we'll have later) with offsets + offsets = {} + for seq in awgData['ch12']['linkList']: + for entry in seq: + if len(wf_sigs) == 0: + break + if isinstance(entry, Compiler.Waveform): + sig = wf_sig(entry) + if sig in wf_sigs: + #store offsets and wavefor lib length + #time ampltidue entries are clamped to ADDRESS_UNIT + wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length + offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], + wf_length) + wf_sigs.discard(sig) + + #break out of outer loop too + if len(wf_sigs) == 0: + break + + #now pickle the label=>offsets + with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: + pickle.dump(offsets, FID) + + # build instruction vector + seq_data = [awgData[s]['linkList'] + for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] + instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) + + #Open the HDF5 file + if os.path.isfile(fileName): + os.remove(fileName) + with h5py.File(fileName, 'w') as FID: + FID['/'].attrs['Version'] = 4.0 + FID['/'].attrs['target hardware'] = 'APS2' + FID['/'].attrs['minimum firmware version'] = 4.0 + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) + + #Create the groups and datasets + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + #Write the waveformLib to file + FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) + + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', + data=instructions) def read_sequence_file(fileName): - """ - Reads a HDF5 sequence file and returns a dictionary of lists. - Dictionary keys are channel strings such as ch1, ch12m1 - Lists are or tuples of time-amplitude pairs (time, output) - """ - chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', - 'mod_phase'] - seqs = {ch: [] for ch in chanStrs} - - def start_new_seq(): - for ct, ch in enumerate(chanStrs): - if (ct < 2) or (ct == 6): - #analog or modulation channel - seqs[ch].append([]) - else: - #marker channel - seqs[ch].append([]) - - with h5py.File(fileName, 'r') as FID: - file_version = FID["/"].attrs["Version"] - wf_lib = {} - wf_lib['ch1'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() - wf_lib['ch2'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() - instructions = FID['/chan_1/instructions'].value.flatten() - NUM_NCO = 2 - freq = np.zeros(NUM_NCO) #radians per timestep - phase = np.zeros(NUM_NCO) - frame = np.zeros(NUM_NCO) - next_freq = np.zeros(NUM_NCO) - next_phase = np.zeros(NUM_NCO) - next_frame = np.zeros(NUM_NCO) - accumulated_phase = np.zeros(NUM_NCO) - reset_flag = [False]*NUM_NCO - - for data in instructions: - instr = Instruction.unflatten(data) - - modulator_opcode = instr.payload >> 44 - - #update phases at these boundaries - if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( - (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): - for ct in range(NUM_NCO): - if reset_flag[ct]: - #would expect this to be zero but this is first non-zero point - accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT - reset_flag[ct] = False - freq[:] = next_freq[:] - phase[:] = next_phase[:] - frame[:] = next_frame[:] - - #Assume new sequence at every WAIT - if instr.opcode == WAIT: - start_new_seq() - - elif instr.opcode == WFM and (( - (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): - addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT - count = (instr.payload >> 24) & 0xfffff - count = (count + 1) * ADDRESS_UNIT - isTA = (instr.payload >> 45) & 0x1 - chan_select_bits = ((instr.header >> 2) & 0x1, - (instr.header >> 3) & 0x1) - #On older firmware we broadcast by default whereas on newer we respect the engine select - for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): - if (file_version < 4) or select_bit: - if isTA: - seqs[chan][-1].append((count, wf_lib[chan][addr])) - else: - for sample in wf_lib[chan][addr:addr + count]: - seqs[chan][-1].append((1, sample)) - - elif instr.opcode == MARKER: - chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) - count = instr.payload & 0xffffffff - count = (count + 1) * ADDRESS_UNIT - state = (instr.payload >> 32) & 0x1 - seqs[chan][-1].append((count, state)) - - elif instr.opcode == MODULATION: - # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} - nco_select_bits = (instr.payload >> 40) & 0xf - if modulator_opcode == 0x0: - #modulate - count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT - nco_select = {0b0001: 0, - 0b0010: 1, - 0b0100: 2, - 0b1000: 3}[nco_select_bits] - seqs['mod_phase'][-1] = np.append( - seqs['mod_phase'][-1], freq[nco_select] * - np.arange(count) + accumulated_phase[nco_select] + - phase[nco_select] + frame[nco_select]) - accumulated_phase += count * freq - else: - phase_rad = 2 * np.pi * (instr.payload & - 0xffffffff) / 2**28 - for ct in range(NUM_NCO): - if (nco_select_bits >> ct) & 0x1: - if modulator_opcode == 0x2: - #reset - next_phase[ct] = 0 - next_frame[ct] = 0 - reset_flag[ct] = True - elif modulator_opcode == 0x6: - #set frequency - next_freq[ct] = phase_rad / ADDRESS_UNIT - elif modulator_opcode == 0xa: - #set phase - next_phase[ct] = phase_rad - elif modulator_opcode == 0xe: - #update frame - next_frame[ct] += phase_rad - - #Apply modulation if we have any - for ct, ( - ch1, ch2, mod_phase - ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): - if len(mod_phase): - #only really works if ch1, ch2 are broadcast together - mod_ch1 = [] - mod_ch2 = [] - cum_time = 0 - for ((time_ch1, amp_ch1), - (time_ch2, amp_ch2)) in zip(ch1, ch2): - if (amp_ch1 != 0) or (amp_ch2 != 0): - assert time_ch1 == time_ch2 - if time_ch1 == 1: - #single timestep - modulated = np.exp(1j * mod_phase[cum_time]) * ( - amp_ch1 + 1j * amp_ch2) - mod_ch1.append((1, modulated.real)) - mod_ch2.append((1, modulated.imag)) - else: - #expand TA - modulated = np.exp( - 1j * - mod_phase[cum_time:cum_time + time_ch1]) * ( - amp_ch1 + 1j * amp_ch2) - for val in modulated: - mod_ch1.append((1, val.real)) - mod_ch2.append((1, val.imag)) - else: - mod_ch1.append((time_ch1, amp_ch1)) - mod_ch2.append((time_ch2, amp_ch2)) - - cum_time += time_ch1 - seqs['ch1'][ct] = mod_ch1 - seqs['ch2'][ct] = mod_ch2 - - del seqs['mod_phase'] - - return seqs + """ + Reads a HDF5 sequence file and returns a dictionary of lists. + Dictionary keys are channel strings such as ch1, ch12m1 + Lists are or tuples of time-amplitude pairs (time, output) + """ + chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', + 'mod_phase'] + seqs = {ch: [] for ch in chanStrs} + + def start_new_seq(): + for ct, ch in enumerate(chanStrs): + if (ct < 2) or (ct == 6): + #analog or modulation channel + seqs[ch].append([]) + else: + #marker channel + seqs[ch].append([]) + + with h5py.File(fileName, 'r') as FID: + file_version = FID["/"].attrs["Version"] + wf_lib = {} + wf_lib['ch1'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() + wf_lib['ch2'] = ( + 1.0 / + MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() + instructions = FID['/chan_1/instructions'].value.flatten() + NUM_NCO = 2 + freq = np.zeros(NUM_NCO) #radians per timestep + phase = np.zeros(NUM_NCO) + frame = np.zeros(NUM_NCO) + next_freq = np.zeros(NUM_NCO) + next_phase = np.zeros(NUM_NCO) + next_frame = np.zeros(NUM_NCO) + accumulated_phase = np.zeros(NUM_NCO) + reset_flag = [False]*NUM_NCO + + for data in instructions: + instr = Instruction.unflatten(data) + + modulator_opcode = instr.payload >> 44 + + #update phases at these boundaries + if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( + (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): + for ct in range(NUM_NCO): + if reset_flag[ct]: + #would expect this to be zero but this is first non-zero point + accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT + reset_flag[ct] = False + freq[:] = next_freq[:] + phase[:] = next_phase[:] + frame[:] = next_frame[:] + + #Assume new sequence at every WAIT + if instr.opcode == WAIT: + start_new_seq() + + elif instr.opcode == WFM and (( + (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): + addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT + count = (instr.payload >> 24) & 0xfffff + count = (count + 1) * ADDRESS_UNIT + isTA = (instr.payload >> 45) & 0x1 + chan_select_bits = ((instr.header >> 2) & 0x1, + (instr.header >> 3) & 0x1) + #On older firmware we broadcast by default whereas on newer we respect the engine select + for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): + if (file_version < 4) or select_bit: + if isTA: + seqs[chan][-1].append((count, wf_lib[chan][addr])) + else: + for sample in wf_lib[chan][addr:addr + count]: + seqs[chan][-1].append((1, sample)) + + elif instr.opcode == MARKER: + chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) + count = instr.payload & 0xffffffff + count = (count + 1) * ADDRESS_UNIT + state = (instr.payload >> 32) & 0x1 + seqs[chan][-1].append((count, state)) + + elif instr.opcode == MODULATION: + # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} + nco_select_bits = (instr.payload >> 40) & 0xf + if modulator_opcode == 0x0: + #modulate + count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT + nco_select = {0b0001: 0, + 0b0010: 1, + 0b0100: 2, + 0b1000: 3}[nco_select_bits] + seqs['mod_phase'][-1] = np.append( + seqs['mod_phase'][-1], freq[nco_select] * + np.arange(count) + accumulated_phase[nco_select] + + phase[nco_select] + frame[nco_select]) + accumulated_phase += count * freq + else: + phase_rad = 2 * np.pi * (instr.payload & + 0xffffffff) / 2**28 + for ct in range(NUM_NCO): + if (nco_select_bits >> ct) & 0x1: + if modulator_opcode == 0x2: + #reset + next_phase[ct] = 0 + next_frame[ct] = 0 + reset_flag[ct] = True + elif modulator_opcode == 0x6: + #set frequency + next_freq[ct] = phase_rad / ADDRESS_UNIT + elif modulator_opcode == 0xa: + #set phase + next_phase[ct] = phase_rad + elif modulator_opcode == 0xe: + #update frame + next_frame[ct] += phase_rad + + #Apply modulation if we have any + for ct, ( + ch1, ch2, mod_phase + ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): + if len(mod_phase): + #only really works if ch1, ch2 are broadcast together + mod_ch1 = [] + mod_ch2 = [] + cum_time = 0 + for ((time_ch1, amp_ch1), + (time_ch2, amp_ch2)) in zip(ch1, ch2): + if (amp_ch1 != 0) or (amp_ch2 != 0): + assert time_ch1 == time_ch2 + if time_ch1 == 1: + #single timestep + modulated = np.exp(1j * mod_phase[cum_time]) * ( + amp_ch1 + 1j * amp_ch2) + mod_ch1.append((1, modulated.real)) + mod_ch2.append((1, modulated.imag)) + else: + #expand TA + modulated = np.exp( + 1j * + mod_phase[cum_time:cum_time + time_ch1]) * ( + amp_ch1 + 1j * amp_ch2) + for val in modulated: + mod_ch1.append((1, val.real)) + mod_ch2.append((1, val.imag)) + else: + mod_ch1.append((time_ch1, amp_ch1)) + mod_ch2.append((time_ch2, amp_ch2)) + + cum_time += time_ch1 + seqs['ch1'][ct] = mod_ch1 + seqs['ch2'][ct] = mod_ch2 + + del seqs['mod_phase'] + + return seqs def update_wf_library(filename, pulses, offsets): - """ - Update a H5 waveform library in place give an iterable of (pulseName, pulse) - tuples and offsets into the waveform library. - """ - assert USE_PHASE_OFFSET_INSTRUCTION == False - #load the h5 file - with h5py.File(filename) as FID: - for label, pulse in pulses.items(): - #create a new waveform - if pulse.isTimeAmp: - shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) - else: - shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape - try: - length = offsets[label][1] - except KeyError: - print("\t{} not found in offsets so skipping".format(pulse)) - continue - for offset in offsets[label][0]: - print("\tUpdating {} at offset {}".format(pulse, offset)) - FID['/chan_1/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.real) - FID['/chan_2/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.imag) + """ + Update a H5 waveform library in place give an iterable of (pulseName, pulse) + tuples and offsets into the waveform library. + """ + assert USE_PHASE_OFFSET_INSTRUCTION == False + #load the h5 file + with h5py.File(filename) as FID: + for label, pulse in pulses.items(): + #create a new waveform + if pulse.isTimeAmp: + shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) + else: + shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape + try: + length = offsets[label][1] + except KeyError: + print("\t{} not found in offsets so skipping".format(pulse)) + continue + for offset in offsets[label][0]: + print("\tUpdating {} at offset {}".format(pulse, offset)) + FID['/chan_1/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.real) + FID['/chan_2/waveforms'][offset:offset + length] = np.int16( + MAX_WAVEFORM_VALUE * shape.imag) def tdm_instructions(seqs): - """ - Generate the TDM instructions for the given sequence. - - This assumes that there is one instruction sequence, not - a list of them (as is generally the case elsewhere). FIXME - """ - instructions = list() - label2addr = dict() # the backpatch table for labels - # turn into a loop, by appending GOTO(0) at end of the sequence - seqs[-1].append(ControlFlow.Goto(BlockLabel.label(seqs[0]))) # BlockLabel.label adds a label at the beginning of the sequence - logger.debug("Appending a GOTO at end to loop") - label = seqs[0][0] - for seq in seqs: - seq = list(flatten(copy(seq))) - - #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label - instructions.append(Sync(label=label)) - # add a WAIT before the first waveform FIXME: there must be a more efficient way - if ControlFlow.Wait not in seq: - ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) - seq.insert(ind_wait, ControlFlow.Wait()) - - - label = None - for s in seq: - if isinstance(s, BlockLabel.BlockLabel): - #label2addr[s.label] = len(instructions) #FIXME this convert a label (A, B, ...) to the instruction number, i.e. the address (typically) - - # carry label forward to next entry - label = s - continue - - if isinstance(s, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(s, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - - elif isinstance(s, TdmInstructions.WriteAddrInstruction) and s.tdm == True: - if s.instruction == 'INVALIDATE': - print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(Invalidate(s.addr, s.value, label=label)) - - elif s.instruction == 'WRITEADDR': - print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(WriteAddr(s.addr, s.value, label=label)) - - elif s.instruction == 'STOREMEAS': - print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % - (str(s.channel), s.addr, s.value)) - instructions.append(StoreMeas(s.addr, s.value, label=label)) - else: # TODO: add CrossBar (no need for explicit QGL call for TDM) - print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % - (s.instruction, str(s.channel), - s.addr, s.value)) - continue - - elif isinstance(s, TdmInstructions.CustomInstruction): - - if s.instruction == 'MAJORITY': - print('MAJORITY(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVote(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'MAJORITYMASK': - print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - MajorityVoteMask(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'TSM': - print('DECODE(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - Decode(s.in_addr, s.out_addr, label=label)) - elif s.instruction == 'TSM_SET_ROUNDS': - print('DECODESETROUNDS(in_addr=%x, out_addr=%x)' % - (s.in_addr, s.out_addr)) - instructions.append( - DecodeSetRounds(s.in_addr, s.out_addr, label=label)) - else: #TODO: add decoder - print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % - (s.instruction, s.in_addr, s.out_addr)) - - elif isinstance(s, ControlFlow.Goto): - instructions.append(Goto(s.target, label=label)) - elif isinstance(s, ControlFlow.Repeat): - instructions.append(Repeat(s.target, label=label)) - elif isinstance(s, ControlFlow.LoadRepeat): - instructions.append(Load(s.value - 1, label=label)) - - elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): - if s.instruction == 'LOADCMPVRAM' and s.tdm == True: - instructions.append( - LoadCmpVram(s.addr, s.mask, label=label)) - - elif isinstance(s, PulseSequencer.Pulse): - if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(CrossBar(s.maddr[1], 0x1, label=label)) # this has to change for sim. msmt's - instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) #1 << s.maddr[1])) - - elif isinstance(s, PulseSequencer.PulseBlock): - sim_meas = [] - for k in s.pulses: - if s.pulses[k].label == 'MEAS' and s.pulses[k].maddr != (-1, 0): - sim_meas.append(s.pulses[k]) - if sim_meas: - maddr = [m.maddr[0] for m in sim_meas] - if len(set(maddr))>1: - raise Exception('Storing simultaneous measurements on different addresses not supported.') - for n,m in enumerate(sim_meas): - instructions.append(CrossBar(2**n, 2**n)) - instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(maddr[0], 1 << 16)) - - elif isinstance(s, list): - # FIXME: - # If this happens, we are confused. - print('FIXME: TDM GOT LIST: %s' % str(s)) - - elif isinstance(s, ControlFlow.ComparisonInstruction): - instructions.append( - Cmp(CMPTABLE[s.operator], s.value, label=label)) - - else: - pass - # use this for debugging purposes - #print('OOPS: unhandled [%s]' % str(type(s))) - - resolve_symbols(instructions) - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) + """ + Generate the TDM instructions for the given sequence. + + This assumes that there is one instruction sequence, not + a list of them (as is generally the case elsewhere). FIXME + """ + instructions = list() + label2addr = dict() # the backpatch table for labels + # turn into a loop, by appending GOTO(0) at end of the sequence + seqs[-1].append(ControlFlow.Goto(BlockLabel.label(seqs[0]))) # BlockLabel.label adds a label at the beginning of the sequence + logger.debug("Appending a GOTO at end to loop") + label = seqs[0][0] + for seq in seqs: + seq = list(flatten(copy(seq))) + + #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label + instructions.append(Sync(label=label)) + # add a WAIT before the first waveform FIXME: there must be a more efficient way + if ControlFlow.Wait not in seq: + ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) + seq.insert(ind_wait, ControlFlow.Wait()) + + + label = None + for s in seq: + if isinstance(s, BlockLabel.BlockLabel): + #label2addr[s.label] = len(instructions) #FIXME this convert a label (A, B, ...) to the instruction number, i.e. the address (typically) + + # carry label forward to next entry + label = s + continue + + if isinstance(s, ControlFlow.Wait): + instructions.append(Wait(label=label)) + elif isinstance(s, ControlFlow.LoadCmp): + instructions.append(LoadCmp(label=label)) + + elif isinstance(s, TdmInstructions.WriteAddrInstruction) and s.tdm == True: + if s.instruction == 'INVALIDATE': + print('o INVALIDATE(channel=%s, addr=0x%x, mask=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(Invalidate(s.addr, s.value, label=label)) + + elif s.instruction == 'WRITEADDR': + print('o WRITEADDR(channel=%s, addr=0x%x, value=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(WriteAddr(s.addr, s.value, label=label)) + + elif s.instruction == 'STOREMEAS': + print('STOREMEAS(channel=%s, addr=0x%x, mapping=0x%x)' % + (str(s.channel), s.addr, s.value)) + instructions.append(StoreMeas(s.addr, s.value, label=label)) + else: # TODO: add CrossBar (no need for explicit QGL call for TDM) + print('UNSUPPORTED WriteAddr: %s(channel=%s, addr=0x%x, val=0x%x)' % + (s.instruction, str(s.channel), + s.addr, s.value)) + continue + + elif isinstance(s, TdmInstructions.CustomInstruction): + + if s.instruction == 'MAJORITY': + print('MAJORITY(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVote(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'MAJORITYMASK': + print('MAJORITYMASK(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + MajorityVoteMask(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'TSM': + print('DECODE(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + Decode(s.in_addr, s.out_addr, label=label)) + elif s.instruction == 'TSM_SET_ROUNDS': + print('DECODESETROUNDS(in_addr=%x, out_addr=%x)' % + (s.in_addr, s.out_addr)) + instructions.append( + DecodeSetRounds(s.in_addr, s.out_addr, label=label)) + else: #TODO: add decoder + print('UNSUPPORTED CUSTOM: %s(in_addr=0x%x, out_addr=0x%x)' % + (s.instruction, s.in_addr, s.out_addr)) + + elif isinstance(s, ControlFlow.Goto): + instructions.append(Goto(s.target, label=label)) + elif isinstance(s, ControlFlow.Repeat): + instructions.append(Repeat(s.target, label=label)) + elif isinstance(s, ControlFlow.LoadRepeat): + instructions.append(Load(s.value - 1, label=label)) + + elif isinstance(s, TdmInstructions.LoadCmpVramInstruction): + if s.instruction == 'LOADCMPVRAM' and s.tdm == True: + instructions.append( + LoadCmpVram(s.addr, s.mask, label=label)) + + elif isinstance(s, PulseSequencer.Pulse): + if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(CrossBar(s.maddr[1], 0x1, label=label)) # this has to change for sim. msmt's + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) #1 << s.maddr[1])) + + elif isinstance(s, PulseSequencer.PulseBlock): + sim_meas = [] + for k in s.pulses: + if s.pulses[k].label == 'MEAS' and s.pulses[k].maddr != (-1, 0): + sim_meas.append(s.pulses[k]) + if sim_meas: + maddr = [m.maddr[0] for m in sim_meas] + if len(set(maddr))>1: + raise Exception('Storing simultaneous measurements on different addresses not supported.') + for n,m in enumerate(sim_meas): + instructions.append(CrossBar(2**n, 2**n)) + instructions.append(LoadCmp(label=label)) + instructions.append(StoreMeas(maddr[0], 1 << 16)) + + elif isinstance(s, list): + # FIXME: + # If this happens, we are confused. + print('FIXME: TDM GOT LIST: %s' % str(s)) + + elif isinstance(s, ControlFlow.ComparisonInstruction): + instructions.append( + Cmp(CMPTABLE[s.operator], s.value, label=label)) + + else: + pass + # use this for debugging purposes + #print('OOPS: unhandled [%s]' % str(type(s))) + + resolve_symbols(instructions) + return np.fromiter((instr.flatten() for instr in instructions), np.uint64, + len(instructions)) def write_tdm_seq(seq, tdm_filename): #Open the HDF5 file @@ -1439,44 +1439,44 @@ def write_tdm_seq(seq, tdm_filename): # Utility Functions for displaying programs def get_channel_instructions_string(channel): - return '/chan_{}/instructions'.format(channel) + return '/chan_{}/instructions'.format(channel) def raw_instructions(filename, channel = 1): - channelStr = get_channel_instructions_string(channel) - with h5py.File(filename, 'r') as fid: - raw_instrs = fid[channelStr].value.flatten() - return raw_instrs + channelStr = get_channel_instructions_string(channel) + with h5py.File(filename, 'r') as fid: + raw_instrs = fid[channelStr].value.flatten() + return raw_instrs def decompile_instructions(instructions, tdm = False): - return [Instruction.unflatten(x, decode_as_tdm = tdm) for x in instructions] + return [Instruction.unflatten(x, decode_as_tdm = tdm) for x in instructions] def read_instructions(filename): - raw_instrs = raw_instructions(filename) - return decompile_instructions(raw_instrs) + raw_instrs = raw_instructions(filename) + return decompile_instructions(raw_instrs) def replace_instructions(filename, instructions, channel = 1): - channelStr = get_channel_instructions_string(channel) - with h5py.File(filename, 'r+') as fid: - del fid[channelStr] - fid.create_dataset(channelStr, data=instructions) + channelStr = get_channel_instructions_string(channel) + with h5py.File(filename, 'r+') as fid: + del fid[channelStr] + fid.create_dataset(channelStr, data=instructions) def display_decompiled_file(filename, tdm = False): - raw = raw_instructions(filename) - display_decompiled_instructions(raw, tdm) + raw = raw_instructions(filename) + display_decompiled_instructions(raw, tdm) def display_decompiled_instructions(raw, tdm = False, display_op_codes = True): - cmds = decompile_instructions(raw, tdm) - opcodeStr = '' - for i,a in enumerate(zip(raw,cmds)): - x,y = a - if display_op_codes: - opcodeStr = "0x{:016x} - ".format(x) - print("{:5}: {}{}".format(i, opcodeStr,y)) + cmds = decompile_instructions(raw, tdm) + opcodeStr = '' + for i,a in enumerate(zip(raw,cmds)): + x,y = a + if display_op_codes: + opcodeStr = "0x{:016x} - ".format(x) + print("{:5}: {}{}".format(i, opcodeStr,y)) def display_raw_instructions(raw): - for x in raw: - print("0x{:016x}".format(x)) + for x in raw: + print("0x{:016x}".format(x)) def display_raw_file(filename): - raw = raw_instructions(filename) - display_raw_instructions(raw) + raw = raw_instructions(filename) + display_raw_instructions(raw) From cbe3539579ce99a13ad126a6e437a3adcf7dbb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:16:37 -0400 Subject: [PATCH 42/67] Delete outdated To be replaced with a current example --- QGL/n.py | 93 -------------------------------------------------------- 1 file changed, 93 deletions(-) delete mode 100644 QGL/n.py diff --git a/QGL/n.py b/QGL/n.py deleted file mode 100644 index bf72a5e9..00000000 --- a/QGL/n.py +++ /dev/null @@ -1,93 +0,0 @@ -# Example program for creating TDM instruction files - -import copy -import json -import numpy - -import QGL.drivers -from QGL import * -import aps2_reader - -from QGL.drivers.APS2TDMPattern import Instruction -import QGL.drivers.APS2TDMPattern - -def pp_instructions(name, instructions): - print('') - print('INSTRUMENT: ' + name) - for i in range(len(instructions)): - instr_bits = instructions[i] - instr_txt = str(Instruction.unflatten(instr_bits)) - print('%5d: 0x%.16x - %s' % (i, instr_bits, instr_txt)) - -def setUp(): - cl = ChannelLibrary(library_file="./meas.yml") - ChannelLibraries.channelLib.build_connectivity_graph() - -setUp() - -q1 = QubitFactory('q1') -q2 = QubitFactory('q2') - -seq = [ - X(q1), - X(q2), - - WriteAddr(1, 7), - MajorityMask(1, 0), - - Invalidate(addr=10, mask=0x7), - - MEASA(q1, maddr=(10, 4)), - MEASA(q1, maddr=(10, 1)), - # MEASA(q1, maddr=(10, 2)), - MEASA(q2, maddr=(10, 2)), - - LoadCmpVram(10, 7), - - MajorityVote(10, 11), - LoadCmpVram(11, 1), - qif(0, [MEASA(q1, maddr=(12, 0)), X(q1)], [Y90(q2)]), - - ] - -# First, compile for the APS units. As a side effect, -# this creates the TDM instructions, but does NOT -# put them into the APS output file. We retrieve -# the TDM instructions, and then recompile the instructions -# to create a template TDM file, and then insert the -# TDM instructions into the template. -# -# Note that the steps of compiling for the TDM and -# inserting the instructions into the file are FRAGILE -# because they require that there be an instrument named -# "BBNAPS1" in the machine config. This name has special -# meaning. - -# IMPORTANT: compilation is destructive: it modifies -# the input sequences (and possibly the instances in those -# sequences. So, running compiler_to_hardware on the -# same sequence twice can FAIL (or give weird results). -# So copy the input sequence each time we use it... - -aps_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/aps') -aps_metadata = json.loads(open(aps_metafile).read()) - -tdm_metafile = compile_to_hardware([copy.copy(seq)], '/tmp/tdm') -tdm_metadata = json.loads(open(tdm_metafile).read()) - -tdm_instr = QGL.drivers.APS2TDMPattern.tdm_instructions(seq) - -aps2_reader.replace_instructions( - tdm_metadata['instruments']['BBNAPS1'], - numpy.array(tdm_instr, dtype=np.uint64)) - -for key in aps_metadata['instruments']: - instructions = aps2_reader.raw_instructions( - aps_metadata['instruments'][key]) - pp_instructions(str(key), instructions) - -# Read the TDM instructions from file, just to make sure -# -tdm_instr_from_file = aps2_reader.raw_instructions( - tdm_metadata['instruments']['BBNAPS1']) -pp_instructions('tdm', tdm_instr_from_file) From 3765a974834f9134c2b08a378223f28f0f05f48e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:30:57 -0400 Subject: [PATCH 43/67] Tdm compile :bug: s --- QGL/Compiler.py | 2 +- QGL/drivers/APS2Pattern.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QGL/Compiler.py b/QGL/Compiler.py index b953b61c..b007c895 100644 --- a/QGL/Compiler.py +++ b/QGL/Compiler.py @@ -442,7 +442,7 @@ def compile_to_hardware(seqs, files['TDM'] = os.path.normpath(os.path.join( config.AWGDir, fileName + '-' + 'TDM' + suffix + data[ 'seqFileExt'])) - aps2tdm_module.write_tdm_seq(files['TDM']) + aps2tdm_module.write_tdm_seq(tdm_instr, files['TDM']) # create meta output if not axis_descriptor: diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 94cf6ce6..2c394418 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1421,7 +1421,7 @@ def tdm_instructions(seqs): return np.fromiter((instr.flatten() for instr in instructions), np.uint64, len(instructions)) -def write_tdm_seq(seq, tdm_filename): +def write_tdm_seq(seq, tdm_fileName): #Open the HDF5 file if os.path.isfile(tdm_fileName): os.remove(tdm_fileName) @@ -1434,7 +1434,7 @@ def write_tdm_seq(seq, tdm_filename): #Create the groups and datasets chanStr = '/chan_{0}'.format(1) chanGroup = FID.create_group(chanStr) - FID.create_dataset(chanStr + '/instructions', data=tdm_instr) + FID.create_dataset(chanStr + '/instructions', data=seq) # Utility Functions for displaying programs From 188c197bb43b88b3d235e2a621c956267fcf8349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:31:41 -0400 Subject: [PATCH 44/67] Make LoadCmpVram a case of qwait Experimenting, not sure what's the best hierarchy --- QGL/ControlFlow.py | 11 ++++++++--- QGL/__init__.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 54147a5d..48909e97 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -2,6 +2,7 @@ from .PulseSequencer import Pulse from functools import wraps from .mm import multimethod +from .TdmInstructions import WriteAddrInstruction, LoadCmpVramInstruction ## QGL control-flow statements ## @@ -83,14 +84,19 @@ def repeatall(n, seqs): return seqs -def qwait(channels=None, kind="TRIG"): +def qwait(kind="TRIG", addr=None, mask=None, channels=None): ''' Insert a WAIT or LOADCMP command on the target channels (an iterable, or None) ''' if kind == "TRIG": return Wait(channels) - else: + elif kind == "EXT": return LoadCmp(channels) + elif kind == "RAM": + if not addr or not mask: + raise Exception('Please specify address and mask') + return [WriteAddrInstruction('INVALIDATE', None, 1, addr, mask, False), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask, False)] + def qsync(channels=None): @@ -216,4 +222,3 @@ def __init__(self, *chanlist): def __str__(self): base = "BARRIER({0})".format(self.chanlist) return base - diff --git a/QGL/__init__.py b/QGL/__init__.py index e1dcce54..2433b99a 100644 --- a/QGL/__init__.py +++ b/QGL/__init__.py @@ -10,4 +10,4 @@ from .Tomography import state_tomo, process_tomo from .Scheduler import schedule -from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, LoadCmpVram, Decode, DecodeSetRounds +from .TdmInstructions import MajorityMask, MajorityVote, WriteAddr, Invalidate, Decode, DecodeSetRounds From e8159d82379e6aa3d3c65c73df1a8ed905f1af70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 7 Jun 2018 17:33:59 -0400 Subject: [PATCH 45/67] Delete accidentally committed backup --- QGL/drivers/APS2Pattern_bck.py | 1151 -------------------------------- 1 file changed, 1151 deletions(-) delete mode 100644 QGL/drivers/APS2Pattern_bck.py diff --git a/QGL/drivers/APS2Pattern_bck.py b/QGL/drivers/APS2Pattern_bck.py deleted file mode 100644 index dcee35ab..00000000 --- a/QGL/drivers/APS2Pattern_bck.py +++ /dev/null @@ -1,1151 +0,0 @@ -''' -Module for writing hdf5 APS2 files from sequences and patterns - -Copyright 2014 Raytheon BBN Technologies - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -''' - -import os -import logging -from warnings import warn -from copy import copy -from future.moves.itertools import zip_longest -import pickle - -import h5py -import numpy as np - -from QGL import Compiler, ControlFlow, BlockLabel, PatternUtils -from QGL.PatternUtils import hash_pulse, flatten - -# Python 2/3 compatibility: use 'int' that subclasses 'long' -from builtins import int - -#Some constants -SAMPLING_RATE = 1.2e9 -ADDRESS_UNIT = 4 #everything is done in units of 4 timesteps -MIN_ENTRY_LENGTH = 8 -MAX_WAVEFORM_PTS = 2**28 #maximum size of waveform memory -WAVEFORM_CACHE_SIZE = 2**17 -MAX_WAVEFORM_VALUE = 2**13 - 1 #maximum waveform value i.e. 14bit DAC -MAX_NUM_INSTRUCTIONS = 2**26 -MAX_REPEAT_COUNT = 2**16 - 1 -MAX_TRIGGER_COUNT = 2**32 - 1 - -# instruction encodings -WFM = 0x0 -MARKER = 0x1 -WAIT = 0x2 -LOAD = 0x3 -REPEAT = 0x4 -CMP = 0x5 -GOTO = 0x6 -CALL = 0x7 -RET = 0x8 -SYNC = 0x9 -MODULATION = 0xA -LOADCMP = 0xB -PREFETCH = 0xC -NOP = 0XF - -# WFM/MARKER op codes -PLAY = 0x0 -WAIT_TRIG = 0x1 -WAIT_SYNC = 0x2 -WFM_PREFETCH = 0x3 -WFM_OP_OFFSET = 46 -TA_PAIR_BIT = 45 - -# CMP op encodings -EQUAL = 0x0 -NOTEQUAL = 0x1 -GREATERTHAN = 0x2 -LESSTHAN = 0x3 - -# Whether we use PHASE_OFFSET modulation commands or bake it into waveform -# Default to false as we usually don't have many variants -USE_PHASE_OFFSET_INSTRUCTION = False - -# Whether to save the waveform offsets for partial compilation -SAVE_WF_OFFSETS = False - -# Do we want a pulse file per instrument or per channel -SEQFILE_PER_CHANNEL = False - -def get_empty_channel_set(): - return {'ch12': {}, 'ch12m1': {}, 'ch12m2': {}, 'ch12m3': {}, 'ch12m4': {}} - - -def get_seq_file_extension(): - return '.h5' - - -def is_compatible_file(filename): - with h5py.File(filename, 'r') as FID: - target = FID['/'].attrs['target hardware'] - if isinstance(target, str): - target = target.encode('utf-8') - if target == b'APS2': - return True - return False - -def create_wf_vector(wfLib, seqs): - ''' - Helper function to create the wf vector and offsets into it. - ''' - max_pts_needed = 0 - for wf in wfLib.values(): - if len(wf) == 1: - max_pts_needed += ADDRESS_UNIT - else: - max_pts_needed += len(wf) - - #If we have more than fits in cache we'll need to align and prefetch - need_prefetch = max_pts_needed > WAVEFORM_CACHE_SIZE - - idx = 0 - - if not need_prefetch: - offsets = [{}] - cache_lines = [] - #if we can fit them all in just pack - wfVec = np.zeros(max_pts_needed, dtype=np.int16) - for key, wf in wfLib.items(): - #Clip the wf - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16(MAX_WAVEFORM_VALUE * wf) - offsets[-1][key] = idx - idx += wf.size - - #Trim the waveform - wfVec.resize(idx) - - else: - #otherwise fill in one cache line at a time - CACHE_LINE_LENGTH = WAVEFORM_CACHE_SIZE / 2 - wfVec = np.zeros(CACHE_LINE_LENGTH, dtype=np.int16) - offsets = [{}] - cache_lines = [] - for seq in seqs: - #go through sequence and see what we need to add - pts_to_add = 0 - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - pts_to_add += entry.length - - #If what we need to add spills over then add a line and start again - if (idx % CACHE_LINE_LENGTH) + pts_to_add > CACHE_LINE_LENGTH: - idx = int(CACHE_LINE_LENGTH * ( - (idx + CACHE_LINE_LENGTH) // CACHE_LINE_LENGTH)) - wfVec = np.append(wfVec, - np.zeros(CACHE_LINE_LENGTH, - dtype=np.int16)) - offsets.append({}) - - for entry in seq: - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig not in offsets[-1]: - wf = wfLib[sig] - wf[wf > 1] = 1.0 - wf[wf < -1] = -1.0 - #TA pairs need to be repeated ADDRESS_UNIT times - if wf.size == 1: - wf = wf.repeat(ADDRESS_UNIT) - #Ensure the wf is an integer number of ADDRESS_UNIT's - trim = wf.size % ADDRESS_UNIT - if trim: - wf = wf[:-trim] - wfVec[idx:idx + wf.size] = np.int16( - MAX_WAVEFORM_VALUE * wf) - offsets[-1][sig] = idx - idx += wf.size - - cache_lines.append(int(idx // CACHE_LINE_LENGTH)) - - return wfVec, offsets, cache_lines - - -class Instruction(object): - def __init__(self, header, payload, label=None, target=None): - self.header = header - self.payload = int(payload) - self.label = label - self.target = target - - @classmethod - def unflatten(cls, instr): - return cls(header=(int(instr) >> 56) & 0xff, - payload=int(instr) & 0xffffffffffffff) - - def __repr__(self): - return self.__str__() - - def __str__(self): - - opCodes = ["WFM", "MARKER", "WAIT", "LOAD", "REPEAT", "CMP", "GOTO", - "CALL", "RET", "SYNC", "MODULATION", "LOADCMP", "PREFETCH", - "NOP", "NOP", "NOP"] - - out = "{0} ".format(self.label) if self.label else "" - - instrOpCode = (self.header >> 4) & 0xf - out += opCodes[instrOpCode] - - if (instrOpCode == MARKER) or (instrOpCode == WFM) or ( - instrOpCode == MODULATION): - if (instrOpCode == MARKER) or (instrOpCode == WFM): - out += "; engine={}, ".format((self.header >> 2) & 0x3) - else: - out += "; " - if self.header & 0x1: - out += "write=1 | " - else: - out += "write=0 | " - - if self.target: - out += " {}".format(self.target) - - if instrOpCode == WFM: - wfOpCode = (self.payload >> 46) & 0x3 - wfOpCodes = ["PLAY", "TRIG", "SYNC", "PREFETCH"] - out += wfOpCodes[wfOpCode] - out += "; TA bit={}".format((self.payload >> 45) & 0x1) - out += ", count = {}".format((self.payload >> 24) & 2**21 - 1) - out += ", addr = {}".format(self.payload & 2**24 - 1) - - elif instrOpCode == MARKER: - mrkOpCode = (self.payload >> 46) & 0x3 - mrkOpCodes = ["PLAY", "TRIG", "SYNC"] - out += mrkOpCodes[mrkOpCode] - out += "; state={}".format((self.payload >> 32) & 0x1) - out += ", count = {}".format(self.payload & 2**32 - 1) - - elif instrOpCode == MODULATION: - modulatorOpCode = (self.payload >> 45) & 0x7 - modulatorOpCodes = ["MODULATE", "RESET_PHASE", "TRIG", "SET_FREQ", - "SYNC", "SET_PHASE", "", "UPDATE_FRAME"] - out += modulatorOpCodes[modulatorOpCode] - out += "; nco_select=0x{:x}".format((self.payload >> 40) & 0xf) - if modulatorOpCode == 0x0: - out += ", count={:d}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x3: - out += ", increment=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x5: - out += ", phase=0x{:08x}".format(self.payload & 0xffffffff) - elif modulatorOpCode == 0x7: - out += ", frame_change=0x{:08x}".format(self.payload & - 0xffffffff) - - elif instrOpCode == CMP: - cmpCodes = ["EQUAL", "NOTEQUAL", "GREATERTHAN", "LESSTHAN"] - cmpCode = (self.payload >> 8) & 0x3 - out += " | " + cmpCodes[cmpCode] - out += ", value = {}".format(self.payload & 0xff) - - elif any( - [instrOpCode == op for op in [GOTO, CALL, RET, REPEAT, PREFETCH]]): - out += " | target addr = {}".format(self.payload & 2**26 - 1) - - elif instrOpCode == LOAD: - out += " | count = {}".format(self.payload) - - return out - - def __eq__(self, other): - return self.header == other.header and self.payload == other.payload and self.label == other.label - - def __ne__(self, other): - return not self == other - - def __hash__(self): - return hash((self.header, self.payload, self.label)) - - @property - def address(self): - return self.payload & 0xffffffff # bottom 32-bits of payload - - @address.setter - def address(self, value): - self.payload |= value & 0xffffffff - - @property - def writeFlag(self): - return self.header & 0x1 - - @writeFlag.setter - def writeFlag(self, value): - self.header |= value & 0x1 - - @property - def opcode(self): - return self.header >> 4 - - def flatten(self): - return int((self.header << 56) | (self.payload & 0xffffffffffff)) - - -def Waveform(addr, count, isTA, write=False, label=None): - header = (WFM << 4) | (0x3 << 2) | (write & - 0x1) #broadcast to both engines - count = int(count) - count = ((count // ADDRESS_UNIT) - 1) & 0x000fffff # 20 bit count - addr = (addr // ADDRESS_UNIT) & 0x00ffffff # 24 bit addr - payload = (PLAY << WFM_OP_OFFSET) | ((int(isTA) & 0x1) - << TA_PAIR_BIT) | (count << 24) | addr - return Instruction(header, payload, label) - - -def WaveformPrefetch(addr): - header = (WFM << 4) | (0x3 << 2) | (0x1) - payload = (WFM_PREFETCH << WFM_OP_OFFSET) | addr - return Instruction(header, payload, None) - - -def Marker(sel, state, count, write=False, label=None): - header = (MARKER << 4) | ((sel & 0x3) << 2) | (write & 0x1) - count = int(count) - four_count = ((count // ADDRESS_UNIT) - 1) & 0xffffffff # 32 bit count - count_rem = count % ADDRESS_UNIT - if state == 0: - transitionWords = {0: 0b0000, 1: 0b1000, 2: 0b1100, 3: 0b1110} - transition = transitionWords[count_rem] - else: - transitionWords = {0: 0b1111, 1: 0b0111, 2: 0b0011, 3: 0b0001} - transition = transitionWords[count_rem] - payload = (PLAY << WFM_OP_OFFSET) | (transition << 33) | ( - (state & 0x1) << 32) | four_count - return Instruction(header, payload, label) - - -def Command(cmd, payload, write=False, label=None): - header = (cmd << 4) - if isinstance(payload, int): - instr = Instruction(header, payload, label) - else: - instr = Instruction(header, 0, label, target=payload) - instr.writeFlag = write - return instr - - -def Sync(label=None): - return Command(SYNC, WAIT_SYNC << WFM_OP_OFFSET, write=True, label=label) - - -def Wait(label=None): - return Command(WAIT, WAIT_TRIG << WFM_OP_OFFSET, write=True, label=label) - - -def LoadCmp(label=None): - return Command(LOADCMP, 0, label=label) - - -def Cmp(op, value, label=None): - return Command(CMP, (op << 8) | (value & 0xff), label=label) - - -def Goto(addr, label=None): - return Command(GOTO, addr, label=label) - - -def Call(addr, label=None): - return Command(CALL, addr, label=label) - - -def Return(label=None): - return Command(RET, 0, label=label) - - -def Load(count, label=None): - return Command(LOAD, count, label=label) - - -def Repeat(addr, label=None): - return Command(REPEAT, addr, label=label) - - -def Prefetch(addr, label=None): - return Command(PREFETCH, addr) - - -def NoOp(): - return Instruction.unflatten(0xffffffffffffffff) - - -def preprocess(seqs, shapeLib): - seqs = PatternUtils.convert_lengths_to_samples( - seqs, SAMPLING_RATE, ADDRESS_UNIT, Compiler.Waveform) - wfLib = build_waveforms(seqs, shapeLib) - inject_modulation_cmds(seqs) - return seqs, wfLib - - -def wf_sig(wf): - ''' - Compute a signature of a Compiler.Waveform that identifies the relevant properties for - two Waveforms to be considered "equal" in the waveform library. For example, we ignore - length of TA waveforms. - ''' - if wf.isZero or wf.isTimeAmp: # 2nd condition necessary until we support RT SSB - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.amp) - else: - return (wf.amp, round(wf.phase * 2**13)) - else: - #TODO: why do we need the length? - if USE_PHASE_OFFSET_INSTRUCTION: - return (wf.key, wf.amp, wf.length) - else: - return (wf.key, round(wf.phase * 2**13), wf.amp, wf.length) - - -class ModulationCommand(object): - """docstring for ModulationCommand""" - - def __init__(self, - instruction, - nco_select, - frequency=0, - phase=0, - length=0): - super(ModulationCommand, self).__init__() - self.instruction = instruction - self.nco_select = nco_select - self.frequency = frequency - self.phase = phase - self.length = length - - def __str__(self): - out = "Modulation({}, nco_select=0x{:x}".format(self.instruction, - self.nco_select) - if self.instruction == "MODULATE": - out += ", length={})".format(self.length) - elif self.instruction == "SET_FREQ": - out += ", frequency={})".format(self.frequency) - elif self.instruction == "SET_PHASE" or self.instruction == "UPDATE_FRAME": - out += ", phase={})".format(self.phase) - else: - out += ")" - return out - - def _repr_pretty_(self, p, cycle): - p.text(str(self)) - - def __repr__(self): - return str(self) - - def to_instruction(self, write_flag=True, label=None): - #Modulator op codes - MODULATOR_OP_OFFSET = 44 - NCO_SELECT_OP_OFFSET = 40 - MODULATION_CLOCK = 300e6 - - op_code_map = {"MODULATE": 0x0, - "RESET_PHASE": 0x2, - "SET_FREQ": 0x6, - "SET_PHASE": 0xa, - "UPDATE_FRAME": 0xe} - payload = (op_code_map[self.instruction] << MODULATOR_OP_OFFSET) | ( - self.nco_select << NCO_SELECT_OP_OFFSET) - if self.instruction == "MODULATE": - #zero-indexed quad count - payload |= np.uint32(self.length / ADDRESS_UNIT - 1) - elif self.instruction == "SET_FREQ": - # frequencies can span -2 to 2 or 0 to 4 in unsigned - payload |= np.uint32( - (self.frequency / MODULATION_CLOCK if self.frequency > 0 else - self.frequency / MODULATION_CLOCK + 4) * 2**28) - elif (self.instruction == "SET_PHASE") | ( - self.instruction == "UPDATE_FRAME"): - #phases can span -0.5 to 0.5 or 0 to 1 in unsigned - payload |= np.uint32(np.mod(self.phase / (2 * np.pi), 1) * 2**28) - - instr = Instruction(MODULATION << 4, payload, label) - instr.writeFlag = write_flag - return instr - -def inject_modulation_cmds(seqs): - """ - Inject modulation commands from phase, frequency and frameChange of waveforms - in an IQ waveform sequence. Assume up to 2 NCOs for now. - """ - cur_freq = 0 - cur_phase = 0 - for ct,seq in enumerate(seqs): - #check whether we have modulation commands - freqs = np.unique([entry.frequency for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]) - if len(freqs) > 2: - raise Exception("Max 2 frequencies on the same channel allowed.") - no_freq_cmds = np.allclose(freqs, 0) - phases = [entry.phase for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_phase_cmds = np.allclose(phases, 0) - frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)] - no_frame_cmds = np.allclose(frame_changes, 0) - no_modulation_cmds = no_freq_cmds and no_phase_cmds and no_frame_cmds - - if no_modulation_cmds: - continue - - mod_seq = [] - pending_frame_update = False - - for entry in seq: - - #copies to avoid same object having different timestamps later - #copy through BlockLabel - if isinstance(entry, BlockLabel.BlockLabel): - mod_seq.append(copy(entry)) - #mostly copy through control-flow - elif isinstance(entry, ControlFlow.ControlInstruction): - #heuristic to insert phase reset before trigger if we have modulation commands - if isinstance(entry, ControlFlow.Wait): - if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): - mod_seq.append(ModulationCommand("RESET_PHASE", 0x3)) - for nco_ind, freq in enumerate(freqs): - mod_seq.append( ModulationCommand("SET_FREQ", nco_ind + 1, frequency = -freq) ) - elif isinstance(entry, ControlFlow.Return): - cur_freq = 0 #makes sure that the frequency is set in the first sequence after the definition of subroutines - mod_seq.append(copy(entry)) - elif isinstance(entry, Compiler.Waveform): - if not no_modulation_cmds: - #select nco - nco_select = (list(freqs)).index(entry.frequency) + 1 - cur_freq = entry.frequency - if USE_PHASE_OFFSET_INSTRUCTION and (entry.length > 0) and (cur_phase != entry.phase): - mod_seq.append( ModulationCommand("SET_PHASE", nco_select, phase=entry.phase) ) - cur_phase = entry.phase - #now apply modulation for count command and waveform command, if non-zero length - if entry.length > 0: - mod_seq.append(entry) - # if we have a modulate waveform modulate pattern and there is no pending frame update we can append length to previous modulation command - if (len(mod_seq) > 1) and (isinstance(mod_seq[-1], Compiler.Waveform)) and (isinstance(mod_seq[-2], ModulationCommand)) and (mod_seq[-2].instruction == "MODULATE") \ - and mod_seq[-1].frequency == freqs[mod_seq[-2].nco_select - 1] and not pending_frame_update: - mod_seq[-2].length += entry.length - else: - mod_seq.append( ModulationCommand("MODULATE", nco_select, length = entry.length)) - pending_frame_update = False - #now apply non-zero frame changes after so it is applied at end - if entry.frameChange != 0: - pending_frame_update = True - #zero length frame changes (Z pulses) need to be combined with the previous frame change or injected where possible - if entry.length == 0: - #if the last is a frame change then we can add to the frame change - if isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "UPDATE_FRAME": - mod_seq[-1].phase += entry.frameChange - #if last entry was pulse without frame change we add frame change - elif (isinstance(mod_seq[-1], Compiler.Waveform)) or (mod_seq[-1].instruction == "MODULATE"): - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #if this is the first entry with a wait for trigger then we can inject a frame change - #before the wait for trigger but after the RESET_PHASE - elif isinstance(mod_seq[-1], ControlFlow.Wait): - mod_seq.insert(-1, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - elif isinstance(mod_seq[-2], ControlFlow.Wait) and isinstance(mod_seq[-1], ModulationCommand) and mod_seq[-1].instruction == "SET_FREQ": - mod_seq.insert(-2, ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - #otherwise drop and error if frame has been defined - else: - raise Exception("Unable to implement zero time Z pulse") - else: - mod_seq.append( ModulationCommand("UPDATE_FRAME", nco_select, phase=entry.frameChange) ) - - seqs[ct] = mod_seq - -def build_waveforms(seqs, shapeLib): - # apply amplitude (and optionally phase) and add the resulting waveforms to the library - wfLib = {} - for wf in flatten(seqs): - if isinstance(wf, Compiler.Waveform) and wf_sig(wf) not in wfLib: - shape = wf.amp * shapeLib[wf.key] - if not USE_PHASE_OFFSET_INSTRUCTION: - shape *= np.exp(1j * wf.phase) - wfLib[wf_sig(wf)] = shape - return wfLib - - -def timestamp_entries(seq): - t = 0 - for ct in range(len(seq)): - seq[ct].startTime = t - t += seq[ct].length - - -def synchronize_clocks(seqs): - # Control-flow instructions (CFIs) must occur at the same time on all channels. - # Therefore, we need to "reset the clock" by synchronizing the accumulated - # time at each CFI to the largest value on any channel - syncInstructions = [list(filter( - lambda s: isinstance(s, ControlFlow.ControlInstruction), seq)) - for seq in seqs if seq] - - # Add length to control-flow instructions to make accumulated time match at end of CFI. - # Keep running tally of how much each channel has been shifted so far. - localShift = [0 for _ in syncInstructions] - for ct in range(len(syncInstructions[0])): - step = [seq[ct] for seq in syncInstructions] - endTime = max((s.startTime + shift - for s, shift in zip(step, localShift))) - for ct, s in enumerate(step): - s.length = endTime - (s.startTime + localShift[ct]) - # localShift[ct] += endTime - (s.startTime + localShift[ct]) - # the += and the last term cancel, therefore: - localShift[ct] = endTime - s.startTime - # re-timestamp to propagate changes across the sequences - for seq in seqs: - timestamp_entries(seq) - # then transfer the control flow "lengths" back into start times - for seq in syncInstructions: - for instr in seq: - instr.startTime += instr.length - instr.length = 0 - - -def create_seq_instructions(seqs, offsets): - ''' - Helper function to create instruction vector from an IR sequence and an offset dictionary - keyed on the wf keys. - - Seqs is a list of lists containing waveform and marker data, e.g. - [wfSeq & modulationSeq, m1Seq, m2Seq, m3Seq, m4Seq] - - We take the strategy of greedily grabbing the next instruction that occurs in time, accross - all waveform and marker channels. - ''' - - # timestamp all entries before filtering (where we lose time information on control flow) - for seq in seqs: - timestamp_entries(seq) - - synchronize_clocks(seqs) - - # create (seq, startTime) pairs over all sequences - timeTuples = [] - for ct, seq in enumerate(seqs): - timeTuples += [(entry.startTime, ct) for entry in seq] - timeTuples.sort() - - # keep track of where we are in each sequence - indexes = np.zeros(len(seqs), dtype=np.int64) - - cmpTable = {'==': EQUAL, '!=': NOTEQUAL, '>': GREATERTHAN, '<': LESSTHAN} - - # always start with SYNC (stealing label from beginning of sequence) - # unless it is a subroutine (using last entry as return as tell) - label = None - instructions = [] - for ct, seq in enumerate(seqs): - if len(seq): - first_non_empty = ct - break - if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): - if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] - timeTuples.pop(0) - indexes[first_non_empty] += 1 - instructions.append(Sync(label=label)) - label = None - - while len(timeTuples) > 0: - #pop off all entries that have the same time - entries = [] - start_time = 0 - while True: - start_time, seq_idx = timeTuples.pop(0) - entries.append((seqs[seq_idx][indexes[seq_idx]], seq_idx)) - indexes[seq_idx] += 1 - next_start_time = timeTuples[0][0] if len(timeTuples) > 0 else -1 - if start_time != next_start_time: - break - - write_flags = [True] * len(entries) - for ct, (entry, seq_idx) in enumerate(entries): - #use first non empty sequence for control flow - if seq_idx == first_non_empty and ( - isinstance(entry, ControlFlow.ControlInstruction) or - isinstance(entry, BlockLabel.BlockLabel)): - if isinstance(entry, BlockLabel.BlockLabel): - # carry label forward to next entry - label = entry - continue - # control flow instructions - elif isinstance(entry, ControlFlow.Wait): - instructions.append(Wait(label=label)) - elif isinstance(entry, ControlFlow.LoadCmp): - instructions.append(LoadCmp(label=label)) - elif isinstance(entry, ControlFlow.Sync): - instructions.append(Sync(label=label)) - elif isinstance(entry, ControlFlow.Return): - instructions.append(Return(label=label)) - # target argument commands - elif isinstance(entry, ControlFlow.Goto): - instructions.append(Goto(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Call): - instructions.append(Call(entry.target, label=label)) - elif isinstance(entry, ControlFlow.Repeat): - instructions.append(Repeat(entry.target, label=label)) - # value argument commands - elif isinstance(entry, ControlFlow.LoadRepeat): - instructions.append(Load(entry.value - 1, label=label)) - elif isinstance(entry, ControlFlow.ComparisonInstruction): - # TODO modify Cmp operator to load from specified address - instructions.append(Cmp(cmpTable[entry.operator], - entry.value, - label=label)) - - continue - - if seq_idx == 0: - #analog - waveforms or modulation - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping Waveform entry of length %s!" % entry.length) - continue - instructions.append(Waveform(offsets[wf_sig(entry)], - entry.length, - entry.isTimeAmp or - entry.isZero, - write=write_flags[ct], - label=label)) - elif isinstance(entry, ModulationCommand): - instructions.append(entry.to_instruction( - write_flag=write_flags[ct], - label=label)) - - else: # a marker engine - if isinstance(entry, Compiler.Waveform): - if entry.length < MIN_ENTRY_LENGTH: - warn("Dropping entry!") - continue - markerSel = seq_idx - 1 - state = not entry.isZero - instructions.append(Marker(markerSel, - state, - entry.length, - write=write_flags[ct], - label=label)) - - #clear label - label = None - - return instructions - - -def create_instr_data(seqs, offsets, cache_lines): - ''' - Constructs the complete instruction data vector, and does basic checks for validity. - - Subroutines will be placed at least 8 cache lines away from sequences and aligned to cache line - ''' - logger = logging.getLogger(__name__) - logger.debug('') - - seq_instrs = [] - need_prefetch = len(cache_lines) > 0 - num_cache_lines = len(set(cache_lines)) - cache_line_changes = np.concatenate( - ([0], np.where(np.diff(cache_lines))[0] + 1)) - for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) - #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line - if need_prefetch and (ct in cache_line_changes): - next_cache_line = cache_lines[cache_line_changes[(np.where( - ct == cache_line_changes)[0][0] + 1) % len( - cache_line_changes)]] - seq_instrs[-1].insert(0, WaveformPrefetch(int( - next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - - #concatenate instructions - instructions = [] - subroutines_start = -1 - for ct, seq in enumerate(seq_instrs): - #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break - instructions += seq - - #if we have any subroutines then group in cache lines - if subroutines_start >= 0: - subroutine_instrs = [] - subroutine_cache_line = {} - CACHE_LINE_LENGTH = 128 - offset = 0 - for sub in seq_instrs[subroutines_start:]: - #TODO for now we don't properly handle prefetching mulitple cache lines - if len(sub) > CACHE_LINE_LENGTH: - warnings.warn( - "Subroutines longer than {} instructions may not be prefetched correctly") - #Don't unecessarily split across a cache line - if (len(sub) + offset > CACHE_LINE_LENGTH) and ( - len(sub) < CACHE_LINE_LENGTH): - pad_instrs = 128 - ((offset + 128) % 128) - subroutine_instrs += [NoOp()] * pad_instrs - offset = 0 - if offset == 0: - line_label = sub[0].label - subroutine_cache_line[sub[0].label] = line_label - subroutine_instrs += sub - offset += len(sub) % CACHE_LINE_LENGTH - logger.debug("Placed {} subroutines into {} cache lines".format( - len(seq_instrs[subroutines_start:]), len(subroutine_instrs) // - CACHE_LINE_LENGTH)) - #inject prefetch commands before waits - wait_idx = [idx for idx, instr in enumerate(instructions) - if (instr.header >> 4) == WAIT] + [len(instructions)] - instructions_with_prefetch = instructions[:wait_idx[0]] - last_prefetch = None - for start, stop in zip(wait_idx[:-1], wait_idx[1:]): - call_targets = [instr.target for instr in instructions[start:stop] - if (instr.header >> 4) == CALL] - needed_lines = set() - for target in call_targets: - needed_lines.add(subroutine_cache_line[target]) - if len(needed_lines) > 8: - raise RuntimeError( - "Unable to prefetch more than 8 cache lines") - for needed_line in needed_lines: - if needed_line != last_prefetch: - instructions_with_prefetch.append(Prefetch(needed_line)) - last_prefetch = needed_line - instructions_with_prefetch += instructions[start:stop] - - instructions = instructions_with_prefetch - #pad out instruction vector to ensure circular cache never loads a subroutine - pad_instrs = 7 * 128 + (128 - ((len(instructions) + 128) % 128)) - instructions += [NoOp()] * pad_instrs - - instructions += subroutine_instrs - - #turn symbols into integers addresses - resolve_symbols(instructions) - - assert len(instructions) < MAX_NUM_INSTRUCTIONS, \ - 'Oops! too many instructions: {0}'.format(len(instructions)) - - return np.fromiter((instr.flatten() for instr in instructions), np.uint64, - len(instructions)) - - -def resolve_symbols(seq): - symbols = {} - # create symbol look-up table - for ct, entry in enumerate(seq): - if entry.label and entry.label not in symbols: - symbols[entry.label] = ct - # then update - for entry in seq: - if entry.target: - entry.address = symbols[entry.target] - - -def compress_marker(markerLL): - ''' - Compresses adjacent entries of the same state into single entries - ''' - for seq in markerLL: - idx = 0 - while idx + 1 < len(seq): - if (isinstance(seq[idx], Compiler.Waveform) and - isinstance(seq[idx + 1], Compiler.Waveform) and - seq[idx].isZero == seq[idx + 1].isZero): - - seq[idx].length += seq[idx + 1].length - del seq[idx + 1] - else: - idx += 1 - - -def write_sequence_file(awgData, fileName): - ''' - Main function to pack channel sequences into an APS2 h5 file. - ''' - # Convert QGL IR into a representation that is closer to the hardware. - awgData['ch12']['linkList'], wfLib = preprocess( - awgData['ch12']['linkList'], awgData['ch12']['wfLib']) - - # compress marker data - for field in ['ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']: - if 'linkList' in awgData[field].keys(): - PatternUtils.convert_lengths_to_samples(awgData[field]['linkList'], - SAMPLING_RATE, 1, - Compiler.Waveform) - compress_marker(awgData[field]['linkList']) - else: - awgData[field]['linkList'] = [] - - #Create the waveform vectors - wfInfo = [] - wfInfo.append(create_wf_vector({key: wf.real - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - wfInfo.append(create_wf_vector({key: wf.imag - for key, wf in wfLib.items()}, awgData[ - 'ch12']['linkList'])) - - if SAVE_WF_OFFSETS: - #create a set of all waveform signatures in offset dictionaries - #we could have multiple offsets for the same pulse becuase it could - #be repeated in multiple cache lines - wf_sigs = set() - for offset_dict in wfInfo[0][1]: - wf_sigs |= set(offset_dict.keys()) - #create dictionary linking entry labels (that's what we'll have later) with offsets - offsets = {} - for seq in awgData['ch12']['linkList']: - for entry in seq: - if len(wf_sigs) == 0: - break - if isinstance(entry, Compiler.Waveform): - sig = wf_sig(entry) - if sig in wf_sigs: - #store offsets and wavefor lib length - #time ampltidue entries are clamped to ADDRESS_UNIT - wf_length = ADDRESS_UNIT if entry.isTimeAmp else entry.length - offsets[entry.label] = ([_[sig] for _ in wfInfo[0][1]], - wf_length) - wf_sigs.discard(sig) - - #break out of outer loop too - if len(wf_sigs) == 0: - break - - #now pickle the label=>offsets - with open(os.path.splitext(fileName)[0] + ".offsets", "wb") as FID: - pickle.dump(offsets, FID) - - # build instruction vector - seq_data = [awgData[s]['linkList'] - for s in ['ch12', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4']] - instructions = create_instr_data(seq_data, wfInfo[0][1], wfInfo[0][2]) - - #Open the HDF5 file - if os.path.isfile(fileName): - os.remove(fileName) - with h5py.File(fileName, 'w') as FID: - FID['/'].attrs['Version'] = 4.0 - FID['/'].attrs['target hardware'] = 'APS2' - FID['/'].attrs['minimum firmware version'] = 4.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) - - #Create the groups and datasets - for chanct in range(2): - chanStr = '/chan_{0}'.format(chanct + 1) - chanGroup = FID.create_group(chanStr) - #Write the waveformLib to file - FID.create_dataset(chanStr + '/waveforms', data=wfInfo[chanct][0]) - - #Write the instructions to channel 1 - if np.mod(chanct, 2) == 0: - FID.create_dataset(chanStr + '/instructions', - data=instructions) - - -def read_sequence_file(fileName): - """ - Reads a HDF5 sequence file and returns a dictionary of lists. - Dictionary keys are channel strings such as ch1, ch12m1 - Lists are or tuples of time-amplitude pairs (time, output) - """ - chanStrs = ['ch1', 'ch2', 'ch12m1', 'ch12m2', 'ch12m3', 'ch12m4', - 'mod_phase'] - seqs = {ch: [] for ch in chanStrs} - - def start_new_seq(): - for ct, ch in enumerate(chanStrs): - if (ct < 2) or (ct == 6): - #analog or modulation channel - seqs[ch].append([]) - else: - #marker channel - seqs[ch].append([]) - - with h5py.File(fileName, 'r') as FID: - file_version = FID["/"].attrs["Version"] - wf_lib = {} - wf_lib['ch1'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_1/waveforms'].value.flatten() - wf_lib['ch2'] = ( - 1.0 / - MAX_WAVEFORM_VALUE) * FID['/chan_2/waveforms'].value.flatten() - instructions = FID['/chan_1/instructions'].value.flatten() - NUM_NCO = 2 - freq = np.zeros(NUM_NCO) #radians per timestep - phase = np.zeros(NUM_NCO) - frame = np.zeros(NUM_NCO) - next_freq = np.zeros(NUM_NCO) - next_phase = np.zeros(NUM_NCO) - next_frame = np.zeros(NUM_NCO) - accumulated_phase = np.zeros(NUM_NCO) - reset_flag = [False]*NUM_NCO - - for data in instructions: - instr = Instruction.unflatten(data) - - modulator_opcode = instr.payload >> 44 - - #update phases at these boundaries - if (instr.opcode == WAIT) | (instr.opcode == SYNC) | ( - (instr.opcode) == MODULATION and (modulator_opcode == 0x0)): - for ct in range(NUM_NCO): - if reset_flag[ct]: - #would expect this to be zero but this is first non-zero point - accumulated_phase[ct] = next_freq[ct] * ADDRESS_UNIT - reset_flag[ct] = False - freq[:] = next_freq[:] - phase[:] = next_phase[:] - frame[:] = next_frame[:] - - #Assume new sequence at every WAIT - if instr.opcode == WAIT: - start_new_seq() - - elif instr.opcode == WFM and (( - (instr.payload >> WFM_OP_OFFSET) & 0x3) == PLAY): - addr = (instr.payload & 0x00ffffff) * ADDRESS_UNIT - count = (instr.payload >> 24) & 0xfffff - count = (count + 1) * ADDRESS_UNIT - isTA = (instr.payload >> 45) & 0x1 - chan_select_bits = ((instr.header >> 2) & 0x1, - (instr.header >> 3) & 0x1) - #On older firmware we broadcast by default whereas on newer we respect the engine select - for chan, select_bit in zip(('ch1', 'ch2'), chan_select_bits): - if (file_version < 4) or select_bit: - if isTA: - seqs[chan][-1].append((count, wf_lib[chan][addr])) - else: - for sample in wf_lib[chan][addr:addr + count]: - seqs[chan][-1].append((1, sample)) - - elif instr.opcode == MARKER: - chan = 'ch12m' + str(((instr.header >> 2) & 0x3) + 1) - count = instr.payload & 0xffffffff - count = (count + 1) * ADDRESS_UNIT - state = (instr.payload >> 32) & 0x1 - seqs[chan][-1].append((count, state)) - - elif instr.opcode == MODULATION: - # modulator_op_code_map = {"MODULATE":0x0, "RESET_PHASE":0x2, "SET_FREQ":0x6, "SET_PHASE":0xa, "UPDATE_FRAME":0xe} - nco_select_bits = (instr.payload >> 40) & 0xf - if modulator_opcode == 0x0: - #modulate - count = ((instr.payload & 0xffffffff) + 1) * ADDRESS_UNIT - nco_select = {0b0001: 0, - 0b0010: 1, - 0b0100: 2, - 0b1000: 3}[nco_select_bits] - seqs['mod_phase'][-1] = np.append( - seqs['mod_phase'][-1], freq[nco_select] * - np.arange(count) + accumulated_phase[nco_select] + - phase[nco_select] + frame[nco_select]) - accumulated_phase += count * freq - else: - phase_rad = 2 * np.pi * (instr.payload & - 0xffffffff) / 2**28 - for ct in range(NUM_NCO): - if (nco_select_bits >> ct) & 0x1: - if modulator_opcode == 0x2: - #reset - next_phase[ct] = 0 - next_frame[ct] = 0 - reset_flag[ct] = True - elif modulator_opcode == 0x6: - #set frequency - next_freq[ct] = phase_rad / ADDRESS_UNIT - elif modulator_opcode == 0xa: - #set phase - next_phase[ct] = phase_rad - elif modulator_opcode == 0xe: - #update frame - next_frame[ct] += phase_rad - - #Apply modulation if we have any - for ct, ( - ch1, ch2, mod_phase - ) in enumerate(zip(seqs['ch1'], seqs['ch2'], seqs['mod_phase'])): - if len(mod_phase): - #only really works if ch1, ch2 are broadcast together - mod_ch1 = [] - mod_ch2 = [] - cum_time = 0 - for ((time_ch1, amp_ch1), - (time_ch2, amp_ch2)) in zip(ch1, ch2): - if (amp_ch1 != 0) or (amp_ch2 != 0): - assert time_ch1 == time_ch2 - if time_ch1 == 1: - #single timestep - modulated = np.exp(1j * mod_phase[cum_time]) * ( - amp_ch1 + 1j * amp_ch2) - mod_ch1.append((1, modulated.real)) - mod_ch2.append((1, modulated.imag)) - else: - #expand TA - modulated = np.exp( - 1j * - mod_phase[cum_time:cum_time + time_ch1]) * ( - amp_ch1 + 1j * amp_ch2) - for val in modulated: - mod_ch1.append((1, val.real)) - mod_ch2.append((1, val.imag)) - else: - mod_ch1.append((time_ch1, amp_ch1)) - mod_ch2.append((time_ch2, amp_ch2)) - - cum_time += time_ch1 - seqs['ch1'][ct] = mod_ch1 - seqs['ch2'][ct] = mod_ch2 - - del seqs['mod_phase'] - - return seqs - - -def update_wf_library(filename, pulses, offsets): - """ - Update a H5 waveform library in place give an iterable of (pulseName, pulse) - tuples and offsets into the waveform library. - """ - assert USE_PHASE_OFFSET_INSTRUCTION == False - #load the h5 file - with h5py.File(filename) as FID: - for label, pulse in pulses.items(): - #create a new waveform - if pulse.isTimeAmp: - shape = np.repeat(pulse.amp * np.exp(1j * pulse.phase), 4) - else: - shape = pulse.amp * np.exp(1j * pulse.phase) * pulse.shape - try: - length = offsets[label][1] - except KeyError: - print("\t{} not found in offsets so skipping".format(pulse)) - continue - for offset in offsets[label][0]: - print("\tUpdating {} at offset {}".format(pulse, offset)) - FID['/chan_1/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.real) - FID['/chan_2/waveforms'][offset:offset + length] = np.int16( - MAX_WAVEFORM_VALUE * shape.imag) From 8cd21391b927127a1535b9cc1bbfe7e5f7de55bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 8 Jun 2018 09:13:12 -0400 Subject: [PATCH 46/67] Restore notation for single-trigger loadcmp Temporary fix for tests. Maybe it makes more sense to change all to EXT, for better distinction with RAM --- QGL/ControlFlow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 48909e97..4f1219ab 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -90,7 +90,7 @@ def qwait(kind="TRIG", addr=None, mask=None, channels=None): ''' if kind == "TRIG": return Wait(channels) - elif kind == "EXT": + elif kind == "CMP": return LoadCmp(channels) elif kind == "RAM": if not addr or not mask: From 835236e559d03cd3865449f936a65740be6f2296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 8 Jun 2018 14:27:27 -0400 Subject: [PATCH 47/67] Commutative property for pulse and blocks Previously, Pulse*PulseBlock was different from PulseBlock*Pulse --- QGL/PulseSequencer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/QGL/PulseSequencer.py b/QGL/PulseSequencer.py index f5e5f3cf..0d9e78d0 100644 --- a/QGL/PulseSequencer.py +++ b/QGL/PulseSequencer.py @@ -221,7 +221,8 @@ def __mul__(self, rhs): return rhs * self # otherwise, we are promoting to a PulseBlock rhs = rhs.promote(ptype) - + if not np.isclose(self.length, rhs.length, atol=1e-10): + return align('center', self, rhs) # copy PulseBlock so we don't modify other references result = copy(self) result.pulses = copy(self.pulses) From d4141d862ef65a3f8a358a2999f0ffb45cbea100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 8 Jun 2018 14:28:02 -0400 Subject: [PATCH 48/67] Moved LoadCmpVram to qwait --- QGL/TdmInstructions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index bf9bfb47..4e0f5bd6 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -111,6 +111,3 @@ def __ne__(self, other): # def LoadCmpVram(addr, mask): # return LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask) - -def LoadCmpVram(addr, mask, channel=None, tdm=False): # this should be for APS2 only - return [WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask, tdm)] From 905c61794e8d0161388f66c6eac965a21a222853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 11 Jun 2018 09:24:45 -0400 Subject: [PATCH 49/67] Example of bit flip code --- QGL/BasicSequences/Feedback.py | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index 79db9709..e79a270e 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -5,6 +5,7 @@ from itertools import product import operator from ..ControlFlow import * +from ..TdmInstructions import * from functools import reduce @@ -99,3 +100,58 @@ def Reset(qubits, if showPlot: plot_pulse_files(metafile) return metafile + + +# do not make it a subroutine for now +def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e-6): + """ + + Encoding on 3-qubit bit-flip code, followed by n rounds of syndrome detection, and final correction using the n results. + + Parameters + ---------- + data_qs : tuple of logical channels for the 3 code qubits + ancilla_qs: tuple of logical channels for the 2 syndrome qubits + theta, phi: longitudinal and azimuthal rotation angles for encoded state (default = no encoding) + meas_delay : delay between syndrome check rounds + docals, calRepeats: enable calibration sequences, repeated calRepeats times + + Returns + ------- + plotHandle : handle to plot window to prevent destruction + """ + if len(data_qs) != 3 or len(ancilla_qs) != 2: + raise Exception("Wrong number of qubits") + seqs = [ + DecodeSetRounds(1,0,2**nrounds-1), + Invalidate(addr=10, mask=2**nrounds-1), + Invalidate(addr=11, mask=0x1)] + + # encode single-qubit state into 3 qubits + if theta and phi: + seqs+=[Utheta(data_qs[1], theta, phi), CNOT(data_qs[1], data_qs[0]), CNOT(data_qs[1], data_qs[2])] + + # multiple rounds of syndrome measurements + for k in range(nrounds): + seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])], + seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])], + seqs+= [MEASA(ancilla_qs[0], maddr=(10,2*k+1))*MEASA(ancilla_qs[1], maddr=(10,2*k+2)), + Id(ancilla_qs[0], meas_delay)* + MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels + seqs+=Decode(10, 11, 2**nrounds-1), + seqs+=qwait("RAM",11, 1) + seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2]) + *MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's just to keep the number + + # apply corrective pulses depending on the decoder result + FbGates = [] + for q in data_qs: + FbGates.append([gate(q) for gate in [Id, X]]) + FbSeq = [reduce(operator.mul, x) for x in product(*FbGates)] + for k in range(8): + seqs += qif(k, [FbSeq[k]]) + if docals: + seqs += create_cal_seqs(qubits, + calRepeats, + measChans=measChans) + return seqs From c7767678ed6aad5d5f7fc8a7319817c41b47e443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 11 Jun 2018 11:24:06 -0400 Subject: [PATCH 50/67] Create empty data for TDM --- QGL/BasicSequences/Feedback.py | 2 +- QGL/drivers/APS2Pattern.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index e79a270e..36ae11b8 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -138,7 +138,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e seqs+= [MEASA(ancilla_qs[0], maddr=(10,2*k+1))*MEASA(ancilla_qs[1], maddr=(10,2*k+2)), Id(ancilla_qs[0], meas_delay)* MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels - seqs+=Decode(10, 11, 2**nrounds-1), + seqs+=Decode(10, 11, 2**nrounds-1) seqs+=qwait("RAM",11, 1) seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2]) *MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's just to keep the number diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 2c394418..7ee1f6f1 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1429,12 +1429,16 @@ def write_tdm_seq(seq, tdm_fileName): FID['/'].attrs['Version'] = 5.0 FID['/'].attrs['target hardware'] = 'TDM' FID['/'].attrs['minimum firmware version'] = 5.0 - FID['/'].attrs['channelDataFor'] = np.uint16([1]) + FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) #Create the groups and datasets - chanStr = '/chan_{0}'.format(1) - chanGroup = FID.create_group(chanStr) - FID.create_dataset(chanStr + '/instructions', data=seq) + for chanct in range(2): + chanStr = '/chan_{0}'.format(chanct + 1) + chanGroup = FID.create_group(chanStr) + FID.create_dataset(chanStr + '/waveforms', data=np.uint16([])) + #Write the instructions to channel 1 + if np.mod(chanct, 2) == 0: + FID.create_dataset(chanStr + '/instructions', data=seq) # Utility Functions for displaying programs From 126a62506e0a7bfe3ffd0e3aa290f6378d5537f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 11 Jun 2018 16:15:05 -0400 Subject: [PATCH 51/67] TDM is APS2 type --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 7ee1f6f1..618c3915 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1427,7 +1427,7 @@ def write_tdm_seq(seq, tdm_fileName): os.remove(tdm_fileName) with h5py.File(tdm_fileName, 'w') as FID: FID['/'].attrs['Version'] = 5.0 - FID['/'].attrs['target hardware'] = 'TDM' + FID['/'].attrs['target hardware'] = 'APS2' FID['/'].attrs['minimum firmware version'] = 5.0 FID['/'].attrs['channelDataFor'] = np.uint16([1, 2]) From 357445e43569d41cb79782690a865a4a34ac3e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 11 Jun 2018 18:01:25 -0400 Subject: [PATCH 52/67] One more heuristic for modulation Including the new LoadCmpVramInstruction for APS2 --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 618c3915..7fdacc86 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -639,7 +639,7 @@ def inject_modulation_cmds(seqs): if isinstance(entry, BlockLabel.BlockLabel): mod_seq.append(copy(entry)) #mostly copy through control-flow - elif isinstance(entry, ControlFlow.ControlInstruction): + elif isinstance(entry, ControlFlow.ControlInstruction) or isinstance(entry, TdmInstructions.LoadCmpVramInstruction): #heuristic to insert phase reset before trigger if we have modulation commands if isinstance(entry, ControlFlow.Wait): if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): From 7bedf9be3e8e4fd887184b6cc52c903d5ff9c115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 12 Jun 2018 09:55:13 -0400 Subject: [PATCH 53/67] Fix typo --- QGL/BasicSequences/Feedback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index 36ae11b8..27d0d47d 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -136,7 +136,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])], seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])], seqs+= [MEASA(ancilla_qs[0], maddr=(10,2*k+1))*MEASA(ancilla_qs[1], maddr=(10,2*k+2)), - Id(ancilla_qs[0], meas_delay)* + Id(ancilla_qs[0], meas_delay), MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels seqs+=Decode(10, 11, 2**nrounds-1) seqs+=qwait("RAM",11, 1) From bfa2555b8cb04aa6f2457e6bdb585f1a9b17b9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 12 Jun 2018 11:44:55 -0400 Subject: [PATCH 54/67] Invalidate also needs to pass through --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 7fdacc86..c3249971 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -639,7 +639,7 @@ def inject_modulation_cmds(seqs): if isinstance(entry, BlockLabel.BlockLabel): mod_seq.append(copy(entry)) #mostly copy through control-flow - elif isinstance(entry, ControlFlow.ControlInstruction) or isinstance(entry, TdmInstructions.LoadCmpVramInstruction): + elif isinstance(entry, ControlFlow.ControlInstruction) or isinstance(entry, TdmInstructions.LoadCmpVramInstruction) or isinstance(entry, TdmInstructions.WriteAddrInstruction): #heuristic to insert phase reset before trigger if we have modulation commands if isinstance(entry, ControlFlow.Wait): if not ( no_modulation_cmds and (cur_freq == 0) and (cur_phase == 0)): From eba3c34fe5fad942a4e6e11006161ce380d42135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 12 Jun 2018 14:20:11 -0400 Subject: [PATCH 55/67] Fix CrossBar for sim. msm'ts fix address --- QGL/drivers/APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index c3249971..b4955774 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1399,7 +1399,7 @@ def tdm_instructions(seqs): if len(set(maddr))>1: raise Exception('Storing simultaneous measurements on different addresses not supported.') for n,m in enumerate(sim_meas): - instructions.append(CrossBar(2**n, 2**n)) + instructions.append(CrossBar(m.maddr[1], 2**n)) instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(maddr[0], 1 << 16)) From ea4e701b6f2141ddc0e9a66a137a3a9d0b3c3c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 12 Jun 2018 14:58:55 -0400 Subject: [PATCH 56/67] Remove second Wait There should already be a WAIT before compiling to TDM. It shouldn't be necessary to move it after the initial invalidate, set_mask etc., but to be tested --- QGL/BasicSequences/Feedback.py | 6 +++--- QGL/drivers/APS2Pattern.py | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index 27d0d47d..71397a96 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -123,8 +123,8 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e if len(data_qs) != 3 or len(ancilla_qs) != 2: raise Exception("Wrong number of qubits") seqs = [ - DecodeSetRounds(1,0,2**nrounds-1), - Invalidate(addr=10, mask=2**nrounds-1), + DecodeSetRounds(1,0,nrounds), + Invalidate(addr=10, mask=4**nrounds-1), Invalidate(addr=11, mask=0x1)] # encode single-qubit state into 3 qubits @@ -138,7 +138,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e seqs+= [MEASA(ancilla_qs[0], maddr=(10,2*k+1))*MEASA(ancilla_qs[1], maddr=(10,2*k+2)), Id(ancilla_qs[0], meas_delay), MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels - seqs+=Decode(10, 11, 2**nrounds-1) + seqs+=Decode(10, 11, 4**nrounds-1) seqs+=qwait("RAM",11, 1) seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2]) *MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's just to keep the number diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index b4955774..500d6e3c 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1304,11 +1304,6 @@ def tdm_instructions(seqs): #add sync at the beginning of the sequence. FIXME: for now, ignore subroutines. Assume that the first entry is a label instructions.append(Sync(label=label)) - # add a WAIT before the first waveform FIXME: there must be a more efficient way - if ControlFlow.Wait not in seq: - ind_wait = min([ind for ind,s in enumerate(seq) if isinstance(s,PulseSequencer.Pulse) or isinstance(s,PulseSequencer.CompositePulse) or isinstance(s,PulseSequencer.CompoundGate) or isinstance(s,PulseSequencer.PulseBlock)]) - seq.insert(ind_wait, ControlFlow.Wait()) - label = None for s in seq: From 2cec3983edc15e8d0e91fa234597749691d0ac74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 13 Jun 2018 08:52:51 -0400 Subject: [PATCH 57/67] Fix maddr in qec3q --- QGL/BasicSequences/Feedback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index 71397a96..ef7c4499 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -135,7 +135,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e for k in range(nrounds): seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])], seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])], - seqs+= [MEASA(ancilla_qs[0], maddr=(10,2*k+1))*MEASA(ancilla_qs[1], maddr=(10,2*k+2)), + seqs+= [MEASA(ancilla_qs[0], maddr=(10,2**(2*n))*MEASA(ancilla_qs[1], maddr=(10,2**(2*n+1))), Id(ancilla_qs[0], meas_delay), MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels seqs+=Decode(10, 11, 4**nrounds-1) From de5b08e3000f55d1731e601a20730c64a3bee344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 13 Jun 2018 09:25:47 -0400 Subject: [PATCH 58/67] Mask not needed when APS2 loads from RAM --- QGL/BasicSequences/Feedback.py | 7 +++---- QGL/ControlFlow.py | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index ef7c4499..e713a76e 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -135,13 +135,12 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e for k in range(nrounds): seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])], seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])], - seqs+= [MEASA(ancilla_qs[0], maddr=(10,2**(2*n))*MEASA(ancilla_qs[1], maddr=(10,2**(2*n+1))), + seqs+= [MEASA(ancilla_qs[0], maddr=(10,2**(2*n)))*MEASA(ancilla_qs[1], maddr=(10,2**(2*n+1))), Id(ancilla_qs[0], meas_delay), MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels seqs+=Decode(10, 11, 4**nrounds-1) - seqs+=qwait("RAM",11, 1) - seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2]) - *MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's just to keep the number + seqs+=qwait("RAM",11, 4**nrounds-1) + seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2])] # apply corrective pulses depending on the decoder result FbGates = [] diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 4f1219ab..9c78ba2b 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -84,7 +84,7 @@ def repeatall(n, seqs): return seqs -def qwait(kind="TRIG", addr=None, mask=None, channels=None): +def qwait(kind="TRIG", addr=None, channels=None): ''' Insert a WAIT or LOADCMP command on the target channels (an iterable, or None) ''' @@ -93,9 +93,9 @@ def qwait(kind="TRIG", addr=None, mask=None, channels=None): elif kind == "CMP": return LoadCmp(channels) elif kind == "RAM": - if not addr or not mask: - raise Exception('Please specify address and mask') - return [WriteAddrInstruction('INVALIDATE', None, 1, addr, mask, False), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, mask, False)] + if not addr: + raise Exception('Please specify address') + return [WriteAddrInstruction('INVALIDATE', None, 1, addr, 0xffffffff, False), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, 0xff, False)] From 1ea664a405fe71c136c69332a123e0b397601c0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Wed, 13 Jun 2018 14:58:39 -0400 Subject: [PATCH 59/67] Final goto already in the tdm seq --- QGL/drivers/APS2Pattern.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 500d6e3c..68b31487 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1295,9 +1295,7 @@ def tdm_instructions(seqs): """ instructions = list() label2addr = dict() # the backpatch table for labels - # turn into a loop, by appending GOTO(0) at end of the sequence - seqs[-1].append(ControlFlow.Goto(BlockLabel.label(seqs[0]))) # BlockLabel.label adds a label at the beginning of the sequence - logger.debug("Appending a GOTO at end to loop") + label = seqs[0][0] for seq in seqs: seq = list(flatten(copy(seq))) From 7a5c7f408502f71cb7958767cc8a4c7de2e3fa8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Thu, 14 Jun 2018 20:15:34 -0400 Subject: [PATCH 60/67] Allow a sequence to end in a branch Carry label over to next sequence --- QGL/drivers/APS2Pattern.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 68b31487..1595ef0d 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -741,7 +741,7 @@ def synchronize_clocks(seqs): instr.length = 0 -def create_seq_instructions(seqs, offsets): +def create_seq_instructions(seqs, offsets, label = None): ''' Helper function to create instruction vector from an IR sequence and an offset dictionary keyed on the wf keys. @@ -770,7 +770,6 @@ def create_seq_instructions(seqs, offsets): # always start with SYNC (stealing label from beginning of sequence) # unless it is a subroutine (using last entry as return as tell) - label = None instructions = [] for ct, seq in enumerate(seqs): if len(seq): @@ -778,7 +777,8 @@ def create_seq_instructions(seqs, offsets): break if not isinstance(seqs[first_non_empty][-1], ControlFlow.Return): if isinstance(seqs[first_non_empty][0], BlockLabel.BlockLabel): - label = seqs[first_non_empty][0] + if not label: + label = seqs[first_non_empty][0] timeTuples.pop(0) indexes[first_non_empty] += 1 instructions.append(Sync(label=label)) @@ -875,9 +875,10 @@ def create_seq_instructions(seqs, offsets): label=label)) #clear label - label = None + if len(timeTuples)>0: + label = None - return instructions + return instructions, label def create_instr_data(seqs, offsets, cache_lines): ''' @@ -893,10 +894,11 @@ def create_instr_data(seqs, offsets, cache_lines): num_cache_lines = len(set(cache_lines)) cache_line_changes = np.concatenate( ([0], np.where(np.diff(cache_lines))[0] + 1)) + label = None for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - seq_instrs.append(create_seq_instructions( list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0])) + if need_prefetch else offsets[0], label = label) + seq_instrs.append(new_instrs) #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line if need_prefetch and (ct in cache_line_changes): next_cache_line = cache_lines[cache_line_changes[(np.where( @@ -904,10 +906,10 @@ def create_instr_data(seqs, offsets, cache_lines): cache_line_changes)]] seq_instrs[-1].insert(0, WaveformPrefetch(int( next_cache_line * WAVEFORM_CACHE_SIZE / 2))) - #steal label - seq_instrs[-1][0].label = seq_instrs[-1][1].label - seq_instrs[-1][1].label = None - + #steal label if necessary + if not seq_instrs[-1][0].label: + seq_instrs[-1][0].label = seq_instrs[-1][1].label + seq_instrs[-1][1].label = None #concatenate instructions instructions = [] subroutines_start = -1 From dc7f5da1666d6b9c212ea8c2b3358b72c3946207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 15 Jun 2018 10:10:24 -0400 Subject: [PATCH 61/67] Forgot pieces behind create_seq_instructions returns new instructions and label --- QGL/drivers/APS2Pattern.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 1595ef0d..283570cc 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -896,8 +896,8 @@ def create_instr_data(seqs, offsets, cache_lines): ([0], np.where(np.diff(cache_lines))[0] + 1)) label = None for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])): - list(seq), offsets[cache_lines[ct]] - if need_prefetch else offsets[0], label = label) + new_instrs, label = create_seq_instructions(list(seq), offsets[cache_lines[ct]] + if need_prefetch else offsets[0], label = label) seq_instrs.append(new_instrs) #if we need wf prefetching and have moved waveform cache lines then inject prefetch for the next line if need_prefetch and (ct in cache_line_changes): @@ -915,9 +915,12 @@ def create_instr_data(seqs, offsets, cache_lines): subroutines_start = -1 for ct, seq in enumerate(seq_instrs): #Use last instruction being return as mark of start of subroutines - if (seq[-1].header >> 4) == RET: - subroutines_start = ct - break + try: + if (seq[-1].header >> 4) == RET: + subroutines_start = ct + break + except: + pass instructions += seq #if we have any subroutines then group in cache lines From 89f4947a1d0743eee5c24a9a5c11a0e64301a5b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 15 Jun 2018 10:46:34 -0400 Subject: [PATCH 62/67] Fix APS2Pattern test --- tests/test_APS2Pattern.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_APS2Pattern.py b/tests/test_APS2Pattern.py index 60320249..636d7843 100644 --- a/tests/test_APS2Pattern.py +++ b/tests/test_APS2Pattern.py @@ -35,7 +35,7 @@ def test_synchronize_control_flow(self): offsets = {APS2Pattern.wf_sig(pulse): 0} instructions = APS2Pattern.create_seq_instructions( - [seq_1, [], seq_2, [], [], []], offsets) + [seq_1, [], seq_2, [], [], []], offsets)[0] instr_types = [ APS2Pattern.SYNC, APS2Pattern.WAIT, APS2Pattern.WFM, From 24175c11e3cb6b8cabf3443ef1c697be29c519ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Fri, 15 Jun 2018 15:22:40 -0400 Subject: [PATCH 63/67] Maj. vote example --- QGL/BasicSequences/Feedback.py | 47 +++++++++++++++++++++++++++++----- QGL/BasicSequences/__init__.py | 2 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index e713a76e..1f54e719 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -103,7 +103,7 @@ def Reset(qubits, # do not make it a subroutine for now -def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e-6): +def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e-6, docals=False, calRepeats=2): """ Encoding on 3-qubit bit-flip code, followed by n rounds of syndrome detection, and final correction using the n results. @@ -118,7 +118,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e Returns ------- - plotHandle : handle to plot window to prevent destruction + metafile : metafile path """ if len(data_qs) != 3 or len(ancilla_qs) != 2: raise Exception("Wrong number of qubits") @@ -140,7 +140,8 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels seqs+=Decode(10, 11, 4**nrounds-1) seqs+=qwait("RAM",11, 4**nrounds-1) - seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2])] + seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2])* + MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's # apply corrective pulses depending on the decoder result FbGates = [] @@ -151,6 +152,40 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e seqs += qif(k, [FbSeq[k]]) if docals: seqs += create_cal_seqs(qubits, - calRepeats, - measChans=measChans) - return seqs + calRepeats) + metafile = compile_to_hardware(seqs, 'BitFlip/BitFlip') + return metafile + +def MajorityVoteN(qubits, nrounds, prep=[], meas_delay=1e-6, docals=False, calRepeats=2): + """ + Majority vote across multiple measurement results (same or different qubits) + + Parameters + ---------- + qubits : tuple of logical channels + nrounds: number of consecutive measurements + meas_delay : delay between measurements + docals, calRepeats: enable calibration sequences, repeated calRepeats times + + Returns + ------- + metafile : metafile path + """ + nqubits = len(qubits) + seqs = [MajorityMask(0,1,2**(nrounds*nqubits)-1), + Invalidate(addr=10, mask=2**(nrounds*nqubits)-1), + Invalidate(addr=11, mask=1)] + if prep: + seqs += [reduce(operator.mul, [X(q) for n,q in enumerate(qubits) if prep[n]])] + for n in range(nrounds): + seqs += [reduce(operator.mul, [MEASA(q, (10, 2**(nqubits*n+m))) for m,q in enumerate(qubits)]), Id(qubits[0],meas_delay)] + seqs+=MajorityVote(10,11,2**(nrounds*nqubits)-1) + seqs+=qwait("RAM", 11) + seqs+=[Id(qubits[0],100e-9)] + seqs+=qif(1,[X(qubits[0])]) # placeholder for any conditional operation + seqs=[seqs] + if docals: + seqs += create_cal_seqs(qubits, + calRepeats) + metafile = compile_to_hardware(seqs, 'MajorityVote/MajorityVote') + return metafile diff --git a/QGL/BasicSequences/__init__.py b/QGL/BasicSequences/__init__.py index 720e7efc..d05862f9 100644 --- a/QGL/BasicSequences/__init__.py +++ b/QGL/BasicSequences/__init__.py @@ -7,4 +7,4 @@ from .helpers import create_cal_seqs, delay_descriptor, cal_descriptor from .CR import EchoCRPhase, EchoCRLen, EchoCRAmp, PiRabi from .AllXY import AllXY -from .Feedback import Reset +from .Feedback import Reset, BitFlip3, MajorityVoteN From efcf427d2255c2313fe79a6d1c9dc1c127948b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Mon, 18 Jun 2018 14:57:55 -0400 Subject: [PATCH 64/67] Fix measChan for cal seqs --- QGL/BasicSequences/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QGL/BasicSequences/helpers.py b/QGL/BasicSequences/helpers.py index 572c250d..7e5aad35 100644 --- a/QGL/BasicSequences/helpers.py +++ b/QGL/BasicSequences/helpers.py @@ -27,7 +27,7 @@ def create_cal_seqs(qubits, numRepeats, measChans=None, waitcmp=False, delay=Non for _ in range(numRepeats)] #Add on the measurement operator. - measBlock = reduce(operator.mul, [MEAS(q) for q in qubits]) + measBlock = reduce(operator.mul, [MEAS(q) for q in measChans]) #Add optional delay full_cal_seqs = [[seq, Id(qubits[0], delay), measBlock] if delay else [seq, measBlock] for seq in cal_seqs] if waitcmp: From e17f6cf362f8a79714d328a34197fd3a34d73bb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 3 Jul 2018 10:37:50 -0400 Subject: [PATCH 65/67] Fix index convention Count from 0, not in powers of 2 --- QGL/BasicSequences/Feedback.py | 22 +++++++++++----------- QGL/TdmInstructions.py | 12 ++++++------ QGL/drivers/APS2Pattern.py | 4 +--- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/QGL/BasicSequences/Feedback.py b/QGL/BasicSequences/Feedback.py index 1f54e719..b7833556 100644 --- a/QGL/BasicSequences/Feedback.py +++ b/QGL/BasicSequences/Feedback.py @@ -124,7 +124,7 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e raise Exception("Wrong number of qubits") seqs = [ DecodeSetRounds(1,0,nrounds), - Invalidate(addr=10, mask=4**nrounds-1), + Invalidate(addr=10, mask=2*nrounds), Invalidate(addr=11, mask=0x1)] # encode single-qubit state into 3 qubits @@ -132,16 +132,16 @@ def BitFlip3(data_qs, ancilla_qs, theta=None, phi=None, nrounds=1, meas_delay=1e seqs+=[Utheta(data_qs[1], theta, phi), CNOT(data_qs[1], data_qs[0]), CNOT(data_qs[1], data_qs[2])] # multiple rounds of syndrome measurements - for k in range(nrounds): + for n in range(nrounds): seqs+=[CNOT(data_qs[0],ancilla_qs[0])*CNOT(data_qs[1],ancilla_qs[1])], seqs+=[CNOT(data_qs[1], ancilla_qs[0])*CNOT(data_qs[2],ancilla_qs[1])], - seqs+= [MEASA(ancilla_qs[0], maddr=(10,2**(2*n)))*MEASA(ancilla_qs[1], maddr=(10,2**(2*n+1))), + seqs+= [MEASA(ancilla_qs[0], maddr=(10, 2*n))*MEASA(ancilla_qs[1], maddr=(10, 2*n+1)), Id(ancilla_qs[0], meas_delay), - MEAS(data_qs[0],amp=0)*MEAS(data_qs[1],amp=0)*MEAS(data_qs[2],amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels - seqs+=Decode(10, 11, 4**nrounds-1) - seqs+=qwait("RAM",11, 4**nrounds-1) + MEAS(data_qs[0], amp=0)*MEAS(data_qs[1], amp=0)*MEAS(data_qs[2], amp=0)] # virtual msmt's just to keep the number of segments uniform across digitizer channels + seqs+=Decode(10, 11, 2*nrounds) + seqs+=qwait("RAM",11) seqs+=[MEAS(data_qs[0])*MEAS(data_qs[1])*MEAS(data_qs[2])* - MEAS(ancilla_qs[0],amp=0)*MEAS(ancilla_qs[1],amp=0)] # virtual msmt's + MEAS(ancilla_qs[0], amp=0)*MEAS(ancilla_qs[1], amp=0)] # virtual msmt's # apply corrective pulses depending on the decoder result FbGates = [] @@ -172,14 +172,14 @@ def MajorityVoteN(qubits, nrounds, prep=[], meas_delay=1e-6, docals=False, calRe metafile : metafile path """ nqubits = len(qubits) - seqs = [MajorityMask(0,1,2**(nrounds*nqubits)-1), - Invalidate(addr=10, mask=2**(nrounds*nqubits)-1), + seqs = [MajorityMask(nrounds*nqubits), + Invalidate(addr=10, mask=nrounds*nqubits), Invalidate(addr=11, mask=1)] if prep: seqs += [reduce(operator.mul, [X(q) for n,q in enumerate(qubits) if prep[n]])] for n in range(nrounds): - seqs += [reduce(operator.mul, [MEASA(q, (10, 2**(nqubits*n+m))) for m,q in enumerate(qubits)]), Id(qubits[0],meas_delay)] - seqs+=MajorityVote(10,11,2**(nrounds*nqubits)-1) + seqs += [reduce(operator.mul, [MEASA(q, (10, nqubits*n+m)) for m,q in enumerate(qubits)]), Id(qubits[0],meas_delay)] + seqs+=MajorityVote(10,11, nrounds*nqubits) seqs+=qwait("RAM", 11) seqs+=[Id(qubits[0],100e-9)] seqs+=qif(1,[X(qubits[0])]) # placeholder for any conditional operation diff --git a/QGL/TdmInstructions.py b/QGL/TdmInstructions.py index 4e0f5bd6..945743c9 100644 --- a/QGL/TdmInstructions.py +++ b/QGL/TdmInstructions.py @@ -37,14 +37,14 @@ def __ne__(self, other): return not self == other -def MajorityVote(in_addr, out_addr, mask): # alternatively, append the loadcmpvram instruction when compiling (see STOREMEAS) - return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('MAJORITY', in_addr, out_addr)] +def MajorityVote(in_addr, out_addr, nmeas): # alternatively, append the loadcmpvram instruction when compiling (see STOREMEAS) + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 2**nmeas-1, True), CustomInstruction('MAJORITY', in_addr, out_addr)] def MajorityMask(in_addr, out_addr, value): return [WriteAddrInstruction('INVALIDATE', None, 1, in_addr, 0x0, True), WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('MAJORITYMASK', in_addr, out_addr)] -def Decode(in_addr, out_addr, mask): - return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, mask, True), CustomInstruction('TSM', in_addr, out_addr)] +def Decode(in_addr, out_addr, nmeas): + return [LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 2**nmeas-1, True), CustomInstruction('TSM', in_addr, out_addr)] def DecodeSetRounds(in_addr, out_addr, value): return [WriteAddrInstruction('INVALIDATE', None, 1, in_addr, 0x0, True), WriteAddrInstruction('WRITEADDR', None, 0, in_addr, value, True), LoadCmpVramInstruction('LOADCMPVRAM', 1, in_addr, 0xffff, True), CustomInstruction('TSM_SET_ROUNDS', in_addr, out_addr)] @@ -77,8 +77,8 @@ def __ne__(self, other): def WriteAddr(addr, value, channel=None, tdm=True): return WriteAddrInstruction('WRITEADDR', channel, 0, addr, value, tdm) -def Invalidate(addr, mask, channel=None, tdm=True): - return WriteAddrInstruction('INVALIDATE', channel, 1, addr, mask, tdm) +def Invalidate(addr, nmeas, channel=None, tdm=True): + return WriteAddrInstruction('INVALIDATE', channel, 1, addr, 2**nmeas-1, tdm) def CrossBar(addr, value, channel=None, tdm=True): # should this be a high-level instruction though? return WriteAddrInstruction('CROSSBAR', channel, 3, addr, value, tdm) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 283570cc..1b1e5372 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1300,7 +1300,7 @@ def tdm_instructions(seqs): """ instructions = list() label2addr = dict() # the backpatch table for labels - + label = seqs[0][0] for seq in seqs: seq = list(flatten(copy(seq))) @@ -1383,7 +1383,6 @@ def tdm_instructions(seqs): elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): - instructions.append(CrossBar(s.maddr[1], 0x1, label=label)) # this has to change for sim. msmt's instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) #1 << s.maddr[1])) @@ -1397,7 +1396,6 @@ def tdm_instructions(seqs): if len(set(maddr))>1: raise Exception('Storing simultaneous measurements on different addresses not supported.') for n,m in enumerate(sim_meas): - instructions.append(CrossBar(m.maddr[1], 2**n)) instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(maddr[0], 1 << 16)) From 8b88682ee9acb2c77d9f18d11dc18783d917f1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 3 Jul 2018 10:38:39 -0400 Subject: [PATCH 66/67] Fix CrossBar --- QGL/drivers/APS2Pattern.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/QGL/drivers/APS2Pattern.py b/QGL/drivers/APS2Pattern.py index 1b1e5372..fb9f00ba 100644 --- a/QGL/drivers/APS2Pattern.py +++ b/QGL/drivers/APS2Pattern.py @@ -1383,8 +1383,9 @@ def tdm_instructions(seqs): elif isinstance(s, PulseSequencer.Pulse): if s.label == 'MEAS' and s.maddr != (-1, 0): + instructions.append(CrossBar(2**s.maddr[1], 0x1, label=label)) instructions.append(LoadCmp(label=label)) - instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) #1 << s.maddr[1])) + instructions.append(StoreMeas(s.maddr[0], 1 << 16, label=label)) elif isinstance(s, PulseSequencer.PulseBlock): sim_meas = [] @@ -1396,6 +1397,7 @@ def tdm_instructions(seqs): if len(set(maddr))>1: raise Exception('Storing simultaneous measurements on different addresses not supported.') for n,m in enumerate(sim_meas): + instructions.append(CrossBar(2**m.maddr[1], 2**n)) instructions.append(LoadCmp(label=label)) instructions.append(StoreMeas(maddr[0], 1 << 16)) From b0b1ebb9a01955d8caebdf8fe4d40ae5b4092486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Rist=C3=A8?= Date: Tue, 3 Jul 2018 16:23:37 -0400 Subject: [PATCH 67/67] Minimal qwait('RAM') test --- QGL/ControlFlow.py | 2 +- tests/test_ControlFlow.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/QGL/ControlFlow.py b/QGL/ControlFlow.py index 9c78ba2b..a735f24e 100644 --- a/QGL/ControlFlow.py +++ b/QGL/ControlFlow.py @@ -93,7 +93,7 @@ def qwait(kind="TRIG", addr=None, channels=None): elif kind == "CMP": return LoadCmp(channels) elif kind == "RAM": - if not addr: + if addr is None: raise Exception('Please specify address') return [WriteAddrInstruction('INVALIDATE', None, 1, addr, 0xffffffff, False), LoadCmpVramInstruction('LOADCMPVRAM', 1, addr, 0xff, False)] diff --git a/tests/test_ControlFlow.py b/tests/test_ControlFlow.py index a4af54f4..2eb40b3f 100644 --- a/tests/test_ControlFlow.py +++ b/tests/test_ControlFlow.py @@ -84,9 +84,11 @@ def test_repeat(self): def test_qwait(self): q1 = self.q1 - seq1 = [qwait(), qwait(kind="CMP")] + seq1 = [qwait(), qwait(kind="CMP"), qwait(kind = "RAM", addr = 0)] assert (isinstance(seq1[0], ControlFlow.Wait)) assert (isinstance(seq1[1], ControlFlow.LoadCmp)) + assert (isinstance(seq1[2][0], TdmInstructions.WriteAddrInstruction)) + assert (isinstance(seq1[2][1], TdmInstructions.LoadCmpVramInstruction)) def test_compile(self): q1 = self.q1