diff options
| author | Martin Braun <martin.braun@ettus.com> | 2017-11-15 14:52:12 -0800 | 
|---|---|---|
| committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:05:07 -0800 | 
| commit | f14b49ff5b9c55972966c74b014850ecc89e75e2 (patch) | |
| tree | 6d98fc58ae9e4184c4936765d45be4d2ae7dce6b /mpm/python | |
| parent | 63f4049a101d66c01f7e89098b03f3f780647cbd (diff) | |
| download | uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.gz uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.tar.bz2 uhd-f14b49ff5b9c55972966c74b014850ecc89e75e2.zip  | |
n3xx: add support for 122.88 and 153.6 MHz sample clock rates
 - re-wrote portions of the LMK driver for flexible rates and configuration
 - tweaked TDC driver for compatibility and ease of debugging
 - updated comments and log statements throughout for uniformity
Diffstat (limited to 'mpm/python')
| -rw-r--r-- | mpm/python/usrp_mpm/chips/lmk04828.py | 45 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/cores/tdc_sync.py | 40 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/lmk_mg.py | 123 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/magnesium.py | 70 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n310.py | 20 | 
5 files changed, 207 insertions, 91 deletions
diff --git a/mpm/python/usrp_mpm/chips/lmk04828.py b/mpm/python/usrp_mpm/chips/lmk04828.py index 121d855ca..3d979e65e 100644 --- a/mpm/python/usrp_mpm/chips/lmk04828.py +++ b/mpm/python/usrp_mpm/chips/lmk04828.py @@ -18,6 +18,7 @@  LMK04828 parent driver class  """ +import math  from builtins import object  from ..mpmlog import get_logger @@ -48,7 +49,7 @@ class LMK04828(object):          Read back the chip ID          """          chip_id = self.peek8(0x03) -        self.log.trace("Read chip ID: {}".format(chip_id)) +        self.log.trace("Chip ID Readback: {}".format(chip_id))          return chip_id      def verify_chip_id(self): @@ -57,7 +58,7 @@ class LMK04828(object):          """          chip_id = self.get_chip_id()          if chip_id != self.LMK_CHIP_ID: -            self.log.error("Wrong chip id 0x{:X}".format(chip_id)) +            self.log.error("Wrong Chip ID 0x{:X}".format(chip_id))              return False          return True @@ -73,7 +74,7 @@ class LMK04828(object):              """              pll_lock_status = self.regs_iface.peek8(addr)              if (pll_lock_status & 0x7) != 0x02: -                self.log.warning("LMK {} reporting unlocked... Status: 0x{:x}".format(pll_id, pll_lock_status)) +                self.log.warning("{} reporting unlocked... Status: 0x{:x}".format(pll_id, pll_lock_status))                  return False              return True          lock_status = \ @@ -81,3 +82,41 @@ class LMK04828(object):                  check_pll_lock("PLL2", 0x183)          return lock_status + +## Register bitfield definitions ## + +    def divide_to_cnth_cntl_reg(self, divide_val): +        """ +        From the divider value, returns the CNTL and CNTH register value. +        Split divider value in half. If odd, round up for the CNTL and down +        for the CNTH based on the datasheet recommendation. +        """ +        cntl = int(math.ceil( divide_val/2.0)) +        cnth = int(math.floor(divide_val/2.0)) +        reg_val = ((cnth & 0xF) << 4) | (cntl & 0xF) +        self.log.trace("From divider value 0d{}, writing CNTH/L as 0x{:02X}." +           .format(divide_val, reg_val)) +        return reg_val + +    def divide_to_reg(self, divide_val, in_drive = 0x1, out_drive = 0x1): +        """ +        From the divider value, returns the register value combined with the other +        register fields. +        """ +        reg_val = (divide_val & 0x1F) | ((in_drive & 0x1) << 5) | ((out_drive & 0x1) << 6) +        self.log.trace("From divider value 0d{}, writing divider register as 0x{:02X}." +           .format(divide_val, reg_val)) +        return reg_val + +    def pll2_pre_to_reg(self, prescaler, osc_field = 0x01, xtal_en = 0x0, ref_2x_en = 0x0): +        """ +        From the prescaler value, returns the register value combined with the other +        register fields. +        """ +        # valid prescaler values are 2-8, where 8 is represented as 0x00. +        assert prescaler in range(2,8+1) +        reg_val = ((prescaler & 0x07) << 5) | ((osc_field & 0x7) << 2) | ((xtal_en & 0x1) << 1) | ((ref_2x_en & 0x1) << 0) +        self.log.trace("From prescaler value 0d{}, writing register as 0x{:02X}." +           .format(prescaler, reg_val)) +        return reg_val + diff --git a/mpm/python/usrp_mpm/cores/tdc_sync.py b/mpm/python/usrp_mpm/cores/tdc_sync.py index 9d5195339..5b1e2dac2 100644 --- a/mpm/python/usrp_mpm/cores/tdc_sync.py +++ b/mpm/python/usrp_mpm/cores/tdc_sync.py @@ -40,12 +40,12 @@ def rsp_table(ref_clk_freq, radio_clk_freq):              20e6: (20, 10),              25e6: (25, 13),          }, -        122.8e6: { +        122.88e6: {              10e6: (250, 125),              20e6: (500, 250),              25e6: (625, 313),          }, -        156.3e6: { +        153.6e6: {              10e6: (250, 125),              20e6: (500, 250),              25e6: (625, 313), @@ -65,10 +65,10 @@ def rtc_table(radio_clk_freq):      Returns a tuple (period, high_time).      """      return { -        125e6:   (125, 63), -        122.8e6: (3072, 1536), -        156.3e6: (3840, 1920), -        104e6:   (104, 52), +        125e6:    (125,  63), +        122.88e6: (3072, 1536), +        153.6e6:  (3840, 1920), +        104e6:    (104,  52),      }[radio_clk_freq] @@ -94,7 +94,6 @@ class ClockSynchronizer(object):      def __init__(              self,              regs_iface, -            dboard_clk_control,              lmk,              phase_dac,              offset, @@ -102,7 +101,6 @@ class ClockSynchronizer(object):              ref_clk_freq,              fine_delay_step,              init_pdac_word, -            lmk_vco_freq,              target_values,              dac_spi_addr_val,              log @@ -113,14 +111,14 @@ class ClockSynchronizer(object):          self.poke32 = lambda addr, data: self._iface.poke32(addr + offset, data)          self.lmk = lmk          self.phase_dac = phase_dac -        self.dboard_clk_control = dboard_clk_control          self.radio_clk_freq = radio_clk_freq          self.ref_clk_freq = ref_clk_freq          self.fine_delay_step = fine_delay_step          self.current_phase_dac_word = init_pdac_word -        self.lmk_vco_freq = lmk_vco_freq +        self.lmk_vco_freq = self.lmk.get_vco_freq()          self.target_values = target_values          self.dac_spi_addr_val = dac_spi_addr_val +        self.meas_clk_freq = 170.542641116e6      def run_sync(self, measurement_only=False):          """ @@ -197,9 +195,8 @@ class ClockSynchronizer(object):              raise RuntimeError("Failed to capture PPS.")          self.log.trace("PPS Captured!") -        meas_clk_freq = 170.542641116e6          measure_offset = lambda: self.read_tdc_meas( -            1.0/meas_clk_freq, 1.0/self.ref_clk_freq, 1.0/self.radio_clk_freq +            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. @@ -207,7 +204,7 @@ class ClockSynchronizer(object):          measure_offset()          # Now, read off 512 measurements and take the mean of them. -        num_meas = 512 +        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.") @@ -238,6 +235,20 @@ class ClockSynchronizer(object):              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. @@ -265,7 +276,8 @@ class ClockSynchronizer(object):          Return the offset (in seconds) the whatever what measured and whatever          the reference is.          """ -        timeout = time.time() + 1.0 +        # Current worst-case time is around 3.5s. +        timeout = time.time() + 4.0 # TODO knock this back down after optimizations          while True:              rtc_offset_msb = self.peek32(self.RTC_OFFSET_1)              if rtc_offset_msb & 0x100 == 0x100: diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py index e6a1821af..dc3fe203b 100644 --- a/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py +++ b/mpm/python/usrp_mpm/dboard_manager/lmk_mg.py @@ -19,27 +19,60 @@ LMK04828 driver for use with Magnesium  """  import time +import math  from builtins import zip  from builtins import hex  from ..mpmlog import get_logger  from ..chips import LMK04828  class LMK04828Mg(LMK04828): -    def __init__(self, regs_iface, spi_lock, ref_clock_freq, log=None): +    def __init__(self, regs_iface, spi_lock, ref_clock_freq, master_clock_freq, log=None):          LMK04828.__init__(self, regs_iface, log) -        self.log.trace("Using reference clock frequency {} MHz".format(ref_clock_freq/1e6)) +        self.log.trace("Using reference clock frequency: {} MHz".format(ref_clock_freq/1e6)) +        self.log.trace("Using master clock frequency: {} MHz".format(master_clock_freq/1e6))          self.spi_lock = spi_lock          assert hasattr(self.spi_lock, 'lock')          assert hasattr(self.spi_lock, 'unlock')          self.ref_clock_freq = ref_clock_freq +        self.master_clock_freq = master_clock_freq +        # VCXO on Mg runs at 96 MHz +        self.vcxo_freq = 96e6 +        # Feedback clock divider is constant for Mg regardless of the master_clock_freq. +        self.clkfb_divider = 24 +        # PLL2 R value is also constant across all sample clock combinations. +        self.pll2_r_divider = 4 +        # PLL1 PFD is 1 MHz. Divide the ref_clock_freq by 1 MHz to set the R divider. +        self.clkin_r_divider = int(math.floor(self.ref_clock_freq/1e6)) +        self.clkout_divider = {122.88e6:   25, 125e6:   24, 153.6e6:   20}[self.master_clock_freq] +        self.pll1_n_divider = {122.88e6:  128, 125e6:  125, 153.6e6:  128}[self.master_clock_freq] +        self.sysref_divider = {122.88e6:  500, 125e6:  480, 153.6e6:  400}[self.master_clock_freq] +        self.pll2_prescaler = {122.88e6:    2, 125e6:    5, 153.6e6:    2}[self.master_clock_freq] +        self.pll2_n_divider = {122.88e6:   64, 125e6:   25, 153.6e6:   64}[self.master_clock_freq] +        self.pll2_vco_freq  = (self.vcxo_freq/self.pll2_r_divider)*self.pll2_prescaler*self.pll2_n_divider +        self.log.trace("Variable Configuration Report: " +                       "clkin1_r = 0d{}, clkout_div = 0d{}, pll1_n = 0d{}" +           .format(self.clkin_r_divider, self.clkout_divider, self.pll1_n_divider)) +        self.log.trace("Variable Configuration Report: " +                       "sysref_divider = 0d{}, pll2_pre = 0d{}, pll2_n = 0d{}" +           .format(self.sysref_divider, self.pll2_prescaler, self.pll2_n_divider)) +        self.log.trace("Variable Configuration Report: " +                       "pll2_vco_freq = 0d{}" +           .format(self.pll2_vco_freq)) +        # Run .init() and .config() right off the bat. Save clock shifty-ness for later.          self.init()          self.config() +    def get_vco_freq(self): +        """ +        Return the calculated VCO frequency in the LMK PLL2. +        """ +        return self.pll2_vco_freq +      def init(self):          """          Basic init. Turns it on. Let's read SPI.          """ -        self.log.info("Reset LMK & Verify") +        self.log.info("Reset and Verify Chip ID")          self.pokes8((              (0x000, 0x90), # Assert reset              (0x000, 0x10), # De-assert reset @@ -47,66 +80,67 @@ class LMK04828Mg(LMK04828):              (0x148, 0x33), # Clock Select as SDO          ))          if not self.verify_chip_id(): -            raise Exception("Unable to locate LMK04828") - +            raise Exception("Unable to locate LMK04828!")      def config(self):          """          Write lots of config foo.          """ -        self.log.trace("LMK Initialization") -        clkin0_r_divider = {10e6: 0x0A, 20e6: 0x14}[self.ref_clock_freq] +        clkout_div_val = self.divide_to_reg(self.clkout_divider) +        clkout_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider) + +        self.log.info("Register Initialization Commencing...")          self.pokes8(( -            (0x100, 0x78), # CLKout Config -            (0x101, 0xCC), # CLKout Config +            (0x100, clkout_div_val), # CLKout Config +            (0x101, clkout_cnt_val), # CLKout Config              (0x102, 0xCC), # CLKout Config              (0x103, 0x00), # CLKout Config              (0x104, 0x20), # CLKout Config              (0x105, 0x00), # CLKout Config              (0x106, 0x72), # CLKout Config MYK: (0xAB where A = SYSREF, B = CLK)              (0x107, 0x15), # CLKout Config 0x15 = LVDS, 0x55 = LVPECL -            (0x108, 0x7E), # CLKout Config -            (0x109, 0xFF), # CLKout Config +            (0x108, clkout_div_val), # CLKout Config +            (0x109, clkout_cnt_val), # CLKout Config              (0x10A, 0xFF), # CLKout Config              (0x10B, 0x00), # CLKout Config              (0x10C, 0x00), # CLKout Config              (0x10D, 0x00), # CLKout Config              (0x10E, 0x70), # CLKout Config              (0x10F, 0x55), # CLKout Config -            (0x110, 0x78), # CLKout Config -            (0x111, 0xCC), # CLKout Config +            (0x110, clkout_div_val), # CLKout Config +            (0x111, clkout_cnt_val), # CLKout Config              (0x112, 0xCC), # CLKout Config              (0x113, 0x00), # CLKout Config              (0x114, 0x00), # CLKout Config              (0x115, 0x00), # CLKout Config              (0x116, 0xF9), # CLKout Config              (0x117, 0x00), # CLKout Config -            (0x118, 0x78), # CLKout Config -            (0x119, 0xCC), # CLKout Config +            (0x118, self.divide_to_reg(self.clkfb_divider)), # CLKout Config +            (0x119, self.divide_to_cnth_cntl_reg(self.clkfb_divider)), # CLKout Config              (0x11A, 0xCC), # CLKout Config              (0x11B, 0x00), # CLKout Config              (0x11C, 0x20), # CLKout Config              (0x11D, 0x00), # CLKout Config              (0x11E, 0xF1), # CLKout Config              (0x11F, 0x00), # CLKout Config -            (0x120, 0x78), # CLKout Config -            (0x121, 0xCC), # CLKout Config +            (0x120, clkout_div_val), # CLKout Config +            (0x121, clkout_cnt_val), # CLKout Config              (0x122, 0xCC), # CLKout Config              (0x123, 0x00), # CLKout Config              (0x124, 0x20), # CLKout Config 0x20 = SYSREF output, 0x00 = DEVCLK              (0x125, 0x00), # CLKout Config              (0x126, 0x72), # CLKout Config FPGA: (0xAB where A = SYSREF, B = CLK)              (0x127, 0x55), # CLKout Config 0x1 = LVDS, 0x5 = LVPECL -            (0x128, 0x78), # CLKout Config -            (0x129, 0xCC), # CLKout Config +            (0x128, clkout_div_val), # CLKout Config +            (0x129, clkout_cnt_val), # CLKout Config              (0x12A, 0xCC), # CLKout Config              (0x12B, 0x00), # CLKout Config              (0x12C, 0x00), # CLKout Config              (0x12D, 0x00), # CLKout Config              (0x12E, 0x72), # CLKout Config              (0x12F, 0xD0), # CLKout Config -            (0x130, 0x78), # CLKout Config -            (0x131, 0xCC), # CLKout Config +            (0x130, clkout_div_val), # CLKout Config +            (0x131, clkout_cnt_val), # CLKout Config              (0x132, 0xCC), # CLKout Config              (0x133, 0x00), # CLKout Config              (0x134, 0x20), # CLKout Config @@ -115,8 +149,8 @@ class LMK04828Mg(LMK04828):              (0x137, 0x05), # CLKout Config              (0x138, 0x30), # VCO_MUX to VCO 1; OSCout off              (0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC -            (0x13A, 0x01), # SYSREF Divide [12:8] -            (0x13B, 0xE0), # SYSREF Divide [7:0] +            (0x13A, (self.sysref_divider & 0x1F00) >> 8), # SYSREF Divide [12:8] +            (0x13B, (self.sysref_divider & 0x00FF) >> 0), # SYSREF Divide [7:0]              (0x13C, 0x00), # SYSREF DDLY [12:8]              (0x13D, 0x08), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved              (0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request @@ -141,13 +175,13 @@ class LMK04828Mg(LMK04828):              (0x151, 0x02), # Holdover Settings (defaults)              (0x152, 0x00), # Holdover Settings (defaults)              (0x153, 0x00), # CLKin0_R divider [13:8], default = 0 -            (0x154, clkin0_r_divider), # CLKin0_R divider [7:0], default = d120 -            (0x155, 0x00), # CLKin1_R divider [13:8], default = 0 -            (0x156, clkin0_r_divider), # CLKin1_R divider [7:0], default = d120 +            (0x154, 0x78), # CLKin0_R divider [7:0], default = d120 +            (0x155, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin1_R divider [13:8], default = 0 +            (0x156, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin1_R divider [7:0], default = d120              (0x157, 0x00), # CLKin2_R divider [13:8], default = 0              (0x158, 0x01), # CLKin2_R divider [7:0], default = d120 -            (0x159, 0x00), # PLL1 N divider [13:8], default = 0 -            (0x15A, 0x7D), # PLL1 N divider [7:0], default = d120 +            (0x159, (self.pll1_n_divider & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0 +            (0x15A, (self.pll1_n_divider & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d120              (0x15B, 0xCF), # PLL1 PFD              (0x15C, 0x27), # PLL1 DLD Count [13:8]              (0x15D, 0x10), # PLL1 DLD Count [7:0] @@ -155,7 +189,7 @@ class LMK04828Mg(LMK04828):              (0x15F, 0x0B), # Status LD1 pin = PLL1 LD, push-pull output              (0x160, 0x00), # PLL2 R divider [11:8];              (0x161, 0x04), # PLL2 R divider [7:0] -            (0x162, 0xA4), # PLL2 prescaler; OSCin freq +            (0x162, self.pll2_pre_to_reg(self.pll2_prescaler)), # PLL2 prescaler; OSCin freq 0xA4              (0x163, 0x00), # PLL2 Cal = PLL2 normal val              (0x164, 0x00), # PLL2 Cal = PLL2 normal val              (0x165, 0x19), # PLL2 Cal = PLL2 normal val @@ -163,9 +197,9 @@ class LMK04828Mg(LMK04828):              (0x172, 0x02), # Write this val after x165              (0x17C, 0x15), # VCo1 Cal; write before x168              (0x17D, 0x33), # VCo1 Cal; write before x168 -            (0x166, 0x00), # PLL2 N[17:16] -            (0x167, 0x00), # PLL2 N[15:8] -            (0x168, 0x19), # PLL2 N[7:0] +            (0x166, (self.pll2_n_divider & 0x030000) >> 16), # PLL2 N[17:16] +            (0x167, (self.pll2_n_divider & 0x00FF00) >> 8), # PLL2 N[15:8] +            (0x168, (self.pll2_n_divider & 0x0000FF) >> 0), # PLL2 N[7:0]              (0x169, 0x51), # PLL2 PFD              (0x16A, 0x27), # PLL2 DLD Count [13:8] = default d32              (0x16B, 0x10), # PLL2 DLD Count [7:0] = default d0 @@ -187,16 +221,14 @@ class LMK04828Mg(LMK04828):                  (0x183, 0x1), # Clear Lock Detect Sticky                  (0x183, 0x0), # Clear Lock Detect Sticky              )) -            # Wait a bit before checking for lock -            # time.sleep(0.050)              if self.check_plls_locked():                  locked = True -                self.log.info("LMK PLLs Locked!") +                self.log.trace("PLLs are Locked!")                  break          if not locked: -            raise RuntimeError("At least one LMK PLL did not lock! Check the logs for details.") +            raise RuntimeError("At least one PLL did not lock! Check the logs for details.") -        self.log.trace("Setting SYNC and SYSREF config...") +        self.log.trace("Synchronizing output dividers...")          self.pokes8((              (0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event              (0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event @@ -204,17 +236,15 @@ class LMK04828Mg(LMK04828):              (0x144, 0xFF), # Disable SYNC on all outputs including sysref              (0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled          )) -        self.log.info("LMK init'd and locked!") +        self.log.info("Clocks Initialized and PLLs Locked!")      def lmk_shift(self, num_shifts=0):          """ -        Apply time shift +        Apply time shift using the dynamic digital delays inside the LMK.          """ -        # TODO: these numbers need to be based off the radio clock freq. -        self.log.trace("LMK04828 Clock Phase Shifting Commencing...") -        ddly_value = 0xCD if num_shifts >= 0 else 0xCB -        ddly_value_sysref_reg0 = 0x01 -        ddly_value_sysref_reg1 = 0xE1 if num_shifts >= 0 else 0xDF # 0xE0 is normal +        self.log.trace("Clock Shifting Commencing using Dynamic Digital Delay...") +        ddly_value = self.divide_to_cnth_cntl_reg(self.clkout_divider+1) if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider-1) +        ddly_value_sysref = self.sysref_divider+1 if num_shifts >= 0 else self.sysref_divider-1          self.pokes8((              (0x141, 0xB1), # Dynamic digital delay enable on outputs 0, 8, 10              (0x143, 0x53), # SYSREF_CLR; SYNC Enabled; SYNC from pulser @ regwrite @@ -225,13 +255,12 @@ class LMK04828Mg(LMK04828):              (0x122, ddly_value), # Hidden register. Write the same as previous based on inc/dec.              (0x129, ddly_value), # Set DDLY values for DCLKout10 +/-1 on low cnt              (0x12A, ddly_value), # Hidden register. Write the same as previous based on inc/dec. -            (0x13C, ddly_value_sysref_reg0), # SYSREF DDLY value -            (0x13D, ddly_value_sysref_reg1), # SYSREF DDLY value +            (0x13C, (ddly_value_sysref & 0x1F00) >> 8), # SYSREF DDLY value [12:8] +            (0x13D, (ddly_value_sysref & 0x00FF) >> 0), # SYSREF DDLY value [7:0]              (0x144, 0x4E), # Enable SYNC on outputs 0, 8, 10          ))          for x in range(abs(num_shifts)):              self.poke8(0x142, 0x1)          # Put everything back the way it was before shifting.          self.poke8(0x144, 0xFF) # Disable SYNC on all outputs including SYSREF -        # self.poke8(0x143, 0xD2) # Reset SYSREF engine to proper SYNC settings          self.poke8(0x143, 0x52) # Pulser selected; SYNC enabled; 1 shot enabled diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index 5b6b81598..6c100e992 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -161,7 +161,7 @@ class DboardClockControl(object):          """          Uninitialize and reset the MMCM          """ -        self.log.trace("Disabling all Radio Clocks, then resetting MMCM...") +        self.log.trace("Disabling all Radio Clocks, then resetting FPGA MMCM...")          self.enable_outputs(False)          self.poke32(self.RADIO_CLK_MMCM, 0x1) @@ -171,16 +171,16 @@ class DboardClockControl(object):          If MMCM is not locked after unreset, an exception is thrown.          """ -        self.log.trace("Un-resetting MMCM...") +        self.log.trace("Enabling FPGA Radio Clock 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.log.error("FPGA Radio Clock MMCM not locked!") +            raise RuntimeError("FPGA Radio Clock MMCM not locked!") +        self.log.trace("FPGA Radio Clock MMCM locked. Enabling clocks to design...")          self.enable_outputs(True)      def check_refclk(self): @@ -387,7 +387,7 @@ class Magnesium(DboardManagerBase):          self.eeprom_fs, self.eeprom_path = self._init_user_eeprom(              self.user_eeprom[self.rev]          ) -        self.radio_regs = UIO( +        self.dboard_ctrl_regs = UIO(              label="dboard-regs-{}".format(self.slot_idx),              read_only=False          ) @@ -397,7 +397,7 @@ class Magnesium(DboardManagerBase):              for key in self._spi_nodes          }          self.cpld = MgCPLD(self._spi_ifaces['cpld'], self.log) -        self.dboard_clk_control = DboardClockControl(self.radio_regs, self.log) +        self.dboard_clk_control = DboardClockControl(self.dboard_ctrl_regs, self.log)      def _power_on(self):          " Turn on power to daughterboard " @@ -472,7 +472,7 @@ class Magnesium(DboardManagerBase):          """          Execute necessary init dance to bring up dboard          """ -        def _init_lmk(lmk_spi, ref_clk_freq, +        def _init_lmk(lmk_spi, ref_clk_freq, master_clk_freq,                        pdac_spi, init_phase_dac_word):              """              Sets the phase DAC to initial value, and then brings up the LMK @@ -483,7 +483,7 @@ class Magnesium(DboardManagerBase):                  init_phase_dac_word              ))              pdac_spi.poke16(0x0, init_phase_dac_word) -            return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, self.log) +            return LMK04828Mg(lmk_spi, self.spi_lock, ref_clk_freq, master_clk_freq, self.log)          def _sync_db_clock(synchronizer):              " Synchronizes the DB clock to the common reference "              synchronizer.run_sync(measurement_only=False) @@ -508,35 +508,48 @@ class Magnesium(DboardManagerBase):              error_msg = "Cannot run init(), peripherals are not initialized!"              self.log.error(error_msg)              raise RuntimeError(error_msg) +        if 'ref_clk_freq' in args: +            self.ref_clock_freq = float(args['ref_clk_freq']) +            assert self.ref_clock_freq in (10e6, 20e6, 25e6) +        if 'master_clock_rate' in args: +            self.master_clock_rate = float(args['master_clock_rate']) +            assert self.master_clock_rate in (122.88e6, 125e6, 153.6e6) +        self.log.trace("Creating jesdcore object") +        self.jesdcore = nijesdcore.NIMgJESDCore(self.dboard_ctrl_regs, self.slot_idx) + +        self.log.info("Reset Daughterboard Clocking and JESD204B interfaces...")          self.dboard_clk_control.reset_mmcm() +        self.jesdcore.reset() +          self.lmk = _init_lmk(              self._spi_ifaces['lmk'],              self.ref_clock_freq, +            self.master_clock_rate,              self._spi_ifaces['phase_dac'],              self.INIT_PHASE_DAC_WORD,          )          self.dboard_clk_control.enable_mmcm()          self.log.info("Sample Clocks and Phase DAC Configured Successfully!")          # Synchronize DB Clocks +        # Future Work: target_value needs to be tweaked to support heterogenous rate sync. +        self.target_value = {122.88e6: 128e-9, 125e6: 128e-9, 153.6e6: 122e-9}[self.master_clock_rate]          self.clock_synchronizer = ClockSynchronizer( -            self.radio_regs, -            self.dboard_clk_control, +            self.dboard_ctrl_regs,              self.lmk,              self._spi_ifaces['phase_dac'], -            0, # TODO this might not actually be zero +            0, # register offset value. future work.              self.master_clock_rate,              self.ref_clock_freq, -            860E-15, # TODO don't hardcode. This should live in the EEPROM +            860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM              self.INIT_PHASE_DAC_WORD, -            3e9,         # lmk_vco_freq -            [128e-9,],   # target_values +            [self.target_value,],   # target_values              0x0,         # spi_addr TODO: make this a constant and replace in _sync_db_clock as well              self.log          )          _sync_db_clock(self.clock_synchronizer)          # Clocks and PPS are now fully active! -        self.init_jesd(self.radio_regs, args) +        self.init_jesd(args)          self.mykonos.start_radio()          return True @@ -571,15 +584,12 @@ class Magnesium(DboardManagerBase):          self.log.debug("args[tracking_cals]=0x{:02X}".format(self._tracking_cals_mask))          self.mykonos.setup_cal(self._init_cals_mask, self._tracking_cals_mask, self._init_cals_timeout) -    def init_jesd(self, uio, args): +    def init_jesd(self, args):          """          Bring up the JESD link between Mykonos and the N310. +        All clocks must be set up and stable before starting this routine.          """ -        # CPLD Register Definition -        self.log.trace("Creating jesdcore object") -        self.jesdcore = nijesdcore.NIMgJESDCore(uio, self.slot_idx)          self.jesdcore.check_core() -        self.jesdcore.unreset_qpll()          self.jesdcore.init()          self.log.trace("Pulsing Mykonos Hard Reset...") @@ -599,7 +609,7 @@ class Magnesium(DboardManagerBase):          self.mykonos.begin_initialization()          # Multi-chip Sync requires two SYSREF pulses at least 17us apart.          self.jesdcore.send_sysref_pulse() -        time.sleep(0.001) +        time.sleep(0.001) # 17us... ish.          self.jesdcore.send_sysref_pulse()          self.mykonos.finish_initialization()          # TODO:can we call this after JESD? @@ -618,7 +628,7 @@ class Magnesium(DboardManagerBase):          self.log.trace("Starting Mykonos framer...")          self.mykonos.start_jesd_tx()          self.log.trace("Enable FPGA SYSREF Receiver.") -        self.jesdcore.enable_lmfc() +        self.jesdcore.enable_lmfc(True)          self.jesdcore.send_sysref_pulse()          self.log.trace("Starting FPGA deframer...")          self.jesdcore.init_deframer() @@ -645,11 +655,11 @@ class Magnesium(DboardManagerBase):      def dump_jesd_core(self):          " Debug method to dump all JESD core regs " -        radio_regs = UIO(label="dboard-regs-{}".format(self.slot_idx)) +        dboard_ctrl_regs = UIO(label="dboard-regs-{}".format(self.slot_idx))          for i in range(0x2000, 0x2110, 0x10):              print(("0x%04X " % i), end=' ')              for j in range(0, 0x10, 0x4): -                print(("%08X" % radio_regs.peek32(i + j)), end=' ') +                print(("%08X" % dboard_ctrl_regs.peek32(i + j)), end=' ')              print("")      def get_user_eeprom_data(self): @@ -716,6 +726,16 @@ class Magnesium(DboardManagerBase):          " 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). +        """ +        self.log.info("Changing reference clock frequency to {} MHz".format(freq/1e6)) +        assert self.ref_clock_freq in (10e6, 20e6, 25e6) +        self.ref_clock_freq = freq +        # JEPSON FIXME call init() ? Maybe not yet! +      ##########################################################################      # Sensors diff --git a/mpm/python/usrp_mpm/periph_manager/n310.py b/mpm/python/usrp_mpm/periph_manager/n310.py index 08f6b8c22..e95e3446d 100644 --- a/mpm/python/usrp_mpm/periph_manager/n310.py +++ b/mpm/python/usrp_mpm/periph_manager/n310.py @@ -636,13 +636,16 @@ class n310(PeriphManagerBase):      def set_clock_source(self, *args):          """ -        Enable given external reference clock. +        Switch reference clock.          Throws if clock_source is not a valid value.          """          clock_source = args[0]          assert clock_source in self.get_clock_sources()          self.log.trace("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") @@ -679,7 +682,20 @@ class n310(PeriphManagerBase):          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 `{}' to {} MHz...".format( +                            slot, freq/1e6 +                        ) +                    ) +                    dboard.update_ref_clock_freq(freq)      def get_ref_clock_freq(self):          " Returns the currently active reference clock frequency" @@ -736,7 +752,7 @@ class n310(PeriphManagerBase):              "Enabling" if enable else "Disabling"          ))          self._gpios.set("PWREN-CLK-MAINREF", int(bool(enable))) -     +      def enable_1G_ref_clock(self):          """          Enables 125 MHz refclock for 1G interface.  | 
