diff options
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py')
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py new file mode 100644 index 000000000..44c4a32e2 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py @@ -0,0 +1,480 @@ +# +# Copyright 2019 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X400 RFDC Control Module +""" + +import ast +from collections import OrderedDict +from usrp_mpm import lib # Pulls in everything from C++-land +from usrp_mpm.periph_manager.x4xx_rfdc_regs import RfdcRegsControl +from usrp_mpm.rpc_server import no_rpc + +# Map the interpolation/decimation factor to fabric words. +# Keys: is_dac (False -> ADC, True -> DAC) and factor +FABRIC_WORDS_ARRAY = { # [is_dac][factor] + False: {0: 16, 1: 16, 2: 8, 4: 4, 8: 2}, # ADC + True: {0: -1, 1: -1, 2: 16, 4: 8, 8: 4} # DAC +} + +RFDC_DEVICE_ID = 0 + +class X4xxRfdcCtrl: + """ + Control class for the X4xx's RFDC + + """ + # Label for RFDC UIO + rfdc_regs_label = "rfdc-regs" + # Describes the mapping of ADC/DAC Tiles and Blocks to DB Slot IDs + # Follows the below structure: + # <slot_idx> + # 'adc': [ (<tile_number>, <block_number>), ... ] + # 'dac': [ (<tile_number>, <block_number>), ... ] + RFDC_DB_MAP = [ + { + 'adc': [(0, 1), (0, 0)], + 'dac': [(0, 0), (0, 1)], + }, + { + 'adc': [(2, 1), (2, 0)], + 'dac': [(1, 0), (1, 1)], + }, + ] + + # Maps all possible master_clock_rate (data clk rate * data SPC) values to the + # corresponding sample rate, expected FPGA decimation, whether to configure + # the SPLL in legacy mode (which uses a different divider), and whether half-band + # resampling is used. + # Using an OrderedDict to use the first rates as a preference for the default + # rate for its corresponding decimation. + master_to_sample_clk = OrderedDict({ + # MCR: (SPLL, decimation, legacy mode, half-band resampling) + 122.88e6*4: (2.94912e9, 2, False, False), # RF (1M-8G) + 122.88e6*2: (2.94912e9, 2, False, True), # RF (1M-8G) + 122.88e6*1: (2.94912e9, 8, False, False), # RF (1M-8G) + 125e6*4: (3.00000e9, 2, False, False), # RF (1M-8G) + 200e6: (3.00000e9, 4, True, False), # RF (Legacy Mode) + }) + + + def __init__(self, get_spll_freq, log): + self.log = log.getChild('RFDC') + self._get_spll_freq = get_spll_freq + self._rfdc_regs = RfdcRegsControl(self.rfdc_regs_label, self.log) + self._rfdc_ctrl = lib.rfdc.rfdc_ctrl() + self._rfdc_ctrl.init(RFDC_DEVICE_ID) + + self.set_cal_frozen(1, 0, "both") + self.set_cal_frozen(1, 1, "both") + + @no_rpc + def unset_cbs(self): + """ + Removes any stored references to our owning X4xx class instance + """ + self._get_spll_freq = None + + ########################################################################### + # Public APIs (not available as MPM RPC calls) + ########################################################################### + @no_rpc + def set_reset(self, reset=True): + """ + Resets the RFDC FPGA components or takes them out of reset. + """ + if reset: + # Assert RFDC AXI-S, filters and associated gearbox reset. + self._rfdc_regs.set_reset_adc_dac_chains(reset=True) + self._rfdc_regs.log_status() + # Assert Radio clock PLL reset + self._rfdc_regs.set_reset_mmcm(reset=True) + # Resetting the MMCM will automatically disable clock buffers + return + + # Take upstream MMCM out of reset + self._rfdc_regs.set_reset_mmcm(reset=False) + + # Once the MMCM has locked, enable driving the clocks + # to the rest of the design. Poll lock status for up + # to 1 ms + self._rfdc_regs.wait_for_mmcm_locked(timeout=0.001) + self._rfdc_regs.set_gated_clock_enables(value=True) + + # De-assert RF signal chain reset + self._rfdc_regs.set_reset_adc_dac_chains(reset=False) + + # Restart tiles in XRFdc + # All ADC Tiles + if not self._rfdc_ctrl.reset_tile(-1, False): + self.log.warning('Error starting up ADC tiles') + # All DAC Tiles + if not self._rfdc_ctrl.reset_tile(-1, True): + self.log.warning('Error starting up DAC tiles') + + # Set sample rate for all active tiles + active_converters = set() + for db_idx, db_info in enumerate(self.RFDC_DB_MAP): + db_rfdc_resamp, _ = self._rfdc_regs.get_rfdc_resampling_factor(db_idx) + for converter_type, tile_block_set in db_info.items(): + for tile, block in tile_block_set: + is_dac = converter_type != 'adc' + active_converter_tuple = (tile, block, db_rfdc_resamp, is_dac) + active_converters.add(active_converter_tuple) + for tile, block, resampling_factor, is_dac in active_converters: + self._rfdc_ctrl.reset_mixer_settings(tile, block, is_dac) + self._rfdc_ctrl.set_sample_rate(tile, is_dac, self._get_spll_freq()) + self._set_interpolation_decimation(tile, block, is_dac, resampling_factor) + + self._rfdc_regs.log_status() + + # Set RFDC NCO reset event source to analog SYSREF + for tile, block, _, is_dac in active_converters: + self._rfdc_ctrl.set_nco_event_src(tile, block, is_dac) + + + @no_rpc + def sync(self): + """ + Multi-tile Synchronization on both ADC and DAC + """ + # These numbers are determined from the procedure mentioned in + # PG269 section "Advanced Multi-Tile Synchronization API use". + adc_latency = 1228 # ADC delay in sample clocks + dac_latency = 800 # DAC delay in sample clocks + + # Ideally, this would be a set to avoiding duplicate indices, + # but we need to use a list for compatibility with the rfdc_ctrl + # C++ interface (std::vector) + adc_tiles_to_sync = [] + dac_tiles_to_sync = [] + + rfdc_map = self.RFDC_DB_MAP + for db_id in rfdc_map: + for converter_type, tile_block_set in db_id.items(): + for tile, _ in tile_block_set: + if converter_type == 'adc': + if tile not in adc_tiles_to_sync: + adc_tiles_to_sync.append(tile) + else: # dac + if tile not in dac_tiles_to_sync: + dac_tiles_to_sync.append(tile) + + self._rfdc_ctrl.sync_tiles(adc_tiles_to_sync, False, adc_latency) + self._rfdc_ctrl.sync_tiles(dac_tiles_to_sync, True, dac_latency) + + # We expect all sync'd tiles to have equal latencies + # Sets don't add duplicates, so we can use that to look + # for erroneous tiles + adc_tile_latency_set = set() + for tile in adc_tiles_to_sync: + adc_tile_latency_set.add( + self._rfdc_ctrl.get_tile_latency(tile, False)) + if len(adc_tile_latency_set) != 1: + raise RuntimeError("ADC tiles failed to sync properly") + + dac_tile_latency_set = set() + for tile in dac_tiles_to_sync: + dac_tile_latency_set.add( + self._rfdc_ctrl.get_tile_latency(tile, True)) + if len(dac_tile_latency_set) != 1: + raise RuntimeError("DAC tiles failed to sync properly") + + @no_rpc + def get_default_mcr(self): + """ + Gets the default master clock rate based on FPGA decimation + """ + fpga_decimation, fpga_halfband = self._rfdc_regs.get_rfdc_resampling_factor(0) + for master_clock_rate in self.master_to_sample_clk: + _, decimation, _, halfband = self.master_to_sample_clk[master_clock_rate] + if decimation == fpga_decimation and fpga_halfband == halfband: + return master_clock_rate + raise RuntimeError('No master clock rate acceptable for current fpga ' + 'with decimation of {}'.format(fpga_decimation)) + + @no_rpc + def get_dsp_bw(self): + """ + Return the bandwidth encoded in the RFdc registers. + + Note: This is X4xx-specific, not RFdc-specific. But this class owns the + access to RfdcRegsControl, and the bandwidth is strongly related to the + RFdc settings. + """ + return self._rfdc_regs.get_fabric_dsp_info(0)[0] + + @no_rpc + def get_rfdc_resampling_factor(self, db_idx): + """ + Returns a tuple resampling_factor, halfbands. + + See RfdcRegsControl.get_rfdc_resampling_factor(). + """ + return self._rfdc_regs.get_rfdc_resampling_factor(db_idx) + + + ########################################################################### + # Public APIs that get exposed as MPM RPC calls + ########################################################################### + def rfdc_set_nco_freq(self, direction, slot_id, channel, freq): + """ + Sets the RFDC NCO Frequency for the specified channel + """ + converters = self._find_converters(slot_id, direction, channel) + assert len(converters) == 1 + (tile_id, block_id, is_dac) = converters[0] + + if not self._rfdc_ctrl.set_if(tile_id, block_id, is_dac, freq): + raise RuntimeError("Error setting RFDC IF Frequency") + return self._rfdc_ctrl.get_nco_freq(tile_id, block_id, is_dac) + + def rfdc_get_nco_freq(self, direction, slot_id, channel): + """ + Gets the RFDC NCO Frequency for the specified channel + """ + converters = self._find_converters(slot_id, direction, channel) + assert len(converters) == 1 + (tile_id, block_id, is_dac) = converters[0] + + return self._rfdc_ctrl.get_nco_freq(tile_id, block_id, is_dac) + + ### ADC cal ############################################################### + def set_cal_frozen(self, frozen, slot_id, channel): + """ + Set the freeze state for the ADC cal blocks + + Usage: + > set_cal_frozen <frozen> <slot_id> <channel> + + <frozen> should be 0 to unfreeze the calibration blocks or 1 to freeze them. + """ + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + self._rfdc_ctrl.set_cal_frozen(tile_id, block_id, frozen) + + def get_cal_frozen(self, slot_id, channel): + """ + Get the freeze states for each ADC cal block in the channel + + Usage: + > get_cal_frozen <slot_id> <channel> + """ + return [ + 1 if self._rfdc_ctrl.get_cal_frozen(tile_id, block_id) else 0 + for tile_id, block_id, is_dac in self._find_converters(slot_id, "rx", channel) + ] + + def set_cal_coefs(self, channel, slot_id, cal_block, coefs): + """ + Manually override calibration block coefficients. You probably don't need to use this. + """ + self.log.trace( + "Setting ADC cal coefficients for channel={} slot_id={} cal_block={}".format( + channel, slot_id, cal_block)) + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + self._rfdc_ctrl.set_adc_cal_coefficients( + tile_id, block_id, cal_block, ast.literal_eval(coefs)) + + def get_cal_coefs(self, channel, slot_id, cal_block): + """ + Manually retrieve raw coefficients for the ADC calibration blocks. + + Usage: + > get_cal_coefs <channel, 0-1> <slot_id, 0-1> <cal_block, 0-3> + e.g. + > get_cal_coefs 0 1 3 + Retrieves the coefficients for the TSCB block on channel 0 of DB 1. + + Valid values for cal_block are: + 0 - OCB1 (Unaffected by cal freeze) + 1 - OCB2 (Unaffected by cal freeze) + 2 - GCB + 3 - TSCB + """ + self.log.trace( + "Getting ADC cal coefficients for channel={} slot_id={} cal_block={}".format( + channel, slot_id, cal_block)) + result = [] + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + result.append(self._rfdc_ctrl.get_adc_cal_coefficients(tile_id, block_id, cal_block)) + return result + + ### DAC mux + def set_dac_mux_data(self, i_val, q_val): + """ + Sets the data which is muxed into the DACs when the DAC mux is enabled + + Usage: + > set_dac_mux_data <I> <Q> + e.g. + > set_dac_mux_data 123 456 + """ + self._rfdc_regs.set_cal_data(i_val, q_val) + + def set_dac_mux_enable(self, channel, enable): + """ + Sets whether the DAC mux is enabled for a given channel + + Usage: + > set_dac_mux_enable <channel, 0-3> <enable, 1=enabled> + e.g. + > set_dac_mux_enable 1 0 + """ + self._rfdc_regs.set_cal_enable(channel, bool(enable)) + + ### ADC thresholds + def setup_threshold(self, slot_id, channel, threshold_idx, mode, delay, under, over): + """ + Configure the given ADC threshold block. + + Usage: + > setup_threshold <slot_id> <channel> <threshold_idx> <mode> <delay> <under> <over> + + slot_id: Slot ID to configure, 0 or 1 + channel: Channel on the slot to configure, 0 or 1 + threshold_idx: Threshold block index, 0 or 1 + mode: Mode to configure, one of ["sticky_over", "sticky_under", "hysteresis"] + delay: In hysteresis mode, number of samples before clearing flag. + under: 0-16384, ADC codes to set the "under" threshold to + over: 0-16384, ADC codes to set the "over" threshold to + """ + for tile_id, block_id, _ in self._find_converters(slot_id, "rx", channel): + THRESHOLDS = { + 0: lib.rfdc.threshold_id_options.THRESHOLD_0, + 1: lib.rfdc.threshold_id_options.THRESHOLD_1, + } + MODES = { + "sticky_over": lib.rfdc.threshold_mode_options.TRSHD_STICKY_OVER, + "sticky_under": lib.rfdc.threshold_mode_options.TRSHD_STICKY_UNDER, + "hysteresis": lib.rfdc.threshold_mode_options.TRSHD_HYSTERESIS, + } + if mode not in MODES: + raise RuntimeError( + f"Mode {mode} is not one of the allowable modes {list(MODES.keys())}") + if threshold_idx not in THRESHOLDS: + raise RuntimeError("threshold_idx must be 0 or 1") + delay = int(delay) + under = int(under) + over = int(over) + assert 0 <= under <= 16383 + assert 0 <= over <= 16383 + self._rfdc_ctrl.set_threshold_settings( + tile_id, block_id, + lib.rfdc.threshold_id_options.THRESHOLD_0, + MODES[mode], + delay, + under, + over) + + def get_threshold_status(self, slot_id, channel, threshold_idx): + """ + Read the threshold status bit for the given threshold block from the device. + + Usage: + > get_threshold_status <slot_id> <channel> <threshold_idx> + e.g. + > get_threshold_status 0 1 0 + """ + return self._rfdc_regs.get_threshold_status(slot_id, channel, threshold_idx) != 0 + + + ########################################################################### + # Private helpers (note: x4xx_db_iface calls into those) + ########################################################################### + def _set_interpolation_decimation(self, tile, block, is_dac, factor): + """ + Set the provided interpolation/decimation factor to the + specified ADC/DAC tile, block + + Only gets called from set_reset_rfdc(). + """ + # Map the interpolation/decimation factor to fabric words. + # Keys: is_dac (False -> ADC, True -> DAC) and factor + # Disable FIFO + self._rfdc_ctrl.set_data_fifo_state(tile, is_dac, False) + # Define fabric rate based on given factor. + fab_words = FABRIC_WORDS_ARRAY[is_dac].get(int(factor)) + if fab_words == -1: + raise RuntimeError('Unsupported dec/int factor in RFDC') + # Define dec/int constant based on integer factor + if factor == 0: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_OFF + elif factor == 1: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_1X + elif factor == 2: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_2X + elif factor == 4: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_4X + elif factor == 8: + int_dec = lib.rfdc.interp_decim_options.INTERP_DECIM_8X + else: + raise RuntimeError('Unsupported dec/int factor in RFDC') + # Update tile, block settings... + self.log.debug( + "Setting %s for %s tile %d, block %d to %dx", + ('interpolation' if is_dac else 'decimation'), + 'DAC' if is_dac else 'ADC', tile, block, factor) + if is_dac: + # Set interpolation + self._rfdc_ctrl.set_interpolation_factor(tile, block, int_dec) + self.log.trace( + " interpolation: %s", + self._rfdc_ctrl.get_interpolation_factor(tile, block).name) + # Set fabric write rate + self._rfdc_ctrl.set_data_write_rate(tile, block, fab_words) + self.log.trace( + " Read words: %d", + self._rfdc_ctrl.get_data_write_rate(tile, block, True)) + else: # ADC + # Set decimation + self._rfdc_ctrl.set_decimation_factor(tile, block, int_dec) + self.log.trace( + " Decimation: %s", + self._rfdc_ctrl.get_decimation_factor(tile, block).name) + # Set fabric read rate + self._rfdc_ctrl.set_data_read_rate(tile, block, fab_words) + self.log.trace( + " Read words: %d", + self._rfdc_ctrl.get_data_read_rate(tile, block, False)) + # Clear interrupts + self._rfdc_ctrl.clear_data_fifo_interrupts(tile, block, is_dac) + # Enable FIFO + self._rfdc_ctrl.set_data_fifo_state(tile, is_dac, True) + + + def _find_converters(self, slot, direction, channel): + """ + Returns a list of (tile_id, block_id, is_dac) tuples describing + the data converters associated with a given channel and direction. + """ + if direction not in ('rx', 'tx', 'both'): + self.log.error('Invalid direction "{}". Cannot find ' + 'associated data converters'.format(direction)) + raise RuntimeError('Invalid direction "{}". Cannot find ' + 'associated data converters'.format(direction)) + if str(channel) not in ('0', '1', 'both'): + self.log.error('Invalid channel "{}". Cannot find ' + 'associated data converters'.format(channel)) + raise RuntimeError('Invalid channel "{}". Cannot find ' + 'associated data converters'.format(channel)) + data_converters = [] + rfdc_map = self.RFDC_DB_MAP[slot] + + if direction in ('rx', 'both'): + if str(channel) == '0' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['adc'][0] + data_converters.append((tile_id, block_id, False)) + if str(channel) == '1' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['adc'][1] + data_converters.append((tile_id, block_id, False)) + if direction in ('tx', 'both'): + if str(channel) == '0' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['dac'][0] + data_converters.append((tile_id, block_id, True)) + if str(channel) == '1' or str(channel) == 'both': + (tile_id, block_id) = rfdc_map['dac'][1] + data_converters.append((tile_id, block_id, True)) + return data_converters |