aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2018-07-13 15:29:21 -0700
committerMartin Braun <martin.braun@ettus.com>2018-07-17 10:49:21 -0700
commit8e02dec0fb4fd8f3e3b87a47209fc1326ee169d6 (patch)
tree5b26864952102e0b5b2a03f2d0ab62e9bca4ac44 /mpm
parent2fce1974d9915813bebd09ee9981929b2006fd6c (diff)
downloaduhd-8e02dec0fb4fd8f3e3b87a47209fc1326ee169d6.tar.gz
uhd-8e02dec0fb4fd8f3e3b87a47209fc1326ee169d6.tar.bz2
uhd-8e02dec0fb4fd8f3e3b87a47209fc1326ee169d6.zip
mpm: n3xx: mg: Refactor init code
All code relevant to initializing an N310/N300 daughterboard is moved to its own module (mg_init.py). No functional changes.
Diffstat (limited to 'mpm')
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/magnesium.py514
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/mg_init.py567
-rw-r--r--mpm/python/usrp_mpm/periph_manager/n3xx.py2
4 files changed, 585 insertions, 499 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
index 542497898..6ad2f91f0 100644
--- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
@@ -16,6 +16,7 @@ SET(USRP_MPM_DBMGR_FILES
${CMAKE_CURRENT_SOURCE_DIR}/lmk_mg.py
${CMAKE_CURRENT_SOURCE_DIR}/magnesium.py
${CMAKE_CURRENT_SOURCE_DIR}/magnesium_update_cpld.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/mg_init.py
${CMAKE_CURRENT_SOURCE_DIR}/mg_periphs.py
${CMAKE_CURRENT_SOURCE_DIR}/test.py
${CMAKE_CURRENT_SOURCE_DIR}/unknown.py
diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py
index f81e07f43..6bbdd808f 100644
--- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py
+++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py
@@ -1,5 +1,5 @@
#
-# Copyright 2017 Ettus Research, a National Instruments Company
+# Copyright 2017-2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
@@ -9,62 +9,20 @@ Magnesium dboard implementation module
from __future__ import print_function
import os
-import time
import threading
-import math
-import re
from six import iterkeys, iteritems
from usrp_mpm import lib # Pulls in everything from C++-land
from usrp_mpm.dboard_manager import DboardManagerBase
-from usrp_mpm.dboard_manager.lmk_mg import LMK04828Mg
from usrp_mpm.dboard_manager.mg_periphs import TCA6408, MgCPLD
-from usrp_mpm.dboard_manager.mg_periphs import DboardClockControl
-from usrp_mpm.cores import nijesdcore
+from usrp_mpm.dboard_manager.mg_init import MagnesiumInitManager
from usrp_mpm.mpmlog import get_logger
-from usrp_mpm.mpmutils import async_exec
from usrp_mpm.sys_utils.uio import open_uio
from usrp_mpm.sys_utils.udev import get_eeprom_paths
-from usrp_mpm.cores import ClockSynchronizer
from usrp_mpm.bfrfs import BufferFS
-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,
- }
-
-
+###############################################################################
+# SPI Helpers
+###############################################################################
def create_spidev_iface_lmk(dev_node):
"""
Create a regs iface from a spidev node
@@ -108,9 +66,6 @@ def create_spidev_iface_phasedac(dev_node):
)
###############################################################################
-# Peripherals
-###############################################################################
-###############################################################################
# Main dboard control class
###############################################################################
class Magnesium(DboardManagerBase):
@@ -155,16 +110,6 @@ class Magnesium(DboardManagerBase):
'alignment': 1024,
},
}
- # 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
default_master_clock_rate = 125e6
default_time_source = 'internal'
default_current_jesd_rate = 2500e6
@@ -189,9 +134,6 @@ class Magnesium(DboardManagerBase):
self.eeprom_fs = None
self.eeprom_path = None
self.cpld = None
- self._init_cals_mask = 0
- self._tracking_cals_mask = 0
- self._init_cals_timeout = 0
# Now initialize all peripherals. If that doesn't work, put this class
# into a non-functional state (but don't crash, or we can't talk to it
# any more):
@@ -332,70 +274,6 @@ class Magnesium(DboardManagerBase):
"""
Execute necessary init dance to bring up dboard
"""
- def _init_lmk(lmk_spi, ref_clk_freq, master_clk_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.spi_lock,
- ref_clk_freq,
- master_clk_rate,
- self.log
- )
- def _sync_db_clock():
- " Synchronizes the DB clock to the common reference "
- reg_offset = 0x200
- ref_clk_freq = self.ref_clock_freq
- ext_pps_delay = self.EXT_PPS_DELAY
- if args.get('time_source', self.default_time_source) == 'sfp0':
- reg_offset = 0x400
- ref_clk_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.lmk,
- self._spi_ifaces['phase_dac'],
- reg_offset,
- self.master_clock_rate,
- ref_clk_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
- self.log.debug("Sample Clock Synchronization Complete!")
- ## Go, go, go!
# Sanity checks and input validation:
self.log.debug("init() called with args `{}'".format(
",".join(['{}={}'.format(x, args[x]) for x in args])
@@ -404,9 +282,13 @@ class Magnesium(DboardManagerBase):
error_msg = "Cannot run init(), peripherals are not initialized!"
self.log.error(error_msg)
raise RuntimeError(error_msg)
+ fast_reinit = True
if 'ref_clk_freq' in args:
- self.ref_clock_freq = float(args['ref_clk_freq'])
- assert self.ref_clock_freq in (10e6, 20e6, 25e6)
+ new_ref_clock_freq = float(args['ref_clk_freq'])
+ assert new_ref_clock_freq in (10e6, 20e6, 25e6)
+ if new_ref_clock_freq != self.ref_clock_freq:
+ fast_reinit = False
+ self.ref_clock_freq = float(args['ref_clk_freq'])
assert self.ref_clock_freq is not None
master_clock_rate = \
float(args.get('master_clock_rate',
@@ -416,380 +298,13 @@ class Magnesium(DboardManagerBase):
master_clock_rate / 1e6)
master_clock_rate_changed = master_clock_rate != self.master_clock_rate
if master_clock_rate_changed:
+ fast_reinit = False
self.master_clock_rate = master_clock_rate
self.log.debug("Updating master clock rate to {:.02f} MHz!".format(
self.master_clock_rate / 1e6
))
- # 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(self.slot_idx),
- read_only=False
- ) as dboard_ctrl_regs:
- self.log.trace("Creating jesdcore object...")
- jesdcore = nijesdcore.NIMgJESDCore(dboard_ctrl_regs, self.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.lmk = _init_lmk(
- self._spi_ifaces['lmk'],
- self.ref_clock_freq,
- self.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
- _sync_db_clock()
- self.log.debug("Sample Clocks and Phase DAC Configured Successfully!")
- # Clocks and PPS are now fully active!
- self.mykonos.set_master_clock_rate(self.master_clock_rate)
- self.init_jesd(jesdcore, args)
- jesdcore = None # Help with garbage collection
- # That's all that requires access to the dboard regs!
- 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
-
- def _parse_and_convert_cal_args(self, 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
-
- def init_rf_cal(self, args):
- " Setup RF CAL "
- self.log.debug("Setting up RF CAL...")
- try:
- self._init_cals_mask = \
- self._parse_and_convert_cal_args(
- INIT_CALIBRATION_TABLE,
- args.get('init_cals', 'DEFAULT')
- )
- self._tracking_cals_mask = \
- self._parse_and_convert_cal_args(
- TRACKING_CALIBRATION_TABLE,
- args.get('tracking_cals', 'DEFAULT')
- )
- self._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))
- self._init_cals_mask = self._parse_and_convert_cal_args(
- INIT_CALIBRATION_TABLE, 'DEFAULT')
- self._tracking_cals_mask = self._parse_and_convert_cal_args(
- TRACKING_CALIBRATION_TABLE, 'DEFAULT')
- self._init_cals_timeout = self.mykonos.DEFAULT_INIT_CALS_TIMEOUT
- self.log.debug("args[init_cals]=0x{:02X}"
- .format(self._init_cals_mask))
- self.log.debug("args[tracking_cals]=0x{:02X}"
- .format(self._tracking_cals_mask))
- async_exec(
- self.mykonos,
- "setup_cal",
- self._init_cals_mask,
- self._tracking_cals_mask,
- self._init_cals_timeout
- )
-
- def init_lo_source(self, args):
- """Set all LO
-
- This function will initialize all LO 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_jesd(self, jesdcore, 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 = self.master_clock_rate * M * N * (10.0/8) / L / S
- self.log.trace("Calculated JESD204b lane rate is {} Gbps".format(new_rate/1e9))
- self.set_jesd_rate(jesdcore, new_rate)
-
- self.log.trace("Pulsing Mykonos Hard Reset...")
- self.cpld.reset_mykonos()
- self.log.trace("Initializing Mykonos...")
- 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")
- # TODO:can we call this after JESD?
- 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 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 set_jesd_rate(self, jesdcore, new_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 (self.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.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.current_jesd_rate = new_rate
- return
+ return MagnesiumInitManager(self, self._spi_ifaces).init(
+ args, fast_reinit)
def get_user_eeprom_data(self):
"""
@@ -996,3 +511,4 @@ class Magnesium(DboardManagerBase):
) as dboard_ctrl_regs:
self.log.trace("Writing DB Core Register 0x{:04X} with 0x{:08X}...".format(addr, data))
dboard_ctrl_regs.poke32(addr, data)
+
diff --git a/mpm/python/usrp_mpm/dboard_manager/mg_init.py b/mpm/python/usrp_mpm/dboard_manager/mg_init.py
new file mode 100644
index 000000000..34016e9f5
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/mg_init.py
@@ -0,0 +1,567 @@
+#
+# 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...")
+ 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")
+ # TODO:can we call this after JESD?
+ 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!
+ 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!
+ 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
+
+ def init(self, args, fast_reinit):
+ """
+ Runs the actual initialization.
+ """
+ if not fast_reinit or True:
+ return self._full_init(
+ self.mg_class.slot_idx,
+ self.mg_class.master_clock_rate,
+ self.mg_class.ref_clock_freq,
+ args
+ )
diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py
index c9714054e..bec7aa6fe 100644
--- a/mpm/python/usrp_mpm/periph_manager/n3xx.py
+++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py
@@ -384,6 +384,8 @@ class n3xx(ZynqComponents, PeriphManagerBase):
self.log.warning("The N300 variant does not support "
"external LOs! Setting to internal.")
args[lo_source] = 'internal'
+ # Note: The parent class takes care of calling init() on all the
+ # daughterboards
result = super(n3xx, self).init(args)
# Now the clocks are all enabled, we can also re-enable PPS export if
# it was turned off: