diff options
author | Lars Amsel <lars.amsel@ni.com> | 2021-06-04 08:27:50 +0200 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 12:01:53 -0500 |
commit | 2a575bf9b5a4942f60e979161764b9e942699e1e (patch) | |
tree | 2f0535625c30025559ebd7494a4b9e7122550a73 /mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py | |
parent | e17916220cc955fa219ae37f607626ba88c4afe3 (diff) | |
download | uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2 uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip |
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com>
Co-authored-by: Michael Auchter <michael.auchter@ni.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com>
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Virendra Kakade <virendra.kakade@ni.com>
Co-authored-by: Lane Kolbly <lane.kolbly@ni.com>
Co-authored-by: Max Köhler <max.koehler@ni.com>
Co-authored-by: Andrew Lynch <andrew.lynch@ni.com>
Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com>
Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py')
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py new file mode 100644 index 000000000..95f3cc007 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_regs.py @@ -0,0 +1,263 @@ +# +# Copyright 2021 Ettus Research, a National Instruments Brand +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4xx RFDC register control +""" + +import time +from usrp_mpm.sys_utils.uio import UIO + +class RfdcRegsControl: + """ + Control the FPGA RFDC registers external to the XRFdc API + """ + # pylint: disable=bad-whitespace + IQ_SWAP_OFFSET = 0x10000 + MMCM_RESET_BASE_OFFSET = 0x11000 + RF_RESET_CONTROL_OFFSET = 0x12000 + RF_RESET_STATUS_OFFSET = 0x12008 + RF_STATUS_OFFSET = 0x13000 + FABRIC_DSP_INFO_OFFSET = 0x13008 + CAL_DATA_OFFSET = 0x14000 + CAL_ENABLE_OFFSET = 0x14008 + THRESHOLD_STATUS_OFFSET = 0x15000 + RF_PLL_CONTROL_OFFSET = 0x16000 + RF_PLL_STATUS_OFFSET = 0x16008 + # pylint: enable=bad-whitespace + + def __init__(self, label, log): + self.log = log.getChild("RfdcRegs") + self.regs = UIO( + label=label, + read_only=False + ) + self.poke32 = self.regs.poke32 + self.peek32 = self.regs.peek32 + + # Index corresponds to dboard number. + self._converter_chains_in_reset = True + + def get_threshold_status(self, slot_id, channel, threshold_idx): + """ + Retrieves the status bit for the given threshold block + """ + BITMASKS = { + (0, 0, 0): 0x04, + (0, 0, 1): 0x08, + (0, 1, 0): 0x01, + (0, 1, 1): 0x02, + (1, 0, 0): 0x400, + (1, 0, 1): 0x800, + (1, 1, 0): 0x100, + (1, 1, 1): 0x200, + } + assert (slot_id, channel, threshold_idx) in BITMASKS + status = self.peek(self.THRESHOLD_STATUS_OFFSET) + status_bool = (status & BITMASKS[(slot_id, channel, threshold_idx)]) != 0 + return 1 if status_bool else 0 + + def set_cal_data(self, i, q): + assert 0 <= i < 2**16 + assert 0 <= q < 2**16 + self.poke(self.CAL_DATA_OFFSET, (q << 16) | i) + + def set_cal_enable(self, channel, enable): + assert 0 <= channel <= 3 + assert enable in [False, True] + en = self.peek(self.CAL_ENABLE_OFFSET) + bit_offsets = { + 0: 0, + 1: 1, + 2: 4, + 3: 5, + } + en_mask = 1 << bit_offsets[channel] + en = en & ~en_mask + self.poke(self.CAL_ENABLE_OFFSET, en | (en_mask if enable else 0)) + + def enable_iq_swap(self, enable, db_id, block_id, is_dac): + iq_swap_bit = (int(is_dac) * 8) + (db_id * 4) + block_id + + # Write IQ swap bit with a mask + reg_val = self.peek(self.IQ_SWAP_OFFSET) + reg_val = (reg_val & ~(1 << iq_swap_bit)) \ + | (enable << iq_swap_bit) + self.poke(self.IQ_SWAP_OFFSET, reg_val) + + def set_reset_mmcm(self, reset=True): + if reset: + # Put the MMCM in reset (active low) + self.poke(self.MMCM_RESET_BASE_OFFSET, 0) + else: + # Take the MMCM out of reset + self.poke(self.MMCM_RESET_BASE_OFFSET, 1) + + def wait_for_mmcm_locked(self, timeout=0.001): + """ + Wait for MMCM to come to a stable locked state. + The datasheet specifies a 100us max lock time + """ + DATA_CLK_PLL_LOCKED = 1 << 20 + + POLL_SLEEP = 0.0002 + for _ in range(int(timeout / POLL_SLEEP)): + time.sleep(POLL_SLEEP) + status = self.peek(self.RF_PLL_STATUS_OFFSET) + if status & DATA_CLK_PLL_LOCKED: + self.log.trace("RF MMCM lock detected.") + return + self.log.error("MMCM failed to lock in the expected time.") + raise RuntimeError("MMCM failed to lock within the expected time.") + + def set_gated_clock_enables(self, value=True): + """ + Controls the clock enable for data_clk and + data_clk_2x + """ + ENABLE_DATA_CLK = 1 + ENABLE_DATA_CLK_2X = 1 << 4 + ENABLE_RF_CLK = 1 << 8 + ENABLE_RF_CLK_2X = 1 << 12 + if value: + # Enable buffers gating the clocks + self.poke(self.RF_PLL_CONTROL_OFFSET, + ENABLE_DATA_CLK | + ENABLE_DATA_CLK_2X | + ENABLE_RF_CLK | + ENABLE_RF_CLK_2X + ) + else: + # Disable clock buffers to have clocks gated. + self.poke(self.RF_PLL_CONTROL_OFFSET, 0) + + def get_fabric_dsp_info(self, dboard): + """ + Read the DSP information register and returns the + DSP bandwidth, rx channel count and tx channel count + """ + # Offsets + DSP_BW = 0 + 16*dboard + DSP_RX_CNT = 12 + 16*dboard + DSP_TX_CNT = 14 + 16*dboard + # Masks + DSP_BW_MSK = 0xFFF + DSP_RX_CNT_MSK = 0x3 + DSP_TX_CNT_MSK = 0x3 + + dsp_info = self.peek(self.FABRIC_DSP_INFO_OFFSET) + self.log.trace("Fabric DSP for dboard %d...", dboard) + dsp_bw = (dsp_info >> DSP_BW) & DSP_BW_MSK + self.log.trace(" Bandwidth (MHz): %d", dsp_bw) + dsp_rx_cnt = (dsp_info >> DSP_RX_CNT) & DSP_RX_CNT_MSK + self.log.trace(" Rx channel count: %d", dsp_rx_cnt) + dsp_tx_cnt = (dsp_info >> DSP_TX_CNT) & DSP_TX_CNT_MSK + self.log.trace(" Tx channel count: %d", dsp_tx_cnt) + + return [dsp_bw, dsp_rx_cnt, dsp_tx_cnt] + + def get_rfdc_resampling_factor(self, dboard): + """ + Returns the appropriate decimation/interpolation factor to set in the RFDC. + """ + # DSP vs. RFDC decimation/interpolation dictionary + # Key: bandwidth in MHz + # Value: (RFDC resampling factor, is Half-band resampling used?) + RFDC_RESAMPLING_FACTOR = { + 100: (8, False), # 100 MHz BW requires 8x RFDC resampling + 200: (2, True), # 200 MHz BW requires 2x RFDC resampling + # (400 MHz RFDC DSP used w/ half-band resampling) + 400: (2, False) # 400 MHz BW requires 2x RFDC resampling + } + dsp_bw, _, _ = self.get_fabric_dsp_info(dboard) + # When no RF fabric DSP is present (dsp_bw = 0), MPM should + # simply use the default RFDC resampling factor (400 MHz). + if dsp_bw in RFDC_RESAMPLING_FACTOR: + rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[dsp_bw] + else: + rfdc_resampling_factor, halfband = RFDC_RESAMPLING_FACTOR[400] + self.log.trace(" Using default resampling!") + self.log.trace(" RFDC resampling: %d", rfdc_resampling_factor) + return (rfdc_resampling_factor, halfband) + + def set_reset_adc_dac_chains(self, reset=True): + """ Resets or enables the ADC and DAC chain for the given dboard """ + + def _wait_for_done(done_bit, timeout=5): + """ + Wait for the specified sequence done bit when resetting or + enabling an ADC or DAC chain. Throws an error on timeout. + """ + status = self.peek(self.RF_RESET_STATUS_OFFSET) + if (status & done_bit): + return + for _ in range(0, timeout): + time.sleep(0.001) # 1 ms + status = self.peek(self.RF_RESET_STATUS_OFFSET) + if (status & done_bit): + return + self.log.error("Timeout while resetting or enabling ADC/DAC chains.") + raise RuntimeError("Timeout while resetting or enabling ADC/DAC chains.") + + # CONTROL OFFSET + ADC_RESET = 1 << 4 + DAC_RESET = 1 << 8 + # STATUS OFFSET + ADC_SEQ_DONE = 1 << 7 + DAC_SEQ_DONE = 1 << 11 + + if reset: + if self._converter_chains_in_reset: + self.log.debug('Converters are already in reset. ' + 'The reset bit will NOT be toggled.') + return + # Reset the ADC and DAC chains + self.log.trace('Resetting ADC chain') + self.poke(self.RF_RESET_CONTROL_OFFSET, ADC_RESET) + _wait_for_done(ADC_SEQ_DONE) + self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) + + self.log.trace('Resetting DAC chain') + self.poke(self.RF_RESET_CONTROL_OFFSET, DAC_RESET) + _wait_for_done(DAC_SEQ_DONE) + self.poke(self.RF_RESET_CONTROL_OFFSET, 0x0) + + self._converter_chains_in_reset = True + else: # enable + self._converter_chains_in_reset = False + + def log_status(self): + status = self.peek(self.RF_STATUS_OFFSET) + self.log.debug("Daughterboard 0") + self.log.debug(" @RFDC") + self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 0) & 0x3)) + self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 2) & 0x3)) + self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 6) & 0x3)) + self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 10) & 0x3)) + self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 4) & 0x3)) + self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 8) & 0x3)) + self.log.debug(" @USER") + self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 12) & 0x3)) + self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 14) & 0x3)) + self.log.debug("Daughterboard 1") + self.log.debug(" @RFDC") + self.log.debug(" DAC(1:0) TREADY : {:02b}".format((status >> 16) & 0x3)) + self.log.debug(" DAC(1:0) TVALID : {:02b}".format((status >> 18) & 0x3)) + self.log.debug(" ADC(1:0) I TREADY : {:02b}".format((status >> 22) & 0x3)) + self.log.debug(" ADC(1:0) I TVALID : {:02b}".format((status >> 26) & 0x3)) + self.log.debug(" ADC(1:0) Q TREADY : {:02b}".format((status >> 20) & 0x3)) + self.log.debug(" ADC(1:0) Q TVALID : {:02b}".format((status >> 24) & 0x3)) + self.log.debug(" @USER") + self.log.debug(" ADC(1:0) OUT TVALID: {:02b}".format((status >> 28) & 0x3)) + self.log.debug(" ADC(1:0) OUT TREADY: {:02b}".format((status >> 30) & 0x3)) + + def poke(self, addr, val): + with self.regs: + self.regs.poke32(addr, val) + + def peek(self, addr): + with self.regs: + result = self.regs.peek32(addr) + return result |