# # 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 # JESD core default configuration. JESD_DEFAULT_ARGS = {"bypass_descrambler": False, "lmfc_divider" : 20, "rx_sysref_delay" : 8, "tx_sysref_delay" : 11} 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.NIJESDCore(dboard_ctrl_regs, slot_idx, **self.JESD_DEFAULT_ARGS) # 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) is None: async_exec(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