host: test: Add HBX radio block test

To be similar as ZBX and FBX, this adds a radio block test that checks
for setting frequency, the gain setting (via API and via properties),
gain profiles, power API and the IQ_DC compensation value processing.
This commit is contained in:
Martin Anderseck 2026-03-18 18:18:18 +01:00 committed by mkoop-ni
parent 0455399ad8
commit 1fa8b10720
4 changed files with 1340 additions and 0 deletions

View File

@ -537,6 +537,55 @@ IF(ENABLE_X400)
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/rpclib/include
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/flatbuffers/include
)
UHD_ADD_RFNOC_BLOCK_NONAPI_TEST(
TARGET hbx_radio_block_test.cpp
EXTRA_SOURCES
${UHD_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/ltc5594.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/admv1320.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/admv1420.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/pwr_cal_mgr.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_gpio_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_radio_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/adc_self_calibration.cpp
${UHD_SOURCE_DIR}/lib/rfnoc/complex_gain_iface.cpp
${UHD_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp
${UHD_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
${UHD_SOURCE_DIR}/lib/usrp/mpmd/mpmd_mb_controller.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/complex_gain_3000.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_expert.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/fbx/fbx_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_lo_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_demod_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_admv1320_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_admv1420_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_lo_pd.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_expert.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/hbx/hbx_cpld_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/common/apply_corrections.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
${UHD_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp
${UHD_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/gpio_atr_3000.cpp
${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
${UHD_SOURCE_DIR}/lib/usrp/x400/x400_internal_sync.cpp
${UHD_SOURCE_DIR}/lib/utils/prefs.cpp
${UHD_SOURCE_DIR}/lib/utils/pathslib.cpp
${UHD_SOURCE_DIR}/lib/utils/paths.cpp
${UHD_SOURCE_DIR}/lib/utils/config_parser.cpp
$<TARGET_OBJECTS:uhd_rpclib>
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/rpclib/include
INCLUDE_DIRS ${UHD_SOURCE_DIR}/lib/deps/flatbuffers/include
)
endif(NOT APPLE)
UHD_ADD_NONAPI_TEST(

View File

@ -0,0 +1,565 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "hbx_radio_mock.hpp"
#include "x4xx_hbx_mpm_mock.hpp"
#include <uhd/cal/database.hpp>
#include <uhd/cal/pwr_cal.hpp>
#include <uhd/rfnoc/detail/graph.hpp>
#include <uhd/rfnoc/mock_block.hpp>
#include <uhd/rfnoc/mock_nodes.hpp>
#include <uhd/types/iq_dc_cal_coeffs.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/math.hpp>
#include <uhd/utils/paths.hpp>
#include <boost/test/unit_test.hpp>
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <map>
#include <thread>
using namespace uhd;
using namespace uhd::rfnoc;
using namespace uhd::rfnoc::test;
using namespace std::chrono_literals;
using namespace uhd::usrp::hbx;
using namespace uhd::experts;
namespace {
std::string _sanitize_antenna_name(std::string antenna)
{
std::replace(antenna.begin(), antenna.end(), '/', '+');
std::transform(antenna.begin(), antenna.end(), antenna.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return antenna;
}
void _seed_hbx_mock_power_cal_data(x400_hbx_radio_fixture& fixture)
{
namespace fs = std::filesystem;
static bool seeded = false;
if (seeded) {
return;
}
const fs::path cal_path = fs::path(uhd::get_tmp_path()) / "UHD_HBX_MOCK_CAL";
std::error_code ec;
fs::create_directories(cal_path, ec);
if (ec) {
BOOST_FAIL("Failed to create temporary cal path: " << cal_path.string());
}
#ifdef UHD_PLATFORM_WIN32
const std::string putenv_str = std::string("UHD_CAL_DATA_PATH=") + cal_path.string();
_putenv(putenv_str.c_str());
#else
setenv("UHD_CAL_DATA_PATH", cal_path.string().c_str(), 1);
#endif
const std::string serial = "HBX_MOCK_NO_CAL#HBX";
const std::string tx_key =
"x4xx_pwr_hbx_tx_0_"
+ _sanitize_antenna_name(fixture.test_radio->get_tx_antenna(0));
const std::string rx_key =
"x4xx_pwr_hbx_rx_0_"
+ _sanitize_antenna_name(fixture.test_radio->get_rx_antenna(0));
const auto make_cal_blob = [serial]() {
auto cal_data =
uhd::usrp::cal::pwr_cal::make("HBX mock power cal", serial, 0x12345678);
std::map<double, double> gain_to_power;
for (int gain = 0; gain <= 80; gain++) {
gain_to_power[static_cast<double>(gain)] = static_cast<double>(gain) - 60.0;
}
for (const double freq : {1e9, 3e9, 6e9, 10e9}) {
cal_data->add_power_table(gain_to_power, -60.0, 20.0, freq);
}
return cal_data->serialize();
};
uhd::usrp::cal::database::write_cal_data(tx_key, serial, make_cal_blob());
uhd::usrp::cal::database::write_cal_data(rx_key, serial, make_cal_blob());
seeded = true;
}
} // namespace
BOOST_FIXTURE_TEST_CASE(hbx_api_freq_tx_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_API_TX_FREQUENCY_TEST";
const double ep = 10;
// HBX supports 10 MHz to 20 GHz
uhd::freq_range_t hbx_freq(HBX_MIN_FREQ, HBX_MAX_FREQ, 500e6);
// HBX has only 1 channel
for (const size_t chan : {0}) {
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->RETURN)\n");
for (double iter = hbx_freq.start(); iter <= hbx_freq.stop();
iter += hbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
const double freq = test_radio->set_tx_frequency(iter, chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->GET)\n");
for (double iter = hbx_freq.start(); iter <= hbx_freq.stop();
iter += hbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
test_radio->set_tx_frequency(iter, chan);
const double freq = test_radio->get_tx_frequency(chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_api_freq_rx_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_API_RX_FREQUENCY_TEST";
const double ep = 10;
// HBX supports 10 MHz to 20 GHz
uhd::freq_range_t hbx_freq(HBX_MIN_FREQ, HBX_MAX_FREQ, 500e6);
// HBX has only 1 channel
for (const size_t chan : {0}) {
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->RETURN)\n");
for (double iter = hbx_freq.start(); iter <= hbx_freq.stop();
iter += hbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
const double freq = test_radio->set_rx_frequency(iter, chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->GET)\n");
for (double iter = hbx_freq.start(); iter <= hbx_freq.stop();
iter += hbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
test_radio->set_rx_frequency(iter, chan);
const double freq = test_radio->get_rx_frequency(chan);
BOOST_REQUIRE(abs(iter - freq) < ep);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_frequency_test, x400_hbx_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "HBX_FREQUENCY_TEST";
const double ep = 10;
// HBX supports 10 MHz to 20 GHz
uhd::freq_range_t hbx_freq(HBX_MIN_FREQ, HBX_MAX_FREQ, 500e6);
for (const auto& fe_path : {
fs_path("dboard/tx_frontends/0"),
fs_path("dboard/rx_frontends/0"),
}) {
UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQ CHANGE\n");
for (double iter = hbx_freq.start(); iter <= hbx_freq.stop();
iter += hbx_freq.step()) {
UHD_LOG_INFO(log, "Testing freq: " << iter);
tree->access<double>(fe_path / "freq").set(iter);
const double ret_value = tree->access<double>(fe_path / "freq").get();
BOOST_REQUIRE(abs(iter - ret_value) < ep);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_api_tx_gain_test, x400_hbx_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "HBX_API_TX_GAIN_TEST";
const auto gain_range = test_radio->get_tx_gain_range(0);
uhd::gain_range_t hbx_gain(gain_range.start(), gain_range.stop(), gain_range.step());
for (const size_t chan : {0}) {
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->RETURN)\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
const double ret_gain = test_radio->set_tx_gain(iter, chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->GET)\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
test_radio->set_tx_gain(iter, chan);
const double ret_gain = test_radio->get_tx_gain(chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_api_rx_gain_test, x400_hbx_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "HBX_API_RX_GAIN_TEST";
const auto gain_range = test_radio->get_rx_gain_range(0);
uhd::gain_range_t hbx_gain(gain_range.start(), gain_range.stop(), gain_range.step());
for (const size_t chan : {0}) {
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->RETURN)\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
const double ret_gain = test_radio->set_rx_gain(iter, chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->GET)\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
test_radio->set_rx_gain(iter, chan);
const double ret_gain = test_radio->get_rx_gain(chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_tx_power_api, x400_hbx_radio_fixture)
{
_seed_hbx_mock_power_cal_data(*this);
const std::string log = "HBX_TX_POWER_TRACKING_TEST";
const size_t chan = 0;
const auto gain_range = test_radio->get_tx_gain_range(chan);
const double tx_given_gain = (gain_range.start() + gain_range.stop()) / 2.0;
constexpr double tx_given_power = -30.0;
UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " POWER TRACKING API\n");
BOOST_REQUIRE_EQUAL(test_radio->set_tx_gain(tx_given_gain, chan), tx_given_gain);
for (const double freq : {1e9, 3e9, 6e9, 10e9}) {
BOOST_REQUIRE_NO_THROW(test_radio->set_tx_power_reference(tx_given_power, chan));
test_radio->set_tx_frequency(freq, chan);
const double actual_power = test_radio->get_tx_power_reference(chan);
const double pow_diff = std::abs(tx_given_power - actual_power);
BOOST_CHECK_MESSAGE(pow_diff < 3.0,
"TX power differential is too large ("
<< pow_diff << "): expected close to " << tx_given_power << " actual "
<< actual_power << " frequency " << (freq / 1e6) << " MHz");
BOOST_REQUIRE_EQUAL(test_radio->set_tx_gain(tx_given_gain, chan), tx_given_gain);
}
}
BOOST_FIXTURE_TEST_CASE(hbx_rx_power_api, x400_hbx_radio_fixture)
{
_seed_hbx_mock_power_cal_data(*this);
const std::string log = "HBX_RX_POWER_TRACKING_TEST";
const size_t chan = 0;
const auto gain_range = test_radio->get_rx_gain_range(chan);
const double rx_given_gain = (gain_range.start() + gain_range.stop()) / 2.0;
constexpr double rx_given_power = -30.0;
UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " POWER TRACKING API\n");
BOOST_REQUIRE_EQUAL(test_radio->set_rx_gain(rx_given_gain, chan), rx_given_gain);
for (const double freq : {1e9, 3e9, 6e9, 10e9}) {
BOOST_REQUIRE_NO_THROW(test_radio->set_rx_power_reference(rx_given_power, chan));
test_radio->set_rx_frequency(freq, chan);
const double actual_power = test_radio->get_rx_power_reference(chan);
const double pow_diff = std::abs(rx_given_power - actual_power);
BOOST_CHECK_MESSAGE(pow_diff < 3.0,
"RX power differential is too large ("
<< pow_diff << "): expected close to " << rx_given_power << " actual "
<< actual_power << " frequency " << (freq / 1e6) << " MHz");
BOOST_REQUIRE_EQUAL(test_radio->set_rx_gain(rx_given_gain, chan), rx_given_gain);
}
}
BOOST_FIXTURE_TEST_CASE(hbx_api_tx_gain_stage_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_API_TX_GAIN_STAGE_TEST";
for (const size_t chan : {0}) {
test_radio->set_tx_gain_profile(HBX_GAIN_PROFILE_MANUAL, chan);
UHD_LOG_INFO(
log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
for (const auto& gain_stage : HBX_TX_GAIN_STAGES_MANUAL) {
const auto gain_range = test_radio->get_tx_gain_range(gain_stage, chan);
const double prime_gain = (gain_range.start() == gain_range.stop())
? gain_range.start()
: gain_range.stop();
test_radio->set_tx_gain(prime_gain, gain_stage, chan);
if (gain_range.step() == 0.0) {
const double ret_gain =
test_radio->set_tx_gain(gain_range.start(), gain_stage, chan);
BOOST_CHECK_EQUAL(gain_range.start(), ret_gain);
continue;
}
for (double iter = gain_range.start(); iter <= gain_range.stop();
iter += gain_range.step()) {
UHD_LOG_INFO(log, "Testing " << gain_stage << ": " << iter);
const double ret_gain = test_radio->set_tx_gain(iter, gain_stage, chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_api_rx_gain_stage_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_API_RX_GAIN_STAGE_TEST";
for (const size_t chan : {0}) {
test_radio->set_rx_gain_profile(HBX_GAIN_PROFILE_MANUAL, chan);
UHD_LOG_INFO(
log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
for (const auto& gain_stage : HBX_RX_GAIN_STAGES_MANUAL) {
const auto gain_range = test_radio->get_rx_gain_range(gain_stage, chan);
const double prime_gain = (gain_range.start() == gain_range.stop())
? gain_range.start()
: gain_range.stop();
test_radio->set_rx_gain(prime_gain, gain_stage, chan);
if (gain_range.step() == 0.0) {
const double ret_gain =
test_radio->set_rx_gain(gain_range.start(), gain_stage, chan);
BOOST_CHECK_EQUAL(gain_range.start(), ret_gain);
continue;
}
for (double iter = gain_range.start(); iter <= gain_range.stop();
iter += gain_range.step()) {
UHD_LOG_INFO(log, "Testing " << gain_stage << ": " << iter);
const double ret_gain = test_radio->set_rx_gain(iter, gain_stage, chan);
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_tx_gain_test, x400_hbx_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "HBX_TX_GAIN_TEST";
const auto gain_range = test_radio->get_tx_gain_range(0);
uhd::gain_range_t hbx_gain(gain_range.start(), gain_range.stop(), gain_range.step());
for (const auto& fe_path : {fs_path("dboard/tx_frontends/0")}) {
UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
const auto gain_path = fe_path / "gains" / HBX_GAIN_STAGE_ALL / "value";
tree->access<double>(gain_path).set(iter);
const double ret_gain = tree->access<double>(gain_path).get();
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_rx_gain_test, x400_hbx_radio_fixture)
{
auto tree = test_radio->get_tree();
const std::string log = "HBX_RX_GAIN_TEST";
const auto gain_range = test_radio->get_rx_gain_range(0);
uhd::gain_range_t hbx_gain(gain_range.start(), gain_range.stop(), gain_range.step());
for (const auto& fe_path : {fs_path("dboard/rx_frontends/0")}) {
UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
for (double iter = hbx_gain.start(); iter <= hbx_gain.stop();
iter += hbx_gain.step()) {
UHD_LOG_INFO(log, "Testing gain: " << iter);
const auto gain_path = fe_path / "gains" / HBX_GAIN_STAGE_ALL / "value";
tree->access<double>(gain_path).set(iter);
const double ret_gain = tree->access<double>(gain_path).get();
BOOST_CHECK_EQUAL(iter, ret_gain);
}
}
}
BOOST_FIXTURE_TEST_CASE(hbx_rx_antenna_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_RX_ANTENNA_TEST";
const size_t chan = 0; // HBX has only 1 channel
UHD_LOG_INFO(log, "BEGIN TEST: get available RX antennas\n");
auto available_antennas = test_radio->get_rx_antennas(chan);
UHD_LOG_INFO(log, "Available RX antennas: ");
for (const auto& ant : available_antennas) {
UHD_LOG_INFO(log, " " << ant);
}
// Test that setting and getting the antennas works
for (const std::string& ant : available_antennas) {
UHD_LOG_INFO(log, "Testing antenna: " << ant);
test_radio->set_rx_antenna(ant, chan);
BOOST_REQUIRE_EQUAL(ant, test_radio->get_rx_antenna(chan));
}
}
BOOST_FIXTURE_TEST_CASE(hbx_tx_antenna_test, x400_hbx_radio_fixture)
{
const std::string log = "HBX_TX_ANTENNA_TEST";
const size_t chan = 0; // HBX has only 1 channel
UHD_LOG_INFO(log, "BEGIN TEST: get available TX antennas\n");
auto available_antennas = test_radio->get_tx_antennas(chan);
UHD_LOG_INFO(log, "Available TX antennas: ");
for (const auto& ant : available_antennas) {
UHD_LOG_INFO(log, " " << ant);
}
auto prev_ant = test_radio->get_tx_antenna(chan);
for (const std::string& ant : available_antennas) {
UHD_LOG_INFO(log, "Testing antenna: " << ant);
test_radio->set_tx_antenna(ant, chan);
BOOST_REQUIRE_EQUAL(ant, test_radio->get_tx_antenna(chan));
prev_ant = ant;
}
}
/******************************************************************************
* RFNoC Graph Test
*
* This test case ensures that the Radio Block can be added to an RFNoC graph.
*****************************************************************************/
BOOST_FIXTURE_TEST_CASE(hbx_radio_test_graph, x400_hbx_radio_fixture)
{
detail::graph_t graph{};
detail::graph_t::graph_edge_t edge_port_info0;
edge_port_info0.src_port = 0;
edge_port_info0.dst_port = 0;
edge_port_info0.edge = detail::graph_t::graph_edge_t::DYNAMIC;
mock_radio_node_t mock_radio_block{0};
mock_terminator_t mock_sink_term(1, {}, "MOCK_SINK");
mock_terminator_t mock_source_term(1, {}, "MOCK_SOURCE");
UHD_LOG_INFO("TEST", "Priming mock block properties");
node_accessor.init_props(&mock_radio_block);
mock_source_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::OUTPUT_EDGE, 0});
mock_sink_term.set_edge_property<std::string>(
"type", "sc16", {res_source_info::INPUT_EDGE, 0});
UHD_LOG_INFO("TEST", "Creating graph...");
graph.connect(&mock_source_term, test_radio.get(), edge_port_info0);
graph.connect(test_radio.get(), &mock_sink_term, edge_port_info0);
UHD_LOG_INFO("TEST", "Committing graph...");
graph.commit();
UHD_LOG_INFO("TEST", "Commit complete.");
}
// Helper lambdas that replicate the encoding math from hbx_iq_dc_correction_expert.
// coeff_to_fixed: convert a double coefficient to the COEFFS_FRAC_BITS fixed-point
// representation used by the RF core (COEFF_WIDTH bits wide).
// iq_to_dc_offset: pack I/Q DC offset into a single 32-bit register value.
// Both lambdas match the formulas in hbx_expert.cpp exactly.
//
// Note: _rfdc_dc_conv_offset is {0,0} for TX and {0,0} for RX in the mock
// (get_cal_coefs returns a single zero, giving i_avg=q_avg=0.0).
namespace {
constexpr auto coeff_to_fixed = [](double c) -> uint32_t {
return static_cast<uint32_t>(c * (1u << COEFFS_FRAC_BITS))
& ((1u << COEFF_WIDTH) - 1u);
};
constexpr auto iq_to_dc_offset = [](std::complex<double> dc) -> uint32_t {
// rfdc_dc_conv_offset is {0,0} in the mock, so no subtraction needed.
const int32_t i_s = static_cast<int32_t>(std::lrint(dc.real() * (1 << 15)));
const int32_t q_s = static_cast<int32_t>(std::lrint(dc.imag() * (1 << 15)));
return (static_cast<uint32_t>(static_cast<uint16_t>(q_s)) << 16)
| static_cast<uint32_t>(static_cast<uint16_t>(i_s));
};
} // namespace
BOOST_FIXTURE_TEST_CASE(hbx_tx_iq_dc_compensation_test, x400_hbx_radio_fixture)
{
// Prime the coerced TX frequency to above HBX_SWITCH_FREQ so the expert
// uses the user-supplied coefficients instead of IQ_DC_DEFAULT_VALUES.
test_radio->set_tx_frequency(8e9, 0);
// Coefficients chosen so all expected register values are exact powers of 2,
// making the assertions easy to reason about.
uhd::iq_dc_cal_coeffs_t test_coeffs;
test_coeffs.scaling_coeff = 0.5; // iinline = 0x400000
test_coeffs.coeffs = {{0.25, 0.125}}; // icross = 0x200000, qinline = 0x100000
test_coeffs.group_delay = 4.0; // group_delay register = 4
test_coeffs.dc_offset = {0.0, 0.0}; // DC register = 0
// Writing the coefficients into the property tree immediately fires the
// IQ/DC correction expert (AUTO_RESOLVE_ON_WRITE), which encodes them and
// pokes the RF core registers.
test_radio->get_tree()
->access<uhd::iq_dc_cal_coeffs_t>("dboard/tx_frontends/0/iq_balance/coeffs/value")
.set(test_coeffs);
// Verify that each register received the correctly encoded value.
// The loop writes coefficients in reverse index order, so idx=0 is written last;
// write_log therefore holds the encoding of coeffs[0] for ICROSS and QINLINE.
const uint32_t iq_base = radio_control_impl::regmap::PERIPH_BASE + RF_CORE_ADDR_OFFSET
+ IQ_IMPAIRMENTS_TX_OFFSET;
const uint32_t dc_base =
radio_control_impl::regmap::PERIPH_BASE + RF_CORE_ADDR_OFFSET + DC_TX_OFFSET;
const auto& wl = reg_iface->write_log;
BOOST_CHECK_EQUAL(wl.at(iq_base + IINLINE_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.scaling_coeff));
BOOST_CHECK_EQUAL(wl.at(iq_base + ICROSS_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.coeffs[0].real()));
BOOST_CHECK_EQUAL(wl.at(iq_base + QINLINE_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.coeffs[0].imag()));
BOOST_CHECK_EQUAL(wl.at(iq_base + GROUP_DELAY_REG_OFFSET),
static_cast<uint32_t>(test_coeffs.group_delay));
BOOST_CHECK_EQUAL(
wl.at(dc_base + DC_VALUE_OFFSET), iq_to_dc_offset(test_coeffs.dc_offset));
}
BOOST_FIXTURE_TEST_CASE(hbx_rx_iq_dc_compensation_test, x400_hbx_radio_fixture)
{
test_radio->set_rx_frequency(8e9, 0);
uhd::iq_dc_cal_coeffs_t test_coeffs;
test_coeffs.scaling_coeff = 0.5;
test_coeffs.coeffs = {{0.25, 0.125}};
test_coeffs.group_delay = 4.0;
test_coeffs.dc_offset = {0.0, 0.0};
test_radio->get_tree()
->access<uhd::iq_dc_cal_coeffs_t>("dboard/rx_frontends/0/iq_balance/coeffs/value")
.set(test_coeffs);
const uint32_t iq_base = radio_control_impl::regmap::PERIPH_BASE + RF_CORE_ADDR_OFFSET
+ IQ_IMPAIRMENTS_RX_OFFSET;
const uint32_t dc_base =
radio_control_impl::regmap::PERIPH_BASE + RF_CORE_ADDR_OFFSET + DC_RX_OFFSET;
const auto& wl = reg_iface->write_log;
BOOST_CHECK_EQUAL(wl.at(iq_base + IINLINE_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.scaling_coeff));
BOOST_CHECK_EQUAL(wl.at(iq_base + ICROSS_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.coeffs[0].real()));
BOOST_CHECK_EQUAL(wl.at(iq_base + QINLINE_COEFF_REG_OFFSET),
coeff_to_fixed(test_coeffs.coeffs[0].imag()));
BOOST_CHECK_EQUAL(wl.at(iq_base + GROUP_DELAY_REG_OFFSET),
static_cast<uint32_t>(test_coeffs.group_delay));
BOOST_CHECK_EQUAL(
wl.at(dc_base + DC_VALUE_OFFSET), iq_to_dc_offset(test_coeffs.dc_offset));
}

View File

@ -0,0 +1,304 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include "../../lib/usrp/x400/x400_radio_control.hpp"
#include "x4xx_hbx_mpm_mock.hpp"
#include <uhd/rfnoc/mock_block.hpp>
#include <uhd/rfnoc/node_accessor.hpp>
#include <uhd/utils/log.hpp>
#include <array>
#include <chrono>
#include <iostream>
#include <map>
using namespace uhd;
using namespace uhd::rfnoc;
using namespace std::chrono_literals;
using namespace uhd::usrp::hbx;
using namespace uhd::experts;
namespace {
/* This class extends mock_reg_iface_t by adding a constructor that initializes
* some of the read memory to contain the memory size for the radio block.
*/
class hbx_radio_mock_reg_iface_t : public mock_reg_iface_t
{
// Start address of CPLD register space
static constexpr uint32_t cpld_offset = radio_control_impl::regmap::PERIPH_BASE;
// Start address of RFDC control register space
static constexpr uint32_t rfdc_offset =
radio_control_impl::regmap::PERIPH_BASE + 0x8000;
static constexpr uint32_t spi_offset =
radio_control_impl::regmap::PERIPH_BASE
+ 0xC000 /*DIO Window*/ + 0x2000 /*DIO Regmap*/;
static constexpr uint32_t gpio_offset =
radio_control_impl::regmap::PERIPH_BASE
+ 0xC000 /*DIO Window*/ + 0x1000 /*GPIO Regmap*/;
// In HBX, SPI info register encoding is:
// - bits [7:0] data width
// - bits [15:8] address width
// - bits [31:16] FIFO depth
static constexpr uint32_t spi_info_value = 16 /* data width */
| (8 /* address width */ << 8)
| (1 /* fifo depth */ << 16);
static constexpr uint32_t spi_status_ready = (1u << 31) | (1u << 30);
public:
hbx_radio_mock_reg_iface_t(size_t num_channels)
{
for (size_t chan = 0; chan < num_channels; chan++) {
const uint32_t reg_compat =
radio_control_impl::regmap::REG_COMPAT_NUM
+ chan * radio_control_impl::regmap::REG_CHAN_OFFSET;
read_memory[reg_compat] = (radio_control_impl::MINOR_COMPAT
| (radio_control_impl::MAJOR_COMPAT << 16));
}
read_memory[radio_control_impl::regmap::REG_RADIO_WIDTH] =
(32 /* bits per sample */ << 16) | 1 /* sample per clock */;
// Ensure that the SPI Status is always SPI_READY
read_memory[spi_offset + 0x18] |= 1 << 24;
// Initialize all HBX CPLD SPI engines that are used by the HBX dboard init
for (const uint32_t spi_info_offset :
{0x1000u, 0x1010u, 0x1020u, 0x1030u, 0x1040u, 0x1050u, 0x1060u}) {
read_memory[cpld_offset + spi_info_offset] = spi_info_value;
read_memory[cpld_offset + spi_info_offset + 0x8] = spi_status_ready;
_lo_muxout_to_lock[cpld_offset + spi_info_offset] = false;
}
// Setup the GPIO addresses
read_memory[gpio_offset + 0x4] = 0;
// Disable the complex gain feature
read_memory[radio_control_impl::regmap::RADIO_BASE_ADDR
+ radio_control_impl::regmap::REG_FEATURES_PRESENT] = 0;
// RFDC IQ/DC correction metadata used by HBX experts.
// Address 0x8A020 (PERIPH_BASE + 0xA020) is read during init to determine
// the available number of IQ correction coefficients.
// Addresses PERIPH_BASE + 0xA000 (TX) and +0xA020 (RX) are read during
// init to determine the available number of IQ correction coefficients.
read_memory[rfdc_offset + 0x2000] = 64;
read_memory[rfdc_offset + 0x2020] = 64;
}
void _poke_cb(uint32_t addr, uint32_t data, uhd::time_spec_t, bool) override
{
// Are we on the peripheral?
if (addr >= radio_control_impl::regmap::PERIPH_BASE) {
// handle all the periphs stuff that is not CPLD here
} else {
return;
}
// Are we poking the RFDC controls?
if (addr >= rfdc_offset) {
_poke_rfdc_cb(addr, data);
return;
}
// Handle CPLD register space
if (addr >= cpld_offset && addr < rfdc_offset) {
if (_is_spi_setup_addr(addr)) {
_poke_spi_setup(addr, data);
return;
}
read_memory[addr] = data;
return;
}
}
void _poke_rfdc_cb(const uint32_t addr, const uint32_t data)
{
read_memory[addr] |= data;
// Track the last value written to each address so tests can verify
// exact register contents without the OR-accumulation of read_memory.
write_log[addr] = data;
}
// Records the last value poked to each address in the RFDC register space.
std::map<uint32_t, uint32_t> write_log;
void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) override
{
// HBX radio code may read peripheral registers that have no explicit mock
// behavior. Default those reads to zero instead of throwing from
// mock_reg_iface_t.
if (addr >= cpld_offset && read_memory.count(addr) == 0) {
read_memory[addr] = 0;
}
}
bool _is_spi_setup_addr(const uint32_t addr) const
{
const uint32_t offset = addr - cpld_offset;
switch (offset) {
case 0x1004:
case 0x1014:
case 0x1024:
case 0x1034:
case 0x1044:
case 0x1054:
case 0x1064:
return true;
default:
return false;
}
}
void _poke_spi_setup(const uint32_t addr, const uint32_t data)
{
const uint32_t TX_LO_SPI_INFO = 0x1000;
const uint32_t RX_LO_SPI_INFO = 0x1010;
const uint32_t IQ_DEMOD_SPI_INFO = 0x1040;
const uint32_t spi_base = addr - 0x4;
const uint32_t spi_info = read_memory[spi_base];
uint32_t data_width = spi_info & 0xFF;
uint32_t addr_width = (spi_info >> 8) & 0xFF;
if (data_width == 0) {
data_width = 16;
}
if (addr_width == 0) {
addr_width = 8;
}
const bool start_xact = (data & (1u << 31)) != 0;
if (!start_xact) {
return;
}
const bool is_read = (data & (1u << 30)) != 0;
const uint32_t spi_addr = (data >> data_width) & ((1u << addr_width) - 1);
const uint16_t spi_wdata = static_cast<uint16_t>(data & ((1u << data_width) - 1));
if (!is_read) {
_spi_registers[spi_base][spi_addr] = spi_wdata;
// LMX lock-detect emulation. Register 0 bit 2 selects lock detect mode.
if ((spi_base == cpld_offset + TX_LO_SPI_INFO)
|| (spi_base == cpld_offset + RX_LO_SPI_INFO)) {
if (spi_addr == 0) {
_lo_muxout_to_lock[spi_base] = (spi_wdata & (1u << 2)) != 0;
}
}
}
uint16_t spi_rdata = 0;
{
auto& spi_regs = _spi_registers[spi_base];
const auto it = spi_regs.find(spi_addr);
if (it != spi_regs.end()) {
spi_rdata = it->second;
} else if (spi_base == cpld_offset + IQ_DEMOD_SPI_INFO
&& (spi_addr == 0x0C || spi_addr == 0x0D)) {
// LTC5594 reset defaults for HD2IY and HD2IX.
spi_rdata = 0x80;
}
}
if (is_read
&& ((spi_base == cpld_offset + TX_LO_SPI_INFO)
|| (spi_base == cpld_offset + RX_LO_SPI_INFO))
&& spi_addr == 125) {
// LMX2572 ID register readback expected by initialization code.
spi_rdata = 0x2288;
}
if (is_read
&& ((spi_base == cpld_offset + TX_LO_SPI_INFO)
|| (spi_base == cpld_offset + RX_LO_SPI_INFO))
&& _lo_muxout_to_lock[spi_base]) {
// LMX returns 0xFFFF when lock detect is routed to MUXOUT and PLL is locked.
spi_rdata = 0xFFFF;
}
read_memory[spi_base + 0x8] = spi_status_ready | (spi_addr << data_width)
| (spi_rdata & ((1u << data_width) - 1));
}
// SPI register model keyed by SPI base address and SPI register address
std::map<uint32_t, std::map<uint32_t, uint16_t>> _spi_registers;
std::map<uint32_t, bool> _lo_muxout_to_lock;
}; // class hbx_radio_mock_reg_iface_t
/*
* x400_hbx_radio_fixture is a class which is instantiated before each test
* case is run. It sets up the block container, mock register interface,
* and x400_radio_control object, all of which are accessible to the test
* case. The instance of the object is destroyed at the end of each test
* case.
*/
constexpr size_t DEFAULT_MTU = 8000;
//! Helper class to make sure we get the most logging. The logging level can be
// overridden using a special test-specific environment variable,
// `UHD_UNITTEST_LOG_LEVEL`.
struct uhd_log_enabler
{
uhd_log_enabler(uhd::log::severity_level level)
{
const char* env_p = std::getenv("UHD_UNITTEST_LOG_LEVEL");
if (env_p) {
auto parsed_level = uhd::log::parse_log_level_from_string(env_p);
if (parsed_level) {
level = *parsed_level;
} else {
std::cout << "Unable to parse UHD_UNITTEST_LOG_LEVEL " << env_p
<< std::endl;
}
}
std::cout << "Setting log level to " << level << "..." << std::endl;
uhd::log::set_log_level(level);
uhd::log::set_console_level(level);
std::this_thread::sleep_for(10ms);
}
};
struct x400_hbx_radio_fixture
{
x400_hbx_radio_fixture()
: ule(uhd::log::trace) // Note: When debugging this test, either set
// this to a lower level, or create a
// uhd_log_enabler in the test-under-test
, num_channels(uhd::usrp::hbx::HBX_MAX_NUM_CHANS)
, num_input_ports(num_channels)
, num_output_ports(num_channels)
, reg_iface(std::make_shared<hbx_radio_mock_reg_iface_t>(num_channels))
, rpcs(std::make_shared<uhd::test::x4xx_hbx_mock_rpc_server>(device_info))
, mbc(std::make_shared<mpmd_mb_controller>(rpcs, device_info))
, block_container(get_mock_block(RADIO_BLOCK,
num_channels,
num_channels,
device_info,
DEFAULT_MTU,
X400,
reg_iface,
mbc))
, test_radio(block_container.get_block<x400_radio_control_impl>())
{
node_accessor.init_props(test_radio.get());
}
~x400_hbx_radio_fixture() {}
// Must remain the first member so we make sure the log level is high
uhd_log_enabler ule;
const size_t num_channels;
const size_t num_input_ports;
const size_t num_output_ports;
uhd::device_addr_t device_info = uhd::device_addr_t("master_clock_rate=1.25e9");
std::shared_ptr<hbx_radio_mock_reg_iface_t> reg_iface;
std::shared_ptr<uhd::test::x4xx_hbx_mock_rpc_server> rpcs;
mpmd_mb_controller::sptr mbc;
mock_block_container block_container;
std::shared_ptr<x400_radio_control_impl> test_radio;
node_accessor_t node_accessor{};
};
} // namespace

View File

@ -0,0 +1,422 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#pragma once
#include <uhd/types/device_addr.hpp>
#include <uhdlib/usrp/common/rpc.hpp>
#include <uhdlib/usrp/dboard/hbx/hbx_constants.hpp>
#include <uhdlib/usrp/dboard/hbx/hbx_dboard.hpp>
#include <stdlib.h>
#include <boost/test/unit_test.hpp>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <vector>
using namespace uhd::usrp;
namespace uhd { namespace test {
namespace {
constexpr double DEFAULT_MCR = 1250e6;
}
//! Mock MPM server for X4xx/HBX
//
// This is a mock server that mimics an X4xx with an HBX daughterboard.
class x4xx_hbx_mock_rpc_server : public x400_rpc_iface,
public mpmd_rpc_iface,
public dboard_base_rpc_iface,
public hbx_rpc_iface,
public dio_rpc_iface
{
public:
x4xx_hbx_mock_rpc_server(const uhd::device_addr_t& device_info)
: _device_info(device_info)
{
}
uhd::rpc_client::sptr get_raw_rpc_client() override
{
UHD_THROW_INVALID_CODE_PATH();
}
/**************************************************************************
* RPC Call Mockups
*************************************************************************/
size_t get_num_timekeepers() override
{
return 1;
}
std::vector<std::string> get_mb_sensors() override
{
return {"ref_locked"};
}
std::vector<std::string> get_gpio_banks() override
{
return {"GPIO0", "GPIO1"};
}
bool supports_feature(const std::string& feature) override
{
return feature == "ref_clk_calibration";
}
std::vector<std::map<std::string, std::string>> get_dboard_info() override
{
return {{{"pid", std::to_string(uhd::usrp::hbx::HBX_PID)}}};
}
bool is_db_gpio_ifc_present(const size_t) override
{
return true;
}
void set_tick_period(const size_t, const uint64_t) override
{
// nop
}
double get_master_clock_rate() override
{
return _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
}
std::vector<std::string> get_sensors(const std::string&) override
{
return {};
}
std::map<std::string, std::string> get_sensor(
const std::string&, const std::string&, size_t) override
{
return {};
}
void set_cal_frozen(bool, size_t, size_t, size_t) override
{
// nop
}
std::vector<int> get_cal_frozen(size_t, size_t, size_t) override
{
return {};
}
void set_calibration_mode(size_t, size_t, size_t, std::string) override
{
// nop
}
std::vector<std::vector<double>> get_cal_coefs(
size_t, size_t, size_t, size_t) override
{
return {{0.0}};
}
std::map<std::string, std::vector<uint8_t>> get_db_eeprom(const size_t) override
{
return {{{"pid", s2u8("mock")}, {"serial", s2u8("HBX_MOCK_NO_CAL")}}};
}
double get_dboard_prc_rate() override
{
const double mcr = get_master_clock_rate();
static const std::map<double, double> prc_rate_map{
// One line per entry
{1250e6, 62.5e6},
{491.52e6, 61.44e6},
{245.76e6, 61.44e6},
{250e6, 62.5e6},
// End of entries
};
const auto it = prc_rate_map.find(mcr);
if (it != prc_rate_map.end()) {
return it->second;
}
// Fallback to an LMX2572-supported reference frequency
return 62.5e6;
}
double rfdc_set_nco_freq(const std::string& trx,
const size_t /*db_id*/,
const size_t chan,
const double freq,
const size_t /*ch_mode*/) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
BOOST_REQUIRE(chan < uhd::usrp::hbx::HBX_MAX_NUM_CHANS);
nco_freq[trx][chan] = freq;
return freq;
}
double rfdc_get_nco_freq(const std::string& trx,
const size_t /*db_id*/,
const size_t chan,
const size_t /*ch_mode*/) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
BOOST_REQUIRE(chan < uhd::usrp::hbx::HBX_MAX_NUM_CHANS);
if (nco_freq.find(trx) == nco_freq.end()
|| nco_freq.at(trx).find(chan) == nco_freq.at(trx).end()) {
return 0;
}
return nco_freq.at(trx).at(chan);
}
double get_dboard_sample_rate() override
{
const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
static const std::map<double, double> spll_map{
// One line per entry
{1250e6, 2.5e9},
{491.52e6, 3932160000.0},
{245.76e6, 1966080000.0},
{250e6, 2e9},
// End of entries
};
return spll_map.at(mcr);
}
std::vector<std::map<std::string, std::string>> pop_host_tasks(
const std::string&) override
{
return {};
}
void enable_iq_swap(
const bool is_band_inverted, const std::string& trx, const size_t chan) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
BOOST_REQUIRE(chan < uhd::usrp::hbx::HBX_MAX_NUM_CHANS);
iq_swap[trx][chan] = is_band_inverted;
}
void set_data_path(const size_t mode, const std::string& trx) override
{
BOOST_REQUIRE(trx == "rx" || trx == "tx");
data_path[trx] = mode;
}
std::vector<std::string> get_gpio_srcs(const std::string& /*bank*/) override
{
return {};
}
uint64_t get_timekeeper_time(size_t /*timekeeper_idx*/, bool /*last_pps*/) override
{
return 0;
}
void set_timekeeper_time(
size_t /*timekeeper_idx*/, uint64_t /*ticks*/, bool /*last_pps*/) override
{
// nop
}
std::string get_time_source() override
{
return "";
}
std::vector<std::string> get_time_sources() override
{
return {};
}
std::string get_clock_source() override
{
return "";
}
std::vector<std::string> get_clock_sources() override
{
return {};
}
std::map<std::string, std::string> get_sync_source() override
{
return {};
}
std::vector<std::map<std::string, std::string>> get_sync_sources() override
{
return {};
}
void set_clock_source_out(bool /*enb*/) override
{
// nop
}
void set_trigger_io(const std::string& /*direction*/) override
{
// nop
}
std::map<std::string, std::string> get_mb_eeprom() override
{
return {};
}
std::vector<std::string> get_gpio_src(const std::string& /*bank*/) override
{
return {};
}
void set_gpio_src(
const std::string& /*bank*/, const std::vector<std::string>& /*src*/) override
{
// nop
}
void set_ref_clk_tuning_word(uint32_t /*tuning_word*/) override
{
// nop
}
uint32_t get_ref_clk_tuning_word() override
{
return 0;
}
void store_ref_clk_tuning_word(uint32_t /*tuning_word*/) override
{
// nop
}
sensor_value_t::sensor_map_t get_mb_sensor(const std::string& /*sensor*/) override
{
return {};
}
void set_time_source(const std::string& /*source*/) override
{
// nop
}
void set_clock_source(const std::string& /*source*/) override
{
// nop
}
void set_sync_source(const std::map<std::string, std::string>& /*source*/) override
{
// nop
}
bool get_threshold_status(size_t /*db_number*/,
size_t /*chan*/,
size_t /*mode*/,
size_t /*threshold_block*/) override
{
return false;
}
void set_dac_mux_enable(
size_t /*db_number*/, size_t /*chan*/, int /*enable*/, size_t /*mode*/) override
{
// nop
}
void set_dac_mux_data(size_t /*i*/, size_t /*q*/) override
{
// nop
}
double get_spll_freq() override
{
return 0.0;
}
void setup_threshold(size_t /*db_number*/,
size_t /*chan*/,
size_t /*mix_mode*/,
size_t /*threshold_block*/,
const std::string& /*mode*/,
size_t /*delay*/,
size_t /*under*/,
size_t /*over*/) override
{
// nop
}
void restart_converter(const std::string& /*trx*/,
size_t /*block_count*/,
size_t /*chan*/,
size_t /*mixer_mode*/)
{
// nop
}
std::vector<std::string> dio_get_supported_voltage_levels(const std::string&) override
{
return {"OFF", "1V8", "2V5", "3V3"};
}
void dio_set_voltage_level(const std::string&, const std::string&) override
{
// nop
}
std::string dio_get_voltage_level(const std::string&) override
{
return "3V3";
}
void dio_set_port_mapping(const std::string&) override
{
// nop
}
void dio_set_pin_directions(const std::string&, uint32_t) override
{
// nop
}
void dio_set_external_power(const std::string&, bool) override
{
// nop
}
std::string dio_get_external_power_state(const std::string&) override
{
return "OFF";
}
std::map<std::string, std::string> synchronize(
const std::map<std::string, std::string>&, bool) override
{
return {};
}
std::map<std::string, std::string> aggregate_sync_data(
const std::list<std::map<std::string, std::string>>& collated_sync_data) override
{
return collated_sync_data.front();
}
///////////////////////////////////////////////////////////////////////////
// Public attributes for easy inspection
std::map<std::string, std::map<size_t, double>> nco_freq;
std::map<std::string, std::map<size_t, bool>> iq_swap;
std::map<std::string, size_t> data_path;
///////////////////////////////////////////////////////////////////////////
private:
uhd::device_addr_t _device_info;
static std::vector<uint8_t> s2u8(const std::string& s)
{
return std::vector<uint8_t>(s.begin(), s.end());
}
};
}} // namespace uhd::test