diff options
author | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-21 16:05:56 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-30 13:03:16 -0500 |
commit | 3b39cb90df3e22897091735dc7bc0c3675eef518 (patch) | |
tree | d9a7bdaa291ab7f8667aa45720cc2ff4b8c2d50a /host | |
parent | fec41cd6a1ffc826a0680fd200811a4044008b72 (diff) | |
download | uhd-3b39cb90df3e22897091735dc7bc0c3675eef518.tar.gz uhd-3b39cb90df3e22897091735dc7bc0c3675eef518.tar.bz2 uhd-3b39cb90df3e22897091735dc7bc0c3675eef518.zip |
tests: Add unit test for siggen RFNoC block controller
Diffstat (limited to 'host')
-rw-r--r-- | host/tests/CMakeLists.txt | 4 | ||||
-rw-r--r-- | host/tests/rfnoc_block_tests/siggen_block_test.cpp | 348 |
2 files changed, 352 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 19047eea7..097a5e88c 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -316,6 +316,10 @@ UHD_ADD_RFNOC_BLOCK_TEST( ) UHD_ADD_RFNOC_BLOCK_TEST( + TARGET siggen_block_test.cpp +) + +UHD_ADD_RFNOC_BLOCK_TEST( TARGET split_stream_block_test.cpp ) diff --git a/host/tests/rfnoc_block_tests/siggen_block_test.cpp b/host/tests/rfnoc_block_tests/siggen_block_test.cpp new file mode 100644 index 000000000..d495e7a19 --- /dev/null +++ b/host/tests/rfnoc_block_tests/siggen_block_test.cpp @@ -0,0 +1,348 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#define _USE_MATH_DEFINES + +#include "../rfnoc_graph_mock_nodes.hpp" +#include <uhd/rfnoc/actions.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/mock_block.hpp> +#include <uhd/rfnoc/multichan_register_iface.hpp> +#include <uhd/rfnoc/register_iface_holder.hpp> +#include <uhd/rfnoc/siggen_block_control.hpp> +#include <uhdlib/rfnoc/graph.hpp> +#include <uhdlib/rfnoc/node_accessor.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <boost/test/unit_test.hpp> +#include <cmath> +#include <iostream> + +using namespace uhd::rfnoc; + +// Redeclare this here, since it's only defined outside of UHD_API +noc_block_base::make_args_t::~make_args_t() = default; + +constexpr size_t NUM_PORTS = 4; +constexpr size_t DEFAULT_MTU = 8000; + +/* + * This class extends mock_reg_iface_t, handling three particular registers + */ +class siggen_mock_reg_iface_t : public mock_reg_iface_t +{ +public: + siggen_mock_reg_iface_t(size_t num_ports) + : _num_ports(num_ports) + , enables(_num_ports, false) + , spps(_num_ports, 0) + , waveforms(_num_ports, siggen_waveform::CONSTANT) + , gains(_num_ports, 0) + , constants(_num_ports, 0) + , phase_increments(_num_ports, 0) + , phasors(_num_ports, 0) + { + } + + virtual void _poke_cb( + uint32_t addr, uint32_t data, uhd::time_spec_t /*time*/, bool /*ack*/) + { + const size_t port = addr / siggen_block_control::REG_BLOCK_SIZE; + if (port >= _num_ports) { + throw uhd::assertion_error("Invalid port index"); + } + + const size_t offset = addr % siggen_block_control::REG_BLOCK_SIZE; + if (offset == siggen_block_control::REG_ENABLE_OFFSET) { + enables[port] = (data > 0); + } else if (offset == siggen_block_control::REG_SPP_OFFSET) { + spps[port] = data; + } else if (offset == siggen_block_control::REG_WAVEFORM_OFFSET) { + waveforms[port] = static_cast<siggen_waveform>(data); + } else if (offset == siggen_block_control::REG_GAIN_OFFSET) { + gains[port] = data; + } else if (offset == siggen_block_control::REG_CONSTANT_OFFSET) { + constants[port] = data; + } else if (offset == siggen_block_control::REG_PHASE_INC_OFFSET) { + phase_increments[port] = data; + } else if (offset == siggen_block_control::REG_CARTESIAN_OFFSET) { + phasors[port] = data; + } else { + throw uhd::assertion_error("Invalid write to out of bounds offset"); + } + } + + virtual void _peek_cb(uint32_t addr, uhd::time_spec_t /*time*/) + { + const size_t port = addr / siggen_block_control::REG_BLOCK_SIZE; + if (port >= _num_ports) { + throw uhd::assertion_error("Invalid port index"); + } + + const size_t offset = addr % siggen_block_control::REG_BLOCK_SIZE; + if (offset == siggen_block_control::REG_ENABLE_OFFSET) { + read_memory[addr] = enables[port] ? 1 : 0; + } else if (offset == siggen_block_control::REG_SPP_OFFSET) { + read_memory[addr] = spps[port]; + } else if (offset == siggen_block_control::REG_WAVEFORM_OFFSET) { + read_memory[addr] = static_cast<uint32_t>(waveforms[port]); + } else if (offset == siggen_block_control::REG_GAIN_OFFSET) { + read_memory[addr] = gains[port]; + } else if (offset == siggen_block_control::REG_CONSTANT_OFFSET) { + read_memory[addr] = constants[port]; + } else if (offset == siggen_block_control::REG_PHASE_INC_OFFSET) { + read_memory[addr] = phase_increments[port]; + } else if (offset == siggen_block_control::REG_CARTESIAN_OFFSET) { + read_memory[addr] = phasors[port]; + } else { + throw uhd::assertion_error("Invalid read from out of bounds offset"); + } + } + + template <class T> + static const T clamp(const double v) + { + constexpr T min_t = std::numeric_limits<T>::min(); + constexpr T max_t = std::numeric_limits<T>::max(); + return (v < min_t) ? min_t : (v > max_t) ? max_t : T(v); + } + + static uint32_t gain_to_register(double gain) + { + const int16_t gain_fp = clamp<int16_t>(gain * 32768.0); + return static_cast<uint32_t>(gain_fp) & 0xffff; + } + + static uint32_t constant_to_register(std::complex<double> constant) + { + const int16_t constant_i_fp = clamp<int16_t>(constant.real() * 32768.0); + const int16_t constant_q_fp = clamp<int16_t>(constant.imag() * 32768.0); + return (uint32_t(constant_i_fp) << 16) | (uint32_t(constant_q_fp) & 0xffff); + } + + static uint32_t phase_increment_to_register(double phase_inc) + { + const int16_t phase_inc_scaled_rads_fp = + clamp<int16_t>((phase_inc / M_PI) * 8192.0); + return static_cast<uint32_t>(phase_inc_scaled_rads_fp) & 0xffff; + } + + static uint32_t phasor_to_register(std::complex<double> phasor) + { + phasor /= 1.164435344782938; /* CORDIC scale value--see siggen_block_control */ + const int16_t phasor_i_fp = clamp<int16_t>(phasor.real() * 32767.0); + const int16_t phasor_q_fp = clamp<int16_t>(phasor.imag() * 32767.0); + return (uint32_t(phasor_i_fp) << 16) | (uint32_t(phasor_q_fp) & 0xffff); + } + +private: + const size_t _num_ports; + +public: + std::vector<bool> enables; + std::vector<uint32_t> spps; + std::vector<siggen_waveform> waveforms; + std::vector<uint32_t> gains; + std::vector<uint32_t> constants; + std::vector<uint32_t> phase_increments; + std::vector<uint32_t> phasors; +}; + +/* + * siggen_block_fixture is a class which is instantiated before each test + * case is run. It sets up the block container, mock register interface, + * and siggen_block_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. + */ +struct siggen_block_fixture +{ + siggen_block_fixture() + : reg_iface(std::make_shared<siggen_mock_reg_iface_t>(NUM_PORTS)) + , block_container(get_mock_block(SIGGEN_BLOCK, + NUM_PORTS, + NUM_PORTS, + uhd::device_addr_t(), + DEFAULT_MTU, + ANY_DEVICE, + reg_iface)) + , test_siggen(block_container.get_block<siggen_block_control>()) + { + node_accessor.init_props(test_siggen.get()); + } + + std::shared_ptr<siggen_mock_reg_iface_t> reg_iface; + mock_block_container block_container; + std::shared_ptr<siggen_block_control> test_siggen; + node_accessor_t node_accessor{}; +}; + +/* + * This test case ensures that the hardware is programmed correctly with + * defaults when the siggen block is constructed. + */ +BOOST_FIXTURE_TEST_CASE(siggen_test_construction, siggen_block_fixture) +{ + for(size_t port = 0; port < NUM_PORTS; port++) { + BOOST_CHECK_EQUAL(reg_iface->enables.at(port), 0); + BOOST_CHECK(reg_iface->waveforms.at(port) == siggen_waveform::CONSTANT); + BOOST_CHECK_EQUAL(reg_iface->gains.at(port), siggen_mock_reg_iface_t::gain_to_register(1.0)); + BOOST_CHECK_EQUAL(reg_iface->constants.at(port), siggen_mock_reg_iface_t::constant_to_register({1.0, 1.0})); + BOOST_CHECK_EQUAL(reg_iface->phase_increments.at(port), siggen_mock_reg_iface_t::phase_increment_to_register(1.0)); + BOOST_CHECK_EQUAL(reg_iface->phasors.at(port), + siggen_mock_reg_iface_t::phasor_to_register({0.0, 0.0})); + BOOST_CHECK_EQUAL(reg_iface->spps.at(port), DEFAULT_SPP); + } +} + +/* + * This test case exercises the API and ensures that the registers are + * programmed appropriately. + */ +BOOST_FIXTURE_TEST_CASE(siggen_test_api, siggen_block_fixture) +{ + for(size_t port = 0; port < NUM_PORTS; port++) { + test_siggen->set_enable(true, port); + BOOST_CHECK_EQUAL(reg_iface->enables.at(port), 1); + BOOST_CHECK_EQUAL(test_siggen->get_enable(port), true); + + // Set constant mode on the function generator, and then make sure + // that the gain register stays fixed at 1 and that changing the + // amplitude has no effect on the register or the attribute value + // (which should always return 1 in this mode). + const double amplitude = 0.25 + (port * 0.1); + test_siggen->set_waveform(siggen_waveform::CONSTANT, port); + BOOST_CHECK(reg_iface->waveforms.at(port) == siggen_waveform::CONSTANT); + BOOST_CHECK(test_siggen->get_waveform(port) == siggen_waveform::CONSTANT); + test_siggen->set_amplitude(amplitude, port); + BOOST_CHECK_EQUAL( + reg_iface->gains.at(port), siggen_mock_reg_iface_t::gain_to_register(1.0)); + BOOST_CHECK_EQUAL(test_siggen->get_amplitude(port), 1.0); + + // Set sine wave mode on the function generator, and then make sure + // that the gain register stays fixed at 1, but that the Cartesian + // register changes to reflect the desired sinusoidal amplitude. + test_siggen->set_waveform(siggen_waveform::SINE_WAVE, port); + BOOST_CHECK(reg_iface->waveforms.at(port) == siggen_waveform::SINE_WAVE); + test_siggen->set_amplitude(amplitude, port); + BOOST_CHECK_EQUAL( + reg_iface->gains.at(port), siggen_mock_reg_iface_t::gain_to_register(1.0)); + BOOST_CHECK_EQUAL(reg_iface->phasors.at(port), + siggen_mock_reg_iface_t::phasor_to_register({amplitude, 0.0})); + BOOST_CHECK_EQUAL(test_siggen->get_amplitude(port), amplitude); + + // Set noise mode on the function generator, and then make sure + // that the gain register changes according to the amplitude. + test_siggen->set_waveform(siggen_waveform::NOISE, port); + BOOST_CHECK(reg_iface->waveforms.at(port) == siggen_waveform::NOISE); + BOOST_CHECK(test_siggen->get_waveform(port) == siggen_waveform::NOISE); + test_siggen->set_amplitude(amplitude, port); + BOOST_CHECK_EQUAL(reg_iface->gains.at(port), + siggen_mock_reg_iface_t::gain_to_register(amplitude)); + BOOST_CHECK_EQUAL(test_siggen->get_amplitude(port), amplitude); + + const std::complex<double> constant{-0.5 - (port * 0.05), 0.5 + (port * 0.05)}; + test_siggen->set_constant(constant, port); + BOOST_CHECK_EQUAL(reg_iface->constants.at(port), siggen_mock_reg_iface_t::constant_to_register(constant)); + BOOST_CHECK_EQUAL(test_siggen->get_constant(port), constant); + + const double phase_inc = (port * M_PI / 16.0); + test_siggen->set_sine_phase_increment(phase_inc, port); + BOOST_CHECK_EQUAL(reg_iface->phase_increments.at(port), siggen_mock_reg_iface_t::phase_increment_to_register(phase_inc)); + BOOST_CHECK_EQUAL(test_siggen->get_sine_phase_increment(port), phase_inc); + + const double freq = 1000 + (100 * port); + const double samp_rate = 1e6; + test_siggen->set_sine_frequency(freq, samp_rate, port); + const double calculated_phase_inc = freq / samp_rate * 2.0 * M_PI; + BOOST_CHECK_EQUAL(reg_iface->phase_increments.at(port), + siggen_mock_reg_iface_t::phase_increment_to_register(calculated_phase_inc)); + BOOST_CHECK_EQUAL( + test_siggen->get_sine_phase_increment(port), calculated_phase_inc); + + size_t spp = 100 + (200 * port); + test_siggen->set_samples_per_packet(spp, port); + BOOST_CHECK_EQUAL(reg_iface->spps.at(port), spp); + BOOST_CHECK_EQUAL(test_siggen->get_samples_per_packet(port), spp); + } +} + +/* + * This test case exercises the range checking performed on the siggen + * settings, ensuring that the appropriate exception is thrown when out of + * range. + */ +BOOST_FIXTURE_TEST_CASE(siggen_test_ranges, siggen_block_fixture) +{ + for(size_t port = 0; port < NUM_PORTS; port++) { + BOOST_CHECK_THROW(test_siggen->set_property<int>("waveform", 100, port), uhd::value_error); + + const double bad_amplitude = 100 + (port * 100); + // I got a bad amplitude! + BOOST_CHECK_THROW( + test_siggen->set_amplitude(bad_amplitude, port), uhd::value_error); + BOOST_CHECK_THROW( + test_siggen->set_amplitude(-bad_amplitude, port), uhd::value_error); + + const std::complex<double> bad_constant_i{-100.0 + (port * 100), 0.1}; + BOOST_CHECK_THROW(test_siggen->set_constant(bad_constant_i, port), uhd::value_error); + BOOST_CHECK_THROW(test_siggen->set_constant(-bad_constant_i, port), uhd::value_error); + + const std::complex<double> bad_constant_q{-0.1, 100.0 + (port * 100)}; + BOOST_CHECK_THROW(test_siggen->set_constant(bad_constant_q, port), uhd::value_error); + BOOST_CHECK_THROW(test_siggen->set_constant(-bad_constant_q, port), uhd::value_error); + + const double bad_phase_inc = 5 * M_PI; + BOOST_CHECK_THROW( + test_siggen->set_sine_phase_increment(bad_phase_inc, port), uhd::value_error); + BOOST_CHECK_THROW(test_siggen->set_sine_phase_increment(-bad_phase_inc, port), + uhd::value_error); + + const double bad_samp_rate_zero = 0.0; + BOOST_CHECK_THROW(test_siggen->set_sine_frequency(10.0, bad_samp_rate_zero, port), + uhd::value_error); + + const double bad_samp_rate = 30.0 + (port * 10); + const double bad_freq = bad_samp_rate * 10.0; + BOOST_CHECK_THROW(test_siggen->set_sine_frequency(bad_freq, bad_samp_rate, port), + uhd::value_error); + } +} + +/* + * This test case exercises the coercion of the SPP parameter to ensure that + * it does not surpass the MTU. + */ +BOOST_FIXTURE_TEST_CASE(siggen_test_spp_coercion, siggen_block_fixture) +{ + constexpr size_t mtu_in_samps = DEFAULT_MTU / 4 /* bytes/samp */; + size_t high_spp = mtu_in_samps + 10; + test_siggen->set_samples_per_packet(high_spp, 0); + BOOST_CHECK_EQUAL(test_siggen->get_samples_per_packet(0), mtu_in_samps); +} + +/* + * This test case ensures that the siggen block controller can be added + * to a graph. + */ +BOOST_FIXTURE_TEST_CASE(siggen_test_graph, siggen_block_fixture) +{ + detail::graph_t graph{}; + detail::graph_t::graph_edge_t edge_info{ + 0, 0, detail::graph_t::graph_edge_t::DYNAMIC, true}; + + mock_terminator_t mock_sink_term(NUM_PORTS, {}, "MOCK_SINK"); + + mock_sink_term.set_edge_property<std::string>( + "type", "sc16", {res_source_info::INPUT_EDGE, 0}); + + UHD_LOG_INFO("TEST", "Creating graph..."); + for(size_t port = 0; port < NUM_PORTS; port++) { + graph.connect(test_siggen.get(), &mock_sink_term, {port, port, detail::graph_t::graph_edge_t::DYNAMIC, true}); + } + UHD_LOG_INFO("TEST", "Committing graph..."); + graph.commit(); + UHD_LOG_INFO("TEST", "Commit complete."); +} |