diff options
author | Daniel Jepson <daniel.jepson@ni.com> | 2018-03-07 09:15:13 -0600 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2018-03-30 11:32:07 -0700 |
commit | 7ced80beccf6bfb1a906253b5fd0cedd06962029 (patch) | |
tree | b4588b73c3d2033a9406f33798f4b109b821528d | |
parent | 4e34d35ec47636f5339b0bec8656a485c9200a5c (diff) | |
download | uhd-7ced80beccf6bfb1a906253b5fd0cedd06962029.tar.gz uhd-7ced80beccf6bfb1a906253b5fd0cedd06962029.tar.bz2 uhd-7ced80beccf6bfb1a906253b5fd0cedd06962029.zip |
n3xx: add White Rabbit support
Reviewed-by: Martin Braun <martin.braun@ettus.com>
-rw-r--r-- | images/image_package_mapping.py | 18 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/cores/CMakeLists.txt | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/cores/__init__.py | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/cores/tdc_sync.py | 206 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/cores/white_rabbit.py | 54 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/magnesium.py | 20 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 82 |
7 files changed, 281 insertions, 101 deletions
diff --git a/images/image_package_mapping.py b/images/image_package_mapping.py index f826d430a..19d645bb0 100644 --- a/images/image_package_mapping.py +++ b/images/image_package_mapping.py @@ -95,6 +95,24 @@ PACKAGE_MAPPING = { 'usrp_n310_fpga_XA.dts.md5', 'usrp_n310_fpga_XA.rpt'] }, + "n310_wr": { + "type": "n3xx", + "package_name": "n3xx_n310_fpga_whiterabbit-g{}.zip", + "files": ['usrp_n310_fpga_WX.bit', + 'usrp_n310_fpga_WX.bit.md5', + 'usrp_n310_fpga_WX.dts', + 'usrp_n310_fpga_WX.dts.md5', + 'usrp_n310_fpga_WX.rpt',] + }, + "n300_wr": { + "type": "n3xx", + "package_name": "n3xx_n300_fpga_whiterabbit-g{}.zip", + "files": ['usrp_n300_fpga_WX.bit', + 'usrp_n300_fpga_WX.bit.md5', + 'usrp_n300_fpga_WX.dts', + 'usrp_n300_fpga_WX.dts.md5', + 'usrp_n300_fpga_WX.rpt',] + }, "n310_cpld": { "type": "n3xx", "package_name": "n3xx_n310_cpld_default-g{}.zip", diff --git a/mpm/python/usrp_mpm/cores/CMakeLists.txt b/mpm/python/usrp_mpm/cores/CMakeLists.txt index 85ac2348d..bbed68eb3 100644 --- a/mpm/python/usrp_mpm/cores/CMakeLists.txt +++ b/mpm/python/usrp_mpm/cores/CMakeLists.txt @@ -9,6 +9,7 @@ SET(USRP_MPM_CORE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py ${CMAKE_CURRENT_SOURCE_DIR}/tdc_sync.py ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.py + ${CMAKE_CURRENT_SOURCE_DIR}/white_rabbit.py ) LIST(APPEND USRP_MPM_FILES ${USRP_MPM_CORE_FILES}) SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE) diff --git a/mpm/python/usrp_mpm/cores/__init__.py b/mpm/python/usrp_mpm/cores/__init__.py index 0cd2bc0c7..ab7a505f9 100644 --- a/mpm/python/usrp_mpm/cores/__init__.py +++ b/mpm/python/usrp_mpm/cores/__init__.py @@ -8,3 +8,4 @@ Cores submodule """ from .tdc_sync import ClockSynchronizer +from .white_rabbit import WhiteRabbitRegsControl diff --git a/mpm/python/usrp_mpm/cores/tdc_sync.py b/mpm/python/usrp_mpm/cores/tdc_sync.py index 393dc2759..b54abfe10 100644 --- a/mpm/python/usrp_mpm/cores/tdc_sync.py +++ b/mpm/python/usrp_mpm/cores/tdc_sync.py @@ -9,6 +9,7 @@ TDC clock synchronization import time import math +from fractions import gcd from functools import reduce from builtins import object from usrp_mpm.mpmutils import poll_with_timeout @@ -28,23 +29,23 @@ class ClockSynchronizer(object): The actual synchronization is run in run_sync(). """ # TDC Control Register address constants - TDC_CONTROL = 0x200 - TDC_STATUS = 0x208 - RP_OFFSET_0 = 0x20C - RP_OFFSET_1 = 0x210 - SP_OFFSET_0 = 0x214 - SP_OFFSET_1 = 0x218 - RP_PERIOD_CONTROL = 0x220 - SP_PERIOD_CONTROL = 0x224 - RPT_PERIOD_CONTROL = 0x228 - SPT_PERIOD_CONTROL = 0x22C - TDC_MASTER_RESET = 0x230 - REPULSE_PERIOD_CONTROL_1 = 0x240 - REPULSE_PERIOD_CONTROL_2 = 0x244 - SYNC_SIGNATURE = 0x300 - SYNC_REVISION = 0x304 - SYNC_OLDESTCOMPAT = 0x308 - SYNC_SCRATCH = 0x30C + TDC_CONTROL = 0x000 + TDC_STATUS = 0x008 + RP_OFFSET_0 = 0x00C + RP_OFFSET_1 = 0x010 + SP_OFFSET_0 = 0x014 + SP_OFFSET_1 = 0x018 + RP_PERIOD_CONTROL = 0x020 + SP_PERIOD_CONTROL = 0x024 + RPT_PERIOD_CONTROL = 0x028 + SPT_PERIOD_CONTROL = 0x02C + TDC_MASTER_RESET = 0x030 + REPULSE_PERIOD_CONTROL_1 = 0x040 + REPULSE_PERIOD_CONTROL_2 = 0x044 + SYNC_SIGNATURE = 0x100 + SYNC_REVISION = 0x104 + SYNC_OLDESTCOMPAT = 0x108 + SYNC_SCRATCH = 0x10C def __init__( self, @@ -58,6 +59,7 @@ class ClockSynchronizer(object): init_pdac_word, dac_spi_addr_val, pps_in_pipe_ext_delay, + pps_in_pipe_dynamic_delay, slot_idx ): self._iface = regs_iface @@ -73,6 +75,8 @@ class ClockSynchronizer(object): self.current_phase_dac_word = init_pdac_word self.lmk_vco_freq = self.lmk.get_vco_freq() self.dac_spi_addr_val = dac_spi_addr_val + self.meas_clk_freq = None + self.target_values = [] # Output PPS static delay is the minimum number of radio_clk cycles from the SP-t # rising edge to when PPS appears on the output of the trigger passing module in # the radio_clk domain. 2 cycles are from the trigger crossing structure and @@ -85,24 +89,30 @@ class ClockSynchronizer(object): self.pps_out_pipe_var_delay = 0 # Input PPS delay (in ref_clk cycles) is recorded here and only changes when # the TDC structure changes. This represents the number of ref_clk cycles from - # PPS arriving at the input of the TDC to when the RP/-t pulse occurs. - self.PPS_IN_PIPE_STATIC_DELAY = 5 + # PPS arriving at the input of the TDC to when the RP/-t pulse occurs. There are + # static delays and dynamic delays. Dynamic delay is 0-15 additional cycles. + # Default for older drivers was a total of 5 cycles. Increase the dynamic delay + # to delay the RP/SP pulsers start with respect to the Reset Pulser. + self.PPS_IN_PIPE_STATIC_DELAY = 4 + self.pps_in_pipe_dynamic_delay = pps_in_pipe_dynamic_delay # 0-15 # External input PPS delay is a target-specific value, typically 3 ref_clk cycles. # This represents the number of ref_clk cycles from when PPS is first captured # by the ref_clk to when PPS arrives at the input of the TDC. self.pps_in_pipe_ext_delay = pps_in_pipe_ext_delay self.tdc_rev = 1 # update theses lists whenever more rates are supported - self.supported_ref_clk_freqs = [10e6,20e6,25e6] - if self.ref_clk_freq not in self.supported_ref_clk_freqs: - self.log.error("Clock synchronizer does not support the selected reference clock " - "frequency. Selected rate: {:.2f} MHz".format( + self.SUPPORTED_PULSE_RATES = [1e6, 1.25e6, 1.2288e6] # order matters here! + self.SUPPORTED_REF_CLK_FREQS = [10e6, 20e6, 25e6, 62.5e6] + if self.ref_clk_freq not in self.SUPPORTED_REF_CLK_FREQS: + self.log.error("Clock synchronizer does not support the selected reference " + "clock frequency. Selected rate: {:.2f} MHz".format( self.ref_clk_freq*1e-6)) raise RuntimeError("TDC does not support the selected reference clock rate!") - self.supported_radio_clk_freqs = [104e6,122.88e6,125e6,153.6e6,156.25e6,200e6,250e6] + self.supported_radio_clk_freqs = [104e6, 122.88e6, 125e6, 153.6e6, 156.25e6, \ + 200e6, 250e6] if self.radio_clk_freq not in self.supported_radio_clk_freqs: - self.log.error("Clock synchronizer does not support the selected radio clock " - "frequency. Selected rate: {:.2f} MHz".format( + self.log.error("Clock synchronizer does not support the selected radio clock" + " frequency. Selected rate: {:.2f} MHz".format( self.radio_clk_freq*1e-6)) raise RuntimeError("TDC does not support the selected radio clock rate!") @@ -110,7 +120,7 @@ class ClockSynchronizer(object): # YYMMDDHH self.oldest_compat_version = 0x17060111 # Bump this whenever changes are made to this MPM host code. - self.current_version = 0x18021614 + self.current_version = 0x18032916 self.check_core() self.configured = False @@ -121,24 +131,29 @@ class ClockSynchronizer(object): """ self.log.trace("Checking TDC Core...") if self.peek32(self.SYNC_SIGNATURE) != 0x73796e63: # SYNC in ASCII hex - raise RuntimeError('TDC Core signature mismatch! Check that core is mapped correctly') + raise RuntimeError('TDC Core signature mismatch! Check that core ' + 'is mapped correctly') # Two revision checks are needed: # FPGA Current Rev >= Host Oldest Compatible Rev # Host Current Rev >= FPGA Oldest Compatible Rev - fpga_current_revision = self.peek32(self.SYNC_REVISION) & 0xFFFFFFFF + fpga_current_revision = self.peek32(self.SYNC_REVISION) & 0xFFFFFFFF fpga_old_compat_revision = self.peek32(self.SYNC_OLDESTCOMPAT) & 0xFFFFFFFF if fpga_current_revision < self.oldest_compat_version: self.log.error("Revision check failed! MPM oldest supported revision " "(0x{:08X}) is too new for this FPGA revision (0x{:08X})." .format(self.oldest_compat_version, fpga_current_revision)) - raise RuntimeError('This MPM version does not support the loaded FPGA image. Please update images!') + raise RuntimeError('This MPM version does not support the loaded FPGA image.' + ' Please update images!') if self.current_version < fpga_old_compat_revision: self.log.error("Revision check failed! FPGA oldest compatible revision " "(0x{:08X}) is too new for this MPM version (0x{:08X})." .format(fpga_current_revision, self.current_version)) - raise RuntimeError('The loaded FPGA version is too new for MPM. Please update MPM!') - self.log.trace("TDC Core current revision: 0x{:08X}".format(fpga_current_revision)) - self.log.trace("TDC Core oldest compatible revision: 0x{:08X}".format(fpga_old_compat_revision)) + raise RuntimeError('The loaded FPGA version is too new for MPM. ' + 'Please update MPM!') + self.log.trace("TDC Core current revision: 0x{:08X}" \ + .format(fpga_current_revision)) + self.log.trace("TDC Core oldest compatible revision: 0x{:08X}" \ + .format(fpga_old_compat_revision)) # Versioning notes: # TDC 1.0 = [0, 0x18021614) # TDC 2.0 = [0x18021614, today] @@ -191,9 +206,9 @@ class ClockSynchronizer(object): report_only = (len(num_meas) > 1) & (x == (len(num_meas)-1)) meas = self.measure(num_meas[x]) offset = self.align( - target_offset=target_offset, - current_value=meas, - report_only=report_only) + target_offset=target_offset, + current_value=meas, + report_only=report_only) return offset @@ -238,6 +253,18 @@ class ClockSynchronizer(object): .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. @@ -247,74 +274,66 @@ class ClockSynchronizer(object): # 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 + 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 = 10e3 + pulse_rate = find_rate(clk_freq, [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 {}!" \ + self.log.error("TDC Setup Failure: Unrecognized pulse name given: {}" \ .format(pulser)) - raise RuntimeError("TDC Failed to Initialize. Check your clock rates " \ - "for compatibility!") + raise RuntimeError("TDC Failed to Initialize. " + "Unrecognized pulse name given!") 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. + # 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 - return (hi_time << 16) | period + 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!") - 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)) + 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) @@ -339,6 +358,12 @@ class ClockSynchronizer(object): 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. @@ -391,16 +416,17 @@ class ClockSynchronizer(object): max_skew = 0.5e-9 # 500 ps of tolerated skew either direction meas_err = bool(sum([x < current_value-max_skew for x in measurements])) or \ bool(sum([x > current_value+max_skew for x in measurements])) + meas_range = max(measurements) - min(measurements) + + self.log.trace("TDC Measurements Collected! Average = {:.3f} ns. " + "Range: {:.3f} ns".format(current_value*1e9, meas_range*1e9)) + self.log.trace("TDC Measurement Duration: {:.3f} s" \ + .format(time.time()-tdc_start_time)) if meas_err: self.log.error("TDC measurements show a wide range of values! " "Check your clock rates for incompatibilities.") raise RuntimeError("TDC measurement out of expected range!") - self.log.trace("TDC Measurements Collected! Average = {:.3f} ns".format( - current_value*1e9)) - - self.log.trace("TDC Measurement Duration: {:.3f} s".format( - time.time()-tdc_start_time)) return current_value diff --git a/mpm/python/usrp_mpm/cores/white_rabbit.py b/mpm/python/usrp_mpm/cores/white_rabbit.py new file mode 100644 index 000000000..c30c325e9 --- /dev/null +++ b/mpm/python/usrp_mpm/cores/white_rabbit.py @@ -0,0 +1,54 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +White Rabbit Control +""" + +from builtins import object +from usrp_mpm.sys_utils.uio import UIO + + +class WhiteRabbitRegsControl(object): + """ + Control and read the FPGA White Rabbit core registers + """ + # Memory Map + # 0x00000000: I/D Memory + # 0x00020000: Peripheral interconnect + # +0x000: Minic + # +0x100: Endpoint + # +0x200: Softpll + # +0x300: PPS gen + # +0x400: Syscon + # +0x500: UART + # +0x600: OneWire + # +0x700: Auxillary space (Etherbone config, etc) + # +0x800: WRPC diagnostics registers + PERIPH_INTERCON_BASE = 0x20000 + + # PPS_GEN Map + PPSG_ESCR = 0x31C + + def __init__(self, label, log): + self.log = log + self.regs = UIO( + label=label, + read_only=False + ) + self.periph_peek32 = lambda addr: self.regs.peek32(addr + self.PERIPH_INTERCON_BASE) + self.periph_poke32 = lambda addr, data: self.regs.poke32(addr + self.PERIPH_INTERCON_BASE, data) + + def get_time_lock_status(self): + """ + Retrieves and decodes the lock status for the PPS out of the WR core. + """ + with self.regs.open(): + ext_sync_status = self.periph_peek32(self.PPSG_ESCR) + # bit 2: PPS_VALID + # bit 3: TM_VALID (timecode) + # All other bits MUST be ignored since they are not guaranteed to be zero or + # stable! + return (ext_sync_status & 0b1100) == 0b1100 diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index 9c36abe89..6ed4dfd84 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -158,7 +158,13 @@ class Magnesium(DboardManagerBase): # point, so we set it to the (carefully calculated) alternate value instead. INIT_PHASE_DAC_WORD = 31000 # Intentionally decimal PHASE_DAC_SPI_ADDR = 0x0 + # External PPS pipeline delay from the PPS captured at the FPGA to TDC input, + # in reference clock ticks + EXT_PPS_DELAY = 5 + # Variable PPS delay before the RP/SP pulsers begin. Fixed value for the N3xx devices. + N3XX_INT_PPS_DELAY = 4 default_master_clock_rate = 125e6 + default_time_source = 'internal' default_current_jesd_rate = 2500e6 def __init__(self, slot_idx, **kwargs): @@ -336,17 +342,25 @@ class Magnesium(DboardManagerBase): ) def _sync_db_clock(): " Synchronizes the DB clock to the common reference " + reg_offset = 0x200 + ref_clk_freq = self.ref_clock_freq + ext_pps_delay = self.EXT_PPS_DELAY + if args.get('time_source', self.default_time_source) == 'sfp0': + reg_offset = 0x400 + ref_clk_freq = 62.5e6 + ext_pps_delay = 1 # only 1 flop between the WR core output and the TDC input synchronizer = ClockSynchronizer( dboard_ctrl_regs, self.lmk, self._spi_ifaces['phase_dac'], - 0, # register offset value. + reg_offset, self.master_clock_rate, - self.ref_clock_freq, + ref_clk_freq, 860E-15, # fine phase shift. TODO don't hardcode. This should live in the EEPROM self.INIT_PHASE_DAC_WORD, self.PHASE_DAC_SPI_ADDR, - 5, # External PPS pipeline delay from the PPS captured at the FPGA to TDC input + ext_pps_delay, + self.N3XX_INT_PPS_DELAY, self.slot_idx) # The radio clock traces on the motherboard are 69 ps longer for Daughterboard B # than Daughterboard A. We want both of these clocks to align at the converters diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py index 373689521..ec7fedad9 100644 --- a/mpm/python/usrp_mpm/periph_manager/n3xx.py +++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py @@ -13,14 +13,16 @@ import copy import shutil import subprocess import json +import time import datetime import threading from six import iteritems, itervalues from builtins import object +from usrp_mpm.cores import WhiteRabbitRegsControl from usrp_mpm.gpsd_iface import GPSDIface from usrp_mpm.periph_manager import PeriphManagerBase from usrp_mpm.mpmtypes import SID -from usrp_mpm.mpmutils import assert_compat_number, str2bool +from usrp_mpm.mpmutils import assert_compat_number, str2bool, poll_with_timeout from usrp_mpm.rpc_server import no_rpc from usrp_mpm.sys_utils import dtoverlay from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank @@ -35,7 +37,7 @@ N3XX_DEFAULT_ENABLE_GPS = True N3XX_DEFAULT_ENABLE_FPGPIO = True N3XX_FPGA_COMPAT = (5, 2) N3XX_MONITOR_THREAD_INTERVAL = 1.0 # seconds -N3XX_SFP_TYPES = {0:"", 1:"1G", 2:"10G", 3:"A"} +N3XX_SFP_TYPES = {0:"", 1:"1G", 2:"10G", 3:"A", 4:"W"} ############################################################################### # Additional peripheral controllers specific to Magnesium @@ -193,6 +195,8 @@ class MboardRegsControl(object): MB_CLOCK_CTRL_PPS_SEL_INT_25 = 1 MB_CLOCK_CTRL_PPS_SEL_EXT = 2 MB_CLOCK_CTRL_PPS_SEL_GPSDO = 3 + MB_CLOCK_CTRL_PPS_SEL_SFP0 = 5 + MB_CLOCK_CTRL_PPS_SEL_SFP1 = 6 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 @@ -317,11 +321,20 @@ class MboardRegsControl(object): elif time_source == 'gpsdo': self.log.debug("Setting time source to gpsdo...") pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_GPSDO + elif time_source == 'sfp0': + self.log.debug("Setting time source to sfp0...") + pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP0 + elif time_source == 'sfp1': + self.log.debug("Setting time source to sfp1...") + pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP1 else: assert False + with self.regs.open(): - reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFFF0 - reg_val = reg_val | (pps_sel_val & 0xF) + reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFF90 + # prevent glitches by writing a cleared value first, then the final value. + self.poke32(self.MB_CLOCK_CTRL, reg_val) + reg_val = reg_val | (pps_sel_val & 0x6F) self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) self.poke32(self.MB_CLOCK_CTRL, reg_val) @@ -397,12 +410,26 @@ class MboardRegsControl(object): return "XA" elif (sfp0_type == "A") and (sfp1_type == "A"): return "AA" + elif (sfp0_type == "W") and (sfp1_type == "10G"): + return "WX" else: self.log.warning("Unrecognized SFP type combination: ({}, {})".format( sfp0_type, sfp1_type )) return "" + def get_sfp0_link_status(self): + """ + Reads the type of the FPGA image currently loaded + Returns a string with the type (ie HG, XG, AA, etc.) + """ + with self.regs.open(): + sfp0_info_rb = self.peek32(self.MB_SFP0_INFO) + # Print the registers values as 32-bit hex values + self.log.trace("SFP0 Info: 0x{0:0{1}X}".format(sfp0_info_rb, 8)) + sfp0_link = bool((sfp0_info_rb & 0x00010000) >> 16) + return sfp0_link + ############################################################################### # Transport managers @@ -483,6 +510,8 @@ class n3xx(PeriphManagerBase): # N3xx-specific settings # Label for the mboard UIO mboard_regs_label = "mboard-regs" + # Label for the white rabbit UIO + wr_regs_label = "wr-regs" # Override the list of updateable components updateable_components = { 'fpga': { @@ -627,6 +656,7 @@ class n3xx(PeriphManagerBase): self.log.trace("Enabling power of MGT156MHZ clk") self._gpios.set("PWREN-CLK-MGT156MHz") self.enable_1g_ref_clock() + self.enable_wr_ref_clock() self.enable_gps( enable=str2bool( args.get('enable_gps', N3XX_DEFAULT_ENABLE_GPS) @@ -892,7 +922,7 @@ class n3xx(PeriphManagerBase): def get_time_sources(self): " Returns list of valid time sources " - return ['internal', 'external', 'gpsdo'] + return ['internal', 'external', 'gpsdo', 'sfp0'] def get_time_source(self): " Return the currently selected time source " @@ -902,8 +932,36 @@ class n3xx(PeriphManagerBase): " 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()) + 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.") + def set_fp_gpio_master(self, value): """set driver for front panel GPIO @@ -980,7 +1038,7 @@ class n3xx(PeriphManagerBase): Enables 125 MHz refclock for 1G interface. """ self.log.trace("Enable 125 MHz Clock for 1G SFP interface.") - self._gpios.set("NETCLK-CE") + self._gpios.set("NETCLK-CE", 1) self._gpios.set("NETCLK-RESETn", 0) self._gpios.set("NETCLK-PR0", 1) self._gpios.set("NETCLK-PR1", 1) @@ -991,6 +1049,14 @@ class n3xx(PeriphManagerBase): self.log.trace("Finished configuring NETCLK CDCM.") self._gpios.set("NETCLK-RESETn", 1) + def enable_wr_ref_clock(self): + """ + Enables 20 MHz WR refclk. Note that enable_1g_ref_clock() is also required for this + interface to work, although calling it here is redundant. + """ + self.log.trace("Enable White Rabbit reference clock.") + self._gpios.set("PWREN-CLK-WB-20MHz", 1) + ########################################################################### # Sensors ########################################################################### |