aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/rfnoc/siggen_block_control.cpp
diff options
context:
space:
mode:
authorAaron Rossetto <aaron.rossetto@ni.com>2020-07-24 10:27:05 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-07-30 13:03:16 -0500
commit19171e719c1173b2f57dcbe355b74e401956e0c6 (patch)
treecdc13580d7fcb1516b9ed72d7e9e8aa7f2b7a828 /host/lib/rfnoc/siggen_block_control.cpp
parent1e94f85b8bafc3f9acab7ef35d2675fa7e61f6f4 (diff)
downloaduhd-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.cpp343
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")