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/periph_manager/n3xx.py | |
| 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/periph_manager/n3xx.py')
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 230 | 
1 files changed, 133 insertions, 97 deletions
| 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 | 
