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
|
#
# Copyright 2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Helper class to initialize a Magnesium daughterboard
"""
from __future__ import print_function
import re
import time
import math
from builtins import object
from usrp_mpm.sys_utils.uio import open_uio
from usrp_mpm.dboard_manager.lmk_mg import LMK04828Mg
from usrp_mpm.dboard_manager.mg_periphs import DboardClockControl
from usrp_mpm.cores import ClockSynchronizer
from usrp_mpm.cores import nijesdcore
from usrp_mpm.mpmutils import async_exec
INIT_CALIBRATION_TABLE = {"TX_BB_FILTER" : 0x0001,
"ADC_TUNER" : 0x0002,
"TIA_3DB_CORNER" : 0x0004,
"DC_OFFSET" : 0x0008,
"TX_ATTENUATION_DELAY" : 0x0010,
"RX_GAIN_DELAY" : 0x0020,
"FLASH_CAL" : 0x0040,
"PATH_DELAY" : 0x0080,
"TX_LO_LEAKAGE_INTERNAL" : 0x0100,
"TX_LO_LEAKAGE_EXTERNAL" : 0x0200,
"TX_QEC_INIT" : 0x0400,
"LOOPBACK_RX_LO_DELAY" : 0x0800,
"LOOPBACK_RX_RX_QEC_INIT" : 0x1000,
"RX_LO_DELAY" : 0x2000,
"RX_QEC_INIT" : 0x4000,
"BASIC" : 0x4F,
"OFF" : 0x00,
"DEFAULT" : 0x4DFF,
"ALL" : 0x7DFF,
}
TRACKING_CALIBRATION_TABLE = {"TRACK_RX1_QEC" : 0x01,
"TRACK_RX2_QEC" : 0x02,
"TRACK_ORX1_QEC" : 0x04,
"TRACK_ORX2_QEC" : 0x08,
"TRACK_TX1_LOL" : 0x10,
"TRACK_TX2_LOL" : 0x20,
"TRACK_TX1_QEC" : 0x40,
"TRACK_TX2_QEC" : 0x80,
"OFF" : 0x00,
"RX_QEC" : 0x03,
"TX_QEC" : 0xC0,
"TX_LOL" : 0x30,
"DEFAULT" : 0xC3,
"ALL" : 0xF3,
}
class MagnesiumInitManager(object):
"""
Helper class: Holds all the logic to initialize an N310/N300 (Magnesium)
daughterboard.
"""
# DAC is initialized to midscale automatically on power-on: 16-bit DAC, so
# midpoint is at 2^15 = 32768. However, the linearity of the DAC is best
# just below that point, so we set it to the (carefully calculated)
# alternate value instead.
INIT_PHASE_DAC_WORD = 31000 # Intentionally decimal
PHASE_DAC_SPI_ADDR = 0x0
# External PPS pipeline delay from the PPS captured at the FPGA to TDC input,
# in reference clock ticks
EXT_PPS_DELAY = 5
# Variable PPS delay before the RP/SP pulsers begin. Fixed value for the
# N3xx devices.
N3XX_INT_PPS_DELAY = 4
def __init__(self, mg_class, spi_ifaces):
self.mg_class = mg_class
self._spi_ifaces = spi_ifaces
self.mykonos = mg_class.mykonos
self.slot_idx = mg_class.slot_idx
self.log = mg_class.log.getChild('init')
def check_mykonos_framer_status(self):
" Return True if Mykonos Framer is in good state "
rb = self.mykonos.get_framer_status()
self.log.trace("Mykonos Framer Status Register: 0x{:04X}".format(rb & 0xFF))
tx_state = {0: 'CGS',
1: 'ILAS',
2: 'ADC Data'}[rb & 0b11]
ilas_state = {0: 'CGS',
1: '1st Multframe',
2: '2nd Multframe',
3: '3rd Multframe',
4: '4th Multframe',
5: 'Last Multframe',
6: 'invalid state',
7: 'ILAS Complete'}[(rb & 0b11100) >> 2]
sysref_rx = (rb & (0b1 << 5)) > 0
fifo_ptr_delta_changed = (rb & (0b1 << 6)) > 0
sysref_phase_error = (rb & (0b1 << 7)) > 0
# According to emails with ADI, fifo_ptr_delta_changed may be buggy.
# Deterministic latency is still achieved even when this bit is toggled, so
# ADI's recommendation is to ignore it. The expected state of this bit 0, but
# occasionally it toggles to 1. It is unclear why exactly this happens.
success = ((tx_state == 'ADC Data') &
(ilas_state == 'ILAS Complete') &
sysref_rx &
(not sysref_phase_error))
logger = self.log.trace if success else self.log.warning
logger("Mykonos Framer, TX State: %s", tx_state)
logger("Mykonos Framer, ILAS State: %s", ilas_state)
logger("Mykonos Framer, SYSREF Received: {}".format(sysref_rx))
logger("Mykonos Framer, FIFO Ptr Delta Change: {} (ignored, possibly buggy)"
.format(fifo_ptr_delta_changed))
logger("Mykonos Framer, SYSREF Phase Error: {}"
.format(sysref_phase_error))
return success
def check_mykonos_deframer_status(self):
" Return True if Mykonos Deframer is in good state "
rb = self.mykonos.get_deframer_status()
self.log.trace("Mykonos Deframer Status Register: 0x{:04X}".format(rb & 0xFF))
frame_symbol_error = (rb & (0b1 << 0)) > 0
ilas_multifrm_error = (rb & (0b1 << 1)) > 0
ilas_framing_error = (rb & (0b1 << 2)) > 0
ilas_checksum_valid = (rb & (0b1 << 3)) > 0
prbs_error = (rb & (0b1 << 4)) > 0
sysref_received = (rb & (0b1 << 5)) > 0
deframer_irq = (rb & (0b1 << 6)) > 0
success = ((not frame_symbol_error) &
(not ilas_multifrm_error) &
(not ilas_framing_error) &
ilas_checksum_valid &
(not prbs_error) &
sysref_received &
(not deframer_irq))
logger = self.log.trace if success else self.log.warning
logger("Mykonos Deframer, Frame Symbol Error: {}".format(frame_symbol_error))
logger("Mykonos Deframer, ILAS Multiframe Error: {}".format(ilas_multifrm_error))
logger("Mykonos Deframer, ILAS Frame Error: {}".format(ilas_framing_error))
logger("Mykonos Deframer, ILAS Checksum Valid: {}".format(ilas_checksum_valid))
logger("Mykonos Deframer, PRBS Error: {}".format(prbs_error))
logger("Mykonos Deframer, SYSREF Received: {}".format(sysref_received))
logger("Mykonos Deframer, Deframer IRQ Received: {}".format(deframer_irq))
return success
def _init_lmk(
self,
lmk_spi,
ref_clock_freq,
master_clock_rate,
pdac_spi,
init_phase_dac_word,
phase_dac_spi_addr):
"""
Sets the phase DAC to initial value, and then brings up the LMK
according to the selected ref clock frequency.
Will throw if something fails.
"""
self.log.trace("Initializing Phase DAC to d{}.".format(
init_phase_dac_word
))
pdac_spi.poke16(phase_dac_spi_addr, init_phase_dac_word)
return LMK04828Mg(
lmk_spi,
self.mg_class.spi_lock,
ref_clock_freq,
master_clock_rate,
self.log
)
def _sync_db_clock(
self,
dboard_ctrl_regs,
master_clock_rate,
ref_clock_freq,
args):
""" Synchronizes the DB clock to the common reference """
reg_offset = 0x200
ext_pps_delay = self.EXT_PPS_DELAY
if args.get('time_source', self.mg_class.default_time_source) == 'sfp0':
reg_offset = 0x400
ref_clock_freq = 62.5e6
ext_pps_delay = 1 # only 1 flop between the WR core output and the TDC input
synchronizer = ClockSynchronizer(
dboard_ctrl_regs,
self.mg_class.lmk,
self._spi_ifaces['phase_dac'],
reg_offset,
master_clock_rate,
ref_clock_freq,
860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM
self.INIT_PHASE_DAC_WORD,
self.PHASE_DAC_SPI_ADDR,
ext_pps_delay,
self.N3XX_INT_PPS_DELAY,
self.slot_idx
)
# The radio clock traces on the motherboard are 69 ps longer for
# Daughterboard B than Daughterboard A. We want both of these clocks to
# align at the converters on each board, so adjust the target value for
# DB B. This is an N3xx series peculiarity and will not apply to other
# motherboards.
trace_delay_offset = {0: 0.0e-0,
1: 69.0e-12}[self.slot_idx]
offset = synchronizer.run(
num_meas=[512, 128],
target_offset=trace_delay_offset)
offset_error = abs(offset)
if offset_error > 100e-12:
self.log.error("Clock synchronizer measured an offset of {:.1f} ps!".format(
offset_error*1e12
))
raise RuntimeError("Clock synchronizer measured an offset of {:.1f} ps!".format(
offset_error*1e12
))
else:
self.log.debug("Residual synchronization error: {:.1f} ps.".format(
offset_error*1e12
))
synchronizer = None # Help garbage collector
self.log.debug("Sample Clock Synchronization Complete!")
def set_jesd_rate(self, jesdcore, new_rate, current_jesd_rate, force=False):
"""
Make the QPLL and GTX changes required to change the JESD204B core rate.
"""
# The core is directly compiled for 125 MHz sample rate, which
# corresponds to a lane rate of 2.5 Gbps. The same QPLL and GTX settings
# apply for the 122.88 MHz sample rate.
#
# The higher LTE rate, 153.6 MHz, requires changes to the default
# configuration of the MGT components. This function performs the
# required changes in the following order (as recommended by UG476).
#
# 1) Modify any QPLL settings.
# 2) Perform the QPLL reset routine by pulsing reset then waiting for lock.
# 3) Modify any GTX settings.
# 4) Perform the GTX reset routine by pulsing reset and waiting for reset done.
assert new_rate in (2457.6e6, 2500e6, 3072e6)
# On first run, we have no idea how the FPGA is configured... so let's force an
# update to our rate.
force = force or current_jesd_rate is None
skip_drp = False
if not force:
# Current New Skip?
skip_drp = {2457.6e6 : {2457.6e6: True, 2500.0e6: True, 3072.0e6:False},
2500.0e6 : {2457.6e6: True, 2500.0e6: True, 3072.0e6:False},
3072.0e6 : {2457.6e6: False, 2500.0e6: False, 3072.0e6:True}}[self.mg_class.current_jesd_rate][new_rate]
if skip_drp:
self.log.trace(
"Current lane rate is compatible with the new rate. "
"Skipping reconfiguration.")
# These are the only registers in the QPLL and GTX that change based on the
# selected line rate. The MGT wizard IP was generated for each of the rates and
# reference clock frequencies and then diffed to create this table.
QPLL_CFG = {2457.6e6: 0x680181, 2500e6: 0x680181, 3072e6: 0x06801C1}[new_rate]
QPLL_FBDIV = {2457.6e6: 0x120, 2500e6: 0x120, 3072e6: 0x80}[new_rate]
MGT_PMA_RSV = {2457.6e6: 0x1E7080, 2500e6: 0x1E7080, 3072e6: 0x18480}[new_rate]
MGT_RX_CLK25_DIV = {2457.6e6: 5, 2500e6: 5, 3072e6: 7}[new_rate]
MGT_TX_CLK25_DIV = {2457.6e6: 5, 2500e6: 5, 3072e6: 7}[new_rate]
MGT_RXOUT_DIV = {2457.6e6: 4, 2500e6: 4, 3072e6: 2}[new_rate]
MGT_TXOUT_DIV = {2457.6e6: 4, 2500e6: 4, 3072e6: 2}[new_rate]
MGT_RXCDR_CFG = {2457.6e6:0x03000023ff10100020, 2500e6:0x03000023ff10100020, 3072e6:0x03000023ff10200020}[new_rate]
# 1-2) Do the QPLL first
if not skip_drp:
self.log.trace("Changing QPLL settings to support {} Gbps".format(new_rate/1e9))
jesdcore.set_drp_target('qpll', 0)
# QPLL_CONFIG is spread across two regs: 0x32 (dedicated) and 0x33 (shared)
reg_x32 = QPLL_CFG & 0xFFFF # [16:0] -> [16:0]
reg_x33 = jesdcore.drp_access(rd=True, addr=0x33)
reg_x33 = (reg_x33 & 0xF800) | ((QPLL_CFG >> 16) & 0x7FF) # [26:16] -> [11:0]
jesdcore.drp_access(rd=False, addr=0x32, wr_data=reg_x32)
jesdcore.drp_access(rd=False, addr=0x33, wr_data=reg_x33)
# QPLL_FBDIV is shared with other settings in reg 0x36
reg_x36 = jesdcore.drp_access(rd=True, addr=0x36)
reg_x36 = (reg_x36 & 0xFC00) | (QPLL_FBDIV & 0x3FF) # in bits [9:0]
jesdcore.drp_access(rd=False, addr=0x36, wr_data=reg_x36)
# Run the QPLL reset sequence and prep the MGTs for modification.
jesdcore.init()
# 3-4) And the 4 MGTs second
if not skip_drp:
self.log.trace("Changing MGT settings to support {} Gbps"
.format(new_rate/1e9))
for lane in range(4):
jesdcore.set_drp_target('mgt', lane)
# MGT_PMA_RSV is split over 0x99 (LSBs) and 0x9A
reg_x99 = MGT_PMA_RSV & 0xFFFF
reg_x9a = (MGT_PMA_RSV >> 16) & 0xFFFF
jesdcore.drp_access(rd=False, addr=0x99, wr_data=reg_x99)
jesdcore.drp_access(rd=False, addr=0x9A, wr_data=reg_x9a)
# MGT_RX_CLK25_DIV is embedded with others in 0x11. The
# encoding for the DRP register value is one less than the
# desired value.
reg_x11 = jesdcore.drp_access(rd=True, addr=0x11)
reg_x11 = (reg_x11 & 0xF83F) | \
((MGT_RX_CLK25_DIV-1 & 0x1F) << 6) # [10:6]
jesdcore.drp_access(rd=False, addr=0x11, wr_data=reg_x11)
# MGT_TX_CLK25_DIV is embedded with others in 0x6A. The
# encoding for the DRP register value is one less than the
# desired value.
reg_x6a = jesdcore.drp_access(rd=True, addr=0x6A)
reg_x6a = (reg_x6a & 0xFFE0) | (MGT_TX_CLK25_DIV-1 & 0x1F) # [4:0]
jesdcore.drp_access(rd=False, addr=0x6A, wr_data=reg_x6a)
# MGT_RXCDR_CFG is split over 0xA8 (LSBs) through 0xAD
for reg_num, reg_addr in enumerate(range(0xA8, 0xAE)):
reg_data = (MGT_RXCDR_CFG >> 16*reg_num) & 0xFFFF
jesdcore.drp_access(rd=False, addr=reg_addr, wr_data=reg_data)
# MGT_RXOUT_DIV and MGT_TXOUT_DIV are embedded together in
# 0x88. The encoding for the DRP register value is
# drp_val=log2(attribute)
reg_x88 = (int(math.log(MGT_RXOUT_DIV, 2)) & 0x7) | \
((int(math.log(MGT_TXOUT_DIV, 2)) & 0x7) << 4) # RX=[2:0] TX=[6:4]
jesdcore.drp_access(rd=False, addr=0x88, wr_data=reg_x88)
self.log.trace("GTX settings changed to support {} Gbps"
.format(new_rate/1e9))
jesdcore.disable_drp_target()
self.log.trace("JESD204b Lane Rate set to {} Gbps!"
.format(new_rate/1e9))
self.mg_class.current_jesd_rate = new_rate
return
def init_lo_source(self, args):
"""Configure LO sources
This function will initialize all LO sources to user specified sources.
If there's no source is specified, the default one will be used.
Arguments:
args {string:string} -- device arguments.
"""
self.log.debug("Setting up LO source..")
rx_lo_source = args.get("rx_lo_source", "internal")
tx_lo_source = args.get("tx_lo_source", "internal")
self.mykonos.set_lo_source("RX", rx_lo_source)
self.mykonos.set_lo_source("TX", tx_lo_source)
self.log.debug("RX LO source is set at {}".format(self.mykonos.get_lo_source("RX")))
self.log.debug("TX LO source is set at {}".format(self.mykonos.get_lo_source("TX")))
def init_rf_cal(self, args):
""" Setup RF CAL """
def _parse_and_convert_cal_args(table, cal_args):
"""Parse calibration string and convert it to a number
Arguments:
table {dictionary} -- a look up table that map a type of calibration
to its bit mask.(defined in AD9375-UG992)
cal_args {string} -- string arguments from user in form of "CAL1|CAL2|CAL3"
or "CAL1 CAL2 CAL3" or "CAL1;CAL2;CAL3"
Returns:
int -- calibration value bit mask.
"""
value = 0
try:
return int(cal_args, 0)
except ValueError:
pass
for key in re.split(r'[;|\s]\s*', cal_args):
value_tmp = table.get(key.upper())
if (value_tmp) != None:
value |= value_tmp
else:
self.log.warning(
"Calibration key `%s' is not in calibration table. "
"Ignoring this key.",
key.upper()
)
return value
## Go, go, go!
self.log.trace("Setting up RF CAL...")
try:
init_cals_mask = _parse_and_convert_cal_args(
INIT_CALIBRATION_TABLE,
args.get('init_cals', 'DEFAULT')
)
tracking_cals_mask = _parse_and_convert_cal_args(
TRACKING_CALIBRATION_TABLE,
args.get('tracking_cals', 'DEFAULT')
)
init_cals_timeout = int(
args.get(
'init_cals_timeout',
str(self.mykonos.DEFAULT_INIT_CALS_TIMEOUT)
), 0
)
except ValueError as ex:
self.log.warning("init() args missing or error using default \
value seeing following exception print out.")
self.log.warning("{}".format(ex))
init_cals_mask = _parse_and_convert_cal_args(
INIT_CALIBRATION_TABLE, 'DEFAULT')
tracking_cals_mask = _parse_and_convert_cal_args(
TRACKING_CALIBRATION_TABLE, 'DEFAULT')
init_cals_timeout = self.mykonos.DEFAULT_INIT_CALS_TIMEOUT
self.log.debug("args[init_cals]=0x{:02X}".format(init_cals_mask))
self.log.debug("args[tracking_cals]=0x{:02X}".format(tracking_cals_mask))
async_exec(
self.mykonos,
"setup_cal",
init_cals_mask,
tracking_cals_mask,
init_cals_timeout
)
def init_jesd(self, jesdcore, master_clock_rate, args):
"""
Bring up the JESD link between Mykonos and the N310.
All clocks must be set up and stable before starting this routine.
"""
jesdcore.check_core()
# JESD Lane Rate only depends on the master_clock_rate selection, since all
# other link parameters (LMFS,N) remain constant.
L = 4
M = 4
F = 2
S = 1
N = 16
new_rate = master_clock_rate * M * N * (10.0/8) / L / S
self.log.trace("Calculated JESD204b lane rate is {} Gbps".format(new_rate/1e9))
self.mg_class.current_jesd_rate = \
self.set_jesd_rate(
jesdcore,
new_rate,
self.mg_class.current_jesd_rate)
self.log.trace("Pulsing Mykonos Hard Reset...")
self.mg_class.cpld.reset_mykonos()
self.log.trace("Initializing Mykonos...")
# TODO: If we can set the LO source after initialization, that would
# enable us to switch LO sources without doing the entire JESD and
# clocking bringup. For now, we'll keep it here, and every time the LO
# source needs to be changed, we need to re-initialize (this is because
# MYKONOS_initialize() takes in the entire device config, which includes
# the LO source), but we can revisit this if we want to either
# - speed up init when the only change is the LO source, or
# - we want to make the LO source runtime-configurable.
self.init_lo_source(args)
self.mykonos.begin_initialization()
# Multi-chip Sync requires two SYSREF pulses at least 17us apart.
jesdcore.send_sysref_pulse()
time.sleep(0.001) # 17us... ish.
jesdcore.send_sysref_pulse()
async_exec(self.mykonos, "finish_initialization")
# According to the AD9371 user guide, p.57, the RF cal must come before
# the framer/deframer init. We tried otherwise, and failed. So don't
# move this anywhere else.
self.init_rf_cal(args)
self.log.trace("Starting JESD204b Link Initialization...")
# Generally, enable the source before the sink. Start with the DAC side.
self.log.trace("Starting FPGA framer...")
jesdcore.init_framer()
self.log.trace("Starting Mykonos deframer...")
self.mykonos.start_jesd_rx()
# Now for the ADC link. Note that the Mykonos framer will not start issuing CGS
# characters until SYSREF is received by the framer. Therefore we enable the
# framer in Mykonos and the FPGA, send a SYSREF pulse to everyone, and then
# start the deframer in the FPGA.
self.log.trace("Starting Mykonos framer...")
self.mykonos.start_jesd_tx()
jesdcore.enable_lmfc(True)
jesdcore.send_sysref_pulse()
# Allow a bit of time for SYSREF to reach Mykonos and then CGS to
# appear. In several experiments this time requirement was only in the
# 100s of nanoseconds.
time.sleep(0.001)
self.log.trace("Starting FPGA deframer...")
jesdcore.init_deframer()
# Allow a bit of time for CGS/ILA to complete.
time.sleep(0.100)
error_flag = False
if not jesdcore.get_framer_status():
self.log.error("JESD204b FPGA Core Framer is not synced!")
error_flag = True
if not self.check_mykonos_deframer_status():
self.log.error("Mykonos JESD204b Deframer is not synced!")
error_flag = True
if not jesdcore.get_deframer_status():
self.log.error("JESD204b FPGA Core Deframer is not synced!")
error_flag = True
if not self.check_mykonos_framer_status():
self.log.error("Mykonos JESD204b Framer is not synced!")
error_flag = True
if (self.mykonos.get_multichip_sync_status() & 0xB) != 0xB:
self.log.error("Mykonos Multi-chip Sync failed!")
error_flag = True
if error_flag:
raise RuntimeError('JESD204B Link Initialization Failed. See MPM logs for details.')
self.log.debug("JESD204B Link Initialization & Training Complete")
def _full_init(self, slot_idx, master_clock_rate, ref_clock_freq, args):
"""
Run the full initialization sequence. This will bring everything up
from scratch: The LMK, JESD cores, the AD9371, calibrations, and
anything else that is clocking-related.
Depending on the settings, this can take a fair amount of time.
"""
# Init some more periphs:
# The following peripherals are only used during init, so we don't
# want to hang on to them for the full lifetime of the Magnesium
# class. This helps us close file descriptors associated with the
# UIO objects.
with open_uio(
label="dboard-regs-{}".format(slot_idx),
read_only=False
) as dboard_ctrl_regs:
self.log.trace("Creating jesdcore object...")
jesdcore = nijesdcore.NIMgJESDCore(dboard_ctrl_regs, slot_idx)
# Now get cracking with the actual init sequence:
self.log.trace("Creating dboard clock control object...")
db_clk_control = DboardClockControl(dboard_ctrl_regs, self.log)
self.log.debug("Reset Dboard Clocking and JESD204B interfaces...")
db_clk_control.reset_mmcm()
jesdcore.reset()
self.log.trace("Initializing LMK...")
self.mg_class.lmk = self._init_lmk(
self._spi_ifaces['lmk'],
ref_clock_freq,
master_clock_rate,
self._spi_ifaces['phase_dac'],
self.INIT_PHASE_DAC_WORD,
self.PHASE_DAC_SPI_ADDR,
)
db_clk_control.enable_mmcm()
# Synchronize DB Clocks
self._sync_db_clock(
dboard_ctrl_regs,
master_clock_rate,
ref_clock_freq,
args)
self.log.debug(
"Sample Clocks and Phase DAC Configured Successfully!")
# Clocks and PPS are now fully active!
if args.get('skip_rfic', None) == None:
self.mykonos.set_master_clock_rate(master_clock_rate)
self.init_jesd(jesdcore, master_clock_rate, args)
jesdcore = None # Help with garbage collection
# That's all that requires access to the dboard regs!
return True
def init(self, args, old_args, fast_reinit):
"""
Runs the actual initialization.
Arguments:
args -- Dictionary with user-specified args
old_args -- Dictionary with user-specified args from the previous
initialization run.
fast_reinit -- A hint to do a fast reinit. If nothing changes, then
we don't have to re-init everything and their dogs, we
can skip a whole bunch of things.
"""
# If any of these changes, we need a full re-init:
# TODO: This is not very DRY (because we're repeating default values),
# and is generally smelly design. However, we're being super
# conservative for now, because the only reliable reset sequence we
# have for AD9371 is the full Monty. As we learn more about the chip,
# we might be able to get away with a partial (fast) reinit even when
# some of these values change.
args_that_must_not_change = [
('rx_lo_source', 'internal'),
('tx_lo_source', 'internal'),
('init_cals', 'DEFAULT'),
('tracking_cals', 'DEFAULT'),
('init_cals_timeout', str(self.mykonos.DEFAULT_INIT_CALS_TIMEOUT)),
]
if fast_reinit:
for arg_key, arg_default in args_that_must_not_change:
old_value = old_args.get(arg_key, arg_default)
new_value = args.get(arg_key, arg_default)
if old_value != new_value:
self.log.debug(
"The following init arg changed and caused "
"a full re-init sequence: {}".format(arg_key))
fast_reinit = False
# TODO: Maybe we can switch to digital loopback without running the
# initialization. For now, force init when rfic_digital_loopback is
# set because we're being conservative.
if bool(args.get('rfic_digital_loopback')):
self.log.debug("Using rfic_digital_loopback causes a full "
"re-init sequence.")
fast_reinit = False
# If we can't do fast re-init, start from scratch:
if not fast_reinit:
if not self._full_init(
self.mg_class.slot_idx,
self.mg_class.master_clock_rate,
self.mg_class.ref_clock_freq,
args
):
return False
else:
self.log.debug("Running fast re-init with the following settings:")
for arg_key, arg_default in args_that_must_not_change:
self.log.debug(
"{}={}".format(arg_key, args.get(arg_key, arg_default)))
return True
if bool(args.get('rfic_digital_loopback')):
self.log.warning(
"RF Functionality Disabled: JESD204b digital loopback "
"enabled inside Mykonos!")
self.mykonos.enable_jesd_loopback(1)
else:
self.mykonos.start_radio()
return True
|