aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/dboard_manager/dac_rh.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager/dac_rh.py')
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/dac_rh.py342
1 files changed, 342 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/dac_rh.py b/mpm/python/usrp_mpm/dboard_manager/dac_rh.py
new file mode 100644
index 000000000..7fd32e8de
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/dac_rh.py
@@ -0,0 +1,342 @@
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+DAC37J82 driver for use with Rhodium
+"""
+
+import time
+from builtins import object
+from ..mpmlog import get_logger
+
+class DAC37J82Rh(object):
+ """
+ This class provides an interface to configure the DAC37J82 IC through SPI.
+ """
+
+ DAC_VENDOR_ID = 0b01
+ DAC_VERSION_ID = 0b010 # Version used in Rhodium Rev. A
+
+ def __init__(self, slot_idx, regs_iface, parent_log=None):
+ self.log = parent_log.getChild("DAC37J82") if parent_log is not None \
+ else get_logger("DAC37J82-{}".format(slot_idx))
+ self.slot_idx = slot_idx
+ self.regs = regs_iface
+ assert hasattr(self.regs, 'peek16')
+ assert hasattr(self.regs, 'poke16')
+
+ def _verify_chip_id():
+ chip_id = self.regs.peek16(0x7F) & 0x001F
+ self.log.trace("DAC Vendor & Version ID: 0x{:X}".format(chip_id))
+ if chip_id != ((self.DAC_VENDOR_ID << 3) | self.DAC_VERSION_ID):
+ self.log.error("Wrong Vendor & Version 0x{:X}".format(chip_id))
+ return False
+ return True
+
+ self.reset()
+
+ if not _verify_chip_id():
+ raise RuntimeError("Unable to locate DAC37J82")
+
+ # Define variable configuration per slot.
+ # The JESD lanes going to the DAC pins are swapped differently:
+ # DBA: 0 -> 0 / 1 -> 1 / 2 -> 2 / 3 -> 3
+ # DBB: 0 -> 0 / 1 -> 1 / 2 -> 3 / 3 -> 2
+ # Therefore, depending on the DB that is being configured, we need
+ # to change the JESD lanes internal routing in the DAC to compensate
+ # for the board traces swapping.
+ self.lanes_ids_1 = {0: 0x0044, 1: 0x0046}[self.slot_idx] # config70
+ self.lanes_ids_2 = {0: 0x190A, 1: 0x110A}[self.slot_idx] # config71
+ self.octetpath_sel = {0: 0x0123, 1: 0x0132}[self.slot_idx] # config95
+
+ self.init()
+
+
+ def tx_enable(self, enable=False):
+ """
+ Enable/disable the analog TX output.
+ """
+ enable_bit = 0b1 if enable else 0b0
+ prev_val = self.regs.peek16(0x03)
+ self.regs.poke16(0x03, prev_val | enable_bit)
+
+
+ def pokes16(self, addr_vals):
+ """
+ Apply a series of pokes.
+ pokes16((0,1),(0,2)) is the same as calling poke16(0,1), poke16(0,2).
+ """
+ for addr, val in addr_vals:
+ self.regs.poke16(addr, val)
+
+
+ def init(self):
+ """
+ Basic init that disables the analog output.
+ """
+ self.tx_enable(False) # Set TXENABLE low at the DAC
+ self.log.trace("DAC's Analog TX output is disabled.")
+
+ def reset(self):
+ """
+ Reset the DAC state
+ """
+ self.regs.poke16(0x02, 0x2002) # Deassert the reset for the SIF registers
+ self.regs.poke16(0x02, 0x2003) # Assert the reset for the SIF registers
+
+ def config(self):
+ """
+ Check the clock status, and write configuration values!
+ """
+ def _check_pll_lock():
+ pll_ool_alarms = self.regs.peek16(0x6C)
+ if (pll_ool_alarms & 0x0008) != 0x0000:
+ self.log.warning("PLL reporting unlocked... Status: 0x{:x}"
+ .format(pll_ool_alarms))
+ return False
+ return True
+
+ self.log.trace("Reset DAC & Clear alarm bits")
+ self.reset()
+ self.regs.poke16(0x6C, 0x0000) # Clear alarm bits for PLLs
+
+ self.log.trace("DAC Configuration.")
+ self.pokes16((
+ (0x00, 0x0018), # config0: Interpolation 1x; ALARM enabled w/ pos. logic.
+ (0x01, 0x0003), # config1: Rewriting reserved default values.
+ (0x02, 0x0002), # config2: Data not zero when link not established; 2's comp. arrives at input.
+ (0x03, 0x9300), # config3: Coarse DAC = 10 (9+1); TXENABLE internal is kept low.
+ (0x04, 0x0000), # config4: Not masking any lane errors or FIFO flags.
+ (0x05, 0x0000), # config5: Not masking any SYSREF errors, PAPs, or PLL locks.
+ (0x06, 0x0000), # config6: Not masking any lane short test or loss of signal.
+ (0x08, 0x0000), # config8: DAC A offset correction set to zero (default).
+ (0x09, 0x0000), # config9: DAC B offset correction set to zero (default).
+ (0x0A, 0x0000), # config10: DAC C offset correction set to zero (default).
+ (0x0B, 0x0000), # config11: DAC D offset correction set to zero (default).
+ (0x0C, 0x0400), # config12: Default quadrature correction gain A (AB path).
+ (0x0D, 0x0400), # config13: Default coarse mixing options; default quadrature correction gain B (AB path).
+ (0x0E, 0x0400), # config14: Default quadrature correction gain A (CD path).
+ (0x0F, 0x0400), # config15: No output delays to the DACs; default quadrature correction gain A (CD path).
+ (0x10, 0x0000), # config16: Default QMC correction phase (AB path).
+ (0x11, 0x0000), # config17: Default QMC correction phase (AD path).
+ (0x12, 0x0000), # config18: Phase offset for NCO in DACAB path (default).
+ (0x13, 0x0000), # config19: Phase offset for NCO in DACAB path (default).
+ (0x14, 0x0000), # config20: Lower 16 bits of NCO Frequency adjust word for DACAB path (default).
+ (0x15, 0x0000), # config21: Middle 16 bits of NCO Frequency adjust word for DACAB path (default).
+ (0x16, 0x0000), # config22: Upper 16 bits of NCO Frequency adjust word for DACAB path (default).
+ (0x17, 0x0000), # config23: Lower 16 bits of NCO Frequency adjust word for DACCD path (default).
+ (0x18, 0x0000), # config24: Middle 16 bits of NCO Frequency adjust word for DACCD path (default).
+ (0x19, 0x0000), # config25: Upper 16 bits of NCO Frequency adjust word for DACCD path (default).
+ (0x1A, 0x0023), # config26: DAC PLL in sleep mode; DAC C & D in sleep mode.
+ (0x1B, 0x0000), # config27: Testing settings (default).
+ (0x1E, 0x2222), # config30: Sync source for the QMC offset and correction: SYSREF only (Based on VST2).
+ (0x1F, 0x2220), # config31: Sync source for mixers and NCO accums.: SYSREF only (Based on VST2).
+ (0x20, 0x0000), # config32: Sync source for the dithering, PA protection, and FIR filter blocks: none (Based on VST2).
+ (0x22, 0x1B1B), # config34: JESD and DAC routing paths (default).
+ (0x23, 0x01FF), # config35: SLEEP signal from pin allowed to reach all blocks, the pin is not even used.
+ (0x24, 0x0000), # config36: SYSREF syncs clock dividers: use no pulses yet.
+ (0x25, 0x2000), # config37: DACCLK divider to generate JESD clock: div2. (TI recommendation).
+ (0x26, 0x0000), # config38: Dithering disabled default).
+ (0x2D, 0x0000), # config45: Power amplifier protection settings (default).
+ (0x2E, 0xFFFF), # config46: Power amplifier protection threshold (default).
+ (0x2F, 0x0004), # config47: Default values.
+ (0x30, 0x0000), # config48: Constant value sent to DAC when sifdac_ena is asserted (default).
+ (0x31, 0x1000), # config49: DAC PLL in reset and disabled. DACCLK is 491.52 MHz since we bypass the PLL.
+ (0x32, 0x0000), # config50: DAC PLL's VCO feedback and prescaler divided (not used).
+ (0x33, 0x0000), # config51: DAC PLL VCO and CP settings (not used).
+ (0x34, 0x0000), # config52: SYNCB electrical configuration (default @ 1.2V CMV).
+ (0x3B, 0x0000), # config59: SerDes PLL's reference. Source: DACCLK / Divider: 1 (0 +1).
+ (0x3C, 0x1828), # config60: SerDes PLL Control: high loop BW; high range VCO; multiply factor x5.
+ (0x3D, 0x0088), # config61: Upper configuration info for SerDes receivers (TI recommendation).
+ (0x3E, 0x0128), # config62: Upper configuration for SerDes receivers:AC coupling; half rate; 20-bit width.
+ (0x3F, 0x0000), # config63: No SerDes lanes inversion (default).
+ (0x46, self.lanes_ids_1), # config70: JESD ID for lanes 0, 1, and 2.
+ (0x47, self.lanes_ids_2), # config71: JESD ID for lanes 3, 4, and 5.
+ (0x48, 0x31C3), # config72: JESD ID for lanes 6, and 7; JESD204B supported version and class (default).
+ (0x49, 0x0000), # config73: JESD lanes assignment to links (default 0).
+ (0x4A, 0x0F3E), # config74: Lanes 0-3 enabled; test seq. disabled; disable clocks to C/D paths.
+ (0x4B, 0x1700), # config75: RBD = 24 (23 + 1) (Release Buffer Delay); F = 1 (octets per frame).
+ (0x4C, 0x1703), # config76: K = 24 (23 + 1) (frames in multiframe); L = 4 (3 + 1) (number of lanes).
+ (0x4D, 0x0100), # config77: M = 2 (1+1) (number of converters); S = 1 (0+1) (number of samples per frame).
+ (0x4E, 0x0F4F), # config78: HD = 1 (High Density mode enabled, samples split across lanes).
+ (0x4F, 0x1CC1), # config79: Match /R/ char; ILA is supported at TX.
+ (0x50, 0x0000), # config80: Lane config data (link0), not used by 37J82 (default).
+ (0x51, 0x00FF), # config81: Erros that cause a SYNC request (link0): all selected (default).
+ (0x52, 0x00FF), # config82: Errors that are counted in err_c (link0): all selected (default).
+ (0x53, 0x0000), # config83: Lane config data (link1), not used by 37J82 (default).
+ (0x54, 0x0000), # config84: Erros that cause a SYNC request (link1): none selected.
+ (0x55, 0x0000), # config85: Errors that are counted in err_c (link1): none selected.
+ (0x56, 0x0000), # config86: Lane config data (link2), not used by 37J82 (default).
+ (0x57, 0x0000), # config87: Erros that cause a SYNC request (link2): none selected.
+ (0x58, 0x0000), # config88: Errors that are counted in err_c (link2): none selected.
+ (0x59, 0x0000), # config89: Lane config data (link3), not used by 37J82 (default).
+ (0x5A, 0x0000), # config90: Erros that cause a SYNC request (link2): none selected.
+ (0x5B, 0x0000), # config91: Errors that are counted in err_c (link3): none selected.
+ (0x5C, 0x0000), # config92: Links 3:1 don't use SYSREF pulses; link 0 uses no pulses yet.
+ (0x5E, 0x0000), # config94: Cheksum bits for ILA, not used in 37J82 (default).
+ (0x5F, self.octetpath_sel), # config95: Mapping SerDes lanes (0-3) to JESD lanes.
+ (0x60, 0x4567), # config96: Mapping SerDes lanes (4-7) to JESD lanes (default).
+ (0x61, 0x0001), # config97: Use only link 0 to trigger the SYNCB LVDS output.
+ (0x64, 0x0703), # config100: Write to lane 0 errors to clear them (based on VST2).
+ (0x65, 0x0703), # config101: Write to lane 1 errors to clear them (based on VST2).
+ (0x66, 0x0703), # config102: Write to lane 2 errors to clear them (based on VST2).
+ (0x67, 0x0703), # config103: Write to lane 3 errors to clear them (based on VST2).
+ (0x68, 0x0703), # config104: Write to lane 4 errors to clear them (based on VST2).
+ (0x69, 0x0703), # config105: Write to lane 5 errors to clear them (based on VST2).
+ (0x6A, 0x0703), # config106: Write to lane 6 errors to clear them (based on VST2).
+ (0x6B, 0x0703), # config107: Write to lane 7 errors to clear them (based on VST2).
+ (0x6C, 0x0000), # config108: Rewrite the PLLs alarm bits clearing register.
+ (0x6D, 0x0000), # config109: JESD short test alarms (default).
+ (0x6E, 0x0000), # config110: Delay fractional filter settings (default).
+ (0x6F, 0x0000), # config111: Delay fractional filter settings (default).
+ (0x70, 0x0000), # config112: Delay fractional filter settings (default).
+ (0x71, 0x0000), # config113: Delay fractional filter settings (default).
+ (0x72, 0x0000), # config114: Delay fractional filter settings (default).
+ (0x73, 0x0000), # config115: Delay fractional filter settings (default).
+ (0x74, 0x0000), # config116: Delay fractional filter settings (default).
+ (0x75, 0x0000), # config117: Delay fractional filter settings (default).
+ (0x76, 0x0000), # config118: Delay fractional filter settings (default).
+ (0x77, 0x0000), # config119: Delay fractional filter settings (default).
+ (0x78, 0x0000), # config120: Delay fractional filter settings (default).
+ (0x79, 0x0000), # config121: Delay fractional filter settings (default).
+ (0x7A, 0x0000), # config122: Delay fractional filter settings (default).
+ (0x7B, 0x0000), # config123: Delay fractional filter settings (default).
+ (0x7C, 0x0000), # config124: Delay fractional filter settings (default).
+ (0x7D, 0x0000), # config125: Delay fractional filter settings (default).
+ (0x02, 0x2002), # Deassert the reset for the SIF registers
+ ))
+ self.log.trace("DAC register dump finished.")
+
+ self.log.trace("Polling for PLL lock...")
+ locked = False
+ for _ in range(6):
+ time.sleep(0.001)
+ # Clear stickies possibly?
+ self.regs.poke16(0x6C, 0x0000) # Clear alarm bits for PLLs
+ if _check_pll_lock():
+ locked = True
+ self.log.info("DAC PLL Locked!")
+ break
+ if not locked:
+ raise RuntimeError("DAC PLL did not lock! Check the logs for details.")
+
+
+ def enable_sysref_capture(self, enabled=False):
+ """
+ Enable the SYSREF capture block, and enable divider's reset.
+ """
+ self.log.trace("%s DAC SYSREF capture...",
+ {True: 'Enabling', False: 'Disabling'}[enabled])
+ cdrvser_sysref_mode = 0b001 if enabled else 0b000
+ sysref_mode_link0 = 0b001 if enabled else 0b000
+ self.regs.poke16(0x24, cdrvser_sysref_mode << 4) # Enable next SYSREF to reset the clock dividers.
+ self.regs.poke16(0x5C, sysref_mode_link0 << 0) # Enable next SYSREF pulse capture for link 0.
+ self.log.trace("DAC SYSREF capture %s." % {True: 'enabled', False: 'disabled'}[enabled])
+
+
+ def init_deframer(self):
+ """
+ Initialize the DAC's framer.
+ """
+ self.log.trace("Initializing framer...")
+ self.pokes16((
+ (0x4A, 0x0F3F), # config74: Deassert JESD204B block reset.
+ (0x4A, 0x0F21), # config74: Set JESD204B to exit init state.
+ ))
+ self.log.trace("DAC deframer initialized.")
+
+
+ def check_deframer_status(self):
+ """
+ This function checks the status of the framer by checking alarms.
+ """
+ ALARM_ERRORS_DESCRIPTION = {
+ 15 : "Multiframe alignment error",
+ 14 : "Frame alignment error",
+ 13 : "Link configuration error",
+ 12 : "Elastic buffer overflow",
+ 11 : "Elastic buffer match error",
+ 10 : "Code synchronization error",
+ 9 : "8b/10b not-in-table code error",
+ 8 : "8b/10b disparity error",
+ 3 : "FIFO write_error",
+ 2 : "FIFO write_full",
+ 1 : "FIFO read_error",
+ 0 : "FIFO read_empty"
+ }
+
+ self.log.trace("Checking DAC's deframer status.")
+ # Clear lane alarms.
+ for addr in (0x64, 0x65, 0x66, 0x67):
+ self.regs.poke16(addr, 0x0000)
+ time.sleep(0.001)
+
+ # Read lane's alarms
+ lanes_alarms_ary = []
+ lane = 0
+ for addr in (0x64, 0x65, 0x66, 0x67):
+ lanes_alarms_ary.insert(lane, self.regs.peek16(addr) & 0xFF0F)
+ self.log.trace("Lane {} alarms rb: 0x{:X}".format(lane, lanes_alarms_ary[lane]))
+ lane += 1
+
+ enable_analog_output = True
+ # Report warnings based on an error matrix (register_width * lanes).
+ errors_ary = []
+ for error in range(0, 16):
+ errors_ary.insert(error, [])
+ # Extract errors from lanes.
+ for lane in range(0, len(lanes_alarms_ary)):
+ if lanes_alarms_ary[lane] & (0b1 << error) > 0:
+ errors_ary[error].append(lane)
+ if len(errors_ary[error]) > 0:
+ enable_analog_output = False
+ self.log.warning(ALARM_ERRORS_DESCRIPTION[error] +
+ " in lane(s): " + ' '.join(map(str, errors_ary[error])))
+
+ self.tx_enable(enable_analog_output)
+ self.log.debug("%s analog TX output.",
+ {True: 'Enabling', False: 'Disabling'}[enable_analog_output])
+ return enable_analog_output
+
+
+ def test_mode(self, mode='PRBS-31', lane=0):
+ """
+ This method enables the DAC's test mode to verify the SerDes.
+ Users should monitor the ALARM pin to see the results of the test.
+ If the test is failing, ALARM will be high (or toggling if marginal).
+ If the test is passing, the ALARM will be low.
+ """
+ MODE_VAL = {'OFF': 0x0, 'PRBS-7': 0x2, 'PRBS-23': 0x3, 'PRBS-31': 0x4}
+ assert mode.upper() in MODE_VAL
+ assert lane in range(0, 8)
+ self.log.debug("Setting test mode for lane {} at the DAC: {}.".format(lane, mode))
+ # To run the PRBS test on the DAC, users first need to setup the DAC for
+ # normal use, then make the following SPI writes:
+ # 1. config74, set bits 4:0 to 0x1E to disable JESD clock.
+ addr = 0x4A
+ rb = self.regs.peek16(addr)
+ data_w = (rb & ~0x001F) | 0x001E if mode != 'OFF' else 0x0F3E
+ self.log.trace("Writing register {:02X} with {:04X}".format(addr, data_w))
+ self.regs.poke16(addr, data_w)
+ # 2. config61, set bits 14:12 to 0x2 to enable the 7-bit PRBS test pattern; or
+ # set bits 14:12 to 0x3 to enable the 23-bit PRBS test pattern; or
+ # set bits 14:12 to 0x4 to enable the 31-bit PRBS test pattern.
+ addr = 0x3D
+ rb = self.regs.peek16(addr)
+ data_w = (rb & ~0x7000) | (MODE_VAL[mode] << 12)
+ self.log.trace("Writing register {:02X} with {:04X}".format(addr, data_w))
+ self.regs.poke16(addr, data_w)
+ # 3. config27, set bits 11:8 to 0x3 to output PRBS testfail on ALARM pin.
+ # 4. config27, set bits 14:12 to the lane to be tested (0 through 7).
+ addr = 0x1B
+ rb = self.regs.peek16(addr)
+ data_w = (rb & ~0x7F00) | (0x3 << 8) | (lane << 12) if mode != 'OFF' else 0x0000
+ self.log.trace("Writing register {:02X} with {:04X}".format(addr, data_w))
+ self.regs.poke16(addr, data_w)
+ # 5. config62, make sure bits 12:11 are set to 0x0 to disable character alignment.
+ addr = 0x3E
+ rb = self.regs.peek16(addr)
+ if ((rb & 0x58) >> 11) != 0x0:
+ self.log.error("Char alignment is enabled when not expected.")