Esempio n. 1
0
 def set_time_source(self, time_source):
     " Set a time source "
     assert time_source in self.get_time_sources()
     self._time_source = time_source
     self.mboard_regs_control.set_time_source(time_source, self.get_ref_clock_freq())
     if time_source == 'sfp0':
         # This error is specific to slave and master mode for White Rabbit.
         # Grand Master mode will require the external or gpsdo sources (not supported).
         if (time_source == 'sfp0' or time_source == 'sfp1') and \
           self.get_clock_source() != 'internal':
             error_msg = "Time source sfp(0|1) requires the internal clock source!"
             self.log.error(error_msg)
             raise RuntimeError(error_msg)
         if self.updateable_components['fpga']['type'] != 'WX':
             self.log.error("{} time source requires 'WX' FPGA type" \
                            .format(time_source))
             raise RuntimeError("{} time source requires 'WX' FPGA type" \
                            .format(time_source))
         # Only open UIO to the WR core once we're guaranteed it exists.
         wr_regs_control = WhiteRabbitRegsControl(
             self.wr_regs_label, self.log)
         # Wait for time source to become ready. Only applies to SFP0/1. All other
         # targets start their PPS immediately.
         self.log.debug("Waiting for {} timebase to lock..." \
                        .format(time_source))
         if not poll_with_timeout(
                 lambda: wr_regs_control.get_time_lock_status(),
                 40000, # Try for x ms... this number is set from a few benchtop tests
                 1000, # Poll every... second! why not?
             ):
             self.log.error("{} timebase failed to lock within 40 seconds. Status: 0x{:X}" \
                            .format(time_source, wr_regs_control.get_time_lock_status()))
             raise RuntimeError("Failed to lock SFP timebase.")
Esempio n. 2
0
 def pll_sync_trigger(self, clock_ctrl_pps_src):
     """
     Callback for LMK04832 driver to actually trigger the sync. Set PPS
     source accordingly.
     """
     with self.regs:
         # Update clock control config register to use the currently relevant
         # PPS source
         config = self.peek32(self.MB_CLOCK_CTRL)
         trigger_config = \
             (config & ~MboardRegsControl.CLOCK_CTRL_TRIGGER_PPS_SEL) \
             | clock_ctrl_pps_src
         # trigger sync with appropriate configuration
         self.poke32(self.MB_CLOCK_CTRL,
                     trigger_config | self.CLOCK_CTRL_PLL_SYNC_TRIGGER)
         # wait for sync done indication from FPGA
         # The following value is in ms, it was experimentally picked.
         pll_sync_timeout = 1500  # ms
         result = poll_with_timeout(self.is_pll_sync_done, pll_sync_timeout,
                                    10)
         # de-assert sync trigger signal
         self.poke32(self.MB_CLOCK_CTRL, trigger_config)
         if not result:
             self.log.error("PLL_SYNC_DONE not received within timeout")
         return result
Esempio n. 3
0
 def __init__(self, i2c_dev):
     assert i2c_dev is not None
     self._gpios = SysFSGPIO({'label': 'tca6424', 'device/of_node/name': 'rhodium-lodist-gpio'}, 0x1FFF0F, 0x1FFF00, 0x00A500, i2c_dev)
     board_rev = self._gpios.get(self.pins.index('BD_REV_0')) + \
                 self._gpios.get(self.pins.index('BD_REV_1')) << 1 + \
                 self._gpios.get(self.pins.index('BD_REV_2')) << 2
     if  board_rev != self.EXPECTED_BOARD_REV:
         raise RuntimeError('LO distribution board revision did not match: Expected: {0} Actual: {1}'.format(self.EXPECTED_BOARD_REV, board_rev))
     self._gpios.set(self.pins.index('P6_8V_EN'), 1)
     if not poll_with_timeout(
             lambda: bool(self._gpios.get(self.pins.index('P6_8V_PG'))), 
             self.POWER_ON_TIMEOUT, 
             self.POWER_ON_POLL_INTERVAL):
         self._gpios.set(self.pins.index('P6_8V_EN'), 0)
         raise RuntimeError('Power on failure for LO Distribution board')
     self._gpios.set(self.pins.index('P6_5V_LDO_EN'), 1)
     self._gpios.set(self.pins.index('P3_3V_RF_EN'), 1)
Esempio n. 4
0
    def enable_mmcm(self):
        """
        Unreset MMCM and poll lock indicators

        If MMCM is not locked after unreset, an exception is thrown.
        """
        self.log.trace("Un-resetting MMCM...")
        self.poke32(self.RADIO_CLK_MMCM, 0x2)
        if not poll_with_timeout(
                 lambda: bool(self.peek32(self.RADIO_CLK_MMCM) & 0x10),
                 500,
                 10,
                ):
            self.log.error("MMCM not locked!")
            raise RuntimeError("MMCM not locked!")
        self.log.trace("MMCM locked. Enabling output MMCM clocks...")
        self.enable_outputs(True)
Esempio n. 5
0
    def set_voltage_level(self, port, level):
        """
        Change voltage level of a port. This is how EN_<port>, EN_<port>_2V5 and
        EN_<port>_3V3 are set according to level::
            level EN_<port>   EN_<port>_2V5   EN_<port>_3V3
            off       0            0               0
            1V8       1            0               0
            2V5       1            1               0
            3V3       1            0               1
        If level is set to anything other than off this method waits for
        <port>_PG to go high. Waiting stops as soon as <port>_PG goes high or
        a timeout of 1s occurs.
        Note: All pins are set to zero first before the new level is applied.
        :param port: port to change power level for
        :param level: new power level
        :raises RuntimeError: power good pin did not go high
        """
        port = self._normalize_port_name(port)
        level = level.upper()
        assert port in self.DIO_PORTS
        assert level in self.DIO_VOLTAGE_LEVELS
        port_control = self.port_control[port]

        self._current_voltage_level[port] = level

        port_control.enable.set(0)
        port_control.en_2v5.set(0)
        port_control.en_3v3.set(0)
        if level == self.DIO_VOLTAGE_LEVELS[2]:
            port_control.en_2v5.set(1)
        elif level == self.DIO_VOLTAGE_LEVELS[3]:
            port_control.en_3v3.set(1)

        # wait for <port>_PG to go high
        if not level == self.DIO_VOLTAGE_LEVELS[0]:  # off
            port_control.enable.set(1)
            if not poll_with_timeout(
                    lambda: port_control.power_good.get() == 1, 1000, 10):
                raise RuntimeError(
                    "Power good pin did not go high after power up")
