def _sync_db_clock(): " Synchronizes the DB clock to the common reference " synchronizer = ClockSynchronizer( self.dboard_clk_control, self.lmk, self._spi_ifaces['phase_dac'], 0, # register offset value. 104e6, # TODO don't hardcode self.ref_clock_freq, 1.9E-12, # fine phase shift. TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, 0x3, 3, # External PPS pipeline delay from the PPS captured at the FPGA to TDC input 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("Clock Synchronization Complete!")
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 _sync_db_clock(): " Synchronizes the DB clock to the common reference " reg_offset = 0x200 ext_pps_delay = self.EXT_PPS_DELAY #from outdated inst of ClockSync #2.496e9, # lmk_vco_freq synchronizer = ClockSynchronizer( self.radio_regs, self.lmk, self._spi_ifaces['phase_dac'], reg_offset, self.master_clock_rate, self.ref_clock_freq, 1.9E-12, # 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("Clock Synchronization Complete!")
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 init(self, args): """ Execute necessary actions to bring up the daughterboard: - Initializes all the software controls for all the chips and registers - Turns on the power - Initializes clocking - Synchronizes clocks to reference - Forwards PPS to the radio block This assumes that an appropriate overlay was loaded. If not, this will fail loudly complaining about missing devices. For operation (streaming), the ADCs and deframers still need to be initialized. See init_jesd_core_reset_adcs(), init_adcs_and_deframers(), and check_deframer_status(). Note that this function will do nothing if the device was previously initialized, unless force_init was specified in the init args. """ def _init_dboard_regs(): " Create a UIO object to talk to dboard regs " self.log.trace("Getting uio...") return UIO(label="dboard-regs-{}".format(self.slot_idx), read_only=False) def _init_jesd_cores(dboard_regs, slot_idx): " Init abstraction layer for JESD cores. Will also test registers. " return [ JesdCoreEiscat(dboard_regs, slot_idx, core_idx, self.log) for core_idx in range(2) ] def _init_spi_devices(): " Returns abstraction layers to all the SPI devices " self.log.trace("Loading SPI interfaces...") return { key: self.spi_factories[key](self._spi_nodes[key]) for key in self._spi_nodes } def _init_clock_control(dboard_regs): " Create a dboard clock control object and reset it " dboard_clk_control = DboardClockControl(dboard_regs, self.log) dboard_clk_control.reset_mmcm() return dboard_clk_control def _init_lmk(slot_idx, lmk_spi, ref_clk_freq, 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(0x3, init_phase_dac_word) return LMK04828EISCAT(lmk_spi, ref_clk_freq, slot_idx) def _sync_db_clock(): " Synchronizes the DB clock to the common reference " synchronizer = ClockSynchronizer( self.dboard_clk_control, self.lmk, self._spi_ifaces['phase_dac'], 0, # register offset value. 104e6, # TODO don't hardcode self.ref_clock_freq, 1.9E-12, # fine phase shift. TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, 0x3, 3, # External PPS pipeline delay from the PPS captured at the FPGA to TDC input 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("Clock Synchronization Complete!") # Go, go, go! if args.get("force_init", False): self.log.info("Forcing re-initialization of dboard.") self.initialized = args.get("force_init", self.initialized) if self.initialized: self.log.debug( "Dboard was previously initialized; skipping init. " \ "Specify force_init=1 to force initialization." ) return True self.log.debug("init() called with args `{}'".format(",".join( ['{}={}'.format(x, args[x]) for x in args]))) self.radio_regs = _init_dboard_regs() self.jesd_cores = _init_jesd_cores(self.radio_regs, self.slot_idx) self.log.debug("Radio-register UIO object successfully generated!") self._spi_ifaces = _init_spi_devices() # Chips don't have power yet! self.log.debug("Loaded SPI interfaces!") self._init_power(self.radio_regs) # Now, we can talk to chips via SPI self.dboard_clk_control = _init_clock_control(self.radio_regs) self.lmk = _init_lmk( self.slot_idx, self._spi_ifaces['lmk'], self.ref_clock_freq, self._spi_ifaces['phase_dac'], self.INIT_PHASE_DAC_WORD, ) self.adcs = [ ADS54J56(self._spi_ifaces[spi_iface], self.log) for spi_iface in ('adc0', 'adc1') ] self.dboard_clk_control.enable_mmcm() self.log.debug("Clocking Configured Successfully!") # Synchronize DB Clocks self.clock_synchronizer = ClockSynchronizer( self.radio_regs, self.dboard_clk_control, self.lmk, self._spi_ifaces['phase_dac'], 0, # TODO this might not actually be zero 104e6, # TODO don't hardcode self.ref_clock_freq, 1.9E-12, # TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, 2.496e9, # lmk_vco_freq 0x3, # spi_addr self.slot_idx) _sync_db_clock(self.clock_synchronizer) # Clocks and PPS are now fully active! return True