mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
260 lines
11 KiB
Python
260 lines
11 KiB
Python
#
|
|
# 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 .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
|
|
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):
|
|
" Initialize framer "
|
|
self.log.trace("Initializing framer...")
|
|
# Disable DAC Sync from requesting CGS & Stop Deframer
|
|
self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0x2002)
|
|
# 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 disable char replacement
|
|
self.regs.poke32(self.MGT_TX_SCRAMBLER_CONTROL, 0x1)
|
|
# 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 the framer and incoming DAC Sync
|
|
self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0x1000)
|
|
self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0x0001)
|
|
|
|
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.
|
|
"""
|
|
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:
|
|
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 down unused CPLLs and QPLLs...")
|
|
self.log.trace("Using {} 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)
|
|
|
|
# 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)
|
|
|
|
# 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)
|
|
rb = self.regs.peek32(self.MGT_QPLL_CONTROL)
|
|
self.log.trace("Reading QPLL status register: {:04X}".format(rb & 0xFFFF))
|
|
# Check for lock on active quads only.
|
|
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!")
|
|
|