Esempio n. 6
0
    def run_sync(self, measurement_only=False):
        """
        Perform the synchronization algorithm. Successful completion of this
        function means the clock output was synchronized to the reference.

        - Set RTC and RSP values in synchronization core
        - Run offset measurements
        - Calcuate LMK shift and phase DAC values from offsets
        - Check it all worked

        """
        # To access registers, use self.peek32() and self.poke32(). It already contains
        # the offset at this point (see __init__), so self.peek32(0x0000) should read the
        # first offset if you kept your reg offset at 0 in your netlist

        self.log.info("Running clock synchronization...")
        self.log.trace("Using reference clock frequency: {} MHz".format(
            self.ref_clk_freq / 1e6))
        self.log.trace("Using master clock frequency: {} MHz".format(
            self.radio_clk_freq / 1e6))

        # Reset and disable TDC, and enable re-runs. Confirm the core is in
        # reset and PPS is cleared. Do not disable the PPS crossing.
        self.poke32(self.TDC_CONTROL, 0x0121)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x01:
            self.log.error(
                "TDC Failed to Reset! Status: 0x{:x}".format(reset_status))
            raise RuntimeError("TDC Failed to reset.")

        # Set the RSP and RTC values based on the Radio Clock and Reference Clock
        # configurations. Registers are packed [27:16] = high time, [11:0] = period.
        def combine_period_hi_time(period, hi_time):
            """
            Registers are packed [27:16] = high time, [11:0] = period.
            """
            assert hi_time <= 0xFFF and period <= 0xFFF
            return (hi_time << 16) | period

        rsp_ctrl_word = combine_period_hi_time(
            *rsp_table(self.ref_clk_freq, self.radio_clk_freq))
        rtc_ctrl_word = combine_period_hi_time(*rtc_table(self.radio_clk_freq))
        self.log.trace(
            "Setting RSP control word to: 0x{:08X}".format(rsp_ctrl_word))
        self.log.trace(
            "Setting RTC control word to: 0x{:08X}".format(rtc_ctrl_word))
        self.poke32(self.RSP_PERIOD_CONTROL, rsp_ctrl_word)
        self.poke32(self.RTC_PERIOD_CONTROL, rtc_ctrl_word)

        # Take the core out of reset, then check the reset done bit cleared.
        self.poke32(self.TDC_CONTROL, 0x2)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x00:
            self.log.error(
                "TDC Reset Failed to Clear! " \
                "Check that your clocks are toggling. Status: 0x{:x}".format(
                    reset_status
            ))
            raise RuntimeError("TDC Reset Failed.")
        self.log.trace("Enabling the TDC")
        # Enable the TDC.
        # As long as PPS is actually a PPS, this doesn't have to happen "synchronously"
        # across all devices.
        self.poke32(self.TDC_CONTROL, 0x10)

        # Since a PPS rising edge comes once per second, we need to wait
        # slightly longer than a second (worst-case) to confirm the TDC
        # received a PPS.
        if not poll_with_timeout(
                lambda: bool(self.peek32(self.TDC_STATUS) == 0x10),
                1100,  # Try for 1.1 seconds
                100,  # Poll every 100 ms
        ):
            # Try one last time, just in case there is weirdness with the polling loop.
            if self.peek32(self.TDC_STATUS) != 0x10:
                self.log.error("Failed to capture PPS within 1.1 seconds. " \
                               "TDC_STATUS: 0x{:X}".format(self.peek32(self.TDC_STATUS)))
                raise RuntimeError("Failed to capture PPS.")
        self.log.trace("PPS Captured!")

        measure_offset = lambda: self.read_tdc_meas(
            1.0 / self.meas_clk_freq, 1.0 / self.ref_clk_freq, 1.0 / self.
            radio_clk_freq)
        # Retrieve the first measurement, but throw it away since it won't align with
        # all the re-run measurements.
        self.log.trace("Throwing away first TDC measurement...")
        measure_offset()

        # Now, read off 512 measurements and take the mean of them.
        num_meas = 256
        self.log.trace(
            "Reading {} TDC measurements from device...".format(num_meas))
        current_value = mean([measure_offset() for _ in range(num_meas)])
        self.log.trace("TDC measurements collected.")

        # The high and low bounds for this are set programmatically based on the
        # Reference and Sample Frequencies and the TDC structure. The bounds are:
        # Low  = T_refclk + T_sampleclk*(3)
        # High = T_refclk + T_sampleclk*(4)
        # For slop, we add in another T_sampleclk on either side.
        low_bound = 1.0 / self.ref_clk_freq + (1.0 / self.radio_clk_freq) * 2
        high_bound = 1.0 / self.ref_clk_freq + (1.0 / self.radio_clk_freq) * 5
        if (current_value < low_bound) or (current_value > high_bound):
            self.log.error("Clock synchronizer measured a "
                           "current value of {:.3f} ns. " \
                           "Range is [{:.3f},{:.3f}] ns".format(
                               current_value*1e9,
                               low_bound*1e9,
                               high_bound*1e9))
            raise RuntimeError("TDC measurement out of range! "
                               "Current value: {:.3f} ns.".format(
                                   current_value * 1e9))

        # TEMP CODE for homogeneous rate sync only! Heterogeneous rate sync requires an
        # identical target value for all devices.
        target = 1.0 / self.ref_clk_freq + (1.0 / self.radio_clk_freq) * 3.5
        # 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-12, 1: 69.0e-12}[self.slot_idx]
        self.target_values = [
            target + trace_delay_offset,
        ]

        # Run the initial value through the oracle to determine the adjustments to make.
        coarse_steps_required, dac_word_delta, distance_to_target = self.oracle(
            self.target_values, current_value, self.lmk_vco_freq,
            self.fine_delay_step)

        # Check the calculated distance_to_target value. It should be less than
        # +/- 1 radio_clk_freq period. The boundary values are set using the same
        # logic as the high and low bound checks above on the current_value.
        if abs(distance_to_target) > 1.0 / self.radio_clk_freq:
            self.log.error("Clock synchronizer measured a "
                           "distance to target of {:.3f} ns. " \
                           "Range is [{:.3f},{:.3f}] ns".format(
                               distance_to_target*1e9,
                               -1.0/self.radio_clk_freq*1e9,
                               1.0/self.radio_clk_freq*1e9))
            raise RuntimeError(
                "TDC measured distance to target is out of range! "
                "Current value: {:.3f} ns.".format(distance_to_target * 1e9))

        if not measurement_only:
            self.log.trace("Applying calculated shifts...")
            # Coarse shift with the LMK.
            self.lmk.lmk_shift(coarse_steps_required)
            self.log.trace("LMK Shift Complete!")
            # Fine shift with the DAC, then give it time to take effect.
            self.write_dac_word(self.current_phase_dac_word + dac_word_delta,
                                0.5)
            if not self.lmk.check_plls_locked():
                raise RuntimeError(
                    "LMK PLLs lost lock during clock synchronization!")
            # After shifting the clocks, we enable the PPS crossing from the
            # RefClk into the SampleClk domain. We never explicitly turn off the
            # crossing from this point forward, even if we re-run this routine.
            self.poke32(self.TDC_CONTROL, 0x1000)

        return distance_to_target
