aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--images/image_package_mapping.py18
-rw-r--r--mpm/python/usrp_mpm/cores/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/cores/__init__.py1
-rw-r--r--mpm/python/usrp_mpm/cores/tdc_sync.py206
-rw-r--r--mpm/python/usrp_mpm/cores/white_rabbit.py54
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/magnesium.py20
-rw-r--r--mpm/python/usrp_mpm/periph_manager/n3xx.py82
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
###########################################################################