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! return True
def set_clk_safe_state(self): """ Disable all components that could react badly to a sudden change in clocking. After calling this method, all clocks will be off. Calling _reinit() will turn them on again. The only downstream receiver of the clock that is not reset here are the lowband LOs, which are controlled through the host UHD interface. """ if self._init_args is None: # Then we're already in a safe state return # Reset Mykonos, since it receives a copy of the clock from the LMK. self.cpld.reset_mykonos(keep_in_reset=True) with open_uio(label="dboard-regs-{}".format(self.slot_idx), read_only=False) as dboard_ctrl_regs: # Clear the Sample Clock enables and place the MMCM in reset. db_clk_control = DboardClockControl(dboard_ctrl_regs, self.log) db_clk_control.reset_mmcm() # Place the JESD204b core in reset, mainly to reset QPLL/CPLLs. jesdcore = nijesdcore.NIMgJESDCore(dboard_ctrl_regs, self.slot_idx) jesdcore.reset()
def init(self, args): """ 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]))) if not self._periphs_initialized: error_msg = "Cannot run init(), peripherals are not initialized!" self.log.error(error_msg) raise RuntimeError(error_msg) if 'ref_clk_freq' in args: self.ref_clock_freq = float(args['ref_clk_freq']) assert self.ref_clock_freq in (10e6, 20e6, 25e6) assert self.ref_clock_freq is not None master_clock_rate = \ float(args.get('master_clock_rate', self.default_master_clock_rate)) assert master_clock_rate in (122.88e6, 125e6, 153.6e6), \ "Invalid master clock rate: {:.02f} MHz".format( master_clock_rate / 1e6) master_clock_rate_changed = master_clock_rate != self.master_clock_rate if master_clock_rate_changed: 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 init(self, args): """ 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): """ 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(0x0, init_phase_dac_word) return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, master_clk_rate, self.log) def _get_clock_synchronizer(): " Return a clock synchronizer object " # Future Work: target_value needs to be tweaked to support # heterogeneous rate sync. target_value = { 122.88e6: 128e-9, 125e6: 128e-9, 153.6e6: 122e-9 }[self.master_clock_rate] return ClockSynchronizer( dboard_ctrl_regs, self.lmk, self._spi_ifaces['phase_dac'], 0, # register offset value. future work. self.master_clock_rate, self.ref_clock_freq, 860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, [ target_value, ], # target_values 0x0, # spi_addr TODO: make this a constant and replace in _sync_db_clock as well self.slot_idx) def _sync_db_clock(synchronizer): " Synchronizes the DB clock to the common reference " synchronizer.check_core() synchronizer.run_sync(measurement_only=False) offset_error = synchronizer.run_sync(measurement_only=True) 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 DAC offset error: {:.1f} ps.".format( offset_error * 1e12)) self.log.info("Sample Clock Synchronization Complete!") ## Go, go, go! # Sanity checks and input validation: self.log.info("init() called with args `{}'".format(",".join( ['{}={}'.format(x, args[x]) for x in args]))) if not self._periphs_initialized: error_msg = "Cannot run init(), peripherals are not initialized!" self.log.error(error_msg) raise RuntimeError(error_msg) if 'ref_clk_freq' in args: self.ref_clock_freq = float(args['ref_clk_freq']) assert self.ref_clock_freq in (10e6, 20e6, 25e6) assert self.ref_clock_freq is not None master_clock_rate = \ float(args.get('master_clock_rate', self.default_master_clock_rate)) assert master_clock_rate in (122.88e6, 125e6, 153.6e6), \ "Invalid master clock rate: {:.02f} MHz".format( master_clock_rate / 1e6) master_clock_rate_changed = master_clock_rate != self.master_clock_rate if master_clock_rate_changed: 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, ) db_clk_control.enable_mmcm() self.log.info( "Sample Clocks and Phase DAC Configured Successfully!") # Synchronize DB Clocks _sync_db_clock(_get_clock_synchronizer()) # 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! self.mykonos.start_radio() return True