aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/cores
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2017-12-12 09:59:50 -0800
committerMartin Braun <martin.braun@ettus.com>2017-12-22 15:05:58 -0800
commitd3e6dd11406893bfbc5537dfbe74d8151bbc1280 (patch)
tree8663263b3c5a4ff7202e01a5f9c5c6bb23969829 /mpm/python/usrp_mpm/cores
parentea7cc7f8250cd9bdb41c1f22703d38be91fb6a84 (diff)
downloaduhd-d3e6dd11406893bfbc5537dfbe74d8151bbc1280.tar.gz
uhd-d3e6dd11406893bfbc5537dfbe74d8151bbc1280.tar.bz2
uhd-d3e6dd11406893bfbc5537dfbe74d8151bbc1280.zip
mpm: Harmonize imports, tidy + sort modules
- Moved nijesdcore to cores/ - Moved udev, net, dtoverlay, uio to sys_utils/ - Made all imports non-relative (except in __init__.py files) - Removed some unnecessary imports - Reordered some imports for Python conventions
Diffstat (limited to 'mpm/python/usrp_mpm/cores')
-rw-r--r--mpm/python/usrp_mpm/cores/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/cores/nijesdcore.py325
-rw-r--r--mpm/python/usrp_mpm/cores/tdc_sync.py2
3 files changed, 327 insertions, 1 deletions
diff --git a/mpm/python/usrp_mpm/cores/CMakeLists.txt b/mpm/python/usrp_mpm/cores/CMakeLists.txt
index 9103ad994..25949099d 100644
--- a/mpm/python/usrp_mpm/cores/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/cores/CMakeLists.txt
@@ -19,6 +19,7 @@ SET(USRP_MPM_FILES ${USRP_MPM_FILES})
SET(USRP_MPM_CORE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/tdc_sync.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.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/nijesdcore.py b/mpm/python/usrp_mpm/cores/nijesdcore.py
new file mode 100644
index 000000000..12be51181
--- /dev/null
+++ b/mpm/python/usrp_mpm/cores/nijesdcore.py
@@ -0,0 +1,325 @@
+#
+# Copyright 2017 Ettus Research (National Instruments)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+JESD FPGA Core Interface
+"""
+
+import time
+from builtins import hex
+from builtins import object
+from usrp_mpm.mpmlog import get_logger
+
+class NIMgJESDCore(object):
+ """
+ Provide interface for the FPGA JESD Core.
+ Works with Magnesium/Mykonos daughterboards only.
+
+ Arguments:
+ regs -- regs class to use for peek/poke
+ """
+
+ DB_ID = 0x0630
+ MGT_QPLL_CONTROL = 0x2000
+ MGT_PLL_POWER_DOWN_CONTROL = 0x200C
+ MGT_TX_RESET_CONTROL = 0x2020
+ MGT_RX_RESET_CONTROL = 0x2024
+ MGT_RECEIVER_CONTROL = 0x2040
+ MGT_RX_DESCRAMBLER_CONTROL = 0x2050
+ MGT_TRANSMITTER_CONTROL = 0x2060
+ MGT_TX_TRANSCEIVER_CONTROL = 0x2064
+ MGT_TX_SCRAMBLER_CONTROL = 0x2068
+ LMK_SYNC_CONTROL = 0x206C
+ JESD_MGT_DRP_CONTROL = 0x2070
+ SYSREF_CAPTURE_CONTROL = 0x2078
+ JESD_SIGNATURE_REG = 0x2100
+ JESD_REVISION_REG = 0x2104
+
+
+ def __init__(self, regs, slot_idx=0):
+ self.regs = regs
+ self.log = get_logger("NIMgJESDCore-{}".format(slot_idx))
+ assert hasattr(self.regs, 'peek32')
+ assert hasattr(self.regs, 'poke32')
+ # FutureWork: The following are constants for the Magnesium board. These need
+ # to change to variables to support other interfaces.
+ self.qplls_used = 1
+ self.cplls_used = 0
+ # Number of FPGA clock cycles per LMFC period.
+ self.lmfc_divider = 20
+
+ def unreset_qpll(self):
+ # new_val = self.regs.peek32(0x0) & ~0x8
+ # self.log.trace("Unresetting MMCM, writing value {:X}".format(new_val))
+ self.regs.poke32(0x0, 0x7)
+
+ def check_core(self):
+ """
+ Verify JESD core returns correct ID
+ """
+ self.log.trace("Checking JESD Core...")
+ if self.regs.peek32(self.JESD_SIGNATURE_REG) != 0x4A455344:
+ raise Exception('JESD Core signature mismatch! Check that core is mapped correctly')
+ #if self.regs.peek32(JESD_REVISION_REG) != 0xFF
+ #error here for date revision mismatch
+ self.log.trace("JESD Core build code: {0}".format(hex(self.regs.peek32(self.JESD_REVISION_REG))))
+ self.log.trace("DB Slot #: {}".format( (self.regs.peek32(self.DB_ID) & 0x10000) >> 16 ))
+ self.log.trace("DB PID: {:X}" .format( self.regs.peek32(self.DB_ID) & 0xFFFF ))
+ return True
+
+ def reset(self):
+ """
+ Reset to the core. Places the PLLs, TX MGTs and RX MGTs (along with the glue
+ logic) in reset. Also disables the SYSREF sampler.
+ """
+ self.log.trace("Resetting the JESD204B FPGA core(s)...")
+ self._gt_reset('tx', reset_only=True)
+ self._gt_reset('rx', reset_only=True)
+ self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=True)
+ # Disable SYSREF Sampler
+ self.enable_lmfc(False)
+
+ def init_deframer(self):
+ " Initialize deframer "
+ self.log.trace("Initializing deframer...")
+ self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x2)
+ self.regs.poke32(self.MGT_RX_DESCRAMBLER_CONTROL, 0x0)
+ self._gt_reset('rx', reset_only=False)
+ self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x0)
+
+ def init_framer(self, bypass_scrambler = True):
+ " Initialize framer "
+ self.log.trace("Initializing framer...")
+ # Disable DAC Sync from requesting CGS & Stop Deframer
+ self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, (0b1 << 13) | (0b1 << 1))
+ # Reset, unreset, and check the GTs
+ self._gt_reset('tx', reset_only=False)
+ # MGT phy control... enable TX Driver Swing
+ self.regs.poke32(self.MGT_TX_TRANSCEIVER_CONTROL, 0xF0000)
+ time.sleep(0.001)
+ # Bypass scrambler and char replacement. If the scrambler is bypassed,
+ # then the char replacement is also disabled.
+ reg_val = {True: 0x01, False: 0x10}[bypass_scrambler]
+ self.regs.poke32(self.MGT_TX_SCRAMBLER_CONTROL, reg_val)
+ # Check for Framer in Idle state
+ rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL)
+ if rb & 0x100 != 0x100:
+ raise Exception('TX Framer is not idle after reset')
+ # Enable incoming DAC Sync
+ self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 12)
+ # Enable the framer
+ self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0b1 << 0)
+
+ def get_framer_status(self):
+ " Return True if framer is in good status "
+ rb = self.regs.peek32(self.MGT_TRANSMITTER_CONTROL)
+ self.log.trace("FPGA Framer status: {0}".format(hex(rb & 0xFF0)))
+ if rb & (0b1 << 8) == 0b1 << 8:
+ self.log.warning("Framer warning: Framer is Idle!")
+ elif rb & (0b1 << 6) == 0b0 << 6:
+ self.log.warning("Framer warning: Code Group Sync failed to complete!")
+ elif rb & (0b1 << 7) == 0b0 << 7:
+ self.log.warning("Framer warning: Lane Alignment failed to complete!")
+ return rb & 0xFF0 == 0x6C0
+
+ def get_deframer_status(self):
+ " Return True if deframer is in good status "
+ rb = self.regs.peek32(self.MGT_RECEIVER_CONTROL)
+ self.log.trace("FPGA Deframer status: {0}".format(hex(rb & 0xFFFFFFFF)))
+ if rb & (0b1 << 2) == 0b0 << 2:
+ self.log.warning("Deframer warning: Code Group Sync failed to complete!")
+ elif rb & (0b1 << 3) == 0b0 << 3:
+ self.log.warning("Deframer warning: Channel Bonding failed to complete!")
+ elif rb & (0b1 << 21) == 0b1 << 21:
+ self.log.warning("Deframer warning: Misc link error!")
+ return rb & 0xFFFFFFFF == 0xF000001C
+
+ def init(self):
+ """
+ Initializes the core. Must happen after the reference clock is stable.
+ """
+ self.log.trace("Initializing JESD204B FPGA core(s)...")
+ self._gt_pll_power_control(self.qplls_used, self.cplls_used)
+ self._gt_reset('tx', reset_only=True)
+ self._gt_reset('rx', reset_only=True)
+ self._gt_pll_lock_control(self.qplls_used, self.cplls_used, reset_only=False)
+ # Disable SYSREF Sampler
+ self.enable_lmfc(False)
+
+ def enable_lmfc(self, enable=False):
+ """
+ Enable/disable LMFC generator in FPGA.
+ """
+ self.log.trace("%s FPGA SYSREF Receiver..." % {True: 'Enabling', False: 'Disabling'}[enable])
+ disable_bit = 0b1
+ if enable:
+ disable_bit = 0b0
+ reg_val = ((self.lmfc_divider-1) << 23) | (disable_bit << 6)
+ self.log.trace("Setting SYSREF Capture reg: 0x{:08X}".format(reg_val))
+ self.regs.poke32(self.SYSREF_CAPTURE_CONTROL, reg_val)
+
+ def send_sysref_pulse(self):
+ """
+ Toggles the LMK pin that triggers a SYSREF pulse.
+ Note: SYSREFs must be enabled on LMK separately beforehand.
+ """
+ self.log.trace("Sending SYSREF pulse...")
+ self.regs.poke32(self.LMK_SYNC_CONTROL, 0b1 << 30) # Bit 30. Self-clears.
+
+ def _gt_reset(self, tx_or_rx, reset_only=False):
+ " Put MGTs into reset. Optionally unresets and enables them "
+ assert tx_or_rx.lower() in ('rx', 'tx')
+ mgt_reg = {'tx': self.MGT_TX_RESET_CONTROL, 'rx': self.MGT_RX_RESET_CONTROL}[tx_or_rx]
+ self.log.trace("Resetting %s MGTs..." % tx_or_rx.upper())
+ self.regs.poke32(mgt_reg, 0x10)
+ if not reset_only:
+ self.regs.poke32(mgt_reg, 0x20)
+ rb = -1
+ for _ in range(20):
+ rb = self.regs.peek32(mgt_reg)
+ if rb & 0xFFFF0000 == 0x000F0000:
+ self.log.trace("%s MGT Reset Cleared!" % tx_or_rx.upper())
+ return True
+ time.sleep(0.001)
+ raise Exception('Timeout in GT {trx} Reset (Readback: 0x{rb:X})'.format(
+ trx=tx_or_rx.upper(),
+ rb=(rb & 0xFFFF0000),
+ ))
+ return True
+
+ def _gt_pll_power_control(self, qplls = 0, cplls = 0):
+ " Power down unused CPLLs and QPLLs "
+ assert qplls in range(4+1) # valid is 0 - 4
+ assert cplls in range(8+1) # valid is 0 - 8
+ self.log.trace("Powering up {} CPLLs and {} QPLLs".format(cplls, qplls))
+ reg_val = 0xFFFF000F
+ reg_val_on = 0x0
+ # Power down state is when the corresponding bit is set. For the PLLs we wish to
+ # use, clear those bits.
+ for x in range(qplls):
+ reg_val_on = reg_val_on | 0x1 << x # QPLL bits are 0-3
+ for y in range(16, 16 + cplls):
+ reg_val_on = reg_val_on | 0x1 << y # CPLL bits are 16-23, others are reserved
+ reg_val = reg_val ^ reg_val_on
+ self.regs.poke32(self.MGT_PLL_POWER_DOWN_CONTROL, reg_val)
+
+ def _gt_pll_lock_control(self, qplls = 0, cplls = 0, reset_only=False):
+ """
+ Turn on the PLLs we're using, and make sure lock bits are set.
+ QPLL bitfield mapping: the following nibble is repeated for each QPLL. For
+ example, QPLL0 get bits 0-3, QPLL1 get bits 4-7, etc.
+ [0] = reset
+ [1] = locked
+ [2] = unlocked sticky
+ [3] = ref clock lost sticky
+ ...
+ [16] = sticky reset (strobe)
+ """
+ # FutureWork: CPLLs are NOT supported yet!!!
+ assert cplls == 0
+ assert qplls in range(4+1) # valid is 0 - 4
+
+ # Reset QPLLs.
+ reg_val = 0x1111 # by default assert all resets
+ self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val)
+ self.log.trace("Resetting QPLL(s)...")
+
+ # Unreset the PLLs in use and check for lock.
+ if not reset_only:
+ if qplls > 0:
+ # Unreset only the QPLLs we are using.
+ reg_val_on = 0x0
+ for nibble in range(qplls):
+ reg_val_on = reg_val_on | 0x1 << nibble*4
+ reg_val = reg_val ^ reg_val_on
+ self.regs.poke32(self.MGT_QPLL_CONTROL, reg_val)
+ self.log.trace("Clearing QPLL reset...")
+
+ # Check for lock a short time later.
+ time.sleep(0.010)
+ # Clear all QPLL sticky bits
+ self.regs.poke32(self.MGT_QPLL_CONTROL, 0b1 << 16)
+ # Check for lock on active quads only.
+ rb = self.regs.peek32(self.MGT_QPLL_CONTROL)
+ rb_mask = 0x0
+ locked_val = 0x0
+ for nibble in range(qplls):
+ if (rb & (0xF << nibble*4)) != (0x2 << nibble*4):
+ self.log.warning("GT QPLL {} failed to lock!".format(nibble))
+ locked_val = locked_val | 0x2 << nibble*4
+ rb_mask = rb_mask | 0xF << nibble*4
+ if (rb & rb_mask) != locked_val:
+ raise Exception("One or more GT QPLLs failed to lock!")
+ self.log.trace("QPLL(s) reporting locked!")
+
+ def set_drp_target(self, mgt_or_qpll, dev_num):
+ """
+ Sets up access to the specified MGT or QPLL. This must be called
+ prior to drp_access(). It may be called repeatedly to change DRP targets
+ without calling the disable function first.
+ """
+ MAX_MGTS = 4
+ MAX_QPLLs = 1
+ DRP_ENABLE_VAL = 0b1
+ assert mgt_or_qpll.lower() in ('mgt', 'qpll')
+
+ self.log.trace("Enabling DRP access to %s #%d...",mgt_or_qpll.upper(), dev_num)
+
+ # Enable access to the DRP ports and select the correct channel. Channels are
+ # one-hot encoded with the MGT ports in bit locations [0, (MAX_MGTS-1)] and the
+ # QPLL in [MAX_MGTS, MAX_MGTs+MAX_QPLLs-1].
+ drp_ch_sel = {'mgt': dev_num, 'qpll': dev_num + MAX_MGTS}[mgt_or_qpll.lower()]
+ assert drp_ch_sel in range(MAX_MGTS + MAX_QPLLs)
+ reg_val = (0b1 << drp_ch_sel) | (DRP_ENABLE_VAL << 16)
+ self.log.trace("Writing DRP Control Register (offset 0x{:04X}) with 0x{:08X}"
+ .format(self.JESD_MGT_DRP_CONTROL, reg_val))
+ self.regs.poke32(self.JESD_MGT_DRP_CONTROL, reg_val)
+
+ def disable_drp_target(self):
+ """
+ Tears down access to the DRP ports. This must be called after drp_access().
+ """
+ self.regs.poke32(self.JESD_MGT_DRP_CONTROL, 0x0)
+ self.log.trace("DRP accesses disabled!")
+
+ def drp_access(self, rd = True, addr = 0, wr_data = 0):
+ """
+ Provides register access to the DRP ports on the MGTs or QPLLs buried inside
+ the JESD204b logic. Reads will return the DRP data directly. Writes will return
+ zeros.
+ """
+ # Check the DRP port is not busy.
+ if (self.regs.peek32(self.JESD_MGT_DRP_CONTROL) & (0b1 << 20)) != 0:
+ self.log.error("MGT/QPLL DRP Port is reporting busy during an attempted access.")
+ raise Exception("MGT/QPLL DRP Port is reporting busy during an attempted access.")
+
+ # Access the DRP registers...
+ rd_data = 0x0
+ core_offset = 0x2800 + (addr << 2)
+ if rd:
+ rd_data = self.regs.peek32(core_offset)
+ rd_data_valid = rd_data & 0xFFFF
+ self.log.trace("Reading DRP register 0x{:04X} at DB Core offset 0x{:04X}... "
+ "0x{:04X}"
+ .format(addr, core_offset, rd_data))
+ else:
+ self.log.trace("Writing DRP register 0x{:04X} with 0x{:04X}...".format(addr, wr_data))
+ self.regs.poke32(core_offset, wr_data)
+ if self.regs.peek32(core_offset) != wr_data:
+ self.log.error("DRP read after write failed to match!")
+
+ return rd_data
+
diff --git a/mpm/python/usrp_mpm/cores/tdc_sync.py b/mpm/python/usrp_mpm/cores/tdc_sync.py
index 194449b65..255810710 100644
--- a/mpm/python/usrp_mpm/cores/tdc_sync.py
+++ b/mpm/python/usrp_mpm/cores/tdc_sync.py
@@ -19,8 +19,8 @@ TDC clock synchronization
import time
import math
-from builtins import object
from functools import reduce
+from builtins import object
from usrp_mpm.mpmutils import poll_with_timeout
from usrp_mpm.mpmlog import get_logger