Esempio n. 7
0
    def set_sync_source(self, args):
        """
        Selects reference clock and PPS sources. Unconditionally re-applies the time
        source to ensure continuity between the reference clock and time rates.
        """

        clock_source = args.get('clock_source', self._clock_source)
        assert clock_source in self.get_clock_sources()
        time_source = args.get('time_source', self._time_source)
        assert time_source in self.get_time_sources()
        if (clock_source == self._clock_source) and (time_source
                                                     == self._time_source):
            # Nothing change no need to do anything
            self.log.trace("New sync source assignment matches"
                           "previous assignment. Ignoring update command.")
            return
        assert (clock_source, time_source) in self.valid_sync_sources
        # Start setting sync source
        self.log.debug("Setting clock source to `{}'".format(clock_source))
        # Place the DB clocks in a safe state to allow reference clock
        # transitions. This leaves all the DB clocks OFF.
        for slot, dboard in enumerate(self.dboards):
            if hasattr(dboard, 'set_clk_safe_state'):
                self.log.trace(
                    "Setting dboard %d components to safe clocking state...",
                    slot)
                dboard.set_clk_safe_state()
        # Disable the Ref Clock in the FPGA before throwing the external switches.
        self.mboard_regs_control.enable_ref_clk(False)
        # Set the external switches to bring in the new source.
        if clock_source == 'internal':
            self._gpios.set("CLK-MAINSEL-EX_B")
            self._gpios.set("CLK-MAINSEL-25MHz")
            self._gpios.reset("CLK-MAINSEL-GPS")
        elif clock_source == 'gpsdo':
            self._gpios.set("CLK-MAINSEL-EX_B")
            self._gpios.reset("CLK-MAINSEL-25MHz")
            self._gpios.set("CLK-MAINSEL-GPS")
        else:  # external
            self._gpios.reset("CLK-MAINSEL-EX_B")
            self._gpios.reset("CLK-MAINSEL-GPS")
            # SKY13350 needs to be in known state
            self._gpios.set("CLK-MAINSEL-25MHz")
        self._clock_source = clock_source
        self.log.debug("Reference clock source is: {}" \
                       .format(self._clock_source))
        self.log.debug("Reference clock frequency is: {} MHz" \
                       .format(self.get_ref_clock_freq()/1e6))
        # Enable the Ref Clock in the FPGA after giving it a chance to
        # settle. The settling time is a guess.
        time.sleep(0.100)
        self.mboard_regs_control.enable_ref_clk(True)
        self.log.debug("Setting time source to `{}'".format(time_source))
        self._time_source = time_source
        ref_clk_freq = self.get_ref_clock_freq()
        self.mboard_regs_control.set_time_source(time_source, ref_clk_freq)
        if time_source == 'sfp0':
            # This error is specific to slave and master mode for White Rabbit.
            # Grand Master mode will require the external or gpsdo
            # sources (not supported).
            if time_source in ('sfp0', 'sfp1') \
                    and self.get_clock_source() != 'internal':
                error_msg = "Time source {} requires `internal` clock source!".format(
                    time_source)
                self.log.error(error_msg)
                raise RuntimeError(error_msg)
            sfp_time_source_images = ('WX', )
            if self.updateable_components['fpga'][
                    'type'] not in sfp_time_source_images:
                self.log.error("{} time source requires FPGA types {}" \
                               .format(time_source, sfp_time_source_images))
                raise RuntimeError("{} time source requires FPGA types {}" \
                               .format(time_source, sfp_time_source_images))
            # Only open UIO to the WR core once we're guaranteed it exists.
            wr_regs_control = WhiteRabbitRegsControl(self.wr_regs_label,
                                                     self.log)
            # Wait for time source to become ready. Only applies to SFP0/1. All other
            # targets start their PPS immediately.
            self.log.debug("Waiting for {} timebase to lock..." \
                           .format(time_source))
            if not poll_with_timeout(
                    lambda: wr_regs_control.get_time_lock_status(),
                    40000,  # Try for x ms... this number is set from a few benchtop tests
                    1000,  # Poll every... second! why not?
            ):
                self.log.error("{} timebase failed to lock within 40 seconds. Status: 0x{:X}" \
                               .format(time_source, wr_regs_control.get_time_lock_status()))
                raise RuntimeError("Failed to lock SFP timebase.")
        # Update the DB with the correct Ref Clock frequency and force a re-init.
        for slot, dboard in enumerate(self.dboards):
            self.log.trace(
                "Updating reference clock on dboard %d to %f MHz...", slot,
                ref_clk_freq / 1e6)
            dboard.update_ref_clock_freq(ref_clk_freq,
                                         time_source=time_source,
                                         clock_source=clock_source,
                                         skip_rfic=args.get('skip_rfic', None))
