diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_db.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_db.py index f5c0b2f05..65ff79a6d 100644 --- a/mpm/python/usrp_mpm/dboard_manager/x4xx_db.py +++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_db.py @@ -149,4 +149,4 @@ class X4xxDbMixin: Return the master clock rate. This is the rate that UHD cares about, in the 125-500 MHz range for X410/ZBX. """ - return self.db_iface.mboard.clk_mgr.get_master_clock_rate() + return self.db_iface.mboard.clk_mgr.get_master_clock_rate(self.slot_idx) diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt index e64e40f4d..142524a5a 100644 --- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt +++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt @@ -22,6 +22,7 @@ set(USRP_MPM_PERIPHMGR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_periphs.py ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_aux.py ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_mgr.py + ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clock_policy.py ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clock_types.py ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_dio_control.py ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_sample_pll.py diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx.py b/mpm/python/usrp_mpm/periph_manager/x4xx.py index 9382e741e..28bb8b665 100644 --- a/mpm/python/usrp_mpm/periph_manager/x4xx.py +++ b/mpm/python/usrp_mpm/periph_manager/x4xx.py @@ -35,6 +35,7 @@ from usrp_mpm.periph_manager.x4xx_clk_aux import ClockingAuxBrdControl from usrp_mpm.periph_manager.x4xx_clk_mgr import X4xxClockMgr from usrp_mpm.periph_manager.x4xx_gps_mgr import X4xxGPSMgr from usrp_mpm.periph_manager.x4xx_rfdc_ctrl import X4xxRfdcCtrl +from usrp_mpm.periph_manager.x4xx_clock_policy import get_clock_policy from usrp_mpm.dboard_manager.x4xx_db_iface import X4xxDboardIface @@ -465,6 +466,7 @@ class x4xx(ZynqComponents, PeriphManagerBase): # the clocks, and fix the MCR value later (in init()). self.clk_mgr = X4xxClockMgr( args, + clk_policy=get_clock_policy(self.mboard_info, self.dboard_infos, args, self.log), clk_aux_board=self._clocking_auxbrd, cpld_control=self.cpld_control, log=self.log) @@ -492,7 +494,7 @@ class x4xx(ZynqComponents, PeriphManagerBase): # available after the RFDC object is created. # Create control for RFDC - self.rfdc = X4xxRfdcCtrl(self.clk_mgr.get_spll_freq, self.log) + self.rfdc = X4xxRfdcCtrl(self.log) self._add_public_methods( self.rfdc, prefix="", filter_cb=lambda name, method: not hasattr(method, '_norpc') diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py index 0cbc8ec91..04cb7b89a 100644 --- a/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_mgr.py @@ -80,7 +80,6 @@ For this reason, it requires callbacks to reset RFDC and daughterboard clocks. """ from enum import Enum -from dataclasses import dataclass from usrp_mpm import lib # Pulls in everything from C++-land from usrp_mpm.sys_utils import i2c_dev from usrp_mpm.sys_utils.gpio import Gpio @@ -146,6 +145,7 @@ class X4xxClockMgr: def __init__(self, args, + clk_policy, clk_aux_board, cpld_control, log): @@ -153,12 +153,9 @@ class X4xxClockMgr: self.log = log.getChild("ClkMgr") self._cpld_control = cpld_control self._clocking_auxbrd = clk_aux_board - self._master_clock_rate = float( - args.get('master_clock_rate', self.X400_DEFAULT_MASTER_CLOCK_RATE)) - from usrp_mpm.periph_manager.x4xx_rfdc_ctrl import X4xxRfdcCtrl - sample_clock_freq, _, is_legacy_mode, _ = \ - X4xxRfdcCtrl.master_to_sample_clk[self._master_clock_rate] - + self.clk_policy = clk_policy + self._master_clock_rate = \ + float(args.get('master_clock_rate', self.clk_policy.get_default_mcr())) self._safe_sync_source = { 'clock_source': self.X400_DEFAULT_CLOCK_SOURCE, 'time_source': self.X400_DEFAULT_TIME_SOURCE, @@ -183,7 +180,6 @@ class X4xxClockMgr: self._reference_pll = None self._rpll_i2c_bus = None self._base_ref_clk_select = None - self._set_reset_rfdc = lambda **kwargs: None self._set_reset_db_clocks = lambda *args: None self._rpll_reference_sources = {} # Init peripherals @@ -193,8 +189,6 @@ class X4xxClockMgr: self._init_ref_clock_and_time( self._clock_source, self._ext_clock_freq, - sample_clock_freq, - is_legacy_mode, ) self._init_meas_clock() self._cpld_control.enable_pll_ref_clk() @@ -258,8 +252,6 @@ class X4xxClockMgr: def _init_ref_clock_and_time(self, clock_source, ref_clock_freq, - sample_clock_freq, - is_legacy_mode, ): """ Initialize clock and time sources. After this function returns, the @@ -291,7 +283,9 @@ class X4xxClockMgr: X400_DEFAULT_MGT_CLOCK_RATE, X400_DEFAULT_INT_CLOCK_FREQ, X400_DEFAULT_RPLL_REF_SOURCE) - self._config_spll(sample_clock_freq, is_legacy_mode) + clk_config = self.clk_policy.get_config( + self.get_ref_clock_freq(), [self._master_clock_rate,]) + self._config_spll(clk_config.spll_config) self._reset_clocks(value=False, reset_list=['cpld']) def _init_meas_clock(self): @@ -315,29 +309,32 @@ class X4xxClockMgr: """ self.mboard_regs_control = mboard_regs_control self.rfdc = rfdc + # Update the policy with new info on the FPGA image: + self.clk_policy.set_dsp_info(self.rfdc.get_dsp_info()) + + # Now do the full MCR init for the first time. When this is done, all + # the clocks will be ticking. Now, the policy should know about the + # FPGA capabilities, and can choose the correct default MCR. + initial_mcr = \ + float(args.get('master_clock_rate', self.clk_policy.get_default_mcr())) + try: + initial_mcr = self.clk_policy.coerce_mcr([initial_mcr])[0] + except ValueError: + self.log.warning( + f"Requested initial master clock rate {initial_mcr/1e6} MHz is invalid!") + initial_mcr = self.clk_policy.get_default_mcr() + self._master_clock_rate = initial_mcr # Force reset the RFDC to ensure it is in a good state - self.rfdc.set_reset(reset=True) - self.rfdc.set_reset(reset=False) + self._reset_clocks(value=True, reset_list=('rfdc',)) + self._reset_clocks(value=False, reset_list=('rfdc',)) # Synchronize SYSREF and clock distributed to all converters self.rfdc.sync() - self.set_rfdc_reset_cb(self.rfdc.set_reset) - # The initial default mcr only works if we have an FPGA with - # a decimation of 2. But we need the overlay applied before we - # can detect decimation, and that requires clocks to be initialized. - self.set_master_clock_rate(self.rfdc.get_default_mcr()) + self.set_master_clock_rate(initial_mcr) - @no_rpc - def set_rfdc_reset_cb(self, rfdc_reset_cb): - """ - Set reference to RFDC control. Ideally, we'd get that in __init__(), but - due to order of operations, it's not ready yet when we call that. - """ - self._set_reset_rfdc = rfdc_reset_cb - @no_rpc def set_dboard_reset_cb(self, db_reset_cb): """ @@ -372,8 +369,8 @@ class X4xxClockMgr: """ Removes any stored references to our owning X4xx class instance """ - self._set_reset_rfdc = None self._set_reset_db_clocks = None + self.rfdc = None @no_rpc def config_pps_to_timekeeper(self, master_clock_rate): @@ -409,17 +406,6 @@ class X4xxClockMgr: sample_pll_status['PLL2 lock'], ]) - @no_rpc - def set_spll_rate(self, sample_clock_freq, is_legacy_mode): - """ - Safely set the output rate of the sample PLL. - - This will do the required resets. - """ - self._reset_clocks(value=True, reset_list=('rfdc', 'cpld', 'db_clock')) - self._config_spll(sample_clock_freq, is_legacy_mode) - self._reset_clocks(value=False, reset_list=('rfdc', 'cpld', 'db_clock')) - @no_rpc def _set_sync_source(self, clock_source, time_source): """ @@ -465,27 +451,19 @@ class X4xxClockMgr: Sets the master clock rate by configuring the RFDC decimation and SPLL, and then resetting downstream clocks. """ - if master_clock_rate not in self.rfdc.master_to_sample_clk: - self.log.error('Unsupported master clock rate selection {}' - .format(master_clock_rate)) - raise RuntimeError('Unsupported master clock rate selection') - sample_clock_freq, decimation, is_legacy_mode, halfband = \ - self.rfdc.master_to_sample_clk[master_clock_rate] - for db_idx in (0, 1): - db_rfdc_resamp, db_halfband = self.rfdc.get_rfdc_resampling_factor(db_idx) - if db_rfdc_resamp != decimation or db_halfband != halfband: - msg = (f'master_clock_rate {master_clock_rate} is not compatible ' - f'with FPGA which expected decimation {db_rfdc_resamp}') - self.log.error(msg) - raise RuntimeError(msg) + master_clock_rate = self.clk_policy.coerce_mcr([master_clock_rate])[0] + clk_config = self.clk_policy.get_config( + self.get_ref_clock_freq(), [master_clock_rate]) self.log.trace(f"Set master clock rate (SPLL) to: {master_clock_rate}") - self.set_spll_rate(sample_clock_freq, is_legacy_mode) + self._reset_clocks(value=True, reset_list=('rfdc', 'cpld', 'db_clock')) + self._config_spll(clk_config.spll_config) + self._reset_clocks(value=False, reset_list=('rfdc', 'cpld', 'db_clock')) self._master_clock_rate = master_clock_rate self.rfdc.sync() self.config_pps_to_timekeeper(master_clock_rate) @no_rpc - def get_master_clock_rate(self): + def get_master_clock_rate(self, db_idx=0): """ Return the master clock rate set during init """ return self._master_clock_rate @@ -807,7 +785,7 @@ class X4xxClockMgr: if 'cpld' in reset_list: self._cpld_control.enable_pll_ref_clk(enable=False) if 'rfdc' in reset_list: - self._set_reset_rfdc(reset=True) + self.rfdc.set_reset(reset=True) if 'spll' in reset_list: self._sample_pll.reset(value, hard=True) if 'rpll' in reset_list: @@ -819,7 +797,9 @@ class X4xxClockMgr: if 'spll' in reset_list: self._sample_pll.reset(value, hard=True) if 'rfdc' in reset_list: - self._set_reset_rfdc(reset=False) + clk_config = self.clk_policy.get_config( + self.get_ref_clock_freq(), [self._master_clock_rate]) + self.rfdc.set_reset(reset=False, rfdc_configs=clk_config.rfdc_configs) if 'cpld' in reset_list: self._cpld_control.enable_pll_ref_clk(enable=True) if 'db_clock' in reset_list: @@ -864,79 +844,12 @@ class X4xxClockMgr: # with a new internal BRC rate self._int_clock_freq = internal_brc_rate - def _config_spll(self, sample_clock_freq, is_legacy_mode): + def _config_spll(self, spll_config): """ Configures the SPLL for the specified master clock rate. """ self._sample_pll.init() - @dataclass - class SpllConfig: - """ - Provide all relevant SPLL settings. - """ - # The reference frequency for the SPLL (e.g. from the external reference input, - # often 10 MHz) - ref_freq: float - # The frequency that is generated at the SPLL output for the ADC/DACs. In - # other words, the reference frequency for the RFDC PLLs. - output_freq: float - # Output divider for the ADC/DAC clock signal - output_divider: int - # The output divider for the PRC output (PRC is thus PLL2 VCO rate divided - # by this) - prc_divider: int - vcxo_freq: Spll1Vco - sysref_div: int - clkin0_r_div: int - pll1_n_div: int - pll2_prescaler: int - pll2_n_cal_div: int - pll2_n_div: int - - ref_clock_freq = self.get_ref_clock_freq() - pfd1 = {2.94912e9: 40e3, 3e9: 50e3, 3.072e9: 40e3}[sample_clock_freq] - spll_args = { - 2.94912e9: { - 'ref_freq': ref_clock_freq, - 'output_freq': sample_clock_freq, - 'output_divider': 1, - 'prc_divider': 0x3C if is_legacy_mode else 0x30, - 'vcxo_freq': Spll1Vco.VCO122_88MHz, - 'sysref_div': 1152, - 'clkin0_r_div': int(ref_clock_freq / pfd1), - 'pll1_n_div': 64, - 'pll2_prescaler': 2, - 'pll2_n_cal_div': 12, - 'pll2_n_div': 12, - }, - 3e9: { - 'ref_freq': ref_clock_freq, - 'output_freq': sample_clock_freq, - 'output_divider': 1, - 'prc_divider': 0x3C if is_legacy_mode else 0x30, - 'vcxo_freq': Spll1Vco.VCO100MHz, - 'sysref_div': 1200, - 'clkin0_r_div': int(ref_clock_freq / pfd1), - 'pll1_n_div': 50, - 'pll2_prescaler': 3, - 'pll2_n_cal_div': 10, - 'pll2_n_div': 10, - }, - 3.072e9: { - 'ref_freq': ref_clock_freq, - 'output_freq': sample_clock_freq, - 'output_divider': 1, - 'prc_divider': 0x3C if is_legacy_mode else 0x30, - 'vcxo_freq': Spll1Vco.VCO122_88MHz, - 'sysref_div': 1200, - 'clkin0_r_div': int(ref_clock_freq / pfd1), - 'pll1_n_div': 64, - 'pll2_prescaler': 5, - 'pll2_n_cal_div': 5, - 'pll2_n_div': 5, - }, - }[sample_clock_freq] - self._sample_pll.config(SpllConfig(**spll_args)) + self._sample_pll.config(spll_config) def _set_brc_source(self, clock_source): """ @@ -1063,11 +976,9 @@ class X4xxClockMgr: """ if (freq < 1e6) or (freq > 50e6): raise RuntimeError('External reference clock frequency is out of the valid range.') - if (freq % 40e3) != 0: - # TODO: implement exception of a 50e3 step size for 200MSPS - raise RuntimeError('External reference clock frequency is of incorrect step size.') + self.clk_policy.validate_ref_clock_freq(freq, [self._master_clock_rate]) self._ext_clock_freq = freq # If the external source is currently selected we also need to re-apply the # time_source. This call also updates the dboards' rates. if self.get_clock_source() == self.CLOCK_SOURCE_EXTERNAL: - self.set_sync_source(self._clock_source, self._time_source) + self._set_sync_source(self._clock_source, self._time_source) diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_clock_policy.py b/mpm/python/usrp_mpm/periph_manager/x4xx_clock_policy.py new file mode 100644 index 000000000..37898f5ee --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_clock_policy.py @@ -0,0 +1,341 @@ +# +# Copyright 2022 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +X4xx Clocking Policies + +These clocking policies are sets of rules for configuring the various clocks on +X4xx motherboards. +""" +import math +from collections import OrderedDict +from dataclasses import dataclass, field +from usrp_mpm.mpmutils import parse_multi_device_arg +from usrp_mpm.periph_manager.x4xx_clock_types import Spll1Vco +from usrp_mpm.dboard_manager import ZBX + + +############################################################################### +# Define Dataclasses for component settings +############################################################################### +# pylint: disable=too-many-instance-attributes +@dataclass +class RfdcConfig: + """ + Provide all relevant RFdc settings. + + Since we can have different settings per daughterboard, we have one of these + per daughterboard. + """ + # The frequency that the RFDC PLL will output + conv_rate: float + # This is both the interpolation and decimation factor. Because we always + # have one radio block per daughterboard, the radio rates for Tx and Rx are + # always identical, and thus the resampling factor is the same, too. + resampling: int = 1 + # If these latency values are set to None, then we auto-detect them. If they + # are an integer value, they are applied as-is to all ADCs/DACs, respectively, + # that are associated with this daughterboard's RfdcConfig. + adc_latency: int = None + dac_latency: int = None + + +@dataclass +class SpllConfig: + """ + Provide all relevant SPLL settings. + """ + # The reference frequency for the SPLL (e.g. from the external reference input, + # often 10 MHz) + ref_freq: float + # The frequency that is generated at the SPLL output for the ADC/DACs. In + # other words, the reference frequency for the RFDC PLLs. + output_freq: float + # Output divider for the ADC/DAC clock signal + output_divider: int + # The output divider for the PRC output (PRC is thus PLL2 VCO rate divided + # by this) + prc_divider: int + vcxo_freq: Spll1Vco + sysref_div: int + clkin0_r_div: int + pll1_n_div: int + pll2_prescaler: int + pll2_n_cal_div: int + pll2_n_div: int + +@dataclass +class X4xxClockConfig: + """ + Stores all clock-related settings to achieve a requested master clock rate. + Consolidates settings for the main PLL (SPLL, the LMK04832) as well as for + the RFDC and MMCM. + """ + spll_config: SpllConfig + rfdc_configs: list # list of RfdcConfig + # If bitfile defaults should be used set to true + mmcm_use_defaults: bool + # The feedback divider of the MMCM + mmcm_feedback_divider: int = 1 + # The output divider of the MMCM (dict of clock name and div value) + mmcm_output_div_map: dict = field(default_factory=dict) + # Input divider of the MMCM + mmcm_input_divider: int = 1 + +def lcm(x, y): + """ + Least common multiple, can be taken from math if we upgrade to Python >= 3.9.0 + """ + x = int(x) + y = int(y) + return int(x * y / math.gcd(x, y)) + +# pylint: enable=too-many-instance-attributes + +class X4xxClockPolicy: + """ + Base class for X4xx clock policies. + + Such a policy would be used by the X4xxClockManager to determine settings. + """ + def __init__(self, args, log): + self._initial_args = args + self.args = args + + def set_dsp_info(self, dsp_info): + """ + Store the DSP info of the current FPGA image. + + The individual clock policies can choose what to do with this info. + """ + raise NotImplementedError() + + def get_default_mcr(self): + """ + Return a reasonable default master clock rate. This method is called + during initialization, to decide which rate to configure the device to, + when no MCR is given by the user. + + This method is called twice: Once before set_dsp_info() is called, and + once afterwards. The first time a valid, sensible clock rate must be + returned which can be used to initialize the device enough to enable + the FPGA. When the FPGA is running, the DSP info can be read back and + then this function is queried again, because the first time around, we + may have chosen an MCR value that is not useful for the capabilities of + this particular bitfile. It is therefore valid to return different values + depending on whether or not the DSP info is set. + """ + raise NotImplementedError() + + def get_num_rates(self): + """ + Returns the number of different master clock rates we can handle. If it + is a value greater than one, then we need to be able to handle vectors + of MCRs in the following functions. + """ + raise NotImplementedError() + + def validate_ref_clock_freq(self, ref_clock_freq, master_clock_rates): + """ + Verify that ref_clock_freq is a valid reference clock frequency for the + given master clock rates. + + Throw a RuntimeError if not. + + Must not modify the state of this policy. + """ + raise NotImplementedError() + + def coerce_mcr(self, master_clock_rates): + """ + Validate that the requested master clock rate is valid. + + May coerce the master clock rates if not, but may also throw a ValueError + if there is no reasonable way to coerce the desired rates. + + Must not modify the state of this policy. + """ + raise NotImplementedError() + + def get_config(self, ref_clock_freq, master_clock_rates): + """ + This is where the action happens: Generate the clock configuration. + + This will return a X4xxClockConfig class which can be used by X4xxClockMgr + to actually configure various clocks. + + This method may change the state of this class. + """ + raise NotImplementedError() + +class X410ClockPolicy(X4xxClockPolicy): + """ + This is a pretty dumb policy, everything is hardcoded. + Some properties: + + - We only allow 3 different converter rates + - RFdc PLL is unused + - Only one rate is allowed (same rate on both dboards) + """ + + DEFAULT_MASTER_CLOCK_RATE = 122.88e6 # Keep this a low value + + master_to_sample_clk = OrderedDict({ + # MCR: (DSP BW, SPLL, decim, legacy mode) + 122.88e6*4: (400, 2.94912e9, 2, False), # RF (1M-8G) + 122.88e6*2: (200, 2.94912e9, 2, False), # RF (1M-8G) + 122.88e6*1: (100, 2.94912e9, 8, False), # RF (1M-8G) + 125e6*1: (100, 3.00000e9, 8, False), # RF (1M-8G) + 125e6*2: (200, 3.00000e9, 2, False), # RF (1M-8G) + 125e6*4: (400, 3.00000e9, 2, False), # RF (1M-8G) + 200e6: (400, 3.00000e9, 4, True ), # RF (Legacy Mode) + }) + + def __init__(self, mboard_info, dboard_infos, args, log): + super().__init__(args, log) + self._dsp_info = None + self._dsp_bw = None + + def set_dsp_info(self, dsp_info): + """ + Store the DSP info of the current FPGA image. + """ + self._dsp_info = dsp_info + self._dsp_bw = dsp_info[0]['bw'] + assert self._dsp_bw in [x[0] for x in self.master_to_sample_clk.values()] + + def get_default_mcr(self): + """ + Return a reasonable default master clock rate. + """ + if self._dsp_bw is None: + return self.DEFAULT_MASTER_CLOCK_RATE + for mcr, cfg in self.master_to_sample_clk.items(): + if cfg[0] == self._dsp_bw: + return mcr + raise AssertionError("Cannot determine default MCR.") + + # pylint: disable=no-self-use + def get_num_rates(self): + """ + Returns the number of different master clock rates we can handle. + """ + return 1 + + # pylint: enable=no-self-use + + def validate_ref_clock_freq(self, ref_clock_freq, master_clock_rates): + """ + Verify that ref_clock_freq is a valid reference clock frequency for the + given master clock rates. + + Will throw a RuntimeError if not. + + Does not modify the state of this policy. + """ + self.coerce_mcr(master_clock_rates) + mcr = master_clock_rates[0] + is_legacy_mode = self.master_to_sample_clk[mcr][3] + step_size = 50e3 if is_legacy_mode else 40e3 + if ref_clock_freq % step_size != 0: + raise RuntimeError( + 'External reference clock frequency is of incorrect step size.') + + def coerce_mcr(self, master_clock_rates): + """ + Validate that the requested master clock rate is valid. + + Will throw a ValueError if not. + + Does not modify the state of this policy. + """ + assert len(master_clock_rates) == 1 + mcr = master_clock_rates[0] + if mcr not in self.master_to_sample_clk or \ + (self._dsp_bw and self.master_to_sample_clk[mcr][0] != self._dsp_bw): + raise ValueError( + f"Invalid master clock rate: {mcr/1e6} MHz for current FPGA image " + f"with bandwidth {self._dsp_bw} MHz!") + return master_clock_rates + + + def get_config(self, ref_clock_freq, master_clock_rates): + """ + This is where the action happens: Generate the clock configuration. + """ + assert ref_clock_freq + assert master_clock_rates + self.coerce_mcr(master_clock_rates) + mcr = master_clock_rates[0] + rfdc_freq = self.master_to_sample_clk[mcr][1] + legacy_mode = self.master_to_sample_clk[mcr][3] + pfd1 = {2.94912e9: 40e3, 3e9: 50e3, 3.072e9: 40e3}[rfdc_freq] + spll_args = { + 2.94912e9: { + 'ref_freq': ref_clock_freq, + 'output_freq': rfdc_freq, + 'output_divider': 1, + 'prc_divider': 0x3C if legacy_mode else 0x30, + 'vcxo_freq': Spll1Vco.VCO122_88MHz, + 'sysref_div': 1152, + 'clkin0_r_div': int(ref_clock_freq / pfd1), + 'pll1_n_div': 64, + 'pll2_prescaler': 2, + 'pll2_n_cal_div': 12, + 'pll2_n_div': 12, + }, + 3e9: { + 'ref_freq': ref_clock_freq, + 'output_freq': rfdc_freq, + 'output_divider': 1, + 'prc_divider': 0x3C if legacy_mode else 0x30, + 'vcxo_freq': Spll1Vco.VCO100MHz, + 'sysref_div': 1200, + 'clkin0_r_div': int(ref_clock_freq / pfd1), + 'pll1_n_div': 50, + 'pll2_prescaler': 3, + 'pll2_n_cal_div': 10, + 'pll2_n_div': 10, + }, + 3.072e9: { + 'ref_freq': ref_clock_freq, + 'output_freq': rfdc_freq, + 'output_divider': 1, + 'prc_divider': 0x3C if legacy_mode else 0x30, + 'vcxo_freq': Spll1Vco.VCO122_88MHz, + 'sysref_div': 1200, + 'clkin0_r_div': int(ref_clock_freq / pfd1), + 'pll1_n_div': 64, + 'pll2_prescaler': 5, + 'pll2_n_cal_div': 5, + 'pll2_n_div': 5, + }, + }[rfdc_freq] + spll_config = SpllConfig(**spll_args) + resampling_factor = self.master_to_sample_clk[mcr][2] + rfdc_config = RfdcConfig( + conv_rate=rfdc_freq, + resampling=resampling_factor, + # Note: Those latency values were experimentally determined using + # the algorith shown in X4xxRfdcCtrl.determine_tile_latency(). We + # should be not hard coding this in accordance with pg269, but since + # these numbers have worked since the initial release of X410, we + # won't change that now to minimize risk. + adc_latency=1272, + dac_latency=816, + ) + return X4xxClockConfig( + spll_config=spll_config, + rfdc_configs=[rfdc_config, rfdc_config], + mmcm_use_defaults=True + ) + +def get_clock_policy(mboard_info, dboard_infos, args, log): + """ + Return a clocking policy object based on the available hardware. + """ + if dboard_infos[0]['pid'] in ZBX.pids: + return X410ClockPolicy(mboard_info, dboard_infos, args, log) + raise RuntimeError("Could not resolve clock policy.") diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py index 0417656c6..6f45462b5 100644 --- a/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_rfdc_ctrl.py @@ -45,27 +45,8 @@ class X4xxRfdcCtrl: }, ] - # 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*1: (3.00000e9, 8, False, False), # RF (1M-8G) - 125e6*2: (3.00000e9, 2, False, True), # 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): + def __init__(self, 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) @@ -94,14 +75,13 @@ class X4xxRfdcCtrl: Removes any stored references to our owning X4xx class instance and destructs anything that must happen at teardown """ - self._get_spll_freq = None del self._rfdc_ctrl ########################################################################### # Public APIs (not available as MPM RPC calls) ########################################################################### @no_rpc - def set_reset(self, reset=True): + def set_reset(self, reset=True, rfdc_configs=None): """ Resets the RFDC FPGA components or takes them out of reset. """ @@ -137,7 +117,8 @@ class X4xxRfdcCtrl: # 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) + rfdc_config = rfdc_configs[db_idx] + db_rfdc_resamp = rfdc_config.resampling for converter_type, tile_block_set in db_info.items(): for tile, block in tile_block_set: is_dac = converter_type != 'adc' @@ -145,7 +126,7 @@ class X4xxRfdcCtrl: 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._rfdc_ctrl.set_sample_rate(tile, is_dac, rfdc_config.conv_rate) self._set_interpolation_decimation(tile, block, is_dac, resampling_factor) self._rfdc_regs.log_status() @@ -202,19 +183,6 @@ class X4xxRfdcCtrl: 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): """ @@ -226,6 +194,14 @@ class X4xxRfdcCtrl: """ return self._rfdc_regs.get_fabric_dsp_info(0)[0] + @no_rpc + def get_dsp_info(self): + """ + Return a dictionary of dictionaries, one per daughterboard, with the DSP + settings that are baked into the FPGA. + """ + return [self._rfdc_regs.get_rfdc_info(db_idx) for db_idx in range(2)] + @no_rpc def get_rfdc_resampling_factor(self, db_idx): """