diff options
author | Lars Amsel <lars.amsel@ni.com> | 2021-06-04 08:27:50 +0200 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 12:01:53 -0500 |
commit | 2a575bf9b5a4942f60e979161764b9e942699e1e (patch) | |
tree | 2f0535625c30025559ebd7494a4b9e7122550a73 /host/tests/rfnoc_block_tests | |
parent | e17916220cc955fa219ae37f607626ba88c4afe3 (diff) | |
download | uhd-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.cpp | 1222 | ||||
-rw-r--r-- | host/tests/rfnoc_block_tests/x4xx_zbx_mpm_mock.hpp | 337 |
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 |