Esempio n. 8
0
    def configure(self, force=False):
        """
        Perform a soft reset on the TDC, then configure the TDC registers in the FPGA
        based on the reference and master clock rates. Enable the TDC and wait for the
        next PPS to arrive. Will throw on error. Otherwise returns nothing.
        """
        if self.configured:
            if not force:
                self.log.debug("TDC is already configured. " \
                               "Skipping configuration sequence!")
                return None
            else:
                # Apparently force is specified... this could lead to some strange
                # TDC behavior, but we do it anyway.
                self.log.debug("TDC is already configured, but Force is specified..." \
                               "reconfiguring the TDC anyway!")

        self.log.debug("Configuring the TDC...")
        self.log.trace("Using reference clock frequency: {:.3f} MHz" \
            .format(self.ref_clk_freq/1e6))
        self.log.trace("Using master clock frequency: {:.3f} MHz" \
            .format(self.radio_clk_freq/1e6))

        meas_clk_ref_freq = 166.666666666667e6
        if self.tdc_rev == 1:
            self.meas_clk_freq = meas_clk_ref_freq * 5.5 / 1 / 5.375
        else:
            self.meas_clk_freq = meas_clk_ref_freq * 21.875 / 3 / 6.125
        self.log.trace("Using measurement clock frequency: {:.10f} MHz" \
            .format(self.meas_clk_freq/1e6))

        self.configured = False
        # Reset and disable TDC, clear PPS crossing, and enable re-runs. Confirm the
        # core is in reset and PPS is cleared.
        self.poke32(self.TDC_CONTROL, 0x2121)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x01:
            self.log.error("TDC Failed to Reset! Check your clocks! Status: 0x{:x}" \
                .format(reset_status))
            raise RuntimeError("TDC Failed to reset.")

        def get_pulse_setup(clk_freq, pulser, compat_mode=False):
            """
            Set the pulser divide values based on the given clock rates.
            Returns register value required to create the desired pulses.
            """
            # Compatibility mode runs at 40 kHz. This only supports these clock rates:
            # 10, 20, 25, 125, 122.88, and 153.6 MHz. Any other rates are expected
            # to use the TDC 2.0 and later.
            if compat_mode:
                pulse_rate = 40e3
            # The RP always runs at 1 MHz since all of our reference clock rates are
            # multiples of 1.
            elif pulser == "rp":
                pulse_rate = 1.00e6
            # The SP either runs at 1.0, 1.2288, or 1.25 MHz. If the clock rate doesn't
            # divide nicely into 1 MHz, we can use the alternative rates.
            elif pulser == "sp":
                pulse_rate = 1.00e6
                if math.modf(clk_freq / pulse_rate)[0] > 0:
                    pulse_rate = 1.2288e6
                    if math.modf(clk_freq / pulse_rate)[0] > 0:
                        pulse_rate = 1.25e6
            # The Restart-pulser must run at the GCD of the RP and SP rates, not the
            # Reference Clock and Radio Clock rates! For ease of implementation,
            # run the pulser at a fixed value.
            elif (pulser == "repulse-1") or (pulser == "repulse-2"):
                pulse_rate = 1.6e3
                # 156.25 MHz: this value needs to be 400 Hz, which is insanely
                # slow and doubles the measurement time... so only use it for this
                # specific case.
                if clk_freq == 156.25e6:
                    pulse_rate = 400
            # The RP-t and SP-t pulsers always run at 10 kHz, which is the GCD of all
            # supported clock rates.
            elif (pulser == "rpt") or (pulser == "spt"):
                pulse_rate = 10e3
            else:
                pulse_rate = 10e3

            # Check that the chosen pulse_rate divides evenly in the clk_freq.
            if math.modf(clk_freq / pulse_rate)[0] > 0:
                self.log.error("TDC Setup Failure: Pulse rate setup failed for {}!" \
                    .format(pulser))
                raise RuntimeError("TDC Failed to Initialize. Check your clock rates " \
                                   "for compatibility!")

            period = int(clk_freq / pulse_rate)
            hi_time = int(math.floor(period / 2))
            if pulser == "repulse-1":
                # The re-pulse is broken into two registers:
                # -1 is the period and -2 is the high time + compatibility bit
                assert period <= 0xFFFFFF  # 24 bits
                return period & 0xFFFFFF
            elif pulser == "repulse-2":
                assert hi_time <= 0x7FFFFF  # 23 bits
                return (hi_time & 0x7FFFFF)
            # All the other registers are packed [30:16] = high time, [15:0] = period.
            # hi_time is period/2 so we only use 15 bits.
            assert hi_time <= 0x7FFF and period <= 0xFFFF
            return (hi_time << 16) | period

        compat_mode = self.tdc_rev < 2
        if compat_mode:
            self.log.warning("Running TDC in Compatibility Mode for v1.0!")

        repulse_ctrl_word1 = get_pulse_setup(self.ref_clk_freq, "repulse-1")
        repulse_ctrl_word2 = get_pulse_setup(self.ref_clk_freq, "repulse-2")
        rp_ctrl_word = get_pulse_setup(self.ref_clk_freq, "rp", compat_mode)
        sp_ctrl_word = get_pulse_setup(self.radio_clk_freq, "sp", compat_mode)
        rpt_ctrl_word = get_pulse_setup(self.ref_clk_freq, "rpt")
        spt_ctrl_word = get_pulse_setup(self.radio_clk_freq, "spt")
        self.log.trace("Setting RePulse-1 control word to: 0x{:08X}".format(
            repulse_ctrl_word1))
        self.log.trace("Setting RePulse-2 control word to: 0x{:08X}".format(
            repulse_ctrl_word2))
        self.log.trace(
            "Setting RP  control word to: 0x{:08X}".format(rp_ctrl_word))
        self.log.trace(
            "Setting SP  control word to: 0x{:08X}".format(sp_ctrl_word))
        self.log.trace(
            "Setting RPT control word to: 0x{:08X}".format(rpt_ctrl_word))
        self.log.trace(
            "Setting SPT control word to: 0x{:08X}".format(spt_ctrl_word))
        self.poke32(self.REPULSE_PERIOD_CONTROL_1, repulse_ctrl_word1)
        self.poke32(self.REPULSE_PERIOD_CONTROL_2, repulse_ctrl_word2)
        self.poke32(self.RP_PERIOD_CONTROL, rp_ctrl_word)
        self.poke32(self.SP_PERIOD_CONTROL, sp_ctrl_word)
        self.poke32(self.RPT_PERIOD_CONTROL, rpt_ctrl_word)
        self.poke32(self.SPT_PERIOD_CONTROL, spt_ctrl_word)

        # Take the core out of reset, then check the reset done bit cleared.
        self.poke32(self.TDC_CONTROL, 0x2)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x00:
            self.log.error(
                "TDC Reset Failed to Clear! " \
                "Check that your clocks are toggling. Status: 0x{:x}"
                .format(reset_status)
            )
            raise RuntimeError("TDC Reset Failed.")

        # Set the PPS crossing delay from the SP-t rising edge to the PPS pulse
        # in the Radio Clock domain.
        # delay = [19..16], update = 20
        reg_val = (self.pps_out_pipe_var_delay & 0xF) << 16 | 0b1 << 20
        self.poke32(self.TDC_CONTROL, reg_val)

        # Enable the TDC to capture the PPS. As long as PPS is actually a PPS, this
        # doesn't have to happen "synchronously" across all devices. Each device can
        # choose a different PPS and still be aligned.
        self.log.trace("Enabling the TDC...")
        self.poke32(self.TDC_CONTROL, 0x10)

        # Since a PPS rising edge comes once per second, we only need to wait
        # slightly longer than a second (worst-case) to confirm the TDC
        # received a PPS.
        if not poll_with_timeout(
                lambda: bool(self.peek32(self.TDC_STATUS) == 0x10),
                1100,  # Try for 1.1 seconds
                100,  # Poll every 100 ms
        ):
            # Try one last time, just in case there is weirdness with the polling loop.
            if self.peek32(self.TDC_STATUS) != 0x10:
                self.log.error("Failed to capture PPS within 1.1 seconds. " \
                               "TDC_STATUS: 0x{:X}".format(self.peek32(self.TDC_STATUS)))
                raise RuntimeError("Failed to capture PPS.")
        self.log.trace("PPS Captured!")
        self.configured = True
