mpm: x4xx: Introduce X4xxClockPolicy

This new class encapsulates all clocking-related infos into a single
file and makes it easier to modify clocking settings.
This commit is contained in:
Martin Braun 2023-05-03 11:57:43 +02:00 committed by Aki Tomita
parent 8a6709588b
commit cf04329058
6 changed files with 400 additions and 169 deletions

View file

@ -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)

View file

@ -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

View file

@ -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')

View file

@ -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)

View file

@ -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.")

View file

@ -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):
"""