x300: allow daughterboards to set dboard clock rate

This moves the dboard clock rate selection based on connected
daugtherboards out of x300_impl, and any daughterboards requiring
a non-default code now set the rate within the daughterboard code.
This commit is contained in:
Grant Meyerhoff 2025-07-19 15:27:10 -05:00 committed by Jörg Hofrichter
parent 60f9011457
commit 6a306ec1c8
9 changed files with 143 additions and 94 deletions

View File

@ -8,6 +8,7 @@
#pragma once
#include <uhd/config.hpp>
#include <uhd/exception.hpp>
#include <uhd/types/serial.hpp>
#include <uhd/types/time_spec.hpp>
#include <uhd/usrp/gpio_defs.hpp>
@ -247,6 +248,17 @@ public:
*/
virtual double get_codec_rate(unit_t unit) = 0;
/*!
* Lock the clock rate for the dboard.
*
* \param unit_t which unit rx or tx
* \return true if clock rate was locked
*/
virtual bool lock_clock_rate(const unit_t)
{
return false; // default implementation does not support locking
}
/*!
* Configure the front-end connection parameters.
*

View File

@ -17,6 +17,7 @@
#include <uhd/utils/static.hpp>
#include <uhdlib/usrp/common/max287x.hpp>
#include <uhdlib/usrp/common/tmp468.hpp>
#include <cmath>
namespace uhd { namespace usrp { namespace dboard { namespace obx {
@ -47,17 +48,23 @@ void obx_xcvr::_initialize_ref_clocks()
_rx_target_pfd_freq = pfd_freq_max;
_tx_target_pfd_freq = pfd_freq_max;
bool can_set_clock_rate = true;
// set dboard clock rates to as close to the max PFD freq as possible
// set dboard clock rates to as close to the max PFD freq as possible while making
// sure the master clock rate is integer-divisible by the chosen rate.
if (_db_iface->get_clock_rate(dboard_iface::UNIT_RX) > pfd_freq_max) {
std::vector<double> rates = _db_iface->get_clock_rates(dboard_iface::UNIT_RX);
double master_clock_rate = _db_iface->get_codec_rate(dboard_iface::UNIT_RX);
double highest_rate = 0.0;
for (double rate : rates) {
if (rate <= pfd_freq_max and rate > highest_rate)
if (rate <= pfd_freq_max and rate > highest_rate
and uhd::math::fp_compare::freq_compare_epsilon(
std::fmod(master_clock_rate, rate))
== 0.0)
highest_rate = rate;
}
try {
_db_iface->set_clock_rate(dboard_iface::UNIT_RX, highest_rate);
} catch (const uhd::not_implemented_error&) {
_db_iface->lock_clock_rate(dboard_iface::UNIT_RX);
} catch (const uhd::runtime_error&) {
UHD_LOG_WARNING("OBX", "Unable to set dboard clock rate - phase will vary");
can_set_clock_rate = false;
}
@ -66,14 +73,19 @@ void obx_xcvr::_initialize_ref_clocks()
if (can_set_clock_rate
and _db_iface->get_clock_rate(dboard_iface::UNIT_TX) > pfd_freq_max) {
std::vector<double> rates = _db_iface->get_clock_rates(dboard_iface::UNIT_TX);
double master_clock_rate = _db_iface->get_codec_rate(dboard_iface::UNIT_TX);
double highest_rate = 0.0;
for (double rate : rates) {
if (rate <= pfd_freq_max and rate > highest_rate)
if (rate <= pfd_freq_max and rate > highest_rate
and uhd::math::fp_compare::freq_compare_epsilon(
std::fmod(master_clock_rate, rate))
== 0.0)
highest_rate = rate;
}
try {
_db_iface->set_clock_rate(dboard_iface::UNIT_TX, highest_rate);
} catch (const uhd::not_implemented_error&) {
_db_iface->lock_clock_rate(dboard_iface::UNIT_TX);
} catch (const uhd::runtime_error&) {
UHD_LOG_WARNING("OBX", "Unable to set dboard clock rate - phase will vary");
}
_tx_target_pfd_freq = highest_rate;

View File

@ -23,6 +23,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/format.hpp>
#include <chrono>
#include <cmath>
#include <functional>
#include <map>
#include <memory>
@ -289,18 +290,24 @@ public:
_tx_target_pfd_freq = pfd_freq_max;
if (_rev >= 1) {
bool can_set_clock_rate = true;
// set dboard clock rates to as close to the max PFD freq as possible
// set dboard clock rates to as close to the max PFD freq as possible while
// making sure the master clock rate is integer-divisible by the chosen rate.
if (_iface->get_clock_rate(dboard_iface::UNIT_RX) > pfd_freq_max) {
std::vector<double> rates =
_iface->get_clock_rates(dboard_iface::UNIT_RX);
double highest_rate = 0.0;
double master_clock_rate = _iface->get_codec_rate(dboard_iface::UNIT_RX);
double highest_rate = 0.0;
for (double rate : rates) {
if (rate <= pfd_freq_max and rate > highest_rate)
if (rate <= pfd_freq_max and rate > highest_rate
and uhd::math::fp_compare::freq_compare_epsilon(
std::fmod(master_clock_rate, rate))
== 0.0)
highest_rate = rate;
}
try {
_iface->set_clock_rate(dboard_iface::UNIT_RX, highest_rate);
} catch (const uhd::not_implemented_error&) {
_iface->lock_clock_rate(dboard_iface::UNIT_RX);
} catch (const uhd::runtime_error&) {
UHD_LOG_WARNING(
"UBX", "Unable to set dboard clock rate - phase will vary");
can_set_clock_rate = false;
@ -311,14 +318,19 @@ public:
and _iface->get_clock_rate(dboard_iface::UNIT_TX) > pfd_freq_max) {
std::vector<double> rates =
_iface->get_clock_rates(dboard_iface::UNIT_TX);
double highest_rate = 0.0;
double master_clock_rate = _iface->get_codec_rate(dboard_iface::UNIT_TX);
double highest_rate = 0.0;
for (double rate : rates) {
if (rate <= pfd_freq_max and rate > highest_rate)
if (rate <= pfd_freq_max and rate > highest_rate
and uhd::math::fp_compare::freq_compare_epsilon(
std::fmod(master_clock_rate, rate))
== 0.0)
highest_rate = rate;
}
try {
_iface->set_clock_rate(dboard_iface::UNIT_TX, highest_rate);
} catch (const uhd::not_implemented_error&) {
_iface->lock_clock_rate(dboard_iface::UNIT_TX);
} catch (const uhd::runtime_error&) {
UHD_LOG_WARNING(
"UBX", "Unable to set dboard clock rate - phase will vary");
}

View File

@ -73,6 +73,9 @@ public:
% TWINRX_REV_AB_PFD_FREQ));
}
}
// Require the clock rate to remain the same after initialization.
_db_iface->lock_clock_rate(dboard_iface::UNIT_RX);
_db_iface->lock_clock_rate(dboard_iface::UNIT_TX);
// Initialize dboard clocks
_db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
_db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true);

View File

@ -76,13 +76,17 @@ public:
const size_t hw_rev,
const double master_clock_rate,
const double dboard_clock_rate,
const double system_ref_rate)
const double system_ref_rate,
const bool user_specified_dboard_clock_rate)
: _spiface(spiface)
, _slaveno(static_cast<int>(slaveno))
, _hw_rev(hw_rev)
, _master_clock_rate(master_clock_rate)
, _dboard_clock_rate(dboard_clock_rate)
, _system_ref_rate(system_ref_rate)
, _user_specified_dboard_clock_rate(user_specified_dboard_clock_rate)
, _rx_dboard_clock_rate_locked(false)
, _tx_dboard_clock_rate_locked(false)
{
init();
}
@ -130,6 +134,10 @@ public:
void set_dboard_rate(const x300_clock_which_t which, double rate) override
{
if (uhd::math::frequencies_are_equal(get_dboard_rate(which), rate)) {
return; // Just return if the requested rate is already set
}
uint16_t div = uint16_t(_vco_freq / rate);
uint16_t* reg = NULL;
uint8_t addr = 0xFF;
@ -157,13 +165,22 @@ public:
return;
// Since the clock rate on one daughter board cannot be changed without
// affecting the other daughter board, don't allow it.
throw uhd::not_implemented_error(
"x3xx set dboard clock rate does not support changing the clock rate");
// This is open source code and users may need to enable this function
// to support other daughterboards. If so, comment out the line above
// that throws the error and allow the program to reach the code below.
// affecting the other daughter board, don't allow it if another daughter board
// has locked the clock rate or the user has manually specified a clock rate.
if (_user_specified_dboard_clock_rate) {
throw uhd::runtime_error("Cannot change the dboard_clock_rate "
"when user has set a specific dboard_clock_rate.");
} else if (((which == X300_CLOCK_WHICH_DB0_TX || which == X300_CLOCK_WHICH_DB1_TX)
&& _tx_dboard_clock_rate_locked)
|| ((which == X300_CLOCK_WHICH_DB0_RX
|| which == X300_CLOCK_WHICH_DB1_RX)
&& _rx_dboard_clock_rate_locked)) {
throw uhd::runtime_error(
"Cannot change the daughterboard clock rate because a daughterboard has "
"locked the clock rate and both daughterboards share the same rate. To "
"manually set a clock rate for both daughterboards, pass in the "
"dboard_clock_rate argument.");
}
// The LMK04816 datasheet says the register must be written twice if SYNC is
// enabled
@ -191,6 +208,22 @@ public:
return rate;
}
void lock_dboard_rate(const x300_clock_which_t which) override
{
switch (which) {
case X300_CLOCK_WHICH_DB0_RX:
case X300_CLOCK_WHICH_DB1_RX:
_rx_dboard_clock_rate_locked = true;
break;
case X300_CLOCK_WHICH_DB0_TX:
case X300_CLOCK_WHICH_DB1_TX:
_tx_dboard_clock_rate_locked = true;
break;
default:
UHD_THROW_INVALID_CODE_PATH();
}
}
std::vector<double> get_dboard_rates(const x300_clock_which_t) override
{
std::vector<double> rates;
@ -993,6 +1026,9 @@ private:
double _master_clock_rate;
const double _dboard_clock_rate;
const double _system_ref_rate;
const bool _user_specified_dboard_clock_rate;
bool _rx_dboard_clock_rate_locked;
bool _tx_dboard_clock_rate_locked;
lmk04816_regs_t _lmk04816_regs;
double _vco_freq;
x300_clk_delays _delays;
@ -1003,8 +1039,14 @@ x300_clock_ctrl::sptr x300_clock_ctrl::make(uhd::spi_iface::sptr spiface,
const size_t hw_rev,
const double master_clock_rate,
const double dboard_clock_rate,
const double system_ref_rate)
const double system_ref_rate,
const bool user_specified_dboard_clock_rate)
{
return sptr(new x300_clock_ctrl_impl(
spiface, slaveno, hw_rev, master_clock_rate, dboard_clock_rate, system_ref_rate));
return sptr(new x300_clock_ctrl_impl(spiface,
slaveno,
hw_rev,
master_clock_rate,
dboard_clock_rate,
system_ref_rate,
user_specified_dboard_clock_rate));
}

View File

@ -37,7 +37,8 @@ public:
const size_t hw_rev,
const double master_clock_rate,
const double dboard_clock_rate,
const double system_ref_rate);
const double system_ref_rate,
const bool user_specified_dboard_clock_rate);
/*! Get the master clock rate of the device.
* \return the clock frequency in Hz
@ -66,6 +67,11 @@ public:
*/
virtual double get_dboard_rate(const x300_clock_which_t which) = 0;
/*! Lock the clock rate on the given daughterboard clock.
* \param which which clock
*/
virtual void lock_dboard_rate(const x300_clock_which_t which) = 0;
/*! Get a list of possible daughterboard clock rates.
* \return a list of clock rates in Hz
*/

