aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Rossetto <aaron.rossetto@ni.com>2020-07-21 16:05:56 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-07-30 13:03:16 -0500
commit3b39cb90df3e22897091735dc7bc0c3675eef518 (patch)
treed9a7bdaa291ab7f8667aa45720cc2ff4b8c2d50a
parentfec41cd6a1ffc826a0680fd200811a4044008b72 (diff)
downloaduhd-3b39cb90df3e22897091735dc7bc0c3675eef518.tar.gz
uhd-3b39cb90df3e22897091735dc7bc0c3675eef518.tar.bz2
uhd-3b39cb90df3e22897091735dc7bc0c3675eef518.zip
tests: Add unit test for siggen RFNoC block controller
-rw-r--r--host/tests/CMakeLists.txt4
-rw-r--r--host/tests/rfnoc_block_tests/siggen_block_test.cpp348
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.");
+}