mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-14 20:58:09 +00:00
1583 lines
63 KiB
C++
1583 lines
63 KiB
C++
//
|
|
// Copyright 2015-2017 Ettus Research, A National Instruments Company
|
|
//
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
|
|
#include "x300_radio_ctrl_impl.hpp"
|
|
#include "x300_dboard_iface.hpp"
|
|
#include <uhd/rfnoc/node_ctrl_base.hpp>
|
|
#include <uhd/transport/chdr.hpp>
|
|
#include <uhd/usrp/dboard_eeprom.hpp>
|
|
#include <uhd/usrp/dboard_iface.hpp>
|
|
#include <uhd/utils/log.hpp>
|
|
#include <uhd/utils/math.hpp>
|
|
#include <uhd/utils/safe_call.hpp>
|
|
#include <uhdlib/rfnoc/wb_iface_adapter.hpp>
|
|
#include <uhdlib/usrp/common/apply_corrections.hpp>
|
|
#include <uhdlib/usrp/cores/gpio_atr_3000.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/date_time/posix_time/posix_time_io.hpp>
|
|
#include <boost/make_shared.hpp>
|
|
#include <bitset>
|
|
#include <chrono>
|
|
#include <thread>
|
|
|
|
using namespace uhd;
|
|
using namespace uhd::usrp;
|
|
using namespace uhd::rfnoc;
|
|
using namespace uhd::usrp::x300;
|
|
|
|
static const size_t IO_MASTER_RADIO = 0;
|
|
|
|
namespace {
|
|
|
|
gain_fcns_t make_gain_fcns_from_subtree(property_tree::sptr subtree)
|
|
{
|
|
gain_fcns_t gain_fcns;
|
|
gain_fcns.get_range = [subtree]() {
|
|
return subtree->access<meta_range_t>("range").get();
|
|
};
|
|
gain_fcns.get_value = [subtree]() { return subtree->access<double>("value").get(); };
|
|
gain_fcns.set_value = [subtree](const double gain) {
|
|
subtree->access<double>("value").set(gain);
|
|
};
|
|
return gain_fcns;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/****************************************************************************
|
|
* Structors
|
|
***************************************************************************/
|
|
UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(x300_radio_ctrl)
|
|
, _ignore_cal_file(false)
|
|
{
|
|
UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::ctor() ";
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Set up basic info
|
|
////////////////////////////////////////////////////////////////////
|
|
_radio_type = (get_block_id().get_block_count() == 0) ? PRIMARY : SECONDARY;
|
|
_radio_slot = (get_block_id().get_block_count() == 0) ? "A" : "B";
|
|
_radio_clk_rate = _tree->access<double>("master_clock_rate").get();
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Set up peripherals
|
|
////////////////////////////////////////////////////////////////////
|
|
wb_iface::sptr ctrl = _get_ctrl(IO_MASTER_RADIO);
|
|
_regs = boost::make_shared<radio_regmap_t>(_radio_type == PRIMARY ? 0 : 1);
|
|
_regs->initialize(*ctrl, true);
|
|
|
|
// Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1
|
|
if (_radio_type == PRIMARY) {
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
|
|
_regs->misc_outs_reg.flush();
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0);
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1);
|
|
_regs->misc_outs_reg.flush();
|
|
}
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Setup peripherals
|
|
////////////////////////////////////////////////////////////////
|
|
_spi = spi_core_3000::make(ctrl,
|
|
regs::sr_addr(radio_ctrl_impl::regs::SPI),
|
|
regs::rb_addr(radio_ctrl_impl::regs::RB_SPI));
|
|
_adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN);
|
|
_dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _radio_clk_rate);
|
|
|
|
if (_radio_type == PRIMARY) {
|
|
_fp_gpio = gpio_atr::gpio_atr_3000::make(
|
|
ctrl, regs::sr_addr(regs::FP_GPIO), regs::rb_addr(regs::RB_FP_GPIO));
|
|
for (const gpio_atr::gpio_attr_map_t::value_type& attr : gpio_atr::gpio_attr_map) {
|
|
switch (attr.first) {
|
|
case usrp::gpio_atr::GPIO_SRC:
|
|
_tree
|
|
->create<std::vector<std::string>>(
|
|
fs_path("gpio") / "FP0" / attr.second)
|
|
.set(std::vector<std::string>(
|
|
32, usrp::gpio_atr::default_attr_value_map.at(attr.first)))
|
|
.add_coerced_subscriber([this](const std::vector<std::string>&) {
|
|
throw uhd::runtime_error("This device does not support "
|
|
"setting the GPIO_SRC attribute.");
|
|
});
|
|
break;
|
|
case usrp::gpio_atr::GPIO_CTRL:
|
|
case usrp::gpio_atr::GPIO_DDR:
|
|
_tree
|
|
->create<std::vector<std::string>>(
|
|
fs_path("gpio") / "FP0" / attr.second)
|
|
.set(std::vector<std::string>(
|
|
32, usrp::gpio_atr::default_attr_value_map.at(attr.first)))
|
|
.add_coerced_subscriber(
|
|
[this, attr](const std::vector<std::string> str_val) {
|
|
uint32_t val = 0;
|
|
for (size_t i = 0; i < str_val.size(); i++) {
|
|
val += usrp::gpio_atr::gpio_attr_value_pair
|
|
.at(attr.second)
|
|
.at(str_val[i])
|
|
<< i;
|
|
}
|
|
_fp_gpio->set_gpio_attr(attr.first, val);
|
|
});
|
|
break;
|
|
case usrp::gpio_atr::GPIO_READBACK:
|
|
_tree->create<uint32_t>(fs_path("gpio") / "FP0" / "READBACK")
|
|
.set_publisher([this]() { return _fp_gpio->read_gpio(); });
|
|
break;
|
|
default:
|
|
_tree->create<uint32_t>(fs_path("gpio") / "FP0" / attr.second)
|
|
.set(0)
|
|
.add_coerced_subscriber([this, attr](const uint32_t val) {
|
|
_fp_gpio->set_gpio_attr(attr.first, val);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// create legacy codec control objects
|
|
////////////////////////////////////////////////////////////////
|
|
_tree->create<int>(
|
|
"rx_codecs" / _radio_slot / "gains"); // phony property so this dir exists
|
|
_tree->create<int>(
|
|
"tx_codecs" / _radio_slot / "gains"); // phony property so this dir exists
|
|
_tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ads62p48");
|
|
_tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("ad9146");
|
|
|
|
_tree->create<meta_range_t>("rx_codecs" / _radio_slot / "gains" / "digital" / "range")
|
|
.set(meta_range_t(0, 6.0, 0.5));
|
|
_tree->create<double>("rx_codecs" / _radio_slot / "gains" / "digital" / "value")
|
|
.add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, _adc, _1))
|
|
.set(0);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// create front-end objects
|
|
////////////////////////////////////////////////////////////////
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
_leds[i] = gpio_atr::gpio_atr_3000::make_write_only(
|
|
_get_ctrl(i), regs::sr_addr(regs::LEDS));
|
|
_leds[i]->set_atr_mode(
|
|
usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL);
|
|
|
|
_rx_fe_map[i].core = rx_frontend_core_3000::make(
|
|
_get_ctrl(i), regs::sr_addr(x300_regs::RX_FE_BASE));
|
|
_rx_fe_map[i].core->set_adc_rate(_radio_clk_rate);
|
|
_rx_fe_map[i].core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE);
|
|
_rx_fe_map[i].core->set_dc_offset_auto(
|
|
rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE);
|
|
_rx_fe_map[i].core->populate_subtree(
|
|
_tree->subtree(_root_path / "rx_fe_corrections" / i));
|
|
|
|
_tx_fe_map[i].core = tx_frontend_core_200::make(
|
|
_get_ctrl(i), regs::sr_addr(x300_regs::TX_FE_BASE));
|
|
_tx_fe_map[i].core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE);
|
|
_tx_fe_map[i].core->set_iq_balance(
|
|
tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE);
|
|
_tx_fe_map[i].core->populate_subtree(
|
|
_tree->subtree(_root_path / "tx_fe_corrections" / i));
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Bind the daughterboard command time to the motherboard level property
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
if (_tree->exists(fs_path("time") / "cmd")) {
|
|
_tree->access<time_spec_t>(fs_path("time") / "cmd")
|
|
.add_coerced_subscriber(
|
|
boost::bind(&x300_radio_ctrl_impl::set_fe_cmd_time, this, _1, i));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Update default SPP (overwrites the default value from the XML file)
|
|
////////////////////////////////////////////////////////////////
|
|
const size_t max_bytes_header =
|
|
uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t);
|
|
const size_t default_spp =
|
|
(_tree->access<size_t>("mtu/recv").get() - max_bytes_header)
|
|
/ (2 * sizeof(int16_t));
|
|
_tree->access<int>(get_arg_path("spp") / "value").set(default_spp);
|
|
}
|
|
|
|
x300_radio_ctrl_impl::~x300_radio_ctrl_impl()
|
|
{
|
|
UHD_SAFE_CALL(
|
|
// Tear down our part of the tree:
|
|
_tree->remove(fs_path("rx_codecs" / _radio_slot));
|
|
_tree->remove(fs_path("tx_codecs" / _radio_slot));
|
|
_tree->remove(_root_path / "rx_fe_corrections");
|
|
_tree->remove(_root_path / "tx_fe_corrections");
|
|
if (_radio_type == PRIMARY) {
|
|
for (const gpio_atr::gpio_attr_map_t::value_type attr :
|
|
gpio_atr::gpio_attr_map) {
|
|
_tree->remove(fs_path("gpio") / "FP0" / attr.second);
|
|
}
|
|
}
|
|
|
|
// Reset peripherals
|
|
if (_radio_type == PRIMARY) {
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
|
|
} _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0);
|
|
_regs->misc_outs_reg.flush();)
|
|
}
|
|
|
|
/****************************************************************************
|
|
* API calls
|
|
***************************************************************************/
|
|
double x300_radio_ctrl_impl::set_rate(double rate)
|
|
{
|
|
const double actual_rate = get_rate();
|
|
if (not uhd::math::frequencies_are_equal(rate, actual_rate)) {
|
|
UHD_LOGGER_WARNING("X300 RADIO")
|
|
<< "Requesting invalid sampling rate from device: " << rate / 1e6
|
|
<< " MHz. Actual rate is: " << actual_rate / 1e6 << " MHz.";
|
|
}
|
|
// On X3x0, tick rate can't actually be changed at runtime
|
|
return actual_rate;
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_fe_cmd_time(const time_spec_t& time, const size_t chan)
|
|
{
|
|
if (_tree->exists(fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "time" / "cmd"))) {
|
|
_tree
|
|
->access<time_spec_t>(
|
|
fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "time" / "cmd"))
|
|
.set(time);
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_tx_antenna(const std::string& ant, const size_t chan)
|
|
{
|
|
_tree
|
|
->access<std::string>(
|
|
fs_path("dboards" / _radio_slot / "tx_frontends"
|
|
/ _tx_fe_map.at(chan).db_fe_name / "antenna" / "value"))
|
|
.set(ant);
|
|
}
|
|
|
|
std::string x300_radio_ctrl_impl::get_tx_antenna(const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<std::string>(
|
|
fs_path("dboards" / _radio_slot / "tx_frontends"
|
|
/ _tx_fe_map.at(chan).db_fe_name / "antenna" / "value"))
|
|
.get();
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_rx_antenna(const std::string& ant, const size_t chan)
|
|
{
|
|
_tree
|
|
->access<std::string>(
|
|
fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "antenna" / "value"))
|
|
.set(ant);
|
|
}
|
|
|
|
std::string x300_radio_ctrl_impl::get_rx_antenna(const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<std::string>(
|
|
fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "antenna" / "value"))
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(fs_path("dboards" / _radio_slot / "tx_frontends"
|
|
/ _tx_fe_map.at(chan).db_fe_name / "freq" / "value"))
|
|
.set(freq)
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_tx_frequency(const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(fs_path("dboards" / _radio_slot / "tx_frontends"
|
|
/ _tx_fe_map.at(chan).db_fe_name / "freq" / "value"))
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "freq" / "value"))
|
|
.set(freq)
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_rx_frequency(const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "freq" / "value"))
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_rx_bandwidth(const double bandwidth, const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(
|
|
fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "bandwidth" / "value"))
|
|
.set(bandwidth)
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_rx_bandwidth(const size_t chan)
|
|
{
|
|
return _tree
|
|
->access<double>(
|
|
fs_path("dboards" / _radio_slot / "rx_frontends"
|
|
/ _rx_fe_map.at(chan).db_fe_name / "bandwidth" / "value"))
|
|
.get();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan)
|
|
{
|
|
auto& gg = _tx_gain_groups.at(chan);
|
|
gg->set_value(gain);
|
|
return gg->get_value();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_tx_gain(const size_t chan)
|
|
{
|
|
return _tx_gain_groups.at(chan)->get_value();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan)
|
|
{
|
|
auto& gg = _rx_gain_groups.at(chan);
|
|
gg->set_value(gain);
|
|
return gg->get_value();
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_rx_gain(const size_t chan)
|
|
{
|
|
return _rx_gain_groups.at(chan)->get_value();
|
|
}
|
|
|
|
std::vector<std::string> x300_radio_ctrl_impl::get_rx_lo_names(const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
std::vector<std::string> lo_names;
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
for (const std::string& name : _tree->list(rx_fe_fe_root / "los")) {
|
|
lo_names.push_back(name);
|
|
}
|
|
}
|
|
return lo_names;
|
|
}
|
|
|
|
std::vector<std::string> x300_radio_ctrl_impl::get_rx_lo_sources(
|
|
const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
if (_tree->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
|
|
// Special value ALL_LOS support atomically sets the source for all LOs
|
|
return _tree
|
|
->access<std::vector<std::string>>(
|
|
rx_fe_fe_root / "los" / ALL_LOS / "source" / "options")
|
|
.get();
|
|
} else {
|
|
return std::vector<std::string>();
|
|
}
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
return _tree
|
|
->access<std::vector<std::string>>(
|
|
rx_fe_fe_root / "los" / name / "source" / "options")
|
|
.get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
// If the daughterboard doesn't expose it's LO(s) then it can only be internal
|
|
return std::vector<std::string>(1, "internal");
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_rx_lo_source(
|
|
const std::string& src, const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
if (_tree->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
|
|
// Special value ALL_LOS support atomically sets the source for all LOs
|
|
_tree
|
|
->access<std::string>(
|
|
rx_fe_fe_root / "los" / ALL_LOS / "source" / "value")
|
|
.set(src);
|
|
} else {
|
|
for (const std::string& n : _tree->list(rx_fe_fe_root / "los")) {
|
|
this->set_rx_lo_source(src, n, chan);
|
|
}
|
|
}
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
_tree
|
|
->access<std::string>(
|
|
rx_fe_fe_root / "los" / name / "source" / "value")
|
|
.set(src);
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
throw uhd::runtime_error(
|
|
"This device does not support manual configuration of LOs");
|
|
}
|
|
}
|
|
|
|
const std::string x300_radio_ctrl_impl::get_rx_lo_source(
|
|
const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
// Special value ALL_LOS support atomically sets the source for all LOs
|
|
return _tree
|
|
->access<std::string>(
|
|
rx_fe_fe_root / "los" / ALL_LOS / "source" / "value")
|
|
.get();
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
return _tree
|
|
->access<std::string>(
|
|
rx_fe_fe_root / "los" / name / "source" / "value")
|
|
.get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
// If the daughterboard doesn't expose it's LO(s) then it can only be internal
|
|
return "internal";
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_rx_lo_export_enabled(
|
|
bool enabled, const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
if (_tree->exists(rx_fe_fe_root / "los" / ALL_LOS)) {
|
|
// Special value ALL_LOS support atomically sets the source for all LOs
|
|
_tree->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export")
|
|
.set(enabled);
|
|
} else {
|
|
for (const std::string& n : _tree->list(rx_fe_fe_root / "los")) {
|
|
this->set_rx_lo_export_enabled(enabled, n, chan);
|
|
}
|
|
}
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
_tree->access<bool>(rx_fe_fe_root / "los" / name / "export").set(enabled);
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
throw uhd::runtime_error(
|
|
"This device does not support manual configuration of LOs");
|
|
}
|
|
}
|
|
|
|
bool x300_radio_ctrl_impl::get_rx_lo_export_enabled(
|
|
const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
// Special value ALL_LOS support atomically sets the source for all LOs
|
|
return _tree->access<bool>(rx_fe_fe_root / "los" / ALL_LOS / "export").get();
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
return _tree->access<bool>(rx_fe_fe_root / "los" / name / "export").get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
// If the daughterboard doesn't expose it's LO(s), assume it cannot export
|
|
return false;
|
|
}
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::set_rx_lo_freq(
|
|
double freq, const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
throw uhd::runtime_error(
|
|
"LO frequency must be set for each stage individually");
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
_tree->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
|
|
.set(freq);
|
|
return _tree
|
|
->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
|
|
.get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
throw uhd::runtime_error(
|
|
"This device does not support manual configuration of LOs");
|
|
}
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_rx_lo_freq(const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
throw uhd::runtime_error(
|
|
"LO frequency must be retrieved for each stage individually");
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
return _tree
|
|
->access<double>(rx_fe_fe_root / "los" / name / "freq" / "value")
|
|
.get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
// Return actual RF frequency if the daughterboard doesn't expose it's LO(s)
|
|
return _tree->access<double>(rx_fe_fe_root / "freq" / " value").get();
|
|
}
|
|
}
|
|
|
|
freq_range_t x300_radio_ctrl_impl::get_rx_lo_freq_range(
|
|
const std::string& name, const size_t chan)
|
|
{
|
|
fs_path rx_fe_fe_root = fs_path(
|
|
"dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name);
|
|
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
if (name == ALL_LOS) {
|
|
throw uhd::runtime_error(
|
|
"LO frequency range must be retrieved for each stage individually");
|
|
} else {
|
|
if (_tree->exists(rx_fe_fe_root / "los")) {
|
|
return _tree
|
|
->access<freq_range_t>(
|
|
rx_fe_fe_root / "los" / name / "freq" / "range")
|
|
.get();
|
|
} else {
|
|
throw uhd::runtime_error("Could not find LO stage " + name);
|
|
}
|
|
}
|
|
} else {
|
|
// Return the actual RF range if the daughterboard doesn't expose it's LO(s)
|
|
return _tree->access<meta_range_t>(rx_fe_fe_root / "freq" / "range").get();
|
|
}
|
|
}
|
|
|
|
template <typename map_type>
|
|
static size_t _get_chan_from_map(std::map<size_t, map_type> map, const std::string& fe)
|
|
{
|
|
for (auto it = map.begin(); it != map.end(); ++it) {
|
|
if (it->second.db_fe_name == fe) {
|
|
return it->first;
|
|
}
|
|
}
|
|
throw uhd::runtime_error(
|
|
str(boost::format("Invalid daughterboard frontend name: %s") % fe));
|
|
}
|
|
|
|
size_t x300_radio_ctrl_impl::get_chan_from_dboard_fe(
|
|
const std::string& fe, const uhd::direction_t direction)
|
|
{
|
|
switch (direction) {
|
|
case uhd::TX_DIRECTION:
|
|
return _get_chan_from_map(_tx_fe_map, fe);
|
|
case uhd::RX_DIRECTION:
|
|
return _get_chan_from_map(_rx_fe_map, fe);
|
|
default:
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
}
|
|
}
|
|
|
|
std::string x300_radio_ctrl_impl::get_dboard_fe_from_chan(
|
|
const size_t chan, const uhd::direction_t direction)
|
|
{
|
|
switch (direction) {
|
|
case uhd::TX_DIRECTION:
|
|
return _tx_fe_map.at(chan).db_fe_name;
|
|
case uhd::RX_DIRECTION:
|
|
return _rx_fe_map.at(chan).db_fe_name;
|
|
default:
|
|
UHD_THROW_INVALID_CODE_PATH();
|
|
}
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::get_output_samp_rate(size_t chan)
|
|
{
|
|
// TODO: chan should never be ANY_PORT, but due to our current graph search
|
|
// method, this can actually happen:
|
|
if (chan == ANY_PORT) {
|
|
chan = 0;
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
if (_is_streamer_active(uhd::RX_DIRECTION, chan)) {
|
|
chan = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return _rx_fe_map.at(chan).core->get_output_rate();
|
|
}
|
|
|
|
std::vector<std::string> x300_radio_ctrl_impl::get_gpio_banks() const
|
|
{
|
|
std::vector<std::string> banks{"RX", "TX"};
|
|
// These pairs are the same, but RXA/TXA are from pre-rfnoc era and are kept for
|
|
// backward compat:
|
|
banks.push_back("RX" + _radio_slot);
|
|
banks.push_back("TX" + _radio_slot);
|
|
if (_fp_gpio) {
|
|
banks.push_back("FP0");
|
|
}
|
|
return banks;
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_gpio_attr(const std::string& bank,
|
|
const std::string& attr,
|
|
const uint32_t value,
|
|
const uint32_t mask)
|
|
{
|
|
if (bank == "FP0" and _fp_gpio) {
|
|
std::vector<std::string> attr_value;
|
|
const auto attr_type = usrp::gpio_atr::gpio_attr_rev_map.at(attr);
|
|
switch (attr_type) {
|
|
case usrp::gpio_atr::GPIO_SRC:
|
|
case usrp::gpio_atr::GPIO_CTRL:
|
|
case usrp::gpio_atr::GPIO_DDR: {
|
|
attr_value =
|
|
_tree->access<std::vector<std::string>>(fs_path("gpio") / bank / attr)
|
|
.get();
|
|
std::bitset<32> bit_mask = std::bitset<32>(mask);
|
|
std::bitset<32> new_value = std::bitset<32>(value);
|
|
for (size_t i = 0; i < bit_mask.size(); i++) {
|
|
if (bit_mask[i] == 1) {
|
|
attr_value[i] =
|
|
usrp::gpio_atr::attr_value_map.at(attr_type).at(new_value[i]);
|
|
}
|
|
}
|
|
_tree->access<std::vector<std::string>>(fs_path("gpio") / bank / attr)
|
|
.set(attr_value);
|
|
return;
|
|
} break;
|
|
default: {
|
|
const uint32_t curr_value =
|
|
_tree->access<uint32_t>(fs_path("gpio") / bank / attr).get();
|
|
uint32_t new_value = (curr_value & ~mask) | (value & mask);
|
|
_tree->access<uint32_t>(fs_path("gpio") / bank / attr).set(new_value);
|
|
} break;
|
|
}
|
|
}
|
|
if (bank.size() > 2 and bank[1] == 'X') {
|
|
const std::string name = bank.substr(2);
|
|
const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX
|
|
: dboard_iface::UNIT_TX;
|
|
dboard_iface::sptr iface =
|
|
_tree->access<dboard_iface::sptr>(fs_path("dboards") / name / "iface").get();
|
|
if (attr == "CTRL")
|
|
iface->set_pin_ctrl(unit, uint16_t(value), uint16_t(mask));
|
|
if (attr == "DDR")
|
|
iface->set_gpio_ddr(unit, uint16_t(value), uint16_t(mask));
|
|
if (attr == "OUT")
|
|
iface->set_gpio_out(unit, uint16_t(value), uint16_t(mask));
|
|
if (attr == "ATR_0X")
|
|
iface->set_atr_reg(
|
|
unit, gpio_atr::ATR_REG_IDLE, uint16_t(value), uint16_t(mask));
|
|
if (attr == "ATR_RX")
|
|
iface->set_atr_reg(
|
|
unit, gpio_atr::ATR_REG_RX_ONLY, uint16_t(value), uint16_t(mask));
|
|
if (attr == "ATR_TX")
|
|
iface->set_atr_reg(
|
|
unit, gpio_atr::ATR_REG_TX_ONLY, uint16_t(value), uint16_t(mask));
|
|
if (attr == "ATR_XX")
|
|
iface->set_atr_reg(
|
|
unit, gpio_atr::ATR_REG_FULL_DUPLEX, uint16_t(value), uint16_t(mask));
|
|
}
|
|
}
|
|
|
|
uint32_t x300_radio_ctrl_impl::get_gpio_attr(
|
|
const std::string& bank, const std::string& attr)
|
|
{
|
|
if (bank == "FP0" and _fp_gpio) {
|
|
const auto attr_type = usrp::gpio_atr::gpio_attr_rev_map.at(attr);
|
|
switch (attr_type) {
|
|
case usrp::gpio_atr::GPIO_SRC:
|
|
case usrp::gpio_atr::GPIO_CTRL:
|
|
case usrp::gpio_atr::GPIO_DDR: {
|
|
auto str_val =
|
|
_tree->access<std::vector<std::string>>(fs_path("gpio") / bank / attr)
|
|
.get();
|
|
uint32_t val = 0;
|
|
for (size_t i = 0; i < str_val.size(); i++) {
|
|
val += usrp::gpio_atr::gpio_attr_value_pair.at(attr).at(str_val[i])
|
|
<< i;
|
|
}
|
|
return val;
|
|
} break;
|
|
default: {
|
|
return _tree->access<uint32_t>(fs_path("gpio") / bank / attr).get();
|
|
} break;
|
|
}
|
|
}
|
|
if (bank.size() > 2 and bank[1] == 'X') {
|
|
const std::string name = bank.substr(2);
|
|
const dboard_iface::unit_t unit = (bank[0] == 'R') ? dboard_iface::UNIT_RX
|
|
: dboard_iface::UNIT_TX;
|
|
dboard_iface::sptr iface =
|
|
_tree->access<dboard_iface::sptr>(fs_path("dboards") / name / "iface").get();
|
|
if (attr == "CTRL")
|
|
return iface->get_pin_ctrl(unit);
|
|
if (attr == "DDR")
|
|
return iface->get_gpio_ddr(unit);
|
|
if (attr == "OUT")
|
|
return iface->get_gpio_out(unit);
|
|
if (attr == "ATR_0X")
|
|
return iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE);
|
|
if (attr == "ATR_RX")
|
|
return iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY);
|
|
if (attr == "ATR_TX")
|
|
return iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY);
|
|
if (attr == "ATR_XX")
|
|
return iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX);
|
|
if (attr == "READBACK")
|
|
return iface->read_gpio(unit);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Radio control and setup
|
|
***************************************************************************/
|
|
void x300_radio_ctrl_impl::setup_radio(uhd::i2c_iface::sptr zpu_i2c,
|
|
x300_clock_ctrl::sptr clock,
|
|
bool ignore_cal_file,
|
|
bool verbose)
|
|
{
|
|
_self_cal_adc_capture_delay(verbose);
|
|
_ignore_cal_file = ignore_cal_file;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// create RF frontend interfacing
|
|
////////////////////////////////////////////////////////////////////
|
|
static const size_t BASE_ADDR = 0x50;
|
|
static const size_t RX_EEPROM_ADDR = 0x5;
|
|
static const size_t TX_EEPROM_ADDR = 0x4;
|
|
static const size_t GDB_EEPROM_ADDR = 0x1;
|
|
const static std::vector<size_t> EEPROM_ADDRS{
|
|
RX_EEPROM_ADDR, TX_EEPROM_ADDR, GDB_EEPROM_ADDR};
|
|
const static std::vector<std::string> EEPROM_PATHS{
|
|
"rx_eeprom", "tx_eeprom", "gdb_eeprom"};
|
|
|
|
const size_t DB_OFFSET = (_radio_slot == "A") ? 0x0 : 0x2;
|
|
const fs_path db_path = ("dboards" / _radio_slot);
|
|
for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) {
|
|
const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET;
|
|
// Load EEPROM
|
|
_db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr);
|
|
// Add to tree
|
|
_tree->create<dboard_eeprom_t>(db_path / EEPROM_PATHS[i])
|
|
.set(_db_eeproms[addr])
|
|
.add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::_set_db_eeprom,
|
|
this,
|
|
zpu_i2c,
|
|
(BASE_ADDR | addr),
|
|
_1));
|
|
}
|
|
|
|
// create a new dboard interface
|
|
x300_dboard_iface_config_t db_config;
|
|
db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_get_ctrl(IO_MASTER_RADIO),
|
|
radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::GPIO),
|
|
radio_ctrl_impl::regs::rb_addr(radio_ctrl_impl::regs::RB_DB_GPIO));
|
|
db_config.spi = _spi;
|
|
db_config.rx_spi_slaveno = DB_RX_SEN;
|
|
db_config.tx_spi_slaveno = DB_TX_SEN;
|
|
db_config.i2c = zpu_i2c;
|
|
db_config.clock = clock;
|
|
db_config.which_rx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_RX
|
|
: X300_CLOCK_WHICH_DB1_RX;
|
|
db_config.which_tx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_TX
|
|
: X300_CLOCK_WHICH_DB1_TX;
|
|
db_config.dboard_slot = (_radio_slot == "A") ? 0 : 1;
|
|
db_config.cmd_time_ctrl = _get_ctrl(IO_MASTER_RADIO);
|
|
|
|
// create a new dboard manager
|
|
boost::shared_ptr<x300_dboard_iface> db_iface =
|
|
boost::make_shared<x300_dboard_iface>(db_config);
|
|
_db_manager = dboard_manager::make(_db_eeproms[RX_EEPROM_ADDR + DB_OFFSET],
|
|
_db_eeproms[TX_EEPROM_ADDR + DB_OFFSET],
|
|
_db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET],
|
|
db_iface,
|
|
_tree->subtree(db_path),
|
|
true // defer daughterboard intitialization
|
|
);
|
|
|
|
size_t rx_chan = 0, tx_chan = 0;
|
|
for (const std::string& fe : _db_manager->get_rx_frontends()) {
|
|
if (rx_chan >= _get_num_radios()) {
|
|
break;
|
|
}
|
|
_rx_fe_map[rx_chan].db_fe_name = fe;
|
|
db_iface->add_rx_fe(fe, _rx_fe_map[rx_chan].core);
|
|
const fs_path fe_path(db_path / "rx_frontends" / fe);
|
|
const std::string conn = _tree->access<std::string>(fe_path / "connection").get();
|
|
const double if_freq =
|
|
(_tree->exists(fe_path / "if_freq/value"))
|
|
? _tree->access<double>(fe_path / "if_freq/value").get()
|
|
: 0.0;
|
|
_rx_fe_map[rx_chan].core->set_fe_connection(usrp::fe_connection_t(conn, if_freq));
|
|
rx_chan++;
|
|
}
|
|
for (const std::string& fe : _db_manager->get_tx_frontends()) {
|
|
if (tx_chan >= _get_num_radios()) {
|
|
break;
|
|
}
|
|
_tx_fe_map[tx_chan].db_fe_name = fe;
|
|
const fs_path fe_path(db_path / "tx_frontends" / fe);
|
|
const std::string conn = _tree->access<std::string>(fe_path / "connection").get();
|
|
_tx_fe_map[tx_chan].core->set_mux(conn);
|
|
tx_chan++;
|
|
}
|
|
UHD_ASSERT_THROW(rx_chan or tx_chan);
|
|
|
|
// Initialize the daughterboards now that frontend cores and connections exist
|
|
_db_manager->initialize_dboards();
|
|
|
|
// now that dboard is created -- register into rx antenna event
|
|
if (not _rx_fe_map.empty()) {
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
if (_tree->exists(db_path / "rx_frontends" / _rx_fe_map[i].db_fe_name
|
|
/ "antenna" / "value")) {
|
|
// We need a desired subscriber for antenna/value because the experts
|
|
// don't coerce that property.
|
|
_tree
|
|
->access<std::string>(db_path / "rx_frontends"
|
|
/ _rx_fe_map[i].db_fe_name / "antenna"
|
|
/ "value")
|
|
.add_desired_subscriber(boost::bind(
|
|
&x300_radio_ctrl_impl::_update_atr_leds, this, _1, i));
|
|
_update_atr_leds(_tree
|
|
->access<std::string>(db_path / "rx_frontends"
|
|
/ _rx_fe_map[i].db_fe_name
|
|
/ "antenna" / "value")
|
|
.get(),
|
|
i);
|
|
} else {
|
|
_update_atr_leds("", i); // init anyway, even if never called
|
|
}
|
|
}
|
|
}
|
|
|
|
// bind frontend corrections to the dboard freq props
|
|
const fs_path db_tx_fe_path = db_path / "tx_frontends";
|
|
if (not _tx_fe_map.empty()) {
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
if (_tree->exists(
|
|
db_tx_fe_path / _tx_fe_map[i].db_fe_name / "freq" / "value")) {
|
|
_tree
|
|
->access<double>(
|
|
db_tx_fe_path / _tx_fe_map[i].db_fe_name / "freq" / "value")
|
|
.add_coerced_subscriber(
|
|
boost::bind(&x300_radio_ctrl_impl::set_tx_fe_corrections,
|
|
this,
|
|
db_path,
|
|
_root_path / "tx_fe_corrections" / _tx_fe_map[i].db_fe_name,
|
|
_1));
|
|
}
|
|
}
|
|
}
|
|
const fs_path db_rx_fe_path = db_path / "rx_frontends";
|
|
if (not _rx_fe_map.empty()) {
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
if (_tree->exists(
|
|
db_rx_fe_path / _tx_fe_map[i].db_fe_name / "freq" / "value")) {
|
|
_tree
|
|
->access<double>(
|
|
db_rx_fe_path / _tx_fe_map[i].db_fe_name / "freq" / "value")
|
|
.add_coerced_subscriber(
|
|
boost::bind(&x300_radio_ctrl_impl::set_rx_fe_corrections,
|
|
this,
|
|
db_path,
|
|
_root_path / "rx_fe_corrections" / _tx_fe_map[i].db_fe_name,
|
|
_1));
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Set tick rate
|
|
////////////////////////////////////////////////////////////////
|
|
const double tick_rate = get_output_samp_rate(0);
|
|
if (_radio_type == PRIMARY) {
|
|
// Slot A is the highlander timekeeper
|
|
_tree->access<double>("tick_rate").set(tick_rate);
|
|
}
|
|
radio_ctrl_impl::set_rate(tick_rate);
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Set gain groups
|
|
// Note: The actual gain control comes from the daughterboard drivers, thus,
|
|
// we need to call into the prop tree at the appropriate location in order
|
|
// to modify the gains.
|
|
////////////////////////////////////////////////////////////////
|
|
// TX
|
|
for (size_t chan = 0; chan < _num_tx_channels; chan++) {
|
|
fs_path rf_gains_path(db_tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "gains");
|
|
if (!_tree->exists(rf_gains_path)) {
|
|
continue;
|
|
}
|
|
|
|
std::vector<std::string> gain_stages = _tree->list(rf_gains_path);
|
|
if (gain_stages.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// DAC does not have a gain path
|
|
auto gg = gain_group::make();
|
|
for (const auto& name : gain_stages) {
|
|
gg->register_fcns(name,
|
|
make_gain_fcns_from_subtree(_tree->subtree(rf_gains_path / name)),
|
|
1 /* high prio */);
|
|
}
|
|
_tx_gain_groups[chan] = gg;
|
|
}
|
|
// RX
|
|
for (size_t chan = 0; chan < _num_rx_channels; chan++) {
|
|
fs_path rf_gains_path(db_rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "gains");
|
|
fs_path adc_gains_path("rx_codecs" / _radio_slot / "gains");
|
|
|
|
auto gg = gain_group::make();
|
|
// ADC also has a gain path
|
|
for (const auto& name : _tree->list(adc_gains_path)) {
|
|
gg->register_fcns("ADC-" + name,
|
|
make_gain_fcns_from_subtree(_tree->subtree(adc_gains_path / name)),
|
|
0 /* low prio */);
|
|
}
|
|
if (_tree->exists(rf_gains_path)) {
|
|
for (const auto& name : _tree->list(rf_gains_path)) {
|
|
gg->register_fcns(name,
|
|
make_gain_fcns_from_subtree(_tree->subtree(rf_gains_path / name)),
|
|
1 /* high prio */);
|
|
}
|
|
}
|
|
_rx_gain_groups[chan] = gg;
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_rx_fe_corrections(
|
|
const fs_path& db_path, const fs_path& rx_fe_corr_path, const double lo_freq)
|
|
{
|
|
if (not _ignore_cal_file) {
|
|
apply_rx_fe_corrections(_tree, db_path, rx_fe_corr_path, lo_freq);
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::set_tx_fe_corrections(
|
|
const fs_path& db_path, const fs_path& tx_fe_corr_path, const double lo_freq)
|
|
{
|
|
if (not _ignore_cal_file) {
|
|
apply_tx_fe_corrections(_tree, db_path, tx_fe_corr_path, lo_freq);
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::reset_codec()
|
|
{
|
|
if (_radio_type == PRIMARY) { // ADC/DAC reset lines only exist in Radio0
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1);
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0);
|
|
_regs->misc_outs_reg.flush();
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0);
|
|
_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1);
|
|
_regs->misc_outs_reg.flush();
|
|
}
|
|
UHD_ASSERT_THROW(bool(_adc));
|
|
UHD_ASSERT_THROW(bool(_dac));
|
|
_adc->reset();
|
|
_dac->reset();
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::self_test_adc(uint32_t ramp_time_ms)
|
|
{
|
|
// Bypass all front-end corrections
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
_rx_fe_map[i].core->bypass_all(true);
|
|
}
|
|
|
|
// Test basic patterns
|
|
_adc->set_test_word("ones", "ones");
|
|
_check_adc(0xfffcfffc);
|
|
_adc->set_test_word("zeros", "zeros");
|
|
_check_adc(0x00000000);
|
|
_adc->set_test_word("ones", "zeros");
|
|
_check_adc(0xfffc0000);
|
|
_adc->set_test_word("zeros", "ones");
|
|
_check_adc(0x0000fffc);
|
|
for (size_t k = 0; k < 14; k++) {
|
|
_adc->set_test_word("zeros", "custom", 1 << k);
|
|
_check_adc(1 << (k + 2));
|
|
}
|
|
for (size_t k = 0; k < 14; k++) {
|
|
_adc->set_test_word("custom", "zeros", 1 << k);
|
|
_check_adc(1 << (k + 18));
|
|
}
|
|
|
|
// Turn on ramp pattern test
|
|
_adc->set_test_word("ramp", "ramp");
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
// Sleep added for SPI transactions to finish and ramp to start before checker is
|
|
// enabled.
|
|
std::this_thread::sleep_for(std::chrono::microseconds(1000));
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(ramp_time_ms));
|
|
_regs->misc_ins_reg.refresh();
|
|
|
|
std::string i_status, q_status;
|
|
if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED))
|
|
if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR))
|
|
i_status = "Bit Errors!";
|
|
else
|
|
i_status = "Good";
|
|
else
|
|
i_status = "Not Locked!";
|
|
|
|
if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED))
|
|
if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR))
|
|
q_status = "Bit Errors!";
|
|
else
|
|
q_status = "Good";
|
|
else
|
|
q_status = "Not Locked!";
|
|
|
|
// Return to normal mode
|
|
_adc->set_test_word("normal", "normal");
|
|
|
|
if ((i_status != "Good") or (q_status != "Good")) {
|
|
throw uhd::runtime_error(
|
|
(boost::format(
|
|
"ADC self-test failed for %s. Ramp checker status: {ADC_A=%s, ADC_B=%s}")
|
|
% unique_id() % i_status % q_status)
|
|
.str());
|
|
}
|
|
|
|
// Restore front-end corrections
|
|
for (size_t i = 0; i < _get_num_radios(); i++) {
|
|
_rx_fe_map[i].core->bypass_all(false);
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::extended_adc_test(
|
|
const std::vector<x300_radio_ctrl_impl::sptr>& radios, double duration_s)
|
|
{
|
|
static const size_t SECS_PER_ITER = 5;
|
|
UHD_LOGGER_INFO("X300 RADIO")
|
|
<< boost::format(
|
|
"Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...")
|
|
% duration_s % SECS_PER_ITER;
|
|
|
|
size_t num_iters = static_cast<size_t>(ceil(duration_s / SECS_PER_ITER));
|
|
size_t num_failures = 0;
|
|
for (size_t iter = 0; iter < num_iters; iter++) {
|
|
// Run self-test
|
|
UHD_LOGGER_INFO("X300 RADIO")
|
|
<< boost::format("Extended ADC Self-Test Iteration %06d... ") % (iter + 1);
|
|
try {
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->self_test_adc((SECS_PER_ITER * 1000) / radios.size());
|
|
}
|
|
UHD_LOGGER_INFO("X300 RADIO")
|
|
<< boost::format("Extended ADC Self-Test Iteration %06d passed ")
|
|
% (iter + 1);
|
|
} catch (std::exception& e) {
|
|
num_failures++;
|
|
UHD_LOGGER_ERROR("X300 RADIO") << e.what();
|
|
}
|
|
}
|
|
if (num_failures == 0) {
|
|
UHD_LOGGER_INFO("X300 RADIO") << "Extended ADC Self-Test PASSED";
|
|
} else {
|
|
throw uhd::runtime_error(
|
|
(boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n")
|
|
% num_failures % num_iters)
|
|
.str());
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::synchronize_dacs(
|
|
const std::vector<x300_radio_ctrl_impl::sptr>& radios)
|
|
{
|
|
if (radios.size() < 2)
|
|
return; // Nothing to synchronize
|
|
|
|
//**PRECONDITION**
|
|
// This function assumes that all the VITA times in "radios" are synchronized
|
|
// to a common reference. Currently, this function is called in get_tx_stream
|
|
// which also has the same precondition.
|
|
|
|
// Get a rough estimate of the cumulative command latency
|
|
boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time();
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->user_reg_read64(
|
|
regs::RB_TIME_NOW); // Discard value. We are just timing the call
|
|
}
|
|
boost::posix_time::time_duration t_elapsed =
|
|
boost::posix_time::microsec_clock::local_time() - t_start;
|
|
|
|
// Add 100% of headroom + uncertainty to the command time
|
|
uint64_t t_sync_us =
|
|
(t_elapsed.total_microseconds() * 2) + 16000 /*Scheduler latency*/;
|
|
|
|
std::string err_str;
|
|
// Try to sync 3 times before giving up
|
|
for (size_t attempt = 0; attempt < 3; attempt++) {
|
|
try {
|
|
// Reinitialize and resync all DACs
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->_dac->sync();
|
|
}
|
|
|
|
// Set tick rate and make sure FRAMEP/N is 0
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->set_command_tick_rate(
|
|
radios[i]->_radio_clk_rate, IO_MASTER_RADIO);
|
|
radios[i]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0);
|
|
}
|
|
|
|
// Pick radios[0] as the time reference.
|
|
uhd::time_spec_t sync_time = radios[0]->_time64->get_time_now()
|
|
+ uhd::time_spec_t(((double)t_sync_us) / 1e6);
|
|
|
|
// Send the sync command
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->set_command_time(sync_time, IO_MASTER_RADIO);
|
|
// Arm FRAMEP/N sync pulse by asserting a rising edge
|
|
radios[i]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 1);
|
|
}
|
|
|
|
// Reset FRAMEP/N to 0 after 2 clock cycles
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->set_command_time(
|
|
sync_time + (2.0 / radios[i]->_radio_clk_rate), IO_MASTER_RADIO);
|
|
radios[i]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0);
|
|
radios[i]->set_command_time(uhd::time_spec_t(0.0), IO_MASTER_RADIO);
|
|
}
|
|
|
|
// Wait and check status
|
|
std::this_thread::sleep_for(std::chrono::microseconds(t_sync_us));
|
|
for (size_t i = 0; i < radios.size(); i++) {
|
|
radios[i]->_dac->verify_sync();
|
|
}
|
|
|
|
return;
|
|
} catch (const uhd::runtime_error& e) {
|
|
err_str = e.what();
|
|
UHD_LOGGER_TRACE("X300 RADIO") << "Retrying DAC synchronization: " << err_str;
|
|
}
|
|
}
|
|
throw uhd::runtime_error(err_str);
|
|
}
|
|
|
|
double x300_radio_ctrl_impl::self_cal_adc_xfer_delay(
|
|
const std::vector<x300_radio_ctrl_impl::sptr>& radios,
|
|
x300_clock_ctrl::sptr clock,
|
|
boost::function<void(double)> wait_for_clk_locked,
|
|
bool apply_delay)
|
|
{
|
|
UHD_LOGGER_INFO("X300 RADIO") << "Running ADC transfer delay self-cal: ";
|
|
|
|
// Effective resolution of the self-cal.
|
|
static const size_t NUM_DELAY_STEPS = 100;
|
|
|
|
double master_clk_period = (1.0e9 / clock->get_master_clock_rate()); // in ns
|
|
double delay_start = 0.0;
|
|
double delay_range = 2 * master_clk_period;
|
|
double delay_incr = delay_range / NUM_DELAY_STEPS;
|
|
|
|
double cached_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_ADC0);
|
|
double fpga_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_FPGA);
|
|
|
|
// Iterate through several values of delays and measure ADC data integrity
|
|
std::vector<std::pair<double, bool>> results;
|
|
for (size_t i = 0; i < NUM_DELAY_STEPS; i++) {
|
|
// Delay the ADC clock (will set both Ch0 and Ch1 delays)
|
|
double delay =
|
|
clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr * i + delay_start);
|
|
wait_for_clk_locked(0.1);
|
|
|
|
uint32_t err_code = 0;
|
|
for (size_t r = 0; r < radios.size(); r++) {
|
|
// Test each channel (I and Q) individually so as to not accidentally trigger
|
|
// on the data from the other channel if there is a swap
|
|
|
|
// -- Test I Channel --
|
|
// Put ADC in ramp test mode. Tie the other channel to all ones.
|
|
radios[r]->_adc->set_test_word("ramp", "ones");
|
|
// Turn on the pattern checker in the FPGA. It will lock when it sees a zero
|
|
// and count deviations from the expected value
|
|
radios[r]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
radios[r]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
|
|
// 50ms @ 200MHz = 10 million samples
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
if (radios[r]->_regs->misc_ins_reg.read(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) {
|
|
err_code += radios[r]->_regs->misc_ins_reg.get(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR);
|
|
} else {
|
|
err_code += 100; // Increment error code by 100 to indicate no lock
|
|
}
|
|
|
|
// -- Test Q Channel --
|
|
// Put ADC in ramp test mode. Tie the other channel to all ones.
|
|
radios[r]->_adc->set_test_word("ones", "ramp");
|
|
// Turn on the pattern checker in the FPGA. It will lock when it sees a zero
|
|
// and count deviations from the expected value
|
|
radios[r]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
radios[r]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
|
|
// 50ms @ 200MHz = 10 million samples
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
if (radios[r]->_regs->misc_ins_reg.read(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) {
|
|
err_code += radios[r]->_regs->misc_ins_reg.get(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR);
|
|
} else {
|
|
err_code += 100; // Increment error code by 100 to indicate no lock
|
|
}
|
|
}
|
|
// UHD_LOGGER_INFO("X300 RADIO") << (boost::format("XferDelay=%fns, Error=%d") %
|
|
// delay % err_code);
|
|
results.push_back(std::pair<double, bool>(delay, err_code == 0));
|
|
}
|
|
|
|
// Calculate the valid window
|
|
int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1;
|
|
for (size_t i = 0; i < results.size(); i++) {
|
|
std::pair<double, bool>& item = results[i];
|
|
if (item.second) { // If data is stable
|
|
if (cur_start_idx == -1) { // This is the first window
|
|
cur_start_idx = i;
|
|
cur_stop_idx = i;
|
|
} else { // We are extending the window
|
|
cur_stop_idx = i;
|
|
}
|
|
} else {
|
|
if (cur_start_idx == -1) { // We haven't yet seen valid data
|
|
// Do nothing
|
|
} else if (win_start_idx == -1) { // We passed the first valid window
|
|
win_start_idx = cur_start_idx;
|
|
win_stop_idx = cur_stop_idx;
|
|
} else { // Update cached window if current window is larger
|
|
double cur_win_len =
|
|
results[cur_stop_idx].first - results[cur_start_idx].first;
|
|
double cached_win_len =
|
|
results[win_stop_idx].first - results[win_start_idx].first;
|
|
if (cur_win_len > cached_win_len) {
|
|
win_start_idx = cur_start_idx;
|
|
win_stop_idx = cur_stop_idx;
|
|
}
|
|
}
|
|
// Reset current window
|
|
cur_start_idx = -1;
|
|
cur_stop_idx = -1;
|
|
}
|
|
}
|
|
if (win_start_idx == -1) {
|
|
throw uhd::runtime_error(
|
|
"self_cal_adc_xfer_delay: Self calibration failed. Convergence error.");
|
|
}
|
|
|
|
double win_center =
|
|
(results[win_stop_idx].first + results[win_start_idx].first) / 2.0;
|
|
double win_length = results[win_stop_idx].first - results[win_start_idx].first;
|
|
if (win_length < master_clk_period / 4) {
|
|
throw uhd::runtime_error(
|
|
"self_cal_adc_xfer_delay: Self calibration failed. Valid window too narrow.");
|
|
}
|
|
|
|
// Cycle slip the relative delay by a clock cycle to prevent sample misalignment
|
|
// fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need
|
|
bool cycle_slip = (win_center - fpga_clk_delay >= master_clk_period);
|
|
if (cycle_slip) {
|
|
win_center -= master_clk_period;
|
|
}
|
|
|
|
if (apply_delay) {
|
|
// Apply delay
|
|
win_center = clock->set_clock_delay(
|
|
X300_CLOCK_WHICH_ADC0, win_center); // Sets ADC0 and ADC1
|
|
wait_for_clk_locked(0.1);
|
|
// Validate
|
|
for (size_t r = 0; r < radios.size(); r++) {
|
|
radios[r]->self_test_adc(2000);
|
|
}
|
|
} else {
|
|
// Restore delay
|
|
clock->set_clock_delay(
|
|
X300_CLOCK_WHICH_ADC0, cached_clk_delay); // Sets ADC0 and ADC1
|
|
}
|
|
|
|
// Teardown
|
|
for (size_t r = 0; r < radios.size(); r++) {
|
|
radios[r]->_adc->set_test_word("normal", "normal");
|
|
radios[r]->_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
}
|
|
UHD_LOGGER_INFO("X300 RADIO")
|
|
<< (boost::format(
|
|
"ADC transfer delay self-cal done (FPGA->ADC=%.3fns%s, Window=%.3fns)")
|
|
% (win_center - fpga_clk_delay) % (cycle_slip ? " +cyc" : "")
|
|
% win_length);
|
|
|
|
return win_center;
|
|
}
|
|
/****************************************************************************
|
|
* Helpers
|
|
***************************************************************************/
|
|
void x300_radio_ctrl_impl::_update_atr_leds(const std::string& rx_ant, const size_t chan)
|
|
{
|
|
// The "RX1" port is used by TwinRX and the "TX/RX" port is used by all
|
|
// other full-duplex dboards. We need to handle both here.
|
|
const bool is_txrx = (rx_ant == "TX/RX" or rx_ant == "RX1");
|
|
const int TXRX_RX = (1 << 0);
|
|
const int TXRX_TX = (1 << 1);
|
|
const int RX2_RX = (1 << 2);
|
|
_leds.at(chan)->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0);
|
|
_leds.at(chan)->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx ? TXRX_RX : RX2_RX);
|
|
_leds.at(chan)->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, TXRX_TX);
|
|
_leds.at(chan)->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, RX2_RX | TXRX_TX);
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::_self_cal_adc_capture_delay(bool print_status)
|
|
{
|
|
if (print_status)
|
|
UHD_LOGGER_INFO("X300 RADIO") << "Running ADC capture delay self-cal...";
|
|
|
|
static const uint32_t NUM_DELAY_STEPS = 32; // The IDELAYE2 element has 32 steps
|
|
static const uint32_t NUM_RETRIES =
|
|
2; // Retry self-cal if it fails in warmup situations
|
|
static const int32_t MIN_WINDOW_LEN = 4;
|
|
|
|
int32_t win_start = -1, win_stop = -1;
|
|
uint32_t iter = 0;
|
|
while (iter++ < NUM_RETRIES) {
|
|
for (uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) {
|
|
// Apply delay
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap);
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1);
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0);
|
|
|
|
uint32_t err_code = 0;
|
|
|
|
// -- Test I Channel --
|
|
// Put ADC in ramp test mode. Tie the other channel to all ones.
|
|
_adc->set_test_word("ramp", "ones");
|
|
// Turn on the pattern checker in the FPGA. It will lock when it sees a zero
|
|
// and count deviations from the expected value
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
|
|
// 5ms @ 200MHz = 1 million samples
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
if (_regs->misc_ins_reg.read(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) {
|
|
err_code += _regs->misc_ins_reg.get(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR);
|
|
} else {
|
|
err_code += 100; // Increment error code by 100 to indicate no lock
|
|
}
|
|
|
|
// -- Test Q Channel --
|
|
// Put ADC in ramp test mode. Tie the other channel to all ones.
|
|
_adc->set_test_word("ones", "ramp");
|
|
// Turn on the pattern checker in the FPGA. It will lock when it sees a zero
|
|
// and count deviations from the expected value
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1);
|
|
// 5ms @ 200MHz = 1 million samples
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
if (_regs->misc_ins_reg.read(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) {
|
|
err_code += _regs->misc_ins_reg.get(
|
|
radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR);
|
|
} else {
|
|
err_code += 100; // Increment error code by 100 to indicate no lock
|
|
}
|
|
|
|
if (err_code == 0) {
|
|
if (win_start == -1) { // This is the first window
|
|
win_start = dly_tap;
|
|
win_stop = dly_tap;
|
|
} else { // We are extending the window
|
|
win_stop = dly_tap;
|
|
}
|
|
} else {
|
|
if (win_start != -1) { // A valid window turned invalid
|
|
if (win_stop - win_start >= MIN_WINDOW_LEN) {
|
|
break; // Valid window found
|
|
} else {
|
|
win_start = -1; // Reset window
|
|
}
|
|
}
|
|
}
|
|
// UHD_LOGGER_INFO("X300 RADIO") << (boost::format("CapTap=%d, Error=%d") %
|
|
// dly_tap % err_code);
|
|
}
|
|
|
|
// Retry the self-cal if it fails
|
|
if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN)
|
|
&& iter < NUM_RETRIES /*not last iteration*/) {
|
|
win_start = -1;
|
|
win_stop = -1;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
_adc->set_test_word("normal", "normal");
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0);
|
|
|
|
if (win_start == -1) {
|
|
throw uhd::runtime_error(
|
|
"self_cal_adc_capture_delay: Self calibration failed. Convergence error.");
|
|
}
|
|
|
|
if (win_stop - win_start < MIN_WINDOW_LEN) {
|
|
throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. "
|
|
"Valid window too narrow.");
|
|
}
|
|
|
|
uint32_t ideal_tap = (win_stop + win_start) / 2;
|
|
_regs->misc_outs_reg.write(
|
|
radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap);
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1);
|
|
_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0);
|
|
|
|
if (print_status) {
|
|
double tap_delay = (1.0e12 / _radio_clk_rate) / (2 * 32); // in ps
|
|
UHD_LOGGER_INFO("X300 RADIO")
|
|
<< boost::format("ADC capture delay self-cal done (Tap=%d, Window=%d, "
|
|
"TapDelay=%.3fps, Iter=%d)")
|
|
% ideal_tap % (win_stop - win_start) % tap_delay % iter;
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::_check_adc(const uint32_t val)
|
|
{
|
|
// Wait for previous control transaction to flush
|
|
user_reg_read64(regs::RB_TEST);
|
|
// Wait for ADC test pattern to propagate
|
|
std::this_thread::sleep_for(std::chrono::microseconds(5));
|
|
// Read value of RX readback register and verify
|
|
uint32_t adc_rb = static_cast<uint32_t>(user_reg_read64(regs::RB_TEST) >> 32);
|
|
adc_rb ^= 0xfffc0000; // adapt for I inversion in FPGA
|
|
if (val != adc_rb) {
|
|
throw uhd::runtime_error(
|
|
(boost::format("ADC self-test failed for %s. (Exp=0x%x, Got=0x%x)")
|
|
% unique_id() % val % adc_rb)
|
|
.str());
|
|
}
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::_set_db_eeprom(
|
|
i2c_iface::sptr i2c, const size_t addr, const uhd::usrp::dboard_eeprom_t& db_eeprom)
|
|
{
|
|
db_eeprom.store(*i2c, addr);
|
|
_db_eeproms[addr] = db_eeprom;
|
|
}
|
|
|
|
void x300_radio_ctrl_impl::_set_command_time(const time_spec_t& spec, const size_t port)
|
|
{
|
|
set_fe_cmd_time(spec, port);
|
|
}
|
|
/****************************************************************************
|
|
* Helpers
|
|
***************************************************************************/
|
|
bool x300_radio_ctrl_impl::check_radio_config()
|
|
{
|
|
UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::check_radio_config() ";
|
|
const fs_path rx_fe_path = fs_path("dboards" / _radio_slot / "rx_frontends");
|
|
for (size_t chan = 0; chan < _num_rx_channels; chan++) {
|
|
if (_tree->exists(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) {
|
|
const bool chan_active = _is_streamer_active(uhd::RX_DIRECTION, chan);
|
|
if (chan_active) {
|
|
_tree
|
|
->access<bool>(
|
|
rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")
|
|
.set(chan_active);
|
|
}
|
|
}
|
|
}
|
|
|
|
const fs_path tx_fe_path = fs_path("dboards" / _radio_slot / "tx_frontends");
|
|
for (size_t chan = 0; chan < _num_tx_channels; chan++) {
|
|
if (_tree->exists(tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled")) {
|
|
const bool chan_active = _is_streamer_active(uhd::TX_DIRECTION, chan);
|
|
if (chan_active) {
|
|
_tree
|
|
->access<bool>(
|
|
tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled")
|
|
.set(chan_active);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Register block
|
|
***************************************************************************/
|
|
UHD_RFNOC_BLOCK_REGISTER(x300_radio_ctrl, "X300Radio");
|