diff options
author | djepson1 <daniel.jepson@ni.com> | 2017-11-22 16:42:14 -0600 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2017-12-22 15:05:58 -0800 |
commit | 66af893e43af6d628c40d26529c154bb7a1f6d97 (patch) | |
tree | eab8f6bc60da8dc8a796eb47d4d8293c2f432670 | |
parent | 58a096dbb661dfeaadad08264e18c57c121e846d (diff) | |
download | uhd-66af893e43af6d628c40d26529c154bb7a1f6d97.tar.gz uhd-66af893e43af6d628c40d26529c154bb7a1f6d97.tar.bz2 uhd-66af893e43af6d628c40d26529c154bb7a1f6d97.zip |
n3xx: enable DRP access to DB MGTS & QPLLs
- re-structured Magnesium.init_jesd() sequence to accomodate the reconfiguration
- added DRP access protocols to nijesdcore.py
- cleaned up a few comments and log statements in nijesdcore.py for flow
Reviewed-By: Martin Braun <martin.braun@ettus.com>
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/magnesium.py | 198 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/nijesdcore.py | 87 |
2 files changed, 249 insertions, 36 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/magnesium.py b/mpm/python/usrp_mpm/dboard_manager/magnesium.py index fdcaea0de..9cbb2bd9b 100644 --- a/mpm/python/usrp_mpm/dboard_manager/magnesium.py +++ b/mpm/python/usrp_mpm/dboard_manager/magnesium.py @@ -22,6 +22,7 @@ from __future__ import print_function import os import time import threading +import math from six import iterkeys, iteritems from . import lib # Pulls in everything from C++-land from .base import DboardManagerBase @@ -316,6 +317,7 @@ class Magnesium(DboardManagerBase): # point, so we set it to the (carefully calculated) alternate value instead. INIT_PHASE_DAC_WORD = 31000 # Intentionally decimal default_master_clock_rate = 125e6 + default_current_jesd_rate = 2500e6 def __init__(self, slot_idx, **kwargs): super(Magnesium, self).__init__(slot_idx, **kwargs) @@ -327,8 +329,9 @@ class Magnesium(DboardManagerBase): # This is a default ref clock freq, it must be updated before init() is # called! self.ref_clock_freq = 10e6 - # This will get updated during init() + # These will get updated during init() self.master_clock_rate = None + self.current_jesd_rate = None # Predeclare some attributes to make linter happy: self.lmk = None self._port_expander = None @@ -567,19 +570,6 @@ class Magnesium(DboardManagerBase): return True - def cpld_peek(self, addr): - """ - Debug for accessing the CPLD via the RPC shell. - """ - return self.cpld.peek16(addr) - - def cpld_poke(self, addr, data): - """ - Debug for accessing the CPLD via the RPC shell. - """ - self.cpld.poke16(addr, data) - return self.cpld.peek16(addr) - def init_rf_cal(self, args): " Setup RF CAL " self.log.info("Setting up RF CAL...") @@ -621,7 +611,17 @@ class Magnesium(DboardManagerBase): All clocks must be set up and stable before starting this routine. """ jesdcore.check_core() - jesdcore.init() + + # JESD Lane Rate only depends on the master_clock_rate selection, since all + # other link parameters (LMFS,N) remain constant. + L = 4 + M = 4 + F = 2 + S = 1 + N = 16 + new_rate = self.master_clock_rate * M * N * (10.0/8) / L / S + self.log.trace("Calculated JESD204b lane rate is {} Gbps".format(new_rate/1e9)) + self.set_jesd_rate(jesdcore, new_rate) self.log.trace("Pulsing Mykonos Hard Reset...") self.cpld.reset_mykonos() @@ -639,7 +639,7 @@ class Magnesium(DboardManagerBase): self.log.trace("Starting JESD204b Link Initialization...") # Generally, enable the source before the sink. Start with the DAC side. self.log.trace("Starting FPGA framer...") - jesdcore.init_framer() + jesdcore.init_framer(bypass_scrambler=True) self.log.trace("Starting Mykonos deframer...") self.mykonos.start_jesd_rx() # Now for the ADC link. Note that the Mykonos framer will not start issuing CGS @@ -648,7 +648,6 @@ class Magnesium(DboardManagerBase): # start the deframer in the FPGA. self.log.trace("Starting Mykonos framer...") self.mykonos.start_jesd_tx() - self.log.trace("Enable FPGA SYSREF Receiver.") jesdcore.enable_lmfc(True) jesdcore.send_sysref_pulse() self.log.trace("Starting FPGA deframer...") @@ -674,14 +673,109 @@ class Magnesium(DboardManagerBase): self.log.info("JESD204B Link Initialization & Training Complete") - def dump_jesd_core(self): - " Debug method to dump all JESD core regs " - 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" % dboard_ctrl_regs.peek32(i + j)), end=' ') - print("") + def set_jesd_rate(self, jesdcore, new_rate, force=False): + """ + Make the QPLL and GTX changes required to change the JESD204B core rate. + """ + # The core is directly compiled for 125 MHz sample rate, which + # corresponds to a lane rate of 2.5 Gbps. The same QPLL and GTX settings apply + # for the 122.88 MHz sample rate. + # + # The higher LTE rate, 153.6 MHz, requires changes to the default configuration + # of the MGT components. This function performs the required changes in the + # following order (as recommended by UG476). + # + # 1) Modify any QPLL settings. + # 2) Perform the QPLL reset routine by pulsing reset then waiting for lock. + # 3) Modify any GTX settings. + # 4) Perform the GTX reset routine by pulsing reset and waiting for reset done. + + assert new_rate in (2457.6e6, 2500e6, 3072e6) + + # On first run, we have no idea how the FPGA is configured... so let's force an + # update to our rate. + force = force or (self.current_jesd_rate is None) + + skip_drp = False + if not force: + # Current New Skip? + skip_drp = {2457.6e6 : {2457.6e6: True, 2500.0e6: True, 3072.0e6:False}, + 2500.0e6 : {2457.6e6: True, 2500.0e6: True, 3072.0e6:False}, + 3072.0e6 : {2457.6e6: False, 2500.0e6: False, 3072.0e6:True}}[self.current_jesd_rate][new_rate] + + if skip_drp: + self.log.trace("Current lane rate is compatible with the new rate. Skipping " + "reconfiguration.") + + # These are the only registers in the QPLL and GTX that change based on the + # selected line rate. The MGT wizard IP was generated for each of the rates and + # reference clock frequencies and then diffed to create this table. + QPLL_CFG = {2457.6e6: 0x680181, 2500e6: 0x680181, 3072e6: 0x06801C1}[new_rate] + QPLL_FBDIV = {2457.6e6: 0x120, 2500e6: 0x120, 3072e6: 0x80}[new_rate] + MGT_PMA_RSV = {2457.6e6: 0x1E7080, 2500e6: 0x1E7080, 3072e6: 0x18480}[new_rate] + MGT_RX_CLK25_DIV = {2457.6e6: 5, 2500e6: 5, 3072e6: 7}[new_rate] + MGT_TX_CLK25_DIV = {2457.6e6: 5, 2500e6: 5, 3072e6: 7}[new_rate] + MGT_RXOUT_DIV = {2457.6e6: 4, 2500e6: 4, 3072e6: 2}[new_rate] + MGT_TXOUT_DIV = {2457.6e6: 4, 2500e6: 4, 3072e6: 2}[new_rate] + MGT_RXCDR_CFG = {2457.6e6:0x03000023ff10100020, 2500e6:0x03000023ff10100020, 3072e6:0x03000023ff10200020}[new_rate] + + + # 1-2) Do the QPLL first + if not skip_drp: + self.log.trace("Changing QPLL settings to support {} Gbps".format(new_rate/1e9)) + jesdcore.set_drp_target('qpll', 0) + # QPLL_CONFIG is spread across two regs: 0x32 (dedicated) and 0x33 (shared) + reg_x32 = QPLL_CFG & 0xFFFF # [16:0] -> [16:0] + reg_x33 = jesdcore.drp_access(rd = True, addr = 0x33) + reg_x33 = (reg_x33 & 0xF800) | ((QPLL_CFG >> 16) & 0x7FF) # [26:16] -> [11:0] + jesdcore.drp_access(rd = False, addr = 0x32, wr_data = reg_x32) + jesdcore.drp_access(rd = False, addr = 0x33, wr_data = reg_x33) + # QPLL_FBDIV is shared with other settings in reg 0x36 + reg_x36 = jesdcore.drp_access(rd = True, addr = 0x36) + reg_x36 = (reg_x36 & 0xFC00) | (QPLL_FBDIV & 0x3FF) # in bits [9:0] + jesdcore.drp_access(rd = False, addr = 0x36, wr_data = reg_x36) + + # Run the QPLL reset sequence and prep the MGTs for modification. + jesdcore.init() + + # 3-4) And the 4 MGTs second + if not skip_drp: + self.log.trace("Changing MGT settings to support {} Gbps".format(new_rate/1e9)) + for lane in range(0,4): + jesdcore.set_drp_target('mgt', lane) + # MGT_PMA_RSV is split over 0x99 (LSBs) and 0x9A + reg_x99 = MGT_PMA_RSV & 0xFFFF + reg_x9A = (MGT_PMA_RSV >> 16) & 0xFFFF + jesdcore.drp_access(rd = False, addr = 0x99, wr_data = reg_x99) + jesdcore.drp_access(rd = False, addr = 0x9A, wr_data = reg_x9A) + # MGT_RX_CLK25_DIV is embedded with others in 0x11. The encoding for + # the DRP register value is one less than the desired value. + reg_x11 = jesdcore.drp_access(rd = True, addr = 0x11) + reg_x11 = (reg_x11 & 0xF83F) | ((MGT_RX_CLK25_DIV-1 & 0x1F) << 6) # [10:6] + jesdcore.drp_access(rd = False, addr = 0x11, wr_data = reg_x11) + # MGT_TX_CLK25_DIV is embedded with others in 0x6A. The encoding for + # the DRP register value is one less than the desired value. + reg_x6A = jesdcore.drp_access(rd = True, addr = 0x6A) + reg_x6A = (reg_x6A & 0xFFE0) | (MGT_TX_CLK25_DIV-1 & 0x1F) # [4:0] + jesdcore.drp_access(rd = False, addr = 0x6A, wr_data = reg_x6A) + # MGT_RXCDR_CFG is split over 0xA8 (LSBs) through 0xAD + RXCDR_REG_BASE = 0xA8 + for reg_num in range(0, 6): + reg_addr = RXCDR_REG_BASE + reg_num + reg_data = (MGT_RXCDR_CFG >> 16*reg_num) & 0xFFFF + jesdcore.drp_access(rd = False, addr = reg_addr, wr_data = reg_data) + # MGT_RXOUT_DIV and MGT_TXOUT_DIV are embedded together in 0x88. The + # encoding for the DRP register value is drp_val=log2(attribute) + reg_x88 = (int(math.log(MGT_RXOUT_DIV,2)) & 0x7) | \ + ((int(math.log(MGT_TXOUT_DIV,2)) & 0x7) << 4) # RX=[2:0] TX=[6:4] + jesdcore.drp_access(rd = False, addr = 0x88, wr_data = reg_x88) + self.log.trace("GTX settings changed to support {} Gbps".format(new_rate/1e9)) + jesdcore.disable_drp_target() + + self.log.trace("JESD204b Lane Rate set to {} Gbps!".format(new_rate/1e9)) + self.current_jesd_rate = new_rate + return + def get_user_eeprom_data(self): """ @@ -837,3 +931,57 @@ class Magnesium(DboardManagerBase): 'value': str(lock_status).lower(), } + + ########################################################################## + # Debug + ########################################################################## + def cpld_peek(self, addr): + """ + Debug for accessing the CPLD via the RPC shell. + """ + return self.cpld.peek16(addr) + + def cpld_poke(self, addr, data): + """ + Debug for accessing the CPLD via the RPC shell. + """ + self.cpld.poke16(addr, data) + return self.cpld.peek16(addr) + + def dump_jesd_core(self): + " Debug method to dump all JESD core regs " + dboard_ctrl_regs = UIO( + label="dboard-regs-{}".format(self.slot_idx), + read_only=False + ) + for i in range(0x2000, 0x2110, 0x10): + print(("0x%04X " % i), end=' ') + for j in range(0, 0x10, 0x4): + print(("%08X" % dboard_ctrl_regs.peek32(i + j)), end=' ') + print("") + dboard_ctrl_regs = None + + def dbcore_peek(self, addr): + """ + Debug for accessing the DB Core registers via the RPC shell. + """ + dboard_ctrl_regs = UIO( + label="dboard-regs-{}".format(self.slot_idx), + read_only=False + ) + rd_data = dboard_ctrl_regs.peek32(addr) + self.log.trace("DB Core Register 0x{:04X} response: 0x{:08X}".format(addr, rd_data)) + dboard_ctrl_regs = None + return rd_data + + def dbcore_poke(self, addr, data): + """ + Debug for accessing the DB Core registers via the RPC shell. + """ + dboard_ctrl_regs = UIO( + label="dboard-regs-{}".format(self.slot_idx), + read_only=False + ) + self.log.trace("Writing DB Core Register 0x{:04X} with 0x{:08X}...".format(addr, data)) + dboard_ctrl_regs.poke32(addr, data) + dboard_ctrl_regs = None diff --git a/mpm/python/usrp_mpm/nijesdcore.py b/mpm/python/usrp_mpm/nijesdcore.py index 00c557fdd..f46809d94 100644 --- a/mpm/python/usrp_mpm/nijesdcore.py +++ b/mpm/python/usrp_mpm/nijesdcore.py @@ -43,6 +43,7 @@ class NIMgJESDCore(object): 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 @@ -99,25 +100,28 @@ class NIMgJESDCore(object): self._gt_reset('rx', reset_only=False) self.regs.poke32(self.MGT_RECEIVER_CONTROL, 0x0) - def init_framer(self): + 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, 0x2002) + 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 disable char replacement - self.regs.poke32(self.MGT_TX_SCRAMBLER_CONTROL, 0x1) + # 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 the framer and incoming DAC Sync - self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0x1000) - self.regs.poke32(self.MGT_TRANSMITTER_CONTROL, 0x0001) + # 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 " @@ -159,6 +163,7 @@ class NIMgJESDCore(object): """ 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 @@ -186,6 +191,7 @@ class NIMgJESDCore(object): 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( @@ -198,8 +204,7 @@ class NIMgJESDCore(object): " 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)) + 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 @@ -230,6 +235,7 @@ class NIMgJESDCore(object): # 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: @@ -240,14 +246,14 @@ class NIMgJESDCore(object): 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) - 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 = self.regs.peek32(self.MGT_QPLL_CONTROL) rb_mask = 0x0 locked_val = 0x0 for nibble in range(qplls): @@ -257,4 +263,63 @@ class NIMgJESDCore(object): 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 |