Esempio n. 9
0
    def configure(self, force=False):
        """
        Perform a soft reset on the TDC, then configure the TDC registers in the FPGA
        based on the reference and master clock rates. Enable the TDC and wait for the
        next PPS to arrive. Will throw on error. Otherwise returns nothing.
        """
        if self.configured:
            if not force:
                self.log.debug("TDC is already configured. " \
                               "Skipping configuration sequence!")
                return None
            else:
                # Apparently force is specified... this could lead to some strange
                # TDC behavior, but we do it anyway.
                self.log.debug("TDC is already configured, but Force is specified..." \
                               "reconfiguring the TDC anyway!")

        self.log.debug("Configuring the TDC...")
        self.log.trace("Using reference clock frequency: {:.3f} MHz" \
            .format(self.ref_clk_freq/1e6))
        self.log.trace("Using master clock frequency: {:.3f} MHz" \
            .format(self.radio_clk_freq/1e6))

        meas_clk_ref_freq = 166.666666666667e6
        if self.tdc_rev == 1:
            self.meas_clk_freq = meas_clk_ref_freq*5.5/1/5.375
        else:
            self.meas_clk_freq = meas_clk_ref_freq*21.875/3/6.125
        self.log.trace("Using measurement clock frequency: {:.10f} MHz" \
            .format(self.meas_clk_freq/1e6))

        self.configured = False
        # Reset and disable TDC, clear PPS crossing, and enable re-runs. Confirm the
        # core is in reset and PPS is cleared.
        self.poke32(self.TDC_CONTROL, 0x2121)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x01:
            self.log.error("TDC Failed to Reset! Check your clocks! Status: 0x{:x}" \
                .format(reset_status))
            raise RuntimeError("TDC Failed to reset.")

        def find_rate(clk_freq, rates):
            """
            Go through the rates list in sequential order, checking each rate for
            even division into our clk_freq. Return the first one we find.
            """
            for rate in rates:
                if math.modf(clk_freq/rate)[0] == 0:
                    return rate
            self.log.error("TDC Setup Failure: Pulse rate setup failed for {:.4f} MHz!" \
                .format(clk_freq/1e6))
            raise RuntimeError("TDC Failed to Initialize. No pulse rate found!")

        def get_pulse_setup(clk_freq, pulser, compat_mode=False):
            """
            Set the pulser divide values based on the given clock rates.
            Returns register value required to create the desired pulses.
            """
            # Compatibility mode runs at 40 kHz. This only supports these clock rates:
            # 10, 20, 25, 125, 122.88, and 153.6 MHz. Any other rates are expected
            # to use the TDC 2.0 and later.
            if compat_mode:
                pulse_rate = find_rate(clk_freq, [40e3])
            elif (pulser == "rp") or (pulser == "sp"):
                pulse_rate = find_rate(clk_freq, self.SUPPORTED_PULSE_RATES)
            # The RP-t and SP-t pulsers always run at 10 kHz, which is the GCD of all
            # supported clock rates.
            elif (pulser == "rpt") or (pulser == "spt"):
                pulse_rate = find_rate(clk_freq, [10e3])
            else:
                self.log.error("TDC Setup Failure: Unrecognized pulse name given: {}" \
                    .format(pulser))
                raise RuntimeError("TDC Failed to Initialize. "
                                   "Unrecognized pulse name given!")

            period = int(clk_freq/pulse_rate)
            hi_time = int(math.floor(period/2))
            # All registers are packed [30:16] = high time, [15:0] = period.
            # hi_time is period/2 so we only use 15 bits.
            assert hi_time <= 0x7FFF and period <= 0xFFFF
            ctrl_word = (hi_time << 16) | period
            return pulse_rate, ctrl_word

        def get_restart_pulse_setup(rp_rate, sp_rate):
            """
            Set the restart pulser divide values based on the repeating pulse rates.
            Returns register value required to create the desired pulses.
            """
            # The Restart-pulser must run at the GCD of the RP and SP rates, not the
            # Reference Clock and Radio Clock rates!
            pulse_rate = find_rate(self.ref_clk_freq, [gcd(rp_rate, sp_rate)])
            period = int(self.ref_clk_freq/pulse_rate)
            hi_time = int(math.floor(period/2))
            # The re-pulse is broken into two registers:
            # -1 is the period and -2 is the high time
            assert period <= 0xFFFFFF # 24 bits
            assert hi_time <= 0x7FFFFF # 23 bits
            return pulse_rate, (period & 0xFFFFFF), (hi_time & 0x7FFFFF)

        compat_mode = self.tdc_rev < 2
        if compat_mode:
            self.log.warning("Running TDC in Compatibility Mode for v1.0!")

        rp_rate, rp_ctrl_word  = get_pulse_setup(self.ref_clk_freq,  "rp", compat_mode)
        sp_rate, sp_ctrl_word  = get_pulse_setup(self.radio_clk_freq,"sp", compat_mode)
        rpt_rate,rpt_ctrl_word = get_pulse_setup(self.ref_clk_freq,  "rpt")
        spt_rate,spt_ctrl_word = get_pulse_setup(self.radio_clk_freq,"spt")
        rep_rate, repulse_ctrl_word1, repulse_ctrl_word2 = \
            get_restart_pulse_setup(rp_rate, sp_rate)
        self.log.trace("Using RP  pulse rate: {} MHz".format(rp_rate/1e6))
        self.log.trace("Using SP  pulse rate: {} MHz".format(sp_rate/1e6))
        self.log.trace("Using RPT pulse rate: {} MHz".format(rpt_rate/1e6))
        self.log.trace("Using SPT pulse rate: {} MHz".format(spt_rate/1e6))
        self.log.trace("Using Restart pulse rate: {} MHz".format(rep_rate/1e6))
        self.log.trace("Setting RP  control word to: 0x{:08X}".format(rp_ctrl_word))
        self.log.trace("Setting SP  control word to: 0x{:08X}".format(sp_ctrl_word))
        self.log.trace("Setting RPT control word to: 0x{:08X}".format(rpt_ctrl_word))
        self.log.trace("Setting SPT control word to: 0x{:08X}".format(spt_ctrl_word))
        self.log.trace("Setting RePulse-1 control word to: 0x{:08X}" \
                       .format(repulse_ctrl_word1))
        self.log.trace("Setting RePulse-2 control word to: 0x{:08X}" \
                       .format(repulse_ctrl_word2))
        self.poke32(self.REPULSE_PERIOD_CONTROL_1, repulse_ctrl_word1)
        self.poke32(self.REPULSE_PERIOD_CONTROL_2, repulse_ctrl_word2)
        self.poke32(self.RP_PERIOD_CONTROL,  rp_ctrl_word)
        self.poke32(self.SP_PERIOD_CONTROL,  sp_ctrl_word)
        self.poke32(self.RPT_PERIOD_CONTROL, rpt_ctrl_word)
        self.poke32(self.SPT_PERIOD_CONTROL, spt_ctrl_word)

        # Take the core out of reset, then check the reset done bit cleared.
        self.poke32(self.TDC_CONTROL, 0x2)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x00:
            self.log.error(
                "TDC Reset Failed to Clear! " \
                "Check that your clocks are toggling. Status: 0x{:x}"
                .format(reset_status)
            )
            raise RuntimeError("TDC Reset Failed.")

        # Set the PPS crossing delay from the SP-t rising edge to the PPS pulse
        # in the Radio Clock domain.
        # delay = [19..16], update = 20
        reg_val = (self.pps_out_pipe_var_delay & 0xF) << 16 | 0b1 << 20
        self.poke32(self.TDC_CONTROL, reg_val)

        # Set the pulser enable delay from the RePulse enable to the RP enable.
        # Actual delay is +1 whatever is written here. Default is 1.
        # delay = [27..24], update = 28
        reg_val = (self.pps_in_pipe_dynamic_delay & 0xF) << 24 | 0b1 << 28
        self.poke32(self.TDC_CONTROL, reg_val)

        # Enable the TDC to capture the PPS. As long as PPS is actually a PPS, this
        # doesn't have to happen "synchronously" across all devices. Each device can
        # choose a different PPS and still be aligned.
        self.log.trace("Enabling the TDC...")
        self.poke32(self.TDC_CONTROL, 0x10)

        # Since a PPS rising edge comes once per second, we only need to wait
        # slightly longer than a second (worst-case) to confirm the TDC
        # received a PPS.
        if not poll_with_timeout(
                lambda: bool(self.peek32(self.TDC_STATUS) == 0x10),
                1100, # Try for 1.1 seconds
                100, # Poll every 100 ms
            ):
            # Try one last time, just in case there is weirdness with the polling loop.
            if self.peek32(self.TDC_STATUS) != 0x10:
                self.log.error("Failed to capture PPS within 1.1 seconds. " \
                               "TDC_STATUS: 0x{:X}".format(self.peek32(self.TDC_STATUS)))
                raise RuntimeError("Failed to capture PPS.")
        self.log.trace("PPS Captured!")
        self.configured = True
