diff options
| author | Daniel Jepson <daniel.jepson@ni.com> | 2018-08-02 09:00:01 -0500 | 
|---|---|---|
| committer | Martin Braun <martin.braun@ettus.com> | 2018-08-02 12:37:34 -0700 | 
| commit | 065740babdea2cac56473db040cd67b84f3fa598 (patch) | |
| tree | ee335769fb98a612663dfef0d85ff2d5c51130d0 /mpm/python/usrp_mpm | |
| parent | 5f624d6592c82d65c14d2d81e6add5147a77c39c (diff) | |
| download | uhd-065740babdea2cac56473db040cd67b84f3fa598.tar.gz uhd-065740babdea2cac56473db040cd67b84f3fa598.tar.bz2 uhd-065740babdea2cac56473db040cd67b84f3fa598.zip | |
mpm: n3xx: clocking API changes for transitioning clock and time sources
Added set_sync_source method to set both the time and clock sources
without forcing a re-init twice. Modified the existing set_time_source
and set_clock_source methods to call into set_sync_source.
Diffstat (limited to 'mpm/python/usrp_mpm')
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/magnesium.py | 84 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/mg_init.py | 1 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 230 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py | 17 | 
4 files changed, 231 insertions, 101 deletions
| diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index 35d3bb22f..c67acdc55 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -15,6 +15,8 @@ from usrp_mpm import lib # Pulls in everything from C++-land  from usrp_mpm.dboard_manager import DboardManagerBase  from usrp_mpm.dboard_manager.mg_periphs import TCA6408, MgCPLD  from usrp_mpm.dboard_manager.mg_init import MagnesiumInitManager +from usrp_mpm.dboard_manager.mg_periphs import DboardClockControl +from usrp_mpm.cores import nijesdcore  from usrp_mpm.mpmlog import get_logger  from usrp_mpm.sys_utils.uio import open_uio  from usrp_mpm.sys_utils.udev import get_eeprom_paths @@ -134,7 +136,8 @@ class Magnesium(DboardManagerBase):          self.eeprom_fs = None          self.eeprom_path = None          self.cpld = None -        self._init_args = {} +        # If _init_args is None, it means that init() hasn't yet been called. +        self._init_args = None          # Now initialize all peripherals. If that doesn't work, put this class          # into a non-functional state (but don't crash, or we can't talk to it          # any more): @@ -291,6 +294,10 @@ class Magnesium(DboardManagerBase):              if new_ref_clock_freq != self.ref_clock_freq:                  self.ref_clock_freq = float(args['ref_clk_freq'])                  ref_clk_freq_changed = True +                self.log.debug( +                    "Updating reference clock frequency to {:.02f} MHz!" +                    .format(self.ref_clock_freq / 1e6) +                )          assert self.ref_clock_freq is not None          # Check if master clock freq changed (would require a full init)          master_clock_rate = \ @@ -390,19 +397,90 @@ class Magnesium(DboardManagerBase):          # This does not stop anyone from killing this process (and the thread)          # while the EEPROM write is happening, though. +    ########################################################################## +    # Clocking control APIs +    ########################################################################## +    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() +            # The reference clock is handled elsewhere since it is a motherboard- +            # level clock. + + +    def _reinit(self, master_clock_rate): +        """ +        This will re-run init(). We store all the settings in _init_args, so we +        will bring the device into the same state that it was before, with the +        exception of frequency and gain. Those need to be re-set by UHD in order +        not to invalidate the UHD caches. +        """ +        args = self._init_args +        args["master_clock_rate"] = master_clock_rate +        args["ref_clk_freq"] = self.ref_clock_freq +        # If we add API calls to reset the cals, they need to update +        # self._init_args +        self.master_clock_rate = None # <= This will force a re-init +        self.init(args) +        # self.master_clock_rate is now OK again + + +    def set_master_clock_rate(self, rate): +        """ +        Set the master clock rate to rate. Note this will trigger a +        re-initialization of the entire clocking, unless rate matches the +        current master clock rate. +        """ +        if rate == self.master_clock_rate: +            self.log.debug( +                "New master clock rate assignment matches previous assignment. " +                "Ignoring set_master_clock_rate() command.") +            return self.master_clock_rate +        self._reinit(rate) +        return rate +      def get_master_clock_rate(self):          " Return master clock rate (== sampling rate) "          return self.master_clock_rate      def update_ref_clock_freq(self, freq):          """ -        Call this function if the frequency of the reference clock changes (the -        10, 20, 25 MHz one). Note: Won't actually re-run any settings. +        Call this function if the frequency of the reference clock changes +        (the 10, 20, 25 MHz one). + +        If this function is called while the device is in an initialized state, +        it will also re-trigger the initialization sequence. + +        No need to set the device in a safe state because (presumably) the user +        has already switched the clock rate externally. All we need to do now +        is re-initialize with the new rate.          """          assert freq in (10e6, 20e6, 25e6), \                  "Invalid ref clock frequency: {}".format(freq)          self.log.trace("Changing ref clock frequency to %f MHz", freq/1e6)          self.ref_clock_freq = freq +        if self._init_args is not None: +            self._reinit(self.master_clock_rate)      ########################################################################## diff --git a/mpm/python/usrp_mpm/dboard_manager/mg_init.py b/mpm/python/usrp_mpm/dboard_manager/mg_init.py index f9e3de32e..213391655 100644 --- a/mpm/python/usrp_mpm/dboard_manager/mg_init.py +++ b/mpm/python/usrp_mpm/dboard_manager/mg_init.py @@ -623,4 +623,3 @@ class MagnesiumInitManager(object):              self.init_rf_cal(args)              self.mykonos.start_radio()          return True - diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py index c770ecfd4..fda229ce8 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py @@ -11,6 +11,7 @@ from __future__ import print_function  import copy  import re  import threading +import time  from six import iteritems, itervalues  from usrp_mpm.cores import WhiteRabbitRegsControl  from usrp_mpm.components import ZynqComponents @@ -235,15 +236,12 @@ class n3xx(ZynqComponents, PeriphManagerBase):              self._clock_source = N3XX_DEFAULT_CLOCK_SOURCE              self._time_source = N3XX_DEFAULT_TIME_SOURCE          else: -            self.set_clock_source( -                default_args.get('clock_source', N3XX_DEFAULT_CLOCK_SOURCE) -            ) -            self.set_time_source( -                default_args.get('time_source', N3XX_DEFAULT_TIME_SOURCE) -            ) -            self.enable_pps_out( -                default_args.get('pps_export', N3XX_DEFAULT_ENABLE_PPS_EXPORT) -            ) +            self.set_sync_source( { +                'clock_source': default_args.get('clock_source', +                                                 N3XX_DEFAULT_CLOCK_SOURCE), +                'time_source' : default_args.get('time_source', +                                                 N3XX_DEFAULT_TIME_SOURCE) +            } )      def _init_meas_clock(self):          """ @@ -370,17 +368,11 @@ class n3xx(ZynqComponents, PeriphManagerBase):              self.log.error(                  "Cannot run init(), device was never fully initialized!")              return False -        # We need to disable the PPS out during clock initialization in order +        # We need to disable the PPS out during clock and dboard initialization in order          # to avoid glitches. -        enable_pps_out_state = self._default_args.get( -            'pps_export', -            N3XX_DEFAULT_ENABLE_PPS_EXPORT -        )          self.enable_pps_out(False) -        if "clock_source" in args: -            self.set_clock_source(args.get("clock_source"))          if "clock_source" in args or "time_source" in args: -            self.set_time_source(args.get("time_source", self.get_time_source())) +            self.set_sync_source(args)          # Uh oh, some hard coded product-related info: The N300 has no LO          # source connectors on the front panel, so we assume that if this was          # selected, it was an artifact from N310-related code. The user gets @@ -394,9 +386,11 @@ class n3xx(ZynqComponents, PeriphManagerBase):          # Note: The parent class takes care of calling init() on all the          # daughterboards          result = super(n3xx, self).init(args) -        # Now the clocks are all enabled, we can also re-enable PPS export if -        # it was turned off: -        self.enable_pps_out(enable_pps_out_state) +        # Now the clocks are all enabled, we can also enable PPS export: +        self.enable_pps_out(args.get( +            'pps_export', +            N3XX_DEFAULT_ENABLE_PPS_EXPORT +        ))          for xport_mgr in itervalues(self._xport_mgrs):              xport_mgr.init(args)          return result @@ -524,74 +518,10 @@ class n3xx(ZynqComponents, PeriphManagerBase):          return self._clock_source      def set_clock_source(self, *args): -        """ -        Switch reference clock. - -        Throws if clock_source is not a valid value. -        """ +        " Sets a new reference clock source "          clock_source = args[0] -        assert clock_source in self.get_clock_sources() -        self.log.debug("Setting clock source to `{}'".format(clock_source)) -        if clock_source == self.get_clock_source(): -            self.log.trace("Nothing to do -- clock source already set.") -            return -        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 -        ref_clk_freq = self.get_ref_clock_freq() -        self.log.debug("Reference clock frequency is: {} MHz".format( -            ref_clk_freq/1e6 -        )) -        for slot, dboard in enumerate(self.dboards): -            if hasattr(dboard, 'update_ref_clock_freq'): -                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) - -    def set_ref_clock_freq(self, freq): -        """ -        Tell our USRP what the frequency of the external reference clock is. - -        Will throw if it's not a valid value. -        """ -        assert freq in (10e6, 20e6, 25e6) -        self.log.debug("We've been told the external reference clock " \ -                       "frequency is {} MHz.".format(freq/1e6)) -        if self._ext_clk_freq == freq: -            self.log.trace("New external reference clock frequency " \ -                           "assignment matches previous assignment. Ignoring " \ -                           "update command.") -            return -        self._ext_clock_freq = freq -        if self.get_clock_source() == 'external': -            for slot, dboard in enumerate(self.dboards): -                if hasattr(dboard, 'update_ref_clock_freq'): -                    self.log.trace( -                        "Updating reference clock on dboard %d to %f MHz...", -                        slot, freq/1e6 -                    ) -                    dboard.update_ref_clock_freq(freq) - -    def get_ref_clock_freq(self): -        " Returns the currently active reference clock frequency" -        return { -            'internal': 25e6, -            'external': self._ext_clock_freq, -            'gpsdo': 20e6, -        }[self._clock_source] +        source = {"clock_source": clock_source} +        self.set_sync_source(source)      def get_time_sources(self):          " Returns list of valid time sources " @@ -603,22 +533,81 @@ class n3xx(ZynqComponents, PeriphManagerBase):      def set_time_source(self, time_source):          " Set a time source " +        source = {"time_source": time_source} +        self.set_sync_source(source) + +    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) +        if clock_source != self._clock_source: +            assert clock_source in self.get_clock_sources() +            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) +        else: +            self.log.trace("New reference clock source " \ +                           "assignment matches previous assignment. Ignoring " \ +                           "update command.") +        # Whenever the clock source changes, re-apply the time source to ensure +        # frequency changes are applied to the internal PPS counters. +        # If the time_source is not passed as an arg, use the current source. +        time_source = args.get('time_source',self._time_source)          assert time_source in self.get_time_sources() +        # Perform the assignment regardless of whether the source was previously +        # selected, since the internal PPS generator needs to change depending on the +        # refclk frequency.          self._time_source = time_source -        self.mboard_regs_control.set_time_source(time_source, self.get_ref_clock_freq()) +        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 == 'sfp0' or time_source == 'sfp1') and \ -              self.get_clock_source() != 'internal': -                error_msg = "Time source sfp(0|1) requires the internal clock source!" +            # 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) -            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)) +            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) @@ -634,7 +623,54 @@ class n3xx(ZynqComponents, PeriphManagerBase):                  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): +            if hasattr(dboard, 'update_ref_clock_freq'): +                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) +    def set_ref_clock_freq(self, freq): +        """ +        Tell our USRP what the frequency of the external reference clock is. + +        Will throw if it's not a valid value. +        """ +        if freq not in (10e6, 20e6, 25e6): +            self.log.error("{} is not a supported external reference clock frequency!" \ +                           .format(freq/1e6)) +            raise RuntimeError("{} is not a supported external reference clock " \ +                               "frequency!".format(freq/1e6)) +        self.log.debug("We've been told the external reference clock " \ +                       "frequency is now {} MHz.".format(freq/1e6)) +        if self._ext_clock_freq == freq: +            self.log.trace("New external reference clock frequency " \ +                           "assignment matches previous assignment. Ignoring " \ +                           "update command.") +            return +        if (freq == 20e6) and (self.get_time_source() != 'external'): +            self.log.error("Setting the external reference clock to {} MHz is only " \ +                           "allowed when using 'external' time_source. Set the " \ +                           "time_source to 'external' first, and then set the new " \ +                           "external clock rate.".format(freq/1e6)) +            raise RuntimeError("Setting the external reference clock to {} MHz is " \ +                               "only allowed when using 'external' time_source." \ +                               .format(freq/1e6)) +        self._ext_clock_freq = freq +        # If the external source is currently selected we also need to re-apply the +        # time_source. This call also updates the dboards' rates. +        if self.get_clock_source() == 'external': +            self.set_time_source(self.get_time_source()) + +    def get_ref_clock_freq(self): +        " Returns the currently active reference clock frequency" +        return { +            'internal': 25e6, +            'external': self._ext_clock_freq, +            'gpsdo': 20e6, +        }[self._clock_source]      def set_fp_gpio_master(self, value):          """set driver for front panel GPIO diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py index 009eb123b..803f056c3 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py @@ -187,6 +187,7 @@ class MboardRegsControl(object):      MB_CLOCK_CTRL_PPS_OUT_EN = 4 # output enabled = 1      MB_CLOCK_CTRL_MEAS_CLK_RESET = 12 # set to 1 to reset mmcm, default is 0      MB_CLOCK_CTRL_MEAS_CLK_LOCKED = 13 # locked indication for meas_clk mmcm +    MB_CLOCK_CTRL_DISABLE_REF_CLK = 16 # to disable the ref_clk, write a '1'      def __init__(self, label, log):          self.log = log @@ -341,6 +342,22 @@ class MboardRegsControl(object):              self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))              self.poke32(self.MB_CLOCK_CTRL, reg_val) +    def enable_ref_clk(self, enable): +        """ +        Enables the reference clock internal to the FPGA +        """ +        self.log.trace("%s the Reference Clock!", +                       "Enabling" if enable else "Disabling") +        mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK) +        with self.regs: +            # mask the bit to clear it and therefore enable the clock: +            reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask +            if not enable: +                # set the bit if not enabled (note this is a DISABLE bit when = 1): +                reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK) +            self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) +            self.poke32(self.MB_CLOCK_CTRL, reg_val) +      def reset_meas_clk_mmcm(self, reset=True):          """          Reset or unreset the MMCM for the measurement clock in the FPGA TDC. | 
