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