Esempio n. 10
0
File: n3xx.py Progetto: dkozel/uhd
    def set_sync_source(self, args):
        """
        Selects reference clock and PPS sources. Unconditionally re-applies the time
        source to ensure continuity between the reference clock and time rates.
        """

        clock_source = args.get('clock_source', self._clock_source)
        assert clock_source in self.get_clock_sources()
        time_source = args.get('time_source', self._time_source)
        assert time_source in self.get_time_sources()
        if (clock_source == self._clock_source) and (time_source == self._time_source):
            # Nothing change no need to do anything
            self.log.trace("New sync source assignment matches"
                           "previous assignment. Ignoring update command.")
            return
        assert (clock_source, time_source) in self.valid_sync_sources
        # Start setting sync source
        self.log.debug("Setting clock source to `{}'".format(clock_source))
        # Place the DB clocks in a safe state to allow reference clock
        # transitions. This leaves all the DB clocks OFF.
        for slot, dboard in enumerate(self.dboards):
            if hasattr(dboard, 'set_clk_safe_state'):
                self.log.trace(
                    "Setting dboard %d components to safe clocking state...", slot)
                dboard.set_clk_safe_state()
        # Disable the Ref Clock in the FPGA before throwing the external switches.
        self.mboard_regs_control.enable_ref_clk(False)
        # Set the external switches to bring in the new source.
        if clock_source == 'internal':
            self._gpios.set("CLK-MAINSEL-EX_B")
            self._gpios.set("CLK-MAINSEL-25MHz")
            self._gpios.reset("CLK-MAINSEL-GPS")
        elif clock_source == 'gpsdo':
            self._gpios.set("CLK-MAINSEL-EX_B")
            self._gpios.reset("CLK-MAINSEL-25MHz")
            self._gpios.set("CLK-MAINSEL-GPS")
        else: # external
            self._gpios.reset("CLK-MAINSEL-EX_B")
            self._gpios.reset("CLK-MAINSEL-GPS")
            # SKY13350 needs to be in known state
            self._gpios.set("CLK-MAINSEL-25MHz")
        self._clock_source = clock_source
        self.log.debug("Reference clock source is: {}" \
                       .format(self._clock_source))
        self.log.debug("Reference clock frequency is: {} MHz" \
                       .format(self.get_ref_clock_freq()/1e6))
        # Enable the Ref Clock in the FPGA after giving it a chance to
        # settle. The settling time is a guess.
        time.sleep(0.100)
        self.mboard_regs_control.enable_ref_clk(True)
        self.log.debug("Setting time source to `{}'".format(time_source))
        self._time_source = time_source
        ref_clk_freq = self.get_ref_clock_freq()
        self.mboard_regs_control.set_time_source(time_source, ref_clk_freq)
        if time_source == 'sfp0':
            # This error is specific to slave and master mode for White Rabbit.
            # Grand Master mode will require the external or gpsdo
            # sources (not supported).
            if time_source in ('sfp0', 'sfp1') \
                    and self.get_clock_source() != 'internal':
                error_msg = "Time source {} requires `internal` clock source!".format(
                    time_source)
                self.log.error(error_msg)
                raise RuntimeError(error_msg)
            sfp_time_source_images = ('WX',)
            if self.updateable_components['fpga']['type'] not in sfp_time_source_images:
                self.log.error("{} time source requires FPGA types {}" \
                               .format(time_source, sfp_time_source_images))
                raise RuntimeError("{} time source requires FPGA types {}" \
                               .format(time_source, sfp_time_source_images))
            # Only open UIO to the WR core once we're guaranteed it exists.
            wr_regs_control = WhiteRabbitRegsControl(
                self.wr_regs_label, self.log)
            # Wait for time source to become ready. Only applies to SFP0/1. All other
            # targets start their PPS immediately.
            self.log.debug("Waiting for {} timebase to lock..." \
                           .format(time_source))
            if not poll_with_timeout(
                    lambda: wr_regs_control.get_time_lock_status(),
                    40000, # Try for x ms... this number is set from a few benchtop tests
                    1000, # Poll every... second! why not?
                ):
                self.log.error("{} timebase failed to lock within 40 seconds. Status: 0x{:X}" \
                               .format(time_source, wr_regs_control.get_time_lock_status()))
                raise RuntimeError("Failed to lock SFP timebase.")
        # Update the DB with the correct Ref Clock frequency and force a re-init.
        for slot, dboard in enumerate(self.dboards):
            self.log.trace(
                "Updating reference clock on dboard %d to %f MHz...",
                slot, ref_clk_freq/1e6
            )
            dboard.update_ref_clock_freq(
                ref_clk_freq,
                time_source=time_source,
                clock_source=clock_source,
                skip_rfic=args.get('skip_rfic', None)
            )
