-
Notifications
You must be signed in to change notification settings - Fork 12
/
APS2Pattern.py
1633 lines (1383 loc) · 66.4 KB
/
APS2Pattern.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'''
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 itertools import zip_longest
import pickle
import struct
import sys
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
MAX_VRAM_ADDRESS = 2**(12-2)-1
MODULATION_CLOCK = 300e6
NUM_NCO = 4
# 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
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
# 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 {'ch1': {}, 'm1': {}, 'm2': {}, 'm3': {}, 'm4': {}}
def get_seq_file_extension():
return '.aps2'
def is_compatible_file(filename):
with open(filename, 'rb') as FID:
byte = FID.read(4)
if byte == 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 = int(np.round(WAVEFORM_CACHE_SIZE / 2)) - 1
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(int(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 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 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
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 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
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
nco_select_bits = {1 : 0b0001,
2 : 0b0010,
3 : 0b0100,
4 : 0b1000,
15: 0b1111}[self.nco_select]
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) | (
(nco_select_bits) << 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) > NUM_NCO:
raise Exception("Max {} frequencies on the same channel allowed.".format(NUM_NCO))
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.all(np.less(np.abs(phases), 1e-8))
frame_changes = [entry.frameChange for entry in filter(lambda s: isinstance(s,Compiler.Waveform), seq)]
no_frame_cmds = np.all(np.less(np.abs(frame_changes), 1e-8))
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) 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)):
mod_seq.append(ModulationCommand("RESET_PHASE", 0xF))
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, label = None):
'''
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)
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):
if not label:
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
if len(timeTuples)>0:
label = None
return instructions, label
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))
label = None
for ct, seq in enumerate(zip_longest(*seqs, fillvalue=[])):
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):
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 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
for ct, seq in enumerate(seq_instrs):
#Use last instruction being return as mark of start of subroutines
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
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):