aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /host/tests
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'host/tests')
-rw-r--r--host/tests/CMakeLists.txt58
-rw-r--r--host/tests/cal_data_dsa_test.cpp91
-rw-r--r--host/tests/devtest/CMakeLists.txt3
-rw-r--r--host/tests/devtest/devtest_x4x0.py75
-rwxr-xr-xhost/tests/devtest/multi_usrp_test.py19
-rw-r--r--host/tests/lmx2572_test.cpp150
-rw-r--r--host/tests/mb_controller_test.cpp4
-rw-r--r--host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp1222
-rw-r--r--host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp337
-rw-r--r--host/tests/streaming_performance/run_X4xx_max_rate_tests.py193
-rw-r--r--host/tests/x400_rfdc_control_test.cpp52
-rw-r--r--host/tests/zbx_cpld_test.cpp135
12 files changed, 2336 insertions, 3 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 980f87d75..9b27e03c1 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -29,6 +29,7 @@ set(test_sources
cal_data_iq_test.cpp
cal_data_gain_pwr_test.cpp
chdr_parse_test.cpp
+ cal_data_dsa_test.cpp
chdr_test.cpp
constrained_device_args_test.cpp
convert_test.cpp
@@ -42,7 +43,6 @@ set(test_sources
isatty_test.cpp
log_test.cpp
math_test.cpp
- mb_controller_test.cpp
narrow_cast_test.cpp
property_test.cpp
ranges_test.cpp
@@ -150,6 +150,8 @@ macro(UHD_ADD_RFNOC_BLOCK_TEST)
EXTRA_SOURCES
${test_EXTRA_SOURCES}
${CMAKE_SOURCE_DIR}/lib/rfnoc/graph.cpp
+ INCLUDE_DIRS
+ ${test_INCLUDE_DIRS}
)
endmacro(UHD_ADD_RFNOC_BLOCK_TEST)
@@ -275,6 +277,24 @@ UHD_ADD_NONAPI_TEST(
${CMAKE_SOURCE_DIR}/lib/rfnoc/client_zero.cpp
)
+UHD_ADD_NONAPI_TEST(
+ TARGET zbx_cpld_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}/lib/ic_reg_maps
+)
+
+UHD_ADD_NONAPI_TEST(
+ TARGET lmx2572_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ INCLUDE_DIRS
+ ${CMAKE_BINARY_DIR}/lib/ic_reg_maps
+)
+
set_source_files_properties(
${CMAKE_SOURCE_DIR}/lib/utils/system_time.cpp
PROPERTIES COMPILE_DEFINITIONS
@@ -329,7 +349,6 @@ UHD_ADD_RFNOC_BLOCK_TEST(
TARGET siggen_block_test.cpp
)
-
UHD_ADD_RFNOC_BLOCK_TEST(
TARGET split_stream_block_test.cpp
)
@@ -346,6 +365,35 @@ UHD_ADD_RFNOC_BLOCK_TEST(
TARGET window_block_test.cpp
)
+UHD_ADD_RFNOC_BLOCK_TEST(
+ TARGET x4xx_radio_block_test.cpp
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/lmx2572.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/common/pwr_cal_mgr.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_radio_control.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/adc_self_calibration.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/mpmd/mpmd_mb_controller.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_dboard_init.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_lo_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_cpld_ctrl.cpp
+ ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/zbx/zbx_expert.cpp
+ ${CMAKE_SOURCE_DIR}/lib/utils/compat_check.cpp
+ ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
+ $<TARGET_OBJECTS:uhd_rpclib>
+ INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/rpclib/include
+ INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/lib/deps/flatbuffers/include
+)
+
+UHD_ADD_NONAPI_TEST(
+ TARGET "mb_controller_test.cpp"
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/features/discoverable_feature_registry.cpp
+)
+
UHD_ADD_NONAPI_TEST(
TARGET "transport_test.cpp"
EXTRA_SOURCES
@@ -382,6 +430,12 @@ UHD_ADD_NONAPI_TEST(
${CMAKE_SOURCE_DIR}/lib/rfnoc/rf_control/gain_profile.cpp
)
+UHD_ADD_NONAPI_TEST(
+ TARGET "x400_rfdc_control_test.cpp"
+ EXTRA_SOURCES
+ ${CMAKE_SOURCE_DIR}/lib/usrp/x400/x400_rfdc_control.cpp
+)
+
########################################################################
# demo of a loadable module
########################################################################
diff --git a/host/tests/cal_data_dsa_test.cpp b/host/tests/cal_data_dsa_test.cpp
new file mode 100644
index 000000000..d3c224f88
--- /dev/null
+++ b/host/tests/cal_data_dsa_test.cpp
@@ -0,0 +1,91 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/dsa_cal.hpp>
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+#include <fstream>
+
+using namespace uhd::usrp::cal;
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_api)
+{
+ const std::string name = "Mock Gain/Power Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+
+ auto dsa_data = zbx_tx_dsa_cal::make(name, serial, timestamp);
+ BOOST_CHECK_EQUAL(dsa_data->get_name(), name);
+ BOOST_CHECK_EQUAL(dsa_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(dsa_data->get_timestamp(), timestamp);
+
+ BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(0, 0), uhd::runtime_error);
+
+ std::array<std::array<uint32_t, 3>, 61> gains1{{{1, 2}, {3, 4}, {5, 6}}};
+ std::array<std::array<uint32_t, 3>, 61> gains2{{{7, 8}, {9, 0}, {1, 2}}};
+
+ dsa_data->add_frequency_band(1E9, "low", gains1);
+ dsa_data->add_frequency_band(4E9, "high", gains2);
+
+ auto expected = gains1[0];
+ auto calculated = dsa_data->get_dsa_setting(1E9, 0);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(1E8, 0);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ expected = gains1[1];
+ calculated = dsa_data->get_dsa_setting(1E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(1E8, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ expected = gains2[1];
+ calculated = dsa_data->get_dsa_setting(3E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ calculated = dsa_data->get_dsa_setting(4E9, 1);
+ BOOST_CHECK_EQUAL_COLLECTIONS(calculated.begin(), calculated.end(), expected.begin(), expected.end());
+
+ BOOST_REQUIRE_THROW(dsa_data->get_dsa_setting(5E9, 1), uhd::value_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_serdes)
+{
+ const std::string name = "Mock Gain/DSA Data";
+ const std::string serial = "FOOBAR";
+ const uint64_t timestamp = 0xCAFEBABE;
+ auto cal_data = zbx_tx_dsa_cal::make(name, serial, timestamp);
+
+ std::vector<double> freqs{2e9, 6e9};
+
+ int i = 0;
+ for (auto freq : freqs) {
+ std::array<std::array<uint32_t, 3>, 61> gains;
+ for (auto& gain : gains) {
+ for (auto& step : gain) {
+ step = i++;
+ }
+ }
+ cal_data->add_frequency_band(freq, std::to_string(freq), gains);
+ }
+
+ const auto serialized = cal_data->serialize();
+ BOOST_REQUIRE_THROW(container::make<zbx_rx_dsa_cal>(serialized), uhd::runtime_error);
+ auto des_cal_data = container::make<zbx_tx_dsa_cal>(serialized);
+ BOOST_CHECK_EQUAL(des_cal_data->get_name(), cal_data->get_name());
+ BOOST_CHECK_EQUAL(des_cal_data->get_serial(), cal_data->get_serial());
+ BOOST_CHECK_EQUAL(des_cal_data->get_timestamp(), cal_data->get_timestamp());
+}
+
+BOOST_AUTO_TEST_CASE(test_pwr_cal_des_fail)
+{
+ std::vector<uint8_t> not_actual_data(42, 23);
+
+ BOOST_REQUIRE_THROW(container::make<zbx_tx_dsa_cal>(not_actual_data), uhd::runtime_error);
+}
diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt
index e6cef17fe..3f09620bf 100644
--- a/host/tests/devtest/CMakeLists.txt
+++ b/host/tests/devtest/CMakeLists.txt
@@ -50,5 +50,8 @@ endif(ENABLE_N300)
if(ENABLE_E320)
ADD_DEVTEST("e320" "e3xx" "E32x")
endif(ENABLE_E320)
+if(ENABLE_X400)
+ ADD_DEVTEST("x4x0" "x4xx" "X4x0")
+endif(ENABLE_X400)
# Formatting
message(STATUS "")
diff --git a/host/tests/devtest/devtest_x4x0.py b/host/tests/devtest/devtest_x4x0.py
new file mode 100644
index 000000000..c0b8bf4aa
--- /dev/null
+++ b/host/tests/devtest/devtest_x4x0.py
@@ -0,0 +1,75 @@
+#
+# Copyright 2015 Ettus Research LLC
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Run device tests for the x4x0 series.
+"""
+
+# pylint: disable=wrong-import-position
+# pylint: disable=unused-import
+from benchmark_rate_test import uhd_benchmark_rate_test
+uhd_benchmark_rate_test.tests = {
+ 'mimo_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 500,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+ 'mimo_fast': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 4.096e6,
+ 'acceptable-underruns': 500,
+ 'tx_buffer': (0.1*12.288e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*12.288e6,
+ },
+ 'siso_chan0_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0',
+ 'rate': 1e6,
+ 'acceptable-underruns': 10,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+ 'siso_chan1_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 10,
+ 'tx_buffer': (0.1*1e6)+32e6*8*1/32, # 32 MB DRAM for each channel (32 bit OTW format),
+ 'rx_buffer': 0.1*1e6,
+ },
+}
+
+from tx_waveforms_test import uhd_tx_waveforms_test
+uhd_tx_waveforms_test.tests = {
+ 'chan0': {
+ 'chan': '0',
+ },
+ 'chan1': {
+ 'chan': '0',
+ },
+ 'both_chans': {
+ 'chan': '0,1',
+ },
+}
+
+from rx_samples_to_file_test import rx_samples_to_file_test
+from tx_bursts_test import uhd_tx_bursts_test
+from test_pps_test import uhd_test_pps_test
+
+# Enable these when GPIO API is enabled
+# from gpio_test import gpio_test
+# from bitbang_test import bitbang_test
+
+from list_sensors_test import list_sensors_test
+from python_api_test import uhd_python_api_test
diff --git a/host/tests/devtest/multi_usrp_test.py b/host/tests/devtest/multi_usrp_test.py
index b9add8381..943733ba9 100755
--- a/host/tests/devtest/multi_usrp_test.py
+++ b/host/tests/devtest/multi_usrp_test.py
@@ -754,6 +754,25 @@ def get_device_config(usrp_type, device_config_path=None):
'get_gpio_banks',
],
}
+ if usrp_type == 'x410':
+ return {
+ 'skip': [
+ # No AGC on ZBX
+ 'set_rx_agc',
+ # No IQ imbalance on ZBX
+ 'set_rx_iq_balance',
+ 'set_tx_iq_balance',
+ # No DC offset on ZBX
+ 'set_rx_dc_offset',
+ 'set_tx_dc_offset',
+ # No LO source control on ZBX
+ 'set_rx_lo_source',
+ 'set_tx_lo_source',
+ 'set_rx_lo_export_enabled',
+ 'set_tx_lo_export_enabled',
+ ],
+ 'clock_sources': ['internal', 'mboard'],
+ }
return {}
def dump_defaults(usrp_type):
diff --git a/host/tests/lmx2572_test.cpp b/host/tests/lmx2572_test.cpp
new file mode 100644
index 000000000..21ed1e7f5
--- /dev/null
+++ b/host/tests/lmx2572_test.cpp
@@ -0,0 +1,150 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "lmx2572_regs.hpp"
+#include <uhdlib/usrp/common/lmx2572.hpp>
+#include <boost/test/unit_test.hpp>
+#include <map>
+
+
+class lmx2572_mem
+{
+public:
+ lmx2572_mem()
+ {
+ // Copy silicone defaults into mem
+ for (uint8_t addr = 0; addr < regs.get_num_regs(); addr++) {
+ mem[addr] = regs.get_reg(addr);
+ }
+ }
+
+ void poke16(const uint8_t addr, const uint16_t data)
+ {
+ if (regs.get_ro_regs().count(addr)) {
+ throw uhd::runtime_error("Writing to RO reg!");
+ }
+ mem[addr] = data;
+ }
+
+ uint16_t peek16(const uint8_t addr)
+ {
+ return mem.at(addr);
+ }
+
+ lmx2572_regs_t regs;
+ std::map<uint8_t, uint16_t> mem;
+};
+
+
+BOOST_AUTO_TEST_CASE(lmx_init_test)
+{
+ auto mem = lmx2572_mem{};
+ auto lo = lmx2572_iface::make(
+ [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); },
+ [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); },
+ [](const uhd::time_spec_t&) {});
+ lo->reset();
+}
+
+void UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t> expected, std::map<uint8_t, uint16_t> actual)
+{
+ for (const auto& expected_r : expected) {
+ // Little hack so if this fails, we see all the info:
+ const std::string exp_str = "R" + std::to_string(expected_r.first)
+ + "==" + std::to_string(expected_r.second);
+ const std::string act_str = "R" + std::to_string(expected_r.first)
+ + "==" + std::to_string(actual.at(expected_r.first));
+ BOOST_CHECK_EQUAL(exp_str, act_str);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(lmx_sync_tune_test)
+{
+ auto mem = lmx2572_mem{};
+ auto lo = lmx2572_iface::make(
+ [&](const uint8_t addr, const uint16_t data) { mem.poke16(addr, data); },
+ [&](const uint8_t addr) -> uint16_t { return mem.peek16(addr); },
+ [](const uhd::time_spec_t&) {});
+ lo->reset();
+ // Mimick ZBX settings:
+ constexpr bool zbx_spur_dodging = false;
+ lo->set_sync_mode(true);
+ lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_A, true);
+ lo->set_output_enable(lmx2572_iface::output_t::RF_OUTPUT_B, false);
+ // Test Category 1A + SYNC:
+ lo->set_frequency(
+ 50 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ // These values are generated with TICS PRO. We don't check all the values,
+ // mainly the ones related to sync operation.
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0032}, // Lower bits of N-divider, integer part
+ {42, 0x0000}, // PLL_NUM upper
+ {43, 0x0000}, // PLL_NUM lower
+ },
+ mem.mem);
+ // Test max frequency just to test boundary conditions:
+ lo->set_frequency(
+ 100 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+
+ // Test Category 1B + SYNC:
+ // Will set CHDIV to 2.
+ lo->set_frequency(
+ 40 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0028},
+ {42, 0x0000},
+ {43, 0x0000},
+ },
+ mem.mem);
+
+ // Test Category 2 + SYNC:
+ // Will set CHDIV to 8.
+ lo->set_frequency(
+ 10 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0050},
+ {42, 0x0000},
+ {43, 0x0000},
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be off in this case, b/c we're using the SYNC pin
+ BOOST_CHECK_EQUAL(mem.mem[0] & (1 << 14), 0);
+
+ // Test Category 3 + SYNC:
+ // Will set CHDIV to 1.
+ lo->set_frequency(
+ 50.5 * 64e6, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {36, 0x0032},
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be on in this case
+ BOOST_CHECK(mem.mem[0] & (1 << 14));
+
+ // Will set CHDIV to 2.
+ lo->set_frequency(
+ 50.5 * 64e6 / 2, 64e6, zbx_spur_dodging);
+ lo->commit();
+ UHD_CHECK_REGMAP(
+ std::map<uint8_t, uint16_t>{
+ {11, 0xB028}, // PLL_R == 2. Note this is a ZBX-specific design choice.
+ {36, 0x0032}, // With PLL_R == 2, you would expect this to be 100, but it's
+ // only half that!
+ },
+ mem.mem);
+ // VCO_PHASE_SYNC_EN must be on in this case
+ BOOST_CHECK(mem.mem[0] & (1 << 14));
+}
diff --git a/host/tests/mb_controller_test.cpp b/host/tests/mb_controller_test.cpp
index 188dddecc..30b9f64f3 100644
--- a/host/tests/mb_controller_test.cpp
+++ b/host/tests/mb_controller_test.cpp
@@ -5,6 +5,7 @@
//
#include <uhd/rfnoc/mb_controller.hpp>
+#include <uhdlib/features/discoverable_feature_registry.hpp>
#include <boost/test/unit_test.hpp>
#include <iostream>
@@ -49,7 +50,8 @@ private:
}
};
-class mock_mb_controller : public mb_controller
+class mock_mb_controller : public mb_controller,
+ public ::uhd::features::discoverable_feature_registry
{
public:
mock_mb_controller()
diff --git a/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp
new file mode 100644
index 000000000..2f2e51d60
--- /dev/null
+++ b/host/tests/rfnoc_block_tests/x4xx_radio_block_test.cpp
@@ -0,0 +1,1222 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "../../lib/usrp/x400/x400_radio_control.hpp"
+#include "../rfnoc_graph_mock_nodes.hpp"
+#include "x4xx_zbx_mpm_mock.hpp"
+#include <uhd/rfnoc/actions.hpp>
+#include <uhd/rfnoc/defaults.hpp>
+#include <uhd/rfnoc/mock_block.hpp>
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhdlib/rfnoc/graph.hpp>
+#include <uhdlib/rfnoc/node_accessor.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_constants.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <math.h>
+#include <boost/test/unit_test.hpp>
+#include <chrono>
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+#include <thread>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+using namespace std::chrono_literals;
+using namespace uhd::usrp::zbx;
+using namespace uhd::experts;
+
+// Redeclare this here, since it's only defined outside of UHD_API
+noc_block_base::make_args_t::~make_args_t() = default;
+
+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 x4xx_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;
+
+public:
+ x4xx_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 */;
+ }
+
+ 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 on the CPLD?
+ if (addr >= cpld_offset && addr < rfdc_offset) {
+ _poke_cpld_cb(addr, data);
+ return;
+ }
+
+ // Are we poking the RFDC controls?
+ if (addr >= rfdc_offset) {
+ _poke_rfdc_cb(addr, data);
+ return;
+ }
+ }
+
+ void _poke_cpld_cb(const uint32_t addr, const uint32_t data)
+ {
+ switch (addr - cpld_offset) {
+ /// CURRENT_CONFIG_REG
+ case 0x1000:
+ // FIXME: We write to all regs during init
+ // BOOST_REQUIRE(false); // Not a write-register
+ break;
+ /// SW_CONFIG
+ case 0x1008: {
+ // This register is RW so update read_memory
+ read_memory[addr] = data;
+ // If we're in SW-defined mode, also update CURRENT_CONFIG_REG
+ uint32_t& rf_opt = read_memory[cpld_offset + 0x1004];
+ uint32_t& ccr = read_memory[cpld_offset + 0x1000];
+ // Check if RF0_OPTION is SW_DEFINED
+ if ((rf_opt & 0x00FF) == 0) {
+ ccr = (ccr & 0xFF00) | (data & 0x00FF);
+ }
+ // Check if RF1_OPTION is SW_DEFINED
+ if ((rf_opt & 0xFF00) == 0) {
+ ccr = (ccr & 0x00FF) | (data & 0xFF00);
+ }
+ } break;
+ /// LO SPI transactions
+ case 0x1020:
+ _poke_lo_spi(addr, data);
+ return;
+ /// LO SYNC
+ case 0x1024:
+ // We make these bits sticky, because they might get strobed in
+ // multiple calls. In order to see what was strobed within an
+ // API call, we keep bits as they are.
+ read_memory[addr] |= data;
+ return;
+ // TX0 Table Select
+ case 0x4000:
+ case 0x4004:
+ case 0x4008:
+ case 0x400C:
+ case 0x4010:
+ case 0x4014: {
+ read_memory[addr] = data;
+ const uint32_t src_table_offset = data * 4;
+ const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4000;
+ // Now we fake the transaction that copies ?X?_TABLE_* to
+ // ?X?_DSA*
+ read_memory[cpld_offset + 0x3000 + dst_table_offset] =
+ read_memory[cpld_offset + 0x5000 + src_table_offset];
+ }
+ return;
+ // RX0 Table Select
+ case 0x4800:
+ case 0x4804:
+ case 0x4808:
+ case 0x480C:
+ case 0x4810:
+ case 0x4814: {
+ read_memory[addr] = data;
+ const uint32_t src_table_offset = data * 4;
+ const uint32_t dst_table_offset = (addr - cpld_offset) - 0x4800;
+ // Now we fake the transaction that copies ?X?_TABLE_* to
+ // ?X?_DSA*
+ read_memory[cpld_offset + 0x3800 + dst_table_offset] =
+ read_memory[cpld_offset + 0x5800 + src_table_offset];
+ }
+ return;
+ default: // All other CPLD registers are read-write
+ read_memory[addr] = data;
+ return;
+ }
+ }
+
+ void _poke_rfdc_cb(const uint32_t addr, const uint32_t data)
+ {
+ read_memory[addr] |= data;
+ }
+
+ void _poke_lo_spi(const uint32_t addr, const uint32_t data)
+ {
+ // UHD_LOG_INFO("TEST", "Detected LO SPI transaction!");
+ const uint16_t spi_data = data & 0xFFFF;
+ const uint8_t spi_addr = (data >> 16) & 0x7F;
+ const bool read = bool(data & (1 << 23));
+ const uint8_t lo_sel = (data >> 24) & 0x7;
+ const bool start_xact = bool(data & (1 << 28));
+ // UHD_LOG_INFO("TEST",
+ // "Transaction record: Read: "
+ // << (read ? "yes" : "no") << " Address: " << int(spi_addr) << std::hex
+ // << " Data: 0x" << spi_data << " LO sel: " << int(lo_sel) << std::dec
+ // << " Start Transaction: " << start_xact);
+ if (!start_xact) {
+ // UHD_LOG_INFO("TEST", "Register probably just initialized. Ignoring.");
+ return;
+ }
+ switch (spi_addr) {
+ case 0:
+ _muxout_to_lock = spi_data & (1 << 2);
+ break;
+ case 125:
+ BOOST_REQUIRE(read);
+ read_memory[addr] = 0x2288;
+ break;
+ default:
+ break;
+ }
+ if (read) {
+ read_memory[addr] = (read_memory[addr] & 0xFFFF) | (spi_addr << 16)
+ | (lo_sel << 24) | (1 << 31);
+ }
+ if (_muxout_to_lock) {
+ // UHD_LOG_INFO("TEST", "Muxout set to lock. Returning all ones.");
+ read_memory[addr] = 0xFFFF;
+ return;
+ }
+ return;
+ }
+
+ bool _muxout_to_lock = false;
+}; // class x4xx_radio_mock_reg_iface_t
+
+/*
+ * x400_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 regardless of environment
+// settings
+struct uhd_log_enabler
+{
+ uhd_log_enabler(uhd::log::severity_level level)
+ {
+ 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_radio_fixture
+{
+ x400_radio_fixture()
+ : ule(uhd::log::warning) // 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::zbx::ZBX_NUM_CHANS)
+ , num_input_ports(num_channels)
+ , num_output_ports(num_channels)
+ , reg_iface(std::make_shared<x4xx_radio_mock_reg_iface_t>(num_channels))
+ , rpcs(std::make_shared<uhd::test::x4xx_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_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=122.88e6");
+ std::shared_ptr<x4xx_radio_mock_reg_iface_t> reg_iface;
+ std::shared_ptr<uhd::test::x4xx_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
+
+
+/******************************************************************************
+ * RFNoC Graph Test
+ *
+ * This test case ensures that the Radio Block can be added to an RFNoC graph.
+ *****************************************************************************/
+BOOST_FIXTURE_TEST_CASE(x400_radio_test_graph, x400_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.property_propagation_active = true;
+ edge_port_info0.edge = detail::graph_t::graph_edge_t::DYNAMIC;
+ detail::graph_t::graph_edge_t edge_port_info1;
+ edge_port_info1.src_port = 1;
+ edge_port_info1.dst_port = 1;
+ edge_port_info1.property_propagation_active = true;
+ edge_port_info1.edge = detail::graph_t::graph_edge_t::DYNAMIC;
+
+ mock_radio_node_t mock_radio_block{0};
+ mock_terminator_t mock_sink_term(2, {}, "MOCK_SINK");
+ mock_terminator_t mock_source_term(2, {}, "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_source_term.set_edge_property<std::string>(
+ "type", "sc16", {res_source_info::OUTPUT_EDGE, 1});
+ mock_sink_term.set_edge_property<std::string>(
+ "type", "sc16", {res_source_info::INPUT_EDGE, 0});
+ mock_sink_term.set_edge_property<std::string>(
+ "type", "sc16", {res_source_info::INPUT_EDGE, 1});
+
+ UHD_LOG_INFO("TEST", "Creating graph...");
+ graph.connect(&mock_source_term, test_radio.get(), edge_port_info0);
+ graph.connect(&mock_source_term, test_radio.get(), edge_port_info1);
+ graph.connect(test_radio.get(), &mock_sink_term, edge_port_info0);
+ graph.connect(test_radio.get(), &mock_sink_term, edge_port_info1);
+ UHD_LOG_INFO("TEST", "Committing graph...");
+ graph.commit();
+ UHD_LOG_INFO("TEST", "Commit complete.");
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_freq_tx_test, x400_radio_fixture)
+{
+ const std::string log = "ZBX_API_TX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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 = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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(zbx_api_freq_rx_test, x400_radio_fixture)
+{
+ const std::string log = "ZBX_API_RX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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 = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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(zbx_frequency_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_FREQUENCY_TEST";
+ const double ep = 10;
+ // TODO: consult step size
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (auto fe_path : {
+ fs_path("dboard/tx_frontends/0"),
+ fs_path("dboard/tx_frontends/1"),
+ fs_path("dboard/rx_frontends/0"),
+ fs_path("dboard/rx_frontends/1"),
+ }) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQ CHANGE\n");
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_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(zbx_api_tx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX TX GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_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 = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_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(zbx_api_tx_gain_stage_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API TX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+
+ UHD_LOG_INFO(
+ log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
+ for (auto gain_stage : ZBX_TX_GAIN_STAGES) {
+ if (gain_stage == ZBX_GAIN_STAGE_AMP) {
+ for (double amp : {ZBX_TX_LOWBAND_GAIN, ZBX_TX_HIGHBAND_GAIN}) {
+ UHD_LOG_INFO(log, "Testing dsa: " << amp);
+ const double ret_gain =
+ test_radio->set_tx_gain(amp, gain_stage, chan);
+ UHD_LOG_INFO(log, "return: " << ret_gain);
+ BOOST_CHECK_EQUAL(amp, ret_gain);
+ }
+ } else {
+ for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << iter);
+ const double ret_gain =
+ test_radio->set_tx_gain(iter, gain_stage, chan);
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_gain_stage_test_set_get, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API TX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_tx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+ UHD_LOG_INFO(log, "BEGIN TEST: tx" << chan << " GAIN STAGE CHANGE (SET->GET)\n");
+ for (auto gain_stage : ZBX_TX_GAIN_STAGES) {
+ if (gain_stage == ZBX_GAIN_STAGE_AMP) {
+ for (double amp :
+ {/*ZBX_TX_BYPASS_GAIN, currently disabled*/ ZBX_TX_LOWBAND_GAIN,
+ ZBX_TX_HIGHBAND_GAIN}) {
+ UHD_LOG_INFO(log, "Testing amp: " << amp);
+ test_radio->set_tx_gain(amp, gain_stage, chan);
+ const double ret_gain = test_radio->get_tx_gain(gain_stage, chan);
+ BOOST_CHECK_EQUAL(amp, ret_gain);
+ }
+ } else {
+ for (unsigned int iter = 0; iter <= ZBX_TX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << iter);
+ test_radio->set_tx_gain(iter, gain_stage, chan);
+ const double ret_gain = test_radio->get_tx_gain(gain_stage, chan);
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_rx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX RX API GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN CHANGE (SET->RETURN)\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_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 = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_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(zbx_api_rx_gain_stage_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX API RX GAIN STAGE TEST";
+
+ for (size_t chan : {0, 1}) {
+ test_radio->set_rx_gain_profile(ZBX_GAIN_PROFILE_MANUAL, chan);
+
+ UHD_LOG_INFO(
+ log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->RETURN)\n");
+ for (auto gain_stage : ZBX_RX_GAIN_STAGES) {
+ for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing dsa: " << gain_stage << " " << iter);
+ const double ret_gain = test_radio->set_rx_gain(iter, gain_stage, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+
+ UHD_LOG_INFO(log, "BEGIN TEST: rx" << chan << " GAIN STAGE CHANGE (SET->GET)\n");
+ for (auto gain_stage : ZBX_RX_GAIN_STAGES) {
+ for (unsigned int iter = 0; iter <= ZBX_RX_DSA_MAX_ATT; iter++) {
+ UHD_LOG_INFO(log, "Testing " << gain_stage << " " << iter);
+
+ test_radio->set_rx_gain(iter, gain_stage, chan);
+ const double ret_gain = test_radio->get_rx_gain(gain_stage, chan);
+
+ BOOST_CHECK_EQUAL(iter, ret_gain);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX GAIN TEST";
+ uhd::freq_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 1);
+
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+ const auto gain_path = fe_path / "gains" / ZBX_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(zbx_rx_gain_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX GAIN TEST";
+ uhd::freq_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 1);
+
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " GAIN CHANGE\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ UHD_LOG_INFO(log, "Testing gain: " << iter);
+ const auto gain_path = fe_path / "gains" / ZBX_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);
+ }
+ }
+}
+
+// Have to be careful about LO testing; it'll throw off the coerced frequency a bunch,
+// possibly to illegal values like negative frequencies, and could make the gain API
+// freak out. We use the center frequency to set initial mixer values, then try to test
+// all LO's in the valid zbx range.
+// TODO: expand this
+const std::map<double, std::vector<std::array<double, 2>>> valid_lo_freq_map = {
+ {1e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}},
+ {2e9, {{4.5e9, 4.5e9}, {5e9, 5e9}, {5.5e9, 5.5e9}, {6e9, 6e9}}}};
+
+// TODO: More frequencies_are_equal issues, too much variance
+BOOST_FIXTURE_TEST_CASE(zbx_api_tx_lo_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX TX TEST";
+ const double ep = 10;
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: TX" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+
+ UHD_LOG_INFO(log,
+ "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq "
+ << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz");
+ // Need to set center frequency first, it'll set all the mixer values
+ test_radio->set_tx_frequency(iter->first, chan);
+ const double lo1_ret =
+ test_radio->set_tx_lo_freq(iter_lo->at(0), ZBX_LO1, chan);
+ const double lo2_ret =
+ test_radio->set_tx_lo_freq(iter_lo->at(1), ZBX_LO2, chan);
+ // No use comparing set_tx_freq, we've already ran that test and
+ // get_tx_frequency would return who knows what at this point
+ BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep);
+ BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_api_rx_lo_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX RX LO TEST";
+ const double ep = 10;
+
+ for (size_t chan : {0, 1}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: RX" << chan << " FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+
+ UHD_LOG_INFO(log,
+ "Testing center freq " << req_freq / 1e6 << "MHz, lo1 freq "
+ << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz");
+ // Need to set center frequency first, it'll set all the mixer values
+ test_radio->set_rx_frequency(iter->first, chan);
+ const double lo1_ret =
+ test_radio->set_rx_lo_freq(iter_lo->at(0), ZBX_LO1, chan);
+ const double lo2_ret =
+ test_radio->set_rx_lo_freq(iter_lo->at(1), ZBX_LO2, chan);
+ // No use comparing set_tx_freq, we've already ran that test and
+ // get_tx_frequency would return who knows what at this point
+ BOOST_REQUIRE(abs(iter_lo->at(0) - lo1_ret) < ep);
+ BOOST_REQUIRE(abs(iter_lo->at(1) - lo2_ret) < ep);
+ }
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_lo_tree_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX LO1 TEST";
+ const double ep = 10;
+
+ for (auto fe_path : {
+ fs_path("dboard/tx_frontends/0"),
+ fs_path("dboard/tx_frontends/1"),
+ fs_path("dboard/rx_frontends/0"),
+ fs_path("dboard/rx_frontends/1"),
+ }) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " LO FREQ CHANGE (SET->RETURN)\n");
+ for (auto iter = valid_lo_freq_map.begin(); iter != valid_lo_freq_map.end();
+ iter++) {
+ for (auto iter_lo = iter->second.begin(); iter_lo != iter->second.end();
+ iter_lo++) {
+ // Just so we're clear about our value mapping
+ const double req_freq = iter->first;
+ const double req_lo1 = iter_lo->at(0);
+ const double req_lo2 = iter_lo->at(1);
+ UHD_LOG_INFO(log,
+ "Testing lo1 freq " << req_lo1 / 1e6 << "MHz, lo2 freq "
+ << req_lo2 / 1e6 << "MHz at center frequency "
+ << req_freq / 1e6 << "MHz");
+ tree->access<double>(fe_path / "freq").set(req_freq);
+ const double ret_lo1 =
+ tree->access<double>(fe_path / "los" / ZBX_LO1 / "freq" / "value")
+ .set(req_lo1)
+ .get();
+ const double ret_lo2 =
+ tree->access<double>(fe_path / "los" / ZBX_LO2 / "freq" / "value")
+ .set(req_lo2)
+ .get();
+ BOOST_REQUIRE(abs(req_lo1 - ret_lo1) < ep);
+ BOOST_REQUIRE(abs(req_lo2 - ret_lo2) < ep);
+ }
+ }
+ }
+}
+
+
+BOOST_FIXTURE_TEST_CASE(zbx_ant_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ std::string log = "ZBX RX ANTENNA TEST";
+
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n");
+ for (auto iter : RX_ANTENNAS) {
+ UHD_LOG_INFO(log, "Testing Antenna: " << iter);
+
+ tree->access<std::string>(fe_path / "antenna/value").set(iter);
+
+ std::string ret_ant =
+ tree->access<std::string>(fe_path / "antenna/value").get();
+ BOOST_CHECK_EQUAL(iter, ret_ant);
+ }
+ }
+ log = "ZBX TX ANTENNA TEST";
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " ANTENNA CHANGE\n");
+ for (auto iter : TX_ANTENNAS) {
+ UHD_LOG_INFO(log, "Testing Antenna: " << iter);
+
+ tree->access<std::string>(fe_path / "antenna/value").set(iter);
+
+ std::string ret_ant =
+ tree->access<std::string>(fe_path / "antenna/value").get();
+ BOOST_CHECK_EQUAL(iter, ret_ant);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_freq_coercion_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_FREQUENCY_COERCION_TEST";
+ const double ep = 10;
+
+ for (auto fe_path : {
+ fs_path("dboard/tx_frontends/0"),
+ fs_path("dboard/tx_frontends/1"),
+ fs_path("dboard/rx_frontends/0"),
+ fs_path("dboard/rx_frontends/1"),
+ }) {
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " FREQUENCY COERCION\n");
+ double ret_value =
+ tree->access<double>(fe_path / "freq").set(ZBX_MIN_FREQ - 1e6).get();
+
+ BOOST_REQUIRE(abs(ZBX_MIN_FREQ - ret_value) < ep);
+
+ ret_value = tree->access<double>(fe_path / "freq").set(ZBX_MAX_FREQ + 1e6).get();
+
+ BOOST_REQUIRE(abs(ZBX_MAX_FREQ - ret_value) < ep);
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_coercion_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_COERCION_TEST";
+
+ for (auto fe_path :
+ {fs_path("dboard/tx_frontends/0"), fs_path("dboard/tx_frontends/1")}) {
+ uhd::gain_range_t zbx_gain(TX_MIN_GAIN, TX_MAX_GAIN, 0.1);
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " TX GAIN COERCION\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ const double ret_val = tree->access<double>(gain_path).set(iter).get();
+ BOOST_CHECK_EQUAL(ret_val, std::round(iter));
+ }
+ }
+ for (auto fe_path :
+ {fs_path("dboard/rx_frontends/0"), fs_path("dboard/rx_frontends/1")}) {
+ uhd::gain_range_t zbx_gain(RX_MIN_GAIN, RX_MAX_GAIN, 0.1);
+ UHD_LOG_INFO(log, "BEGIN TEST: " << fe_path << " RX GAIN COERCION\n");
+ for (double iter = zbx_gain.start(); iter <= zbx_gain.stop();
+ iter += zbx_gain.step()) {
+ const auto gain_path = fe_path / "gains" / ZBX_GAIN_STAGE_ALL / "value";
+ const double ret_val = tree->access<double>(gain_path).set(iter).get();
+ BOOST_CHECK_EQUAL(ret_val, std::round(iter));
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_phase_sync_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_PHASE_SYNC_TEST";
+ constexpr uint32_t lo_sync_addr = 0x1024 + 0x80000;
+ constexpr uint32_t nco_sync_addr = 0x88000;
+ constexpr uint32_t gearbox_addr = 0x88004;
+ auto& regs = reg_iface->read_memory;
+ UHD_LOG_INFO("TEST", "Setting 1 GHz defaults...");
+ // Confirm default
+ test_radio->set_rx_frequency(1e9, 0);
+ test_radio->set_rx_frequency(1e9, 1);
+ test_radio->set_tx_frequency(1e9, 0);
+ test_radio->set_tx_frequency(1e9, 1);
+ // Enable time stamp
+ UHD_LOG_INFO("TEST", "Enabling time stamp chan 0...");
+ test_radio->set_command_time(uhd::time_spec_t(2.0), 0);
+ // Don't pick the ZBX default frequency here
+ UHD_LOG_INFO("TEST", "Setting RX chan 0 to 2.3 GHz...");
+ test_radio->set_rx_frequency(2.3e9, 0);
+ // Check we synced RX LOs chan 0 and RX NCO chan 0, and ADC gearboxes
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 4);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 1);
+ // Reset strobes
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Enabling time stamp chan 1...");
+ test_radio->set_command_time(uhd::time_spec_t(2.0), 1);
+ UHD_LOG_INFO("TEST", "Setting RX chan 1 to 2.3 GHz...");
+ test_radio->set_rx_frequency(2.3e9, 1);
+ // Check we synced RX LOs chan 1 and RX NCO chan 1. ADC gearbox only gets
+ // reset once, and should be left untouched.
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0b11 << 6);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 0);
+ // Reset strobes
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Setting TX chan 0 to 2.3 GHz...");
+ test_radio->set_tx_frequency(2.3e9, 0);
+ // Check we synced TX LOs chan 0 and TX NCO chan 0, and DAC gearboxes
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0x3 << 0);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 1 << 1);
+ // Reset strobe
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+ UHD_LOG_INFO("TEST", "Setting TX chan 1 to 2.3 GHz...");
+ test_radio->set_tx_frequency(2.3e9, 1);
+ // Check we synced TX LOs chan 1 and TX NCO chan 1. DAC gearbox only gets
+ // reset once, and should be left untouched.
+ BOOST_CHECK_EQUAL(regs[lo_sync_addr], 0xC << 0);
+ BOOST_CHECK_EQUAL(regs[nco_sync_addr], 1);
+ BOOST_CHECK_EQUAL(regs[gearbox_addr], 0);
+ // Reset strobe
+ regs[lo_sync_addr] = 0;
+ regs[nco_sync_addr] = 0;
+ regs[gearbox_addr] = 0;
+}
+
+BOOST_FIXTURE_TEST_CASE(can_set_rfdc_test, x400_radio_fixture)
+{
+ test_radio->set_tx_lo_freq(3.141e9, "rfdc", 1);
+ test_radio->get_tx_lo_freq("rfdc", 1);
+
+ test_radio->set_rx_lo_freq(2.141e9, "rfdc", 0);
+ test_radio->get_rx_lo_freq("rfdc", 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_power_api, x400_radio_fixture)
+{
+ constexpr double tx_given_gain = 30;
+ constexpr double tx_given_power = -30;
+
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_TX_POWER_TRACKING_TEST";
+ auto tx_pwr_mgr = test_radio->get_pwr_mgr(TX_DIRECTION);
+
+ for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) {
+ // Start in gain tracking mode
+ double gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan);
+ BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain);
+ for (const double freq : {6e+08, 1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) {
+ // Setting a power reference should kick us into power tracking mode
+ test_radio->set_tx_power_reference(tx_given_power, chan);
+
+ test_radio->set_tx_frequency(freq, chan);
+ // If the tracking mode is properly set, we should not deviate much
+ // regarding power
+ const double pow_diff = std::abs<double>(
+ tx_given_power - test_radio->get_tx_power_reference(chan));
+ BOOST_CHECK_MESSAGE(pow_diff < 3.0, "power differential is too large: " << pow_diff);
+
+ // Back to gain mode
+ gain_coerced = test_radio->set_tx_gain(tx_given_gain, chan);
+ BOOST_CHECK_EQUAL(gain_coerced, tx_given_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_power_api, x400_radio_fixture)
+{
+ constexpr double rx_given_gain = 30;
+ constexpr double rx_given_power = -30;
+
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_RX_POWER_TRACKING_TEST";
+ auto rx_pwr_mgr = test_radio->get_pwr_mgr(RX_DIRECTION);
+
+ for (size_t chan = 0; chan < ZBX_NUM_CHANS; chan++) {
+ // Start in gain tracking mode
+ double gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan);
+ BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain);
+ for (const double freq : {1e+09, 2e+09, 3e+09, 4e+09, 5e+09, 6e+09}) {
+ // Setting a power reference should kick us into power tracking mode
+ test_radio->set_rx_power_reference(rx_given_power, chan);
+ // Now go tune
+ test_radio->set_rx_frequency(freq, chan);
+ // If the tracking mode is properly set, we should match our expected criteria
+ // for power reference levels
+ const double actual_power = test_radio->get_rx_power_reference(chan);
+ const double pow_diff = std::abs<double>(rx_given_power - actual_power);
+ BOOST_CHECK_MESSAGE(pow_diff < 3.0,
+ "power differential is too large ("
+ << pow_diff << "): Expected close to: " << rx_given_power
+ << " Actual: " << actual_power << " Frequency: " << (freq/1e6));
+
+ gain_coerced = test_radio->set_rx_gain(rx_given_gain, chan);
+ BOOST_REQUIRE_EQUAL(gain_coerced, rx_given_gain);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_lo_injection_locking, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+
+ // As of right now, we don't have a way to directly get the DB prc rate, this is the
+ // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate()
+ constexpr double db_prc_rate = 61.44e6;
+ constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE;
+
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_freq.step()) {
+ for (const size_t chan : {0, 1}) {
+ test_radio->set_tx_frequency(iter, chan);
+
+ // The step alignment only applies to the desired LO frequency, the actual
+ // returned frequency may vary slightly
+ const double lo1_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO1, chan));
+ const double lo2_freq = std::round(test_radio->get_tx_lo_freq(ZBX_LO2, chan));
+
+ const double lo1_div = lo1_freq / lo_step_size;
+ const double lo2_div = lo2_freq / lo_step_size;
+
+ // Test whether our tuned frequencies align with the lo step size
+ BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div);
+ BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_lo_injection_locking, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+
+ // As of right now, we don't have a way to directly get the DB prc rate, this is the
+ // value of the prc map per DEFAULT_MCR, in the mock RPC server:db_0_get_db_prc_rate()
+ constexpr double db_prc_rate = 61.44e6;
+ constexpr double lo_step_size = db_prc_rate / ZBX_RELATIVE_LO_STEP_SIZE;
+
+ uhd::freq_range_t zbx_freq(ZBX_MIN_FREQ, ZBX_MAX_FREQ, 100e6);
+
+ for (double iter = zbx_freq.start(); iter <= zbx_freq.stop();
+ iter += zbx_freq.step()) {
+ for (const size_t chan : {0, 1}) {
+ test_radio->set_rx_frequency(iter, chan);
+
+ // The step alignment only applies to the desired LO frequency, the actual
+ // returned frequency may vary slightly
+ const double lo1_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO1, chan));
+ const double lo2_freq = std::round(test_radio->get_rx_lo_freq(ZBX_LO2, chan));
+
+ const double lo1_div = lo1_freq / lo_step_size;
+ const double lo2_div = lo2_freq / lo_step_size;
+
+ // Test whether our tuned frequencies align with the lo step size
+ BOOST_CHECK_EQUAL(std::floor(lo1_div), lo1_div);
+ BOOST_CHECK_EQUAL(std::floor(lo2_div), lo2_div);
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_rx_gain_profile_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_PROFILE_TEST";
+ auto& regs = reg_iface->read_memory;
+ constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000;
+ constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004;
+ constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008;
+ constexpr uint32_t rx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3800;
+ constexpr uint32_t rx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5800;
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default");
+ // Everything should be classic_atr
+ BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101);
+ // Can't set gain stages in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error);
+
+ //** manual gain profile **
+ test_radio->set_rx_gain_profile("manual", 0);
+ // Must provide valid gain name in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(23, 0), uhd::runtime_error);
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "banana", 0), uhd::key_error);
+ // Now manually set the DSAs
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA1", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA2", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3A", 0));
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "DSA3B", 0));
+ // Check the registers were written to correctly (gain 5 == att 10)
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xAAAA);
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xAAAA);
+ // Check the getters:
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA1", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA2", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3A", 0), 5);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain("DSA3B", 0), 5);
+ // Even in 'manual', we can load from the table. Let's create a table entry:
+ regs[rx0_table + 5 * 4] = 0x7777;
+ // Now, let it be loaded into RX and XX:
+ BOOST_CHECK_EQUAL(5, test_radio->set_rx_gain(5, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0x7777);
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0x7777);
+ // Note: If we read back the DSAs via get_rx_gain() now, they will still say
+ // 5. We might want to change that, but it will require extra peeks. The
+ // only good way to do that is to amend set_?x_gain() to do that peek when
+ // updating gains via table.
+ // Test DSA coercion
+ BOOST_CHECK_EQUAL(15, test_radio->set_rx_gain(39, "DSA1", 0));
+ BOOST_CHECK_EQUAL(0, test_radio->set_rx_gain(-17, "DSA1", 0));
+
+ // If we go back to 'default', we also reset the DSAs. That's because the
+ // desired, previously loaded default value will trigger the previous DSA
+ // values again.
+ UHD_LOG_INFO(log, "resetting to default");
+ test_radio->set_rx_gain_profile("default", 0);
+ BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("DSA1", 0));
+
+ //** table_noatr profile : **
+ UHD_LOG_INFO(log, "setting to table_noatr");
+ test_radio->set_rx_gain_profile("table_noatr", 0);
+ // This will set DSA config for chan 0 to 0 == SW_DEFINED
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr");
+ // Yup, this will also change TX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr");
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "all", 0), uhd::key_error);
+ BOOST_CHECK_EQUAL(8.0, test_radio->set_rx_gain(8, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[sw_config], 0x80000);
+ // Returns the current config. Note the asymmetry to the previous API call.
+ // We can't, however, know which entry from the TABLE we used, so we just
+ // return the current config (which is the entry from the DSA table, not the
+ // TABLE it writes to).
+ BOOST_CHECK_EQUAL(0, test_radio->get_rx_gain("TABLE", 0));
+ // Let's pretend we're using config 7
+ regs[current_config] = 0x70000;
+ BOOST_CHECK_EQUAL(7, test_radio->get_rx_gain("TABLE", 0));
+ // And back
+ regs[current_config] = 0x00000;
+ // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We
+ // keep the current config of 0, and update RX0_DSA*[0].
+ regs[rx0_dsa + 0 * 4] = 0x4444; // Turn it up to attenuation 4 == gain 11
+ BOOST_CHECK_EQUAL(11.0, test_radio->get_rx_gain("DSA1", 0));
+
+ //** table profile **
+ test_radio->set_rx_gain_profile("table", 0);
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101);
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table");
+ // Yup, this will also change TX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table");
+ // Create another table entry
+ regs[rx0_table + 23 * 4] = 0xBBBB;
+ BOOST_CHECK_EQUAL(23.0, test_radio->set_rx_gain(23, "TABLE", 0));
+ // get_rx_gain() for "TABLE" returns the current DSA table index, not actual gain
+ BOOST_CHECK_EQUAL(0.0, test_radio->get_rx_gain("TABLE", 0));
+ // This will update RX and XX registers (that's the difference to table_noatr)
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 1 * 4], 0xBBBB); // att 0xB == gain 4.0
+ BOOST_CHECK_EQUAL(regs[rx0_dsa + 3 * 4], 0xBBBB);
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA1", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA2", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3A", 0));
+ BOOST_CHECK_EQUAL(4.0, test_radio->get_rx_gain("DSA3B", 0));
+ // Test table coercion
+ UHD_LOG_INFO(log, "Testing TABLE coercion");
+ BOOST_CHECK_EQUAL(0.0, test_radio->set_rx_gain(-17, "TABLE", 0));
+ BOOST_CHECK_EQUAL(255.0, test_radio->set_rx_gain(1e9, "TABLE", 0));
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_gain_profile_test, x400_radio_fixture)
+{
+ auto tree = test_radio->get_tree();
+ const std::string log = "ZBX_GAIN_PROFILE_TEST";
+ auto& regs = reg_iface->read_memory;
+ constexpr uint32_t current_config = radio_control_impl::regmap::PERIPH_BASE + 0x1000;
+ constexpr uint32_t rf_option = radio_control_impl::regmap::PERIPH_BASE + 0x1004;
+ constexpr uint32_t sw_config = radio_control_impl::regmap::PERIPH_BASE + 0x1008;
+ constexpr uint32_t tx0_dsa = radio_control_impl::regmap::PERIPH_BASE + 0x3000;
+ constexpr uint32_t tx0_table = radio_control_impl::regmap::PERIPH_BASE + 0x5000;
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(1), "default");
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(1), "default");
+ const double default_dsa1 = test_radio->get_tx_gain("DSA1", 0);
+ // Everything should be classic_atr
+ BOOST_CHECK_EQUAL(regs[0x81004], 0x01010101);
+ // Can't set gain stages in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_rx_gain(10, "DSA1", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "DSA1", 0), uhd::key_error);
+
+ //** manual gain profile **
+ test_radio->set_tx_gain_profile("manual", 0);
+ // Must provide valid gain name in this profile
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, 0), uhd::runtime_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(23, "all", 0), uhd::key_error);
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "banana", 0), uhd::key_error);
+ // Now manually set the DSAs
+ BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA1", 0));
+ BOOST_CHECK_EQUAL(21, test_radio->set_tx_gain(21, "DSA2", 0));
+ // Check the registers were written to correctly (gain 5 == att 10)
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0A0A);
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0A0A);
+ // Check the getters:
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA1", 0), 21);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain("DSA2", 0), 21);
+ // Even in 'manual', we can load from the table. Let's create a table entry:
+ regs[tx0_table + 5 * 4] = 0x0707;
+ // Now, let it be loaded into RX and XX:
+ BOOST_CHECK_EQUAL(5, test_radio->set_tx_gain(5, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0707);
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0707);
+ // Note: If we read back the DSAs via get_tx_gain() now, they will still say
+ // 5. We might want to change that, but it will require extra peeks. The
+ // only good way to do that is to amend set_?x_gain() to do that peek when
+ // updating gains via table.
+ // Test DSA coercion
+ BOOST_CHECK_EQUAL(31, test_radio->set_tx_gain(39, "DSA1", 0));
+ BOOST_CHECK_EQUAL(0, test_radio->set_tx_gain(-17, "DSA1", 0));
+
+ // If we go back to 'default', we also reset the DSAs. That's because the
+ // desired, previously loaded default value will trigger the previous DSA
+ // values again.
+ UHD_LOG_INFO(log, "resetting to default");
+ test_radio->set_tx_gain_profile("default", 0);
+ BOOST_CHECK_EQUAL(default_dsa1, test_radio->get_tx_gain("DSA1", 0));
+
+ //** table_noatr profile : **
+ UHD_LOG_INFO(log, "setting to table_noatr");
+ test_radio->set_tx_gain_profile("table_noatr", 0);
+ // This will set DSA config for chan 0 to 0 == SW_DEFINED
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01000101);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table_noatr");
+ // Yup, this will also change RX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table_noatr");
+ BOOST_REQUIRE_THROW(test_radio->set_tx_gain(10, "all", 0), uhd::key_error);
+ BOOST_CHECK_EQUAL(8.0, test_radio->set_tx_gain(8, "TABLE", 0));
+ BOOST_CHECK_EQUAL(regs[sw_config], 0x80000);
+ // Returns the current config. Note the asymmetry to the previous API call.
+ // We can't, however, know which entry from the TABLE we used, so we just
+ // return the current config (which is the entry from the DSA table, not the
+ // TABLE it writes to).
+ BOOST_CHECK_EQUAL(0, test_radio->get_tx_gain("TABLE", 0));
+ // Let's pretend we're using config 7
+ regs[current_config] = 0x70000;
+ BOOST_CHECK_EQUAL(7, test_radio->get_tx_gain("TABLE", 0));
+ // And back
+ regs[current_config] = 0x00000;
+ // Now we fake an FPGA-gain-change transaction that UHD is unaware of. We
+ // keep the current config of 0, and update TX0_DSA*[0].
+ regs[tx0_dsa + 0 * 4] = 0x0404; // Turn it up to attenuation 4 == gain 27
+ BOOST_CHECK_EQUAL(27.0, test_radio->get_tx_gain("DSA1", 0));
+
+ //** table profile **
+ test_radio->set_tx_gain_profile("table", 0);
+ BOOST_CHECK_EQUAL(regs[rf_option], 0x01010101);
+ BOOST_CHECK_EQUAL(test_radio->get_tx_gain_profile(0), "table");
+ // Yup, this will also change RX gain profile; they're coupled.
+ BOOST_CHECK_EQUAL(test_radio->get_rx_gain_profile(0), "table");
+ // Create another table entry
+ regs[tx0_table + 23 * 4] = 0x0B0B;
+ BOOST_CHECK_EQUAL(23.0, test_radio->set_tx_gain(23, "TABLE", 0));
+ // get_tx_gain() for "TABLE" returns the current DSA table index, not actual gain
+ BOOST_CHECK_EQUAL(0.0, test_radio->get_tx_gain("TABLE", 0));
+ // This will update RX and XX registers (that's the difference to table_noatr)
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 2 * 4], 0x0B0B); // att 0xB == gain 20.0
+ BOOST_CHECK_EQUAL(regs[tx0_dsa + 3 * 4], 0x0B0B);
+ BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA1", 0));
+ BOOST_CHECK_EQUAL(20.0, test_radio->get_tx_gain("DSA2", 0));
+ // Test table coercion
+ UHD_LOG_INFO(log, "Testing TABLE coercion");
+ BOOST_CHECK_EQUAL(0.0, test_radio->set_tx_gain(-17, "TABLE", 0));
+ BOOST_CHECK_EQUAL(255.0, test_radio->set_tx_gain(1e9, "TABLE", 0));
+}
+
+// TODO:
+// - concurrent/consecutive configuration
+// - Threading tests
+// - Error cases
diff --git a/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp
new file mode 100644
index 000000000..ad47089a5
--- /dev/null
+++ b/host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp
@@ -0,0 +1,337 @@
+//
+// Copyright 2020 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/zbx/zbx_constants.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_dboard.hpp>
+#include <stdlib.h>
+#include <boost/test/unit_test.hpp>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+using namespace uhd::usrp;
+
+namespace uhd { namespace test {
+
+namespace {
+constexpr double DEFAULT_MCR = 122.88e6;
+}
+//! Mock MPM server for X410/ZBX
+//
+// This is a mock server that mimicks an X410 with a ZBX daughterboard.
+class x4xx_mock_rpc_server : public x400_rpc_iface, public mpmd_rpc_iface, public dboard_base_rpc_iface, public zbx_rpc_iface
+{
+public:
+ x4xx_mock_rpc_server(const uhd::device_addr_t& device_info)
+ : _device_info(device_info)
+ {}
+
+ uhd::rpc_client::sptr get_raw_rpc_client() override
+ {
+ // This function is unimplemented! Perhaps you need to:
+ // - Add it to the appropriate RPC interface,
+ // - Retrofit all calls to your desired function to directly use the RPC interface, and
+ // - Add a mock implementation here.
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ /**************************************************************************
+ * RPC Call Mockups
+ *
+ * The following public methods are replacements of that normally happens in
+ * the Python-based MPM. Some notes on writing mocks:
+ * - These are mocks, so don't go fancy and only let them do the bare
+ * minimum required for tests
+ * - Remember to add them to _init_rpc() further down
+ *************************************************************************/
+ 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 {};
+ }
+
+ 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 {{
+ // One entry per dboard info
+ {"pid", std::to_string(uhd::usrp::zbx::ZBX_PID)}
+ // End of entries
+ }};
+ }
+
+ 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) override
+ {
+ // nop
+ }
+
+ std::vector<int> get_cal_frozen(size_t, size_t) override
+ {
+ return {};
+ }
+
+ std::map<std::string, std::vector<uint8_t>> get_db_eeprom(const size_t) override
+ {
+ return {{
+ // One line per entry
+ {"pid", s2u8("mock")}, // Used to specify power cal API
+ {"serial", s2u8("BADCODE")}
+ // End of entries
+ }};
+ }
+
+ double get_dboard_prc_rate() override
+ {
+ const double mcr = _device_info.cast<double>("master_clock_rate", DEFAULT_MCR);
+ static const std::map<double, double> prc_map{
+ {122.88e6, 61.44e6}, {125e6, 62.5e6}};
+ return prc_map.at(mcr);
+ }
+
+ double rfdc_set_nco_freq(const std::string& trx,
+ const size_t /*db_id*/,
+ const size_t chan,
+ const double freq)
+ {
+ BOOST_REQUIRE(trx == "rx" || trx == "tx");
+ BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_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)
+ {
+ BOOST_REQUIRE(trx == "rx" || trx == "tx");
+ BOOST_REQUIRE(chan < uhd::usrp::zbx::ZBX_NUM_CHANS);
+ // On construction, the expert will ask for the current nco frequency, and our
+ // nco_freq map won't have a value yet.
+ 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
+ {122.88e6, 2.94912e9},
+ {122.88e6 * 4, 2.94912e9}
+ // End of entries
+ };
+ return spll_map.at(mcr);
+ }
+
+ void enable_iq_swap(const bool,
+ const std::string&,
+ const size_t) override
+ {
+ // nop
+ }
+
+ 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 /*threshold_block*/) override
+ {
+ return false;
+ }
+
+ void set_dac_mux_enable(size_t /*motherboard_channel_number*/, int /*enable*/) 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 /*threshold_block*/,
+ const std::string& /*mode*/,
+ size_t /*delay*/,
+ size_t /*under*/,
+ size_t /*over*/) override
+ {
+ // nop
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Public attributes for easy inspection
+ //
+ // Use this in the mock functions to cache values, or expose values that get
+ // tested later
+ std::map<std::string, std::map<size_t, double>> if2_freq;
+ std::map<std::string, std::map<size_t, double>> nco_freq;
+ //
+ ///////////////////////////////////////////////////////////////////////////
+
+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
diff --git a/host/tests/streaming_performance/run_X4xx_max_rate_tests.py b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py
new file mode 100644
index 000000000..ea424f14f
--- /dev/null
+++ b/host/tests/streaming_performance/run_X4xx_max_rate_tests.py
@@ -0,0 +1,193 @@
+#!/usr/bin/env python3
+"""
+Copyright 2021 Ettus Research, A National Instrument Brand
+
+SPDX-License-Identifier: GPL-3.0-or-later
+
+Runs streaming tests for X4xx at rates in the neighborhoood of the maximum
+rate that UHD can sustain. Each test consists of a batch of runs of the
+benchmark rate C++ example with different streaming parameters.
+
+To run all the tests, execute it with all supported options for the test_type
+parameter:
+ 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe
+
+Example usage::
+run_X4xx_max_rate_tests.py --path <benchmark_rate_dir>/benchmark_rate --addr 192.168.10.2 --second_addr 192.168.20.2 --mgmt_addr 192.168.40.2 --test_type 1x100Gbe --use_dpdk 1
+"""
+import argparse
+import sys
+import time
+import datetime
+import batch_run_benchmark_rate
+
+
+def parse_args():
+ """
+ Parse command line arguments
+ """
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--path",
+ type=str,
+ required=True,
+ help="path to benchmark rate example")
+ parser.add_argument(
+ "--test_type",
+ type=str,
+ required=True,
+ choices=["1Gbe", "1x10Gbe", "2x10Gbe", "1x100Gbe", "2x100Gbe"],
+ help="test type you would like to run eg. 1Gbe, 1x10Gbe, 2x10Gbe, 1x100Gbe, 2x100Gbe")
+ parser.add_argument(
+ "--addr",
+ type=str,
+ default = "",
+ help="address of first interface")
+ parser.add_argument(
+ "--second_addr",
+ type=str,
+ default = "",
+ help="address of second interface")
+ parser.add_argument(
+ "--mgmt_addr",
+ type=str,
+ default = "",
+ help="mgmt address")
+ parser.add_argument(
+ "--use_dpdk",
+ default = False,
+ action="store_true",
+ help="enable DPDK (you must run the script as root to use this)")
+
+ return parser.parse_args()
+
+def run_test(path, params, iterations, label):
+ """
+ Runs benchmark rate for the number of iterations in the command line arguments.
+ """
+ print("-----------------------------------------------------------")
+ print(label + "\n")
+ results = batch_run_benchmark_rate.run(path, iterations, params)
+ stats = batch_run_benchmark_rate.calculate_stats(results)
+ print(batch_run_benchmark_rate.get_summary_string(stats, iterations, params))
+
+def run_tests_for_single(path, base_params, iterations, duration, rate):
+
+ base_params["duration"] = duration
+
+ rx_params = base_params.copy()
+
+ # Run 20 Msps RX with one channel
+ rx_params["rx_rate"] = str(rate)
+ rx_params["rx_channels"] = "0"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "1xRX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps with two channels
+ rx_params["rx_rate"] = str(rate/2)
+ rx_params["rx_channels"] = "0,1"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "2xRX @"+ str(rate/2e6) +" Msps")
+
+ tx_params = base_params.copy()
+
+ # Run 20 Msps TX with one channel
+ tx_params["tx_rate"] = str(rate)
+ tx_params["tx_channels"] = "0"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "1xTX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps TX with two channels
+ tx_params["tx_rate"] = "100e5"
+ tx_params["tx_channels"] = "0,1"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "2xTX @"+ str(rate/2e6) +" Msps")
+
+ trx_params = base_params.copy()
+
+ # Run 20 Msps TRX with one channel
+ trx_params["tx_rate"] = str(rate)
+ trx_params["rx_rate"] = str(rate)
+ trx_params["tx_channels"] = "0"
+ trx_params["rx_channels"] = "0"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "1xTRX @"+ str(rate/1e6) +" Msps")
+
+ # Run 10 Msps TRX with two channels
+ trx_params["tx_rate"] = str(rate/2)
+ trx_params["rx_rate"] = str(rate/2)
+ trx_params["tx_channels"] = "0,1"
+ trx_params["rx_channels"] = "0,1"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps")
+
+
+def run_tests_for_dual(base_params, path, duration, iterations, rate):
+
+ base_params["duration"] = duration
+
+ rx_params = base_params.copy()
+ # Run 200 Msps with two channels
+ rx_params["rx_rate"] = str(rate)
+ rx_params["rx_channels"] = "0,1"
+ print(rx_params)
+ run_test(path, rx_params, iterations, "2xRX @"+ str(rate/1e6) +" Msps")
+
+ tx_params = base_params.copy()
+
+ # Run 200 Msps TX with two channels
+ tx_params["tx_rate"] = str(rate)
+ tx_params["tx_channels"] = "0,1"
+ print(tx_params)
+ run_test(path, tx_params, iterations, "2xTX @"+ str(rate/1e6) +" Msps")
+
+ trx_params = base_params.copy()
+
+ # Run 100 Msps TRX with two channels
+ trx_params["tx_rate"] = str(rate/2)
+ trx_params["rx_rate"] = str(rate/2)
+ trx_params["tx_channels"] = "0,1"
+ trx_params["rx_channels"] = "0,1"
+ print(trx_params)
+ run_test(path, trx_params, iterations, "2xTRX @"+ str(rate/2e6) +" Msps")
+
+
+
+def main():
+ args = parse_args()
+
+ base_params = {
+ "args" : f"addr={args.addr},second_addr={args.second_addr}",
+ }
+ if args.use_dpdk:
+ base_params["args"] += f",mgmt_addr={args.mgmt_addr},use_dpdk=1"
+
+ start_time = time.time()
+
+ rate = {
+ "1Gbe": 200e5,
+ "1x10Gbe": 200e6,
+ "2x10Gbe": 200e6,
+ "1x100Gbe": 200e7, # Rate doesn't matter here as 100Gbe has no DUC
+ "2x100Gbe": 200e7}
+
+ test_config = {
+ "1Gbe": run_tests_for_single,
+ "1x10Gbe": run_tests_for_single,
+ "2x10Gbe": run_tests_for_dual,
+ "1x100Gbe": run_tests_for_single,
+ "2x100Gbe": run_tests_for_dual}
+
+
+ # Run 10 test iterations for 60 seconds each
+ test_config[args.test_type](args.path, base_params, 10, 60, rate[args.test_type])
+ # Run 2 test iterations for 600 seconds each
+ test_config[args.test_type](args.path, base_params, 2, 600, rate[args.test_type])
+
+ end_time = time.time()
+ elapsed = end_time - start_time
+ print("Elapsed time: {}".format(datetime.timedelta(seconds=elapsed)))
+ return True
+
+if __name__ == "__main__":
+ sys.exit(not main())
diff --git a/host/tests/x400_rfdc_control_test.cpp b/host/tests/x400_rfdc_control_test.cpp
new file mode 100644
index 000000000..ec80682fa
--- /dev/null
+++ b/host/tests/x400_rfdc_control_test.cpp
@@ -0,0 +1,52 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhdlib/usrp/common/x400_rfdc_control.hpp>
+#include <boost/test/unit_test.hpp>
+
+using uhd::rfnoc::x400::rfdc_control;
+
+struct x400_rfdc_fixture
+{
+ static constexpr size_t RFDC_MEM_SIZE = 1;
+
+ x400_rfdc_fixture()
+ : mem(1 << rfdc_control::regmap::NCO_RESET_DONE_MSB, RFDC_MEM_SIZE)
+ , rfdcc(
+ uhd::memmap32_iface_timed{
+ [&](const uint32_t addr, const uint32_t data, const uhd::time_spec_t&) {
+ mem[addr] = data;
+ },
+ [&](const uint32_t addr) { return mem[addr]; }},
+ "TEST::RFDC")
+ {
+ // nop
+ }
+
+ std::vector<uint32_t> mem;
+ rfdc_control rfdcc;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(test_nco_reset, x400_rfdc_fixture)
+{
+ rfdcc.reset_ncos({rfdc_control::rfdc_type::RX0}, uhd::time_spec_t::ASAP);
+ BOOST_CHECK(mem[rfdc_control::regmap::NCO_RESET] & 0x1);
+ // Fake self-clearing bit
+ mem[rfdc_control::regmap::NCO_RESET] = 1 << rfdc_control::regmap::NCO_RESET_DONE_MSB;
+ // This should print a warning:
+ rfdcc.reset_ncos({}, uhd::time_spec_t::ASAP);
+ BOOST_CHECK_EQUAL(mem[rfdc_control::regmap::NCO_RESET]
+ & (1 << rfdc_control::regmap::NCO_RESET_START_MSB),
+ 0);
+ BOOST_CHECK(rfdcc.get_nco_reset_done());
+}
+
+BOOST_FIXTURE_TEST_CASE(test_nco_freq, x400_rfdc_fixture)
+{
+ // TODO: Add checks when implemented
+ rfdcc.set_nco_freq(rfdc_control::rfdc_type::RX0, 1e9);
+}
diff --git a/host/tests/zbx_cpld_test.cpp b/host/tests/zbx_cpld_test.cpp
new file mode 100644
index 000000000..b125e8435
--- /dev/null
+++ b/host/tests/zbx_cpld_test.cpp
@@ -0,0 +1,135 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/types/time_spec.hpp>
+#include <uhdlib/usrp/dboard/zbx/zbx_cpld_ctrl.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::usrp::zbx;
+
+struct mock_reg_iface_type
+{
+ uint32_t last_addr = 0;
+ zbx_cpld_ctrl::chan_t last_chan;
+ std::map<uint32_t, uint32_t> memory;
+ uhd::time_spec_t sleep_counter = uhd::time_spec_t(0.0);
+};
+
+
+struct zbx_cpld_fixture
+{
+ zbx_cpld_fixture()
+ : cpld(
+ [&](const uint32_t addr,
+ const uint32_t data,
+ const zbx_cpld_ctrl::chan_t chan) {
+ std::cout << "[MOCKREGS] poke32(" << addr << ", " << data << ")"
+ << std::endl;
+ mock_reg_iface.last_addr = addr;
+ mock_reg_iface.last_chan = chan;
+ mock_reg_iface.memory[addr] = data;
+ },
+ [&](const uint32_t addr) -> uint32_t {
+ std::cout << "[MOCKREGS] peek32(" << addr << ") => "
+ << mock_reg_iface.memory.at(addr) << std::endl;
+ return mock_reg_iface.memory.at(addr);
+ },
+ [&](const uhd::time_spec_t& time) { mock_reg_iface.sleep_counter += time; },
+ "TEST::CPLD")
+ {
+ // nop
+ }
+
+ mock_reg_iface_type mock_reg_iface;
+ zbx_cpld_ctrl cpld;
+};
+
+
+BOOST_FIXTURE_TEST_CASE(zbx_cpld_ctrl_test, zbx_cpld_fixture)
+{
+ cpld.set_scratch(23);
+ BOOST_CHECK_EQUAL(cpld.get_scratch(), 23);
+
+ cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1);
+ mock_reg_iface.memory[0x1024] = 0;
+ // Make sure there are no caching issues:
+ cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 1);
+ // Now all:
+ cpld.pulse_lo_sync(0,
+ {zbx_lo_t::TX0_LO1,
+ zbx_lo_t::TX0_LO2,
+ zbx_lo_t::TX1_LO1,
+ zbx_lo_t::TX1_LO2,
+ zbx_lo_t::RX0_LO1,
+ zbx_lo_t::RX0_LO2,
+ zbx_lo_t::RX1_LO1,
+ zbx_lo_t::RX1_LO2});
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0xFF);
+ mock_reg_iface.memory[0x1024] = 0;
+ cpld.set_lo_sync_bypass(true);
+ BOOST_CHECK_THROW(cpld.pulse_lo_sync(0, {zbx_lo_t::TX0_LO1}), uhd::runtime_error);
+ BOOST_CHECK_EQUAL(mock_reg_iface.memory[0x1024], 0x100);
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_tx_amp_test, zbx_cpld_fixture)
+{
+ cpld.set_tx_antenna_switches(
+ 0, 0, uhd::usrp::zbx::DEFAULT_TX_ANTENNA, tx_amp::HIGHBAND);
+ BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false));
+ mock_reg_iface.memory[0x2000] = 0;
+ BOOST_CHECK(tx_amp::HIGHBAND == cpld.get_tx_amp_settings(0, 0, false));
+ BOOST_CHECK(tx_amp::HIGHBAND != cpld.get_tx_amp_settings(0, 0, true));
+ BOOST_CHECK(tx_amp::BYPASS == cpld.get_tx_amp_settings(0, 0, false));
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_get_set_dsa_test, zbx_cpld_fixture)
+{
+ // We only test the first table index
+ constexpr size_t dsa_table_index = 0;
+ for (const size_t chan : {0, 1}) {
+ const uint32_t tx_dsa_table_addr = 0x3000 + chan * 0x400;
+ const uint32_t rx_dsa_table_addr = 0x3800 + chan * 0x400;
+
+ auto& tx_dsa_reg = mock_reg_iface.memory[tx_dsa_table_addr];
+ auto& rx_dsa_reg = mock_reg_iface.memory[rx_dsa_table_addr];
+
+ // We'll skip DSA3A/B, because they work just like the rest and would
+ // make this test much longer and less readable without adding much test
+ // coverage.
+ for (const auto dsa :
+ {zbx_cpld_ctrl::dsa_type::DSA1, zbx_cpld_ctrl::dsa_type::DSA2}) {
+ const size_t tx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 8;
+ const size_t rx_shift = (dsa == zbx_cpld_ctrl::dsa_type::DSA1) ? 0 : 4;
+ // 0xB and 0xC are just random attenuation values. They are valid
+ // for both TX and RX.
+ cpld.set_tx_dsa(chan, dsa_table_index, dsa, 0xB);
+ BOOST_CHECK_EQUAL((tx_dsa_reg >> tx_shift) & 0x1F, 0xB);
+ tx_dsa_reg = 0x0C0C;
+ BOOST_CHECK_EQUAL(0xB, cpld.get_tx_dsa(chan, dsa_table_index, dsa, false));
+ BOOST_CHECK_EQUAL(0xC, cpld.get_tx_dsa(chan, dsa_table_index, dsa, true));
+
+ cpld.set_rx_dsa(chan, 0, dsa, 0xB);
+ BOOST_CHECK_EQUAL((rx_dsa_reg >> rx_shift) & 0xF, 0xB);
+ rx_dsa_reg = 0xCC;
+ BOOST_CHECK_EQUAL(0xB, cpld.get_rx_dsa(chan, dsa_table_index, dsa, false));
+ BOOST_CHECK_EQUAL(0xC, cpld.get_rx_dsa(chan, dsa_table_index, dsa, true));
+ }
+ }
+}
+
+BOOST_FIXTURE_TEST_CASE(zbx_set_from_table_test, zbx_cpld_fixture)
+{
+ constexpr uint32_t tx_sel_addr = 0x4000;
+ constexpr size_t chan = 0;
+ constexpr uint8_t idx = 2;
+
+ auto& tx_table_select = mock_reg_iface.memory[tx_sel_addr + idx * 4];
+ cpld.set_tx_gain_switches(chan, idx, 23);
+ BOOST_REQUIRE_EQUAL(tx_table_select, 23);
+}