Esempio n. 11
0
    def configure(self, force=False):
        """
        Perform a soft reset on the TDC, then configure the TDC registers in the FPGA
        based on the reference and master clock rates. Enable the TDC and wait for the
        next PPS to arrive. Will throw on error. Otherwise returns nothing.
        """
        if self.configured:
            if not force:
                self.log.debug("TDC is already configured. " \
                               "Skipping configuration sequence!")
                return None
            else:
                # Apparently force is specified... this could lead to some strange
                # TDC behavior, but we do it anyway.
                self.log.debug("TDC is already configured, but Force is specified..." \
                               "reconfiguring the TDC anyway!")

        self.log.debug("Configuring the TDC...")
        self.log.trace("Using reference clock frequency: {:.3f} MHz" \
            .format(self.ref_clk_freq/1e6))
        self.log.trace("Using master clock frequency: {:.3f} MHz" \
            .format(self.radio_clk_freq/1e6))

        meas_clk_ref_freq = 166.666666666667e6
        if self.tdc_rev == 1:
            self.meas_clk_freq = meas_clk_ref_freq * 5.5 / 1 / 5.375
        else:
            self.meas_clk_freq = meas_clk_ref_freq * 21.875 / 3 / 6.125
        self.log.trace("Using measurement clock frequency: {:.10f} MHz" \
            .format(self.meas_clk_freq/1e6))

        self.configured = False
        # Reset and disable TDC, clear PPS crossing, and enable re-runs. Confirm the
        # core is in reset and PPS is cleared.
        self.poke32(self.TDC_CONTROL, 0x2121)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x01:
            self.log.error("TDC Failed to Reset! Check your clocks! Status: 0x{:x}" \
                .format(reset_status))
            raise RuntimeError("TDC Failed to reset.")

        def find_rate(clk_freq, rates):
            """
            Go through the rates list in sequential order, checking each rate for
            even division into our clk_freq. Return the first one we find.
            """
            for rate in rates:
                if math.modf(clk_freq / rate)[0] == 0:
                    return rate
            self.log.error("TDC Setup Failure: Pulse rate setup failed for {:.4f} MHz!" \
                .format(clk_freq/1e6))
            raise RuntimeError(
                "TDC Failed to Initialize. No pulse rate found!")

        def get_pulse_setup(clk_freq, pulser, compat_mode=False):
            """
            Set the pulser divide values based on the given clock rates.
            Returns register value required to create the desired pulses.
            """
            # Compatibility mode runs at 40 kHz. This only supports these clock rates:
            # 10, 20, 25, 125, 122.88, and 153.6 MHz. Any other rates are expected
            # to use the TDC 2.0 and later.
            if compat_mode:
                pulse_rate = find_rate(clk_freq, [40e3])
            elif (pulser == "rp") or (pulser == "sp"):
                pulse_rate = find_rate(clk_freq, self.SUPPORTED_PULSE_RATES)
            # The RP-t and SP-t pulsers always run at 10 kHz, which is the GCD of all
            # supported clock rates.
            elif (pulser == "rpt") or (pulser == "spt"):
                pulse_rate = find_rate(clk_freq, [10e3])
            else:
                self.log.error("TDC Setup Failure: Unrecognized pulse name given: {}" \
                    .format(pulser))
                raise RuntimeError("TDC Failed to Initialize. "
                                   "Unrecognized pulse name given!")

            period = int(clk_freq / pulse_rate)
            hi_time = int(math.floor(period / 2))
            # All registers are packed [30:16] = high time, [15:0] = period.
            # hi_time is period/2 so we only use 15 bits.
            assert hi_time <= 0x7FFF and period <= 0xFFFF
            ctrl_word = (hi_time << 16) | period
            return pulse_rate, ctrl_word

        def get_restart_pulse_setup(rp_rate, sp_rate):
            """
            Set the restart pulser divide values based on the repeating pulse rates.
            Returns register value required to create the desired pulses.
            """
            # The Restart-pulser must run at the GCD of the RP and SP rates, not the
            # Reference Clock and Radio Clock rates!
            pulse_rate = find_rate(self.ref_clk_freq, [gcd(rp_rate, sp_rate)])
            period = int(self.ref_clk_freq / pulse_rate)
            hi_time = int(math.floor(period / 2))
            # The re-pulse is broken into two registers:
            # -1 is the period and -2 is the high time
            assert period <= 0xFFFFFF  # 24 bits
            assert hi_time <= 0x7FFFFF  # 23 bits
            return pulse_rate, (period & 0xFFFFFF), (hi_time & 0x7FFFFF)

        compat_mode = self.tdc_rev < 2
        if compat_mode:
            self.log.warning("Running TDC in Compatibility Mode for v1.0!")

        rp_rate, rp_ctrl_word = get_pulse_setup(self.ref_clk_freq, "rp",
                                                compat_mode)
        sp_rate, sp_ctrl_word = get_pulse_setup(self.radio_clk_freq, "sp",
                                                compat_mode)
        rpt_rate, rpt_ctrl_word = get_pulse_setup(self.ref_clk_freq, "rpt")
        spt_rate, spt_ctrl_word = get_pulse_setup(self.radio_clk_freq, "spt")
        rep_rate, repulse_ctrl_word1, repulse_ctrl_word2 = \
            get_restart_pulse_setup(rp_rate, sp_rate)
        self.log.trace("Using RP  pulse rate: {} MHz".format(rp_rate / 1e6))
        self.log.trace("Using SP  pulse rate: {} MHz".format(sp_rate / 1e6))
        self.log.trace("Using RPT pulse rate: {} MHz".format(rpt_rate / 1e6))
        self.log.trace("Using SPT pulse rate: {} MHz".format(spt_rate / 1e6))
        self.log.trace("Using Restart pulse rate: {} MHz".format(rep_rate /
                                                                 1e6))
        self.log.trace(
            "Setting RP  control word to: 0x{:08X}".format(rp_ctrl_word))
        self.log.trace(
            "Setting SP  control word to: 0x{:08X}".format(sp_ctrl_word))
        self.log.trace(
            "Setting RPT control word to: 0x{:08X}".format(rpt_ctrl_word))
        self.log.trace(
            "Setting SPT control word to: 0x{:08X}".format(spt_ctrl_word))
        self.log.trace("Setting RePulse-1 control word to: 0x{:08X}" \
                       .format(repulse_ctrl_word1))
        self.log.trace("Setting RePulse-2 control word to: 0x{:08X}" \
                       .format(repulse_ctrl_word2))
        self.poke32(self.REPULSE_PERIOD_CONTROL_1, repulse_ctrl_word1)
        self.poke32(self.REPULSE_PERIOD_CONTROL_2, repulse_ctrl_word2)
        self.poke32(self.RP_PERIOD_CONTROL, rp_ctrl_word)
        self.poke32(self.SP_PERIOD_CONTROL, sp_ctrl_word)
        self.poke32(self.RPT_PERIOD_CONTROL, rpt_ctrl_word)
        self.poke32(self.SPT_PERIOD_CONTROL, spt_ctrl_word)

        # Take the core out of reset, then check the reset done bit cleared.
        self.poke32(self.TDC_CONTROL, 0x2)
        reset_status = self.peek32(self.TDC_STATUS) & 0xFF
        if reset_status != 0x00:
            self.log.error(
                "TDC Reset Failed to Clear! " \
                "Check that your clocks are toggling. Status: 0x{:x}"
                .format(reset_status)
            )
            raise RuntimeError("TDC Reset Failed.")

        # Set the PPS crossing delay from the SP-t rising edge to the PPS pulse
        # in the Radio Clock domain.
        # delay = [19..16], update = 20
        reg_val = (self.pps_out_pipe_var_delay & 0xF) << 16 | 0b1 << 20
        self.poke32(self.TDC_CONTROL, reg_val)

        # Set the pulser enable delay from the RePulse enable to the RP enable.
        # Actual delay is +1 whatever is written here. Default is 1.
        # delay = [27..24], update = 28
        reg_val = (self.pps_in_pipe_dynamic_delay & 0xF) << 24 | 0b1 << 28
        self.poke32(self.TDC_CONTROL, reg_val)

        # Enable the TDC to capture the PPS. As long as PPS is actually a PPS, this
        # doesn't have to happen "synchronously" across all devices. Each device can
        # choose a different PPS and still be aligned.
        self.log.trace("Enabling the TDC...")
        self.poke32(self.TDC_CONTROL, 0x10)

        # Since a PPS rising edge comes once per second, we only need to wait
        # slightly longer than a second (worst-case) to confirm the TDC
        # received a PPS.
        if not poll_with_timeout(
                lambda: bool(self.peek32(self.TDC_STATUS) == 0x10),
                1100,  # Try for 1.1 seconds
                100,  # Poll every 100 ms
        ):
            # Try one last time, just in case there is weirdness with the polling loop.
            if self.peek32(self.TDC_STATUS) != 0x10:
                self.log.error("Failed to capture PPS within 1.1 seconds. " \
                               "TDC_STATUS: 0x{:X}".format(self.peek32(self.TDC_STATUS)))
                raise RuntimeError("Failed to capture PPS.")
        self.log.trace("PPS Captured!")
        self.configured = True