diff options
author | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-24 10:27:05 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-07-30 13:03:16 -0500 |
commit | 19171e719c1173b2f57dcbe355b74e401956e0c6 (patch) | |
tree | cdc13580d7fcb1516b9ed72d7e9e8aa7f2b7a828 /host/lib/rfnoc/siggen_block_control.cpp | |
parent | 1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4 (diff) | |
download | uhd-19171e719c1173b2f57dcbe355b74e401956e0c6.tar.gz uhd-19171e719c1173b2f57dcbe355b74e401956e0c6.tar.bz2 uhd-19171e719c1173b2f57dcbe355b74e401956e0c6.zip |
rfnoc: Add siggen RFNoC block controller support
Diffstat (limited to 'host/lib/rfnoc/siggen_block_control.cpp')
-rw-r--r-- | host/lib/rfnoc/siggen_block_control.cpp | 343 |
1 files changed, 343 insertions, 0 deletions
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") |