diff options
-rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/defaults.hpp | 1 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/siggen_block_control.hpp | 207 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/siggen_block_control.cpp | 343 |
5 files changed, 553 insertions, 0 deletions
diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt index 7a1602f90..965710a59 100644 --- a/host/include/uhd/rfnoc/CMakeLists.txt +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -44,6 +44,7 @@ UHD_INSTALL(FILES moving_average_block_control.hpp null_block_control.hpp radio_control.hpp + siggen_block_control.hpp split_stream_block_control.hpp switchboard_block_control.hpp vector_iir_block_control.hpp diff --git a/host/include/uhd/rfnoc/defaults.hpp b/host/include/uhd/rfnoc/defaults.hpp index ac230b57d..781beb696 100644 --- a/host/include/uhd/rfnoc/defaults.hpp +++ b/host/include/uhd/rfnoc/defaults.hpp @@ -82,6 +82,7 @@ static const noc_id_t FIR_FILTER_BLOCK = 0xF1120000; static const noc_id_t FOSPHOR_BLOCK = 0x666F0000; static const noc_id_t LOGPWR_BLOCK = 0x4C500000; static const noc_id_t MOVING_AVERAGE_BLOCK = 0xAAD20000; +static const noc_id_t SIGGEN_BLOCK = 0x51663110; static const noc_id_t SPLIT_STREAM_BLOCK = 0x57570000; static const noc_id_t SWITCHBOARD_BLOCK = 0xBE110000; static const noc_id_t RADIO_BLOCK = 0x12AD1000; diff --git a/host/include/uhd/rfnoc/siggen_block_control.hpp b/host/include/uhd/rfnoc/siggen_block_control.hpp new file mode 100644 index 000000000..944ecc4e0 --- /dev/null +++ b/host/include/uhd/rfnoc/siggen_block_control.hpp @@ -0,0 +1,207 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#pragma once + +#define _USE_MATH_DEFINES +#include <uhd/config.hpp> +#include <uhd/rfnoc/noc_block_base.hpp> +#include <cmath> +#include <complex> + +namespace uhd { namespace rfnoc { + +/*! Siggen Control Class + * + * The Siggen Block is an RFNoC block that acts as a simple function + * generating source block. The block supports three functions: generating a + * constant value, generating a sinusoidal wave with a configurable amplitude + * and phase increment between samples (but with a random initial phase + * offset), and a noise source. + */ + +enum class siggen_waveform { CONSTANT, SINE_WAVE, NOISE }; + +class UHD_API siggen_block_control : public noc_block_base +{ +public: + RFNOC_DECLARE_BLOCK(siggen_block_control); + + static const uint32_t REG_BLOCK_SIZE; + static const uint32_t REG_ENABLE_OFFSET; + static const uint32_t REG_SPP_OFFSET; + static const uint32_t REG_WAVEFORM_OFFSET; + static const uint32_t REG_GAIN_OFFSET; + static const uint32_t REG_CONSTANT_OFFSET; + static const uint32_t REG_PHASE_INC_OFFSET; + static const uint32_t REG_CARTESIAN_OFFSET; + + /*! Set the function generator stream enable flag + * + * Enables or disables the stream of function generator data from the + * given port on the block. + * + * \param enable Stream enable/disable flag + * \param port The port on the block to enable or disable + */ + virtual void set_enable(const bool enable, const size_t port) = 0; + + /*! Get the function generator stream enable flag + * + * Returns the current enable flag for function generator stream data from + * the given port on the block. + * + * \param port The port on the block to get the flag from + * \returns Function generator stream data enable/disable flag + */ + virtual bool get_enable(const size_t port) const = 0; + + /*! Set the function generator waveform type + * + * Sets the waveform type that the given port on the block should generate + * when enabled. + * + * \param type Function generator waveform type + * \param port The port on the block whose waveform type to set + */ + virtual void set_waveform(const siggen_waveform type, const size_t port) = 0; + + /*! Get the function generator waveform type + * + * Returns the current waveform type for the given port on the block. + * + * \param port The port on the block whose waveform type to return + * \returns Waveform type + */ + virtual siggen_waveform get_waveform(const size_t port) const = 0; + + /*! Set the amplitude value for noise and sine wave data + * + * Sets the maximum amplitude of the data generated by the given port + * on the block. The hardware can generate function data with an + * amplitude between -1.0 and (2^15-1)/(2^15), or approximately 0.99997, + * so the provided value must reside within this range. However, for + * convenience, clients may pass 1.0 to get the maximum amplitude that + * can be generated. + * + * Setting the amplitude applies only to the noise and to the sine wave + * functions. The value generated when in constant mode is the value that + * is configured via the set_constant() function. Calling this function + * when in constant mode will range check the amplitude value, but will + * otherwise have no impact on the generated signal. + * + * \param amplitude Amplitude of sine wave and noise data + * \param port The port on the block whose amplitude value to set + */ + virtual void set_amplitude(const double amplitude, const size_t port) = 0; + + /*! Get the amplitude value for noise and sine wave data + * + * Returns the current maximum amplitude of the data generated by + * the given port on the block. The value only applies to the noise and + * sine wave functions. Calling this function when in constant mode will + * return 1.0. + * + * \param port The port on the block whose amplitude value to return + * \returns Amplitude value + */ + virtual double get_amplitude(const size_t port) const = 0; + + /*! Set the constant value to generate in constant mode + * + * Sets the value that should be generated by the function generator on + * the given block when the block is configured in constant mode. The + * hardware can output a complex value where the real and imaginary + * components can be between -1.0 and (2^15-1)/(2^15), or approximately + * 0.99997, so the provided value must reside within this range. However, + * for convenience, clients may pass 1.0 for either component to get the + * maximum positive value that can be generated. + * + * \param constant The constant value to generate + * \param port The port on the block whose constant value to set + */ + virtual void set_constant(const std::complex<double> constant, const size_t port) = 0; + + /*! Get the constant value to generate in constant mode + * + * Returns the current constant value to be generated by the function + * generator on the given block when the block is configured in constant + * mode. + * + * \param port The port on the block whose constant value to return + * \returns Constant value to generate + */ + virtual std::complex<double> get_constant(const size_t port) const = 0; + + /*! Set the phase increment between samples in sine wave mode + * + * Sets the phase increment between successive samples generated by the + * function generator on the given block when the block is configured in + * sine wave mode. The value must be in the range [-pi, pi]. + * + * \param phase_inc The phase increment between samples + * \param port The port on the block whose phase increment to set + */ + virtual void set_sine_phase_increment(const double phase_inc, const size_t port) = 0; + + /*! Get the phase increment between samples in sine wave mode + * + * Returns the current phase increment between successive samples generated + * by the function generator on the given block when the block is + * configured in sine wave mode. + * + * \param port The port on the block whose phase increment to return + * \returns Phase increment between samples + */ + virtual double get_sine_phase_increment(const size_t port) const = 0; + + /*! Set the number of samples per packet to generate + * + * Sets the number of samples per packet to be generated by the function + * generator on the given block. The value will be coerced such that + * outgoing packets will not exceed the graph MTU. + * + * \param spp Number of samples per packet to generate + * \param port The port on the block whose samples per packet to set + */ + virtual void set_samples_per_packet(const size_t spp, const size_t port) = 0; + + /*! Get the number of samples per packet to generate + * + * Returns the current number of samples per packet generated by the + * function generator on the given block. + * + * \param port The port on the block whose samples per packet value to get + * \returns Number of samples per packet to generate + */ + virtual size_t get_samples_per_packet(const size_t port) const = 0; + + /*! Configure the sinusoidal waveform generator given frequency and rate + * + * Convenience function to configure the current phase increment between + * successive samples generated by the sinusoidal function generator + * given the desired frequency and assumed sample rate. + * + * \param frequency The desired frequency of the sinusoid + * \param sample_rate The assumed sample rate + * \param port The port on the block whose phase increment to set + */ + inline void set_sine_frequency( + const double frequency, const double sample_rate, const size_t port) + { + if (sample_rate <= 0.0) { + throw uhd::value_error("sample_rate must be > 0.0"); + } + const double phase_inc = (frequency / sample_rate) * 2.0 * M_PI; + if (phase_inc < -M_PI || phase_inc > M_PI) { + throw uhd::value_error("frequency must be in [-samp_rate/2, samp_rate/2]"); + } + set_sine_phase_increment(phase_inc, port); + } +}; + +}} // namespace uhd::rfnoc + diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index a9c3c9ed0..233c536bf 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -52,6 +52,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/moving_average_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/null_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_control_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/siggen_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/split_stream_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/switchboard_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/vector_iir_block_control.cpp diff --git a/host/lib/rfnoc/siggen_block_control.cpp b/host/lib/rfnoc/siggen_block_control.cpp new file mode 100644 index 000000000..416688ce1 --- /dev/null +++ b/host/lib/rfnoc/siggen_block_control.cpp @@ -0,0 +1,343 @@ +// +// Copyright 2020 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#define _USE_MATH_DEFINES +#include <uhd/convert.hpp> +#include <uhd/exception.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <uhd/rfnoc/multichan_register_iface.hpp> +#include <uhd/rfnoc/property.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/rfnoc/siggen_block_control.hpp> +#include <uhdlib/utils/narrow.hpp> +#include <cmath> +#include <limits> +#include <string> + +using namespace uhd::rfnoc; + + +// Register offsets +const uint32_t siggen_block_control::REG_BLOCK_SIZE = 1 << 5; +const uint32_t siggen_block_control::REG_ENABLE_OFFSET = 0x00; +const uint32_t siggen_block_control::REG_SPP_OFFSET = 0x04; +const uint32_t siggen_block_control::REG_WAVEFORM_OFFSET = 0x08; +const uint32_t siggen_block_control::REG_GAIN_OFFSET = 0x0C; +const uint32_t siggen_block_control::REG_CONSTANT_OFFSET = 0x10; +const uint32_t siggen_block_control::REG_PHASE_INC_OFFSET = 0x14; +const uint32_t siggen_block_control::REG_CARTESIAN_OFFSET = 0x18; + +// User property names +const char* const PROP_KEY_ENABLE = "enable"; +const char* const PROP_KEY_WAVEFORM = "waveform"; +const char* const PROP_KEY_AMPLITUDE = "amplitude"; +const char* const PROP_KEY_CONSTANT_I = "constant_i"; +const char* const PROP_KEY_CONSTANT_Q = "constant_q"; +const char* const PROP_KEY_SINE_PHASE_INC = "sine_phase_increment"; + +namespace { +template <class T> +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); +} +} // namespace + +class siggen_block_control_impl : public siggen_block_control +{ +public: + RFNOC_BLOCK_CONSTRUCTOR(siggen_block_control), + _siggen_reg_iface(*this, 0, REG_BLOCK_SIZE) + { + _register_props(); + } + + void set_enable(const bool enable, const size_t port) + { + set_property<bool>(PROP_KEY_ENABLE, enable, port); + } + + bool get_enable(const size_t port) const + { + return _prop_enable.at(port).get(); + } + + void set_waveform(const siggen_waveform waveform, const size_t port) + { + set_property<int>(PROP_KEY_WAVEFORM, static_cast<int>(waveform), port); + } + + siggen_waveform get_waveform(const size_t port) const + { + return static_cast<siggen_waveform>(_prop_waveform.at(port).get()); + } + + void set_amplitude(const double amplitude, const size_t port) + { + set_property<double>(PROP_KEY_AMPLITUDE, amplitude, port); + } + + double get_amplitude(const size_t port) const + { + return _prop_amplitude.at(port).get(); + } + + void set_constant(const std::complex<double> constant, const size_t port) + { + set_property<double>(PROP_KEY_CONSTANT_I, constant.real(), port); + set_property<double>(PROP_KEY_CONSTANT_Q, constant.imag(), port); + } + + std::complex<double> get_constant(const size_t port) const + { + return std::complex<double>( + _prop_constant_i.at(port).get(), _prop_constant_q.at(port).get()); + } + + void set_sine_phase_increment(const double phase_inc, const size_t port) + { + set_property<double>(PROP_KEY_SINE_PHASE_INC, phase_inc, port); + } + + double get_sine_phase_increment(const size_t port) const + { + return _prop_phase_inc.at(port).get(); + } + + void set_samples_per_packet(const size_t spp, const size_t port) + { + set_property<int>(PROP_KEY_SPP, uhd::narrow_cast<int>(spp), port); + } + + size_t get_samples_per_packet(const size_t port) const + { + return _prop_spp.at(port).get(); + } + + /************************************************************************** + * Initialization + *************************************************************************/ +private: + void _register_props() + { + const size_t num_outputs = get_num_output_ports(); + _prop_enable.reserve(num_outputs); + _prop_waveform.reserve(num_outputs); + _prop_amplitude.reserve(num_outputs); + _prop_constant_i.reserve(num_outputs); + _prop_constant_q.reserve(num_outputs); + _prop_phase_inc.reserve(num_outputs); + _prop_spp.reserve(num_outputs); + _prop_type_out.reserve(num_outputs); + + for (size_t port = 0; port < num_outputs; port++) { + // register edge properties + _prop_type_out.emplace_back(property_t<std::string>{ + PROP_KEY_TYPE, IO_TYPE_SC16, {res_source_info::OUTPUT_EDGE, port}}); + register_property(&_prop_type_out.back()); + + // register user properties + _prop_enable.emplace_back( + property_t<bool>{PROP_KEY_ENABLE, false, {res_source_info::USER, port}}); + _prop_waveform.emplace_back(property_t<int>{PROP_KEY_WAVEFORM, + static_cast<int>(siggen_waveform::CONSTANT), + {res_source_info::USER, port}}); + _prop_amplitude.emplace_back(property_t<double>{ + PROP_KEY_AMPLITUDE, 1.0, {res_source_info::USER, port}}); + _prop_constant_i.emplace_back(property_t<double>{ + PROP_KEY_CONSTANT_I, 1.0, {res_source_info::USER, port}}); + _prop_constant_q.emplace_back(property_t<double>{ + PROP_KEY_CONSTANT_Q, 1.0, {res_source_info::USER, port}}); + _prop_phase_inc.emplace_back(property_t<double>{ + PROP_KEY_SINE_PHASE_INC, 1.0, {res_source_info::USER, port}}); + _prop_spp.emplace_back(property_t<int>{ + PROP_KEY_SPP, DEFAULT_SPP, {res_source_info::USER, port}}); + + register_property(&_prop_enable.back(), [this, port]() { + _siggen_reg_iface.poke32(REG_ENABLE_OFFSET, + uint32_t(_prop_enable.at(port).get() ? 1 : 0), + port); + }); + register_property(&_prop_waveform.back()); + register_property(&_prop_amplitude.back()); + register_property(&_prop_constant_i.back(), [this, port]() { + const double constant_i = _prop_constant_i.at(port).get(); + if (constant_i < -1.0 || constant_i > 1.0) { + throw uhd::value_error("Constant real value must be in [-1.0, 1.0]"); + } + _set_constant_register(port); + }); + register_property(&_prop_constant_q.back(), [this, port]() { + const double constant_q = _prop_constant_q.at(port).get(); + if (constant_q < -1.0 || constant_q > 1.0) { + throw uhd::value_error( + "Constant imaginary value must be in [-1.0, 1.0]"); + } + _set_constant_register(port); + }); + register_property(&_prop_phase_inc.back(), [this, port]() { + const double phase_inc = _prop_phase_inc.at(port).get(); + if (phase_inc < (-M_PI) || phase_inc > (M_PI)) { + throw uhd::value_error( + "Phase increment value must be in [-pi, pi]"); + } + const int16_t phase_inc_scaled_rads_fp = + clamp<int16_t>((phase_inc / M_PI) * 8192.0); + _siggen_reg_iface.poke32( + REG_PHASE_INC_OFFSET, phase_inc_scaled_rads_fp & 0xffff, port); + }); + register_property(&_prop_spp.back(), [this, port]() { + const uint32_t spp = _prop_spp.at(port).get(); + RFNOC_LOG_TRACE("Setting samples per packet to " << words_per_pkt + << " on chan " << chan); + _siggen_reg_iface.poke32(REG_SPP_OFFSET, spp, port); + }); + + add_property_resolver({&_prop_waveform.back(), &_prop_amplitude.back()}, + {&_prop_amplitude.back()}, + [this, port]() { + // Range check the waveform and amplitude properties. + // If either are out of range, throw an exception and + // do not set any registers. + const int waveform_val = _prop_waveform.at(port).get(); + const int low_limit = static_cast<int>(siggen_waveform::CONSTANT); + const int high_limit = static_cast<int>(siggen_waveform::NOISE); + if (waveform_val < low_limit || waveform_val > high_limit) { + throw uhd::value_error("Waveform value must be in [" + + std::to_string(low_limit) + ", " + + std::to_string(high_limit) + "]"); + } + const double amplitude = _prop_amplitude.at(port).get(); + if (amplitude < 0.0 || amplitude > 1.0) { + throw uhd::value_error("Amplitude value must be in [0.0, 1.0]"); + } + + // Set the waveform register appropriately. + _siggen_reg_iface.poke32(REG_WAVEFORM_OFFSET, waveform_val, port); + + // Now set the other registers based on the waveform and + // the desired amplitude. + siggen_waveform waveform = static_cast<siggen_waveform>(waveform_val); + switch (waveform) { + case siggen_waveform::CONSTANT: + // The amplitude is fixed at 1 in constant mode. + _prop_amplitude.at(port).set(1.0); + _set_gain_register(1.0, port); + break; + case siggen_waveform::SINE_WAVE: { + // Set the phasor to the appropriate amplitude value and + // fix the gain to 1. + + // The CORDIC IP scales the value written to the Cartesian + // coordinate register (i.e., the phasor that is rotated to + // generate the sinusoid) by this value, so we pre-scale the + // input value before writing. See the comment in the + // rfnoc_block_siggen_regs.vh header file for the derivation + // of this value. + constexpr double cordic_scale_value = 1.164435344782938; + _set_cartesian_register(amplitude / cordic_scale_value, port); + _set_gain_register(1.0, port); + break; + } + case siggen_waveform::NOISE: + // Use the gain register to set the gain of the random noise + // signal. + _set_gain_register(amplitude, port); + break; + } + }); + + add_property_resolver( + {&_prop_spp.back(), + get_mtu_prop_ref({res_source_info::OUTPUT_EDGE, port})}, + {&_prop_spp.back()}, + [this, port]() { + // MTU is max payload size, header with timestamp is already + // accounted for + int spp = _prop_spp.at(port).get(); + const int mtu = + static_cast<int>(get_mtu({res_source_info::OUTPUT_EDGE, port})); + const int mtu_samps = + mtu + / uhd::convert::get_bytes_per_item(_prop_type_out.at(port).get()); + if (spp > mtu_samps) { + RFNOC_LOG_WARNING("spp value " << spp << " exceeds MTU of " << mtu + << "! Coercing to " << mtu_samps); + spp = mtu_samps; + } + if (spp <= 0) { + spp = DEFAULT_SPP; + RFNOC_LOG_WARNING( + "spp must be greater than zero! Coercing to " << spp); + } + _prop_spp.at(port).set(spp); + }); + + // add resolver for type + add_property_resolver({&_prop_type_out.back()}, + {&_prop_type_out.back()}, + [this, port]() { _prop_type_out.at(port).set(IO_TYPE_SC16); }); + } + } + + void _set_constant_register(const size_t port) + { + const int16_t constant_i_fp = + clamp<int16_t>(_prop_constant_i.at(port).get() * 32768.0); + const int16_t constant_q_fp = + clamp<int16_t>(_prop_constant_q.at(port).get() * 32768.0); + const uint32_t constant_reg_value = (uint32_t(constant_i_fp) << 16) + | (uint32_t(constant_q_fp) & 0xffff); + + _siggen_reg_iface.poke32(REG_CONSTANT_OFFSET, constant_reg_value, port); + } + + void _set_gain_register(const double gain, const size_t port) + { + const int16_t gain_fp = clamp<int16_t>(gain * 32768.0); + _siggen_reg_iface.poke32(REG_GAIN_OFFSET, gain_fp, port); + } + + void _set_cartesian_register(const double amplitude, const size_t port) + { + // The rotator that rotates the phasor to generate the sinusoidal + // data has an initial phase offset which is impossible to predict. + // Thus, the Cartesian parameter is largely immaterial, as long as + // the phasor's amplitude matchines with the client has specified. + // For simplicity, the Cartesian parameter is chosen to have a real + // (X) component of 0.0 and an imaginary (Y) component of the desired + // amplitude. + const int16_t cartesian_i_fp = clamp<int16_t>(amplitude * 32767.0); + + // Bits 31:16 represent the imaginary component (the pre-scaled + // fixed point amplitude), while bits 15:0 represents the real + // component (which are zeroed). + const uint32_t cartesian_reg_value = (uint32_t(cartesian_i_fp) << 16); + _siggen_reg_iface.poke32(REG_CARTESIAN_OFFSET, cartesian_reg_value, port); + } + + /************************************************************************** + * Attributes + *************************************************************************/ + std::vector<property_t<bool>> _prop_enable; + std::vector<property_t<int>> _prop_waveform; + std::vector<property_t<double>> _prop_amplitude; + std::vector<property_t<double>> _prop_constant_i; + std::vector<property_t<double>> _prop_constant_q; + std::vector<property_t<double>> _prop_phase_inc; + std::vector<property_t<int>> _prop_spp; + std::vector<property_t<std::string>> _prop_type_out; + + /************************************************************************** + * Register interface + *************************************************************************/ + multichan_register_iface _siggen_reg_iface; +}; + +UHD_RFNOC_BLOCK_REGISTER_DIRECT( + siggen_block_control, SIGGEN_BLOCK, "SigGen", CLOCK_KEY_GRAPH, "bus_clk") |