aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests/rfnoc_block_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/rfnoc_block_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/rfnoc_block_tests')
-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
2 files changed, 1559 insertions, 0 deletions
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