mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-14 20:58:09 +00:00
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:
parent
8a6709588b
commit
cf04329058
6 changed files with 400 additions and 169 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
341
mpm/python/usrp_mpm/periph_manager/x4xx_clock_policy.py
Normal file
341
mpm/python/usrp_mpm/periph_manager/x4xx_clock_policy.py
Normal 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.")
|
||||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in a new issue