View File

@ -31,9 +31,6 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t& config)
this->_write_aux_dac(unit);
}
_clock_rates[UNIT_RX] = _config.clock->get_dboard_rate(_config.which_rx_clk);
_clock_rates[UNIT_TX] = _config.clock->get_dboard_rate(_config.which_tx_clk);
this->set_clock_enabled(UNIT_RX, false);
this->set_clock_enabled(UNIT_TX, false);
}
@ -52,10 +49,6 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate)
if (unit == UNIT_BOTH)
throw uhd::runtime_error("UNIT_BOTH not supported.");
// Just return if the requested rate is already set
if (std::abs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon())
return;
switch (unit) {
case UNIT_RX:
_config.clock->set_dboard_rate(_config.which_rx_clk, rate);
@ -66,14 +59,21 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate)
default:
UHD_THROW_INVALID_CODE_PATH();
}
_clock_rates[unit] = rate; // set to shadow
}
double x300_dboard_iface::get_clock_rate(unit_t unit)
{
if (unit == UNIT_BOTH)
throw uhd::runtime_error("UNIT_BOTH not supported.");
return _clock_rates[unit]; // get from shadow
switch (unit) {
case UNIT_RX:
return _config.clock->get_dboard_rate(_config.which_rx_clk);
case UNIT_TX:
return _config.clock->get_dboard_rate(_config.which_tx_clk);
default:
UHD_THROW_INVALID_CODE_PATH();
}
}
std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit)
@ -111,6 +111,25 @@ double x300_dboard_iface::get_codec_rate(unit_t unit)
return _config.clock->get_master_clock_rate();
}
bool x300_dboard_iface::lock_clock_rate(const unit_t unit)
{
switch (unit) {
case UNIT_RX:
_config.clock->lock_dboard_rate(_config.which_rx_clk);
break;
case UNIT_TX:
_config.clock->lock_dboard_rate(_config.which_tx_clk);
break;
case UNIT_BOTH:
_config.clock->lock_dboard_rate(_config.which_rx_clk);
_config.clock->lock_dboard_rate(_config.which_tx_clk);
break;
default:
UHD_THROW_INVALID_CODE_PATH();
}
return true;
}
/***********************************************************************
* GPIO
**********************************************************************/

View File

@ -80,6 +80,7 @@ public:
std::vector<double> get_clock_rates(unit_t) override;
void set_clock_enabled(unit_t, bool) override;
double get_codec_rate(unit_t) override;
bool lock_clock_rate(const unit_t) override;
void write_spi(unit_t unit,
const uhd::spi_config_t& config,
@ -110,7 +111,6 @@ public:
private:
const x300_dboard_iface_config_t _config;
uhd::dict<unit_t, ad5623_regs_t> _dac_regs;
uhd::dict<unit_t, double> _clock_rates;
uhd::dict<std::string, rx_frontend_core_3000::sptr> _rx_fes;
uhd::dict<std::string, tx_frontend_core_200::sptr> _tx_fes;
void _write_aux_dac(unit_t);

View File

@ -151,31 +151,6 @@ device_addrs_t x300_find(const device_addr_t& hint_)
return addrs;
}
/***********************************************************************
* Daughterboard detection before initialization in software
**********************************************************************/
static std::vector<dboard_id_t> get_dboard_ids(uhd::i2c_iface& zpu_i2c)
{
std::vector<dboard_id_t> dboard_ids;
// Read dboard ids from the EEPROM
constexpr size_t BASE_ADDR = 0x50;
constexpr size_t RX_EEPROM_ADDR = 0x5;
constexpr size_t TX_EEPROM_ADDR = 0x4;
static const std::vector<size_t> DB_OFFSETS{0x0, 0x2};
static const std::vector<size_t> EEPROM_ADDRS{RX_EEPROM_ADDR, TX_EEPROM_ADDR};
for (size_t eeprom_addr : EEPROM_ADDRS) {
for (size_t db_offset : DB_OFFSETS) {
const size_t addr = eeprom_addr + db_offset;
// Load EEPROM
std::unordered_map<size_t, usrp::dboard_eeprom_t> db_eeproms;
db_eeproms[addr].load(zpu_i2c, BASE_ADDR | addr);
uint16_t dboard_id = db_eeproms[addr].id.to_uint16();
dboard_ids.push_back(static_cast<dboard_id_t>(dboard_id));
}
}
return dboard_ids;
}
/***********************************************************************
* Make
**********************************************************************/
@ -370,47 +345,15 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t& dev_addr)
////////////////////////////////////////////////////////////////////
UHD_LOGGER_DEBUG("X300") << "Setting up RF frontend clocking...";
// The default daughterboard clock rate may have to be overridden. This is due to the
// limitation on X300 devices where both daughterboards must use the same clock rate.
// The daughterboards that require specific clock rates are UBX, OBX, and TwinRX.
// TwinRX requires a clock rate of 100 MHz for the best RF performance. UBX and OBX
// daughterboards require a clock rate of no more than the max pfd frequency to
// maintain phase synchronization. If there is no UBX or OBX, the default
// daughterboard clock rate is half of the master clock rate for X300.
const double x300_dboard_clock_rate = [dev_addr, mb]() -> double {
// Do not override use-specified dboard clock rates
if (dev_addr.has_key("dboard_clock_rate")) {
return mb.args.get_dboard_clock_rate();
}
const double mcr = mb.args.get_master_clock_rate();
double dboard_clock_rate = mb.args.get_dboard_clock_rate();
// Check for UBX or OBX daughterboards
std::vector<dboard_id_t> dboard_ids = get_dboard_ids(*mb.zpu_i2c);
for (dboard_id_t dboard_id : dboard_ids) {
bool ubx_db_found = uhd::has(dboard::ubx::ubx_ids, dboard_id);
bool obx_db_found = uhd::has(dboard::obx::obx_ids, dboard_id);
if (ubx_db_found || obx_db_found) {
double dboard_pfd_clock_rate = mcr;
double max_pfd_freq = ubx_db_found
? dboard::ubx::get_max_pfd_freq(dboard_id)
: dboard::obx::get_max_pfd_freq(dboard_id);
for (int i = 2; dboard_pfd_clock_rate > max_pfd_freq; i++) {
dboard_pfd_clock_rate = mcr / i;
}
dboard_clock_rate = std::min(dboard_clock_rate, dboard_pfd_clock_rate);
}
}
return dboard_clock_rate;
}();
// Initialize clock control registers.
// NOTE: This does not configure the LMK yet.
mb.clock = x300_clock_ctrl::make(mb.zpu_spi,
1 /*slaveno*/,
mb.hw_rev,
mb.args.get_master_clock_rate(),
x300_dboard_clock_rate,
mb.args.get_system_ref_rate());
mb.args.get_dboard_clock_rate(),
mb.args.get_system_ref_rate(),
dev_addr.has_key("dboard_clock_rate"));
////////////////////////////////////////////////////////////////////
// create motherboard controller