// // Copyright 2024 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include #include // Datasheet for max2871: // https://www.analog.com/media/en/technical-documentation/data-sheets/MAX2871.pdf // Used to confirm the proper register addresses and values // Based on values set in gen_max2871_regs.py and the constructor of max2871 std::map expected_init_values = { {0, 0x003e8000}, {1, 0x2000fff9}, {2, 0x78006e42}, {3, 0x0100000b}, {4, 0x6080023c}, {5, 0x61400005}, }; // Using macro instead of function so that failures print the line associated with the // call to UHD_CHECK_REGMAP rather than pointing out the line in UHD_CHECK_REGMAP itself #define UHD_CHECK_REGMAP(lo, expected) \ { \ std::map actual; \ for (int i = 0; i < 6; i++) { \ actual.insert(std::pair(i, lo->get_register(i))); \ } \ actual.insert(std::pair(0, 0)); \ for (const auto& expected_r : expected) { \ std::stringstream exp_ss, act_ss; \ exp_ss << std::hex << expected_r.second; \ act_ss << std::hex << actual.at(expected_r.first); \ const std::string exp_str = \ "R" + std::to_string(expected_r.first) + " == 0x" + exp_ss.str(); \ const std::string act_str = \ "R" + std::to_string(expected_r.first) + " == 0x" + act_ss.str(); \ BOOST_CHECK_EQUAL(exp_str, act_str); \ } \ } // Used to set the field of a register to use for an expected value void set_field(uint32_t& reg, uint32_t mask, uint32_t shift, uint32_t value) { reg &= ~(mask << shift); reg |= (value & mask) << shift; } // Test that the set_register and get_register functions work as expected for all // writeable registers (non-address) and also that set_register works when masking // for specific registers BOOST_AUTO_TEST_CASE(max2871_set_get_register_test) { auto test_lo = max287x_iface::make([](const std::vector&) { /* noop */ }); test_lo->set_register(0, 0xFFFFFFF8, 0x12345678); test_lo->set_register(1, 0xFFFFFFF8, 0x12345678); test_lo->set_register(2, 0xFFFFFFF8, 0x12345678); test_lo->set_register(3, 0xFFFFFFF8, 0x12345678); test_lo->set_register(4, 0xFFFFFFF8, 0x12345678); test_lo->set_register(5, 0xFFFFFFF8, 0x12345678); // Registers should differ only by the address bits of each register std::map expected_values = { {0, 0x12345678}, {1, 0x12345679}, {2, 0x1234567a}, {3, 0x1234567b}, {4, 0x1234567c}, {5, 0x1234567d}, }; UHD_CHECK_REGMAP(test_lo, expected_values); test_lo->set_register(3, 0x0F000000, 0xFFFFFFFF); test_lo->set_register(5, 0x000FF000, 0xFFFFFFFF); test_lo->set_register(1, 0xF0000000, 0xFFFFFFFF); set_field(expected_values[3], 0xF, 24, 0XF); set_field(expected_values[5], 0xFF, 12, 0XFF); set_field(expected_values[1], 0xF, 28, 0XF); UHD_CHECK_REGMAP(test_lo, expected_values); } // Test that registers are at the expected values after initialization BOOST_AUTO_TEST_CASE(max2871_init_test) { auto test_lo = max287x_iface::make([](const std::vector&) { /* noop */ }); UHD_CHECK_REGMAP(test_lo, expected_init_values); } // Test that functions that set output power function correctly BOOST_AUTO_TEST_CASE(max2871_set_output_power_test) { auto test_lo = max287x_iface::make([](const std::vector&) { /* noop */ }); // set_output_power should only affect RFOUTA Output Power (bits 4:3 of reg 4) test_lo->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); auto expected_values = expected_init_values; set_field(expected_values[4], 0x3, 3, max287x_iface::OUTPUT_POWER_5DBM); UHD_CHECK_REGMAP(test_lo, expected_values); test_lo->set_output_power(max287x_iface::OUTPUT_POWER_M1DBM); set_field(expected_values[4], 0x3, 3, max287x_iface::OUTPUT_POWER_M1DBM); UHD_CHECK_REGMAP(test_lo, expected_values); // set_aux_output_power should only affect RFOUTB Output Power (bits 7:6 of reg 4) test_lo->set_aux_output_power(max287x_iface::AUX_OUTPUT_POWER_5DBM); set_field(expected_values[4], 0x3, 6, max287x_iface::AUX_OUTPUT_POWER_5DBM); UHD_CHECK_REGMAP(test_lo, expected_values); test_lo->set_aux_output_power(max287x_iface::AUX_OUTPUT_POWER_M1DBM); set_field(expected_values[4], 0x3, 6, max287x_iface::AUX_OUTPUT_POWER_M1DBM); UHD_CHECK_REGMAP(test_lo, expected_values); // set_aux_output_power_enable should only affect RFOUTB Output Mode (bit 8 of reg 4) test_lo->set_aux_output_power_enable(true); set_field(expected_values[4], 0x1, 8, 1); UHD_CHECK_REGMAP(test_lo, expected_values); test_lo->set_aux_output_power_enable(false); set_field(expected_values[4], 0x1, 8, 0); UHD_CHECK_REGMAP(test_lo, expected_values); } // Test that registers that need to be set for phase synchronization are set correctly // Also, test that phase synchronization only advertises as possible when in frac_n mode BOOST_AUTO_TEST_CASE(max2871_phase_sync_test) { auto test_lo = max287x_iface::make([](const std::vector&) { /* noop */ }); test_lo->set_frequency(2e9, 50e6, 50e6, true); BOOST_CHECK_EQUAL(max2871_regs_t::SHUTDOWN_VAS_ENABLED, static_cast( (test_lo->get_register(3) >> 25) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::LOW_NOISE_AND_SPUR_LOW_SPUR_2, static_cast( (test_lo->get_register(2) >> 29) & 0x3)); BOOST_CHECK_EQUAL(max2871_regs_t::F01_AUTO, static_cast((test_lo->get_register(5) >> 24) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::AUX_OUTPUT_SELECT_FUNDAMENTAL, static_cast( (test_lo->get_register(4) >> 9) & 0x1)); // For phase synchronization, ensure the following registers are set to the following test_lo->config_for_sync(true); test_lo->set_frequency(2e9, 50e6, 50e6, true); BOOST_CHECK_EQUAL(max2871_regs_t::SHUTDOWN_VAS_DISABLED, static_cast( (test_lo->get_register(3) >> 25) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE, static_cast( (test_lo->get_register(2) >> 29) & 0x3)); BOOST_CHECK_EQUAL(max2871_regs_t::F01_FRAC_N, static_cast((test_lo->get_register(5) >> 24) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::AUX_OUTPUT_SELECT_DIVIDED, static_cast( (test_lo->get_register(4) >> 9) & 0x1)); test_lo->commit(); // Have configured for sync, but cannot sync in int_n mode BOOST_CHECK(!test_lo->can_sync()); test_lo->set_frequency(2e9, 50e6, 50e6, false); test_lo->commit(); // Now should be possible to sync in frac_n mode BOOST_CHECK(test_lo->can_sync()); } // Test that the registers that need to be set for int_n vs frac_n mode within // the set_frequency function are set correctly BOOST_AUTO_TEST_CASE(max2871_set_frequency_int_n_test) { auto test_lo = max287x_iface::make([](const std::vector&) { /* noop */ }); // set_frequency with int_n = true, ensure that all registers that need to be set for // int_n are set correctly (other arguments set arbitrarily for this test) test_lo->set_frequency(2e9, 50e6, 50e6, true); BOOST_CHECK_EQUAL(max2871_regs_t::FEEDBACK_SELECT_DIVIDED, static_cast( (test_lo->get_register(4) >> 23) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::CPL_DISABLED, static_cast((test_lo->get_register(1) >> 29) & 0x3)); BOOST_CHECK_EQUAL(max2871_regs_t::LDF_INT_N, static_cast((test_lo->get_register(2) >> 8) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::INT_N_MODE_INT_N, static_cast( (test_lo->get_register(0) >> 31) & 0x1)); // set_frequency with int_n = false, ensure that all registers that need to be set for // frac_n are set correctly (other arguments set arbitrarily for this test) test_lo->set_frequency(2e9, 50e6, 50e6, false); BOOST_CHECK_EQUAL(max2871_regs_t::FEEDBACK_SELECT_DIVIDED, static_cast( (test_lo->get_register(4) >> 23) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::CPL_ENABLED, static_cast((test_lo->get_register(1) >> 29) & 0x3)); BOOST_CHECK_EQUAL(max2871_regs_t::LDF_FRAC_N, static_cast((test_lo->get_register(2) >> 8) & 0x1)); BOOST_CHECK_EQUAL(max2871_regs_t::INT_N_MODE_FRAC_N, static_cast( (test_lo->get_register(0) >> 31) & 0x1)); }