aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2020-03-03 16:10:06 -0800
committerAaron Rossetto <aaron.rossetto@ni.com>2020-04-02 11:55:17 -0500
commit3fe5ccf700a0c6f27dca9511386460194dcc593c (patch)
tree19d394faac0581601d3d6e5d35a7a82e325bf245
parent1e016e49e82c430197a90a018fbc7613cc654088 (diff)
downloaduhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.tar.gz
uhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.tar.bz2
uhd-3fe5ccf700a0c6f27dca9511386460194dcc593c.zip
uhd: cal: Add iq_cal calibration data container class
This class can be used to store calibration coefficients for the X300 DC offset and IQ imbalance calibration. Note: This also modifies Doxyfile.in to not document files generated by flatc.
-rw-r--r--host/docs/Doxyfile.in2
-rw-r--r--host/include/uhd/cal/CMakeLists.txt8
-rw-r--r--host/include/uhd/cal/cal_metadata.fbs15
-rw-r--r--host/include/uhd/cal/cal_metadata_generated.h111
-rw-r--r--host/include/uhd/cal/container.hpp9
-rw-r--r--host/include/uhd/cal/iq_cal.fbs32
-rw-r--r--host/include/uhd/cal/iq_cal.hpp78
-rw-r--r--host/include/uhd/cal/iq_cal_generated.h162
-rw-r--r--host/lib/cal/CMakeLists.txt2
-rw-r--r--host/lib/cal/cal_python.hpp38
-rw-r--r--host/lib/cal/iq_cal.cpp197
-rw-r--r--host/python/pyuhd.cpp5
-rw-r--r--host/python/uhd/usrp/__init__.py2
-rw-r--r--host/python/uhd/usrp/cal/__init__.py16
-rw-r--r--host/python/uhd/usrp/cal/libtypes.py2
-rw-r--r--host/tests/CMakeLists.txt1
-rw-r--r--host/tests/cal_data_iq_test.cpp81
17 files changed, 760 insertions, 1 deletions
diff --git a/host/docs/Doxyfile.in b/host/docs/Doxyfile.in
index 36ae2208c..45b65b399 100644
--- a/host/docs/Doxyfile.in
+++ b/host/docs/Doxyfile.in
@@ -702,7 +702,7 @@ EXCLUDE_SYMLINKS = NO
# against the file with absolute path, so to exclude all test directories
# for example use the pattern */test/*
-EXCLUDE_PATTERNS =
+EXCLUDE_PATTERNS = */*_generated.h
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
diff --git a/host/include/uhd/cal/CMakeLists.txt b/host/include/uhd/cal/CMakeLists.txt
index b3f724d2e..2aba1a91c 100644
--- a/host/include/uhd/cal/CMakeLists.txt
+++ b/host/include/uhd/cal/CMakeLists.txt
@@ -8,7 +8,15 @@ UHD_INSTALL(FILES
database.hpp
container.hpp
interpolation.hpp
+ iq_cal.hpp
+ iq_cal_generated.h
DESTINATION ${INCLUDE_DIR}/uhd/cal
COMPONENT headers
)
+UHD_INSTALL(FILES
+ iq_cal.fbs
+ DESTINATION ${PKG_DATA_DIR}/cal
+ COMPONENT headers
+)
+
diff --git a/host/include/uhd/cal/cal_metadata.fbs b/host/include/uhd/cal/cal_metadata.fbs
new file mode 100644
index 000000000..ce380c141
--- /dev/null
+++ b/host/include/uhd/cal/cal_metadata.fbs
@@ -0,0 +1,15 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+// Common metadata for all calibration containers
+table Metadata
+{
+ name: string; // Can be given arbitrary, based on calibration
+ serial: string; // Device serial
+ timestamp: uint64; // Unix timestamp
+ version_major: int; // Cal data version (major)
+ version_minor: int; // Cal data version (minor)
+}
diff --git a/host/include/uhd/cal/cal_metadata_generated.h b/host/include/uhd/cal/cal_metadata_generated.h
new file mode 100644
index 000000000..3b4a6a8e7
--- /dev/null
+++ b/host/include/uhd/cal/cal_metadata_generated.h
@@ -0,0 +1,111 @@
+// automatically generated by the FlatBuffers compiler, do not modify
+
+
+#ifndef FLATBUFFERS_GENERATED_CALMETADATA_H_
+#define FLATBUFFERS_GENERATED_CALMETADATA_H_
+
+#include "flatbuffers/flatbuffers.h"
+
+struct Metadata;
+
+struct Metadata FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_NAME = 4,
+ VT_SERIAL = 6,
+ VT_TIMESTAMP = 8,
+ VT_VERSION_MAJOR = 10,
+ VT_VERSION_MINOR = 12
+ };
+ const flatbuffers::String *name() const {
+ return GetPointer<const flatbuffers::String *>(VT_NAME);
+ }
+ const flatbuffers::String *serial() const {
+ return GetPointer<const flatbuffers::String *>(VT_SERIAL);
+ }
+ uint64_t timestamp() const {
+ return GetField<uint64_t>(VT_TIMESTAMP, 0);
+ }
+ int32_t version_major() const {
+ return GetField<int32_t>(VT_VERSION_MAJOR, 0);
+ }
+ int32_t version_minor() const {
+ return GetField<int32_t>(VT_VERSION_MINOR, 0);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyOffset(verifier, VT_NAME) &&
+ verifier.VerifyString(name()) &&
+ VerifyOffset(verifier, VT_SERIAL) &&
+ verifier.VerifyString(serial()) &&
+ VerifyField<uint64_t>(verifier, VT_TIMESTAMP) &&
+ VerifyField<int32_t>(verifier, VT_VERSION_MAJOR) &&
+ VerifyField<int32_t>(verifier, VT_VERSION_MINOR) &&
+ verifier.EndTable();
+ }
+};
+
+struct MetadataBuilder {
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_name(flatbuffers::Offset<flatbuffers::String> name) {
+ fbb_.AddOffset(Metadata::VT_NAME, name);
+ }
+ void add_serial(flatbuffers::Offset<flatbuffers::String> serial) {
+ fbb_.AddOffset(Metadata::VT_SERIAL, serial);
+ }
+ void add_timestamp(uint64_t timestamp) {
+ fbb_.AddElement<uint64_t>(Metadata::VT_TIMESTAMP, timestamp, 0);
+ }
+ void add_version_major(int32_t version_major) {
+ fbb_.AddElement<int32_t>(Metadata::VT_VERSION_MAJOR, version_major, 0);
+ }
+ void add_version_minor(int32_t version_minor) {
+ fbb_.AddElement<int32_t>(Metadata::VT_VERSION_MINOR, version_minor, 0);
+ }
+ explicit MetadataBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ MetadataBuilder &operator=(const MetadataBuilder &);
+ flatbuffers::Offset<Metadata> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<Metadata>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<Metadata> CreateMetadata(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<flatbuffers::String> name = 0,
+ flatbuffers::Offset<flatbuffers::String> serial = 0,
+ uint64_t timestamp = 0,
+ int32_t version_major = 0,
+ int32_t version_minor = 0) {
+ MetadataBuilder builder_(_fbb);
+ builder_.add_timestamp(timestamp);
+ builder_.add_version_minor(version_minor);
+ builder_.add_version_major(version_major);
+ builder_.add_serial(serial);
+ builder_.add_name(name);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<Metadata> CreateMetadataDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ const char *name = nullptr,
+ const char *serial = nullptr,
+ uint64_t timestamp = 0,
+ int32_t version_major = 0,
+ int32_t version_minor = 0) {
+ auto name__ = name ? _fbb.CreateString(name) : 0;
+ auto serial__ = serial ? _fbb.CreateString(serial) : 0;
+ return CreateMetadata(
+ _fbb,
+ name__,
+ serial__,
+ timestamp,
+ version_major,
+ version_minor);
+}
+
+#endif // FLATBUFFERS_GENERATED_CALMETADATA_H_
diff --git a/host/include/uhd/cal/container.hpp b/host/include/uhd/cal/container.hpp
index c3035c610..14f28d5ff 100644
--- a/host/include/uhd/cal/container.hpp
+++ b/host/include/uhd/cal/container.hpp
@@ -25,6 +25,15 @@ class UHD_API container
public:
virtual ~container() = default;
+ //! Return the name of this calibration table
+ virtual std::string get_name() const = 0;
+
+ //! Return the device serial of this calibration table
+ virtual std::string get_serial() const = 0;
+
+ //! Timestamp of acquisition time
+ virtual uint64_t get_timestamp() const = 0;
+
//! Return a serialized version of this container
virtual std::vector<uint8_t> serialize() = 0;
diff --git a/host/include/uhd/cal/iq_cal.fbs b/host/include/uhd/cal/iq_cal.fbs
new file mode 100644
index 000000000..55c9ae3de
--- /dev/null
+++ b/host/include/uhd/cal/iq_cal.fbs
@@ -0,0 +1,32 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+// Calibration Table for I/Q Balance correction coefficients
+// This is a very simple calibration coefficient table, and
+// works for Gen-3 cals (IQ balance and DC offset) which have
+// a single real/imag pair of coefficients per frequency.
+
+include "cal_metadata.fbs";
+
+namespace uhd.usrp.cal;
+
+struct IQCalCoeff
+{
+ freq: double;
+ coeff_real: double;
+ coeff_imag: double;
+ suppression_abs: double;
+ suppression_delta: double;
+}
+
+table IQCalCoeffs
+{
+ metadata: Metadata;
+ coeffs: [IQCalCoeff];
+}
+
+root_type IQCalCoeffs;
+file_identifier "IQ/f"; // I/Q data per frequency
diff --git a/host/include/uhd/cal/iq_cal.hpp b/host/include/uhd/cal/iq_cal.hpp
new file mode 100644
index 000000000..ded082698
--- /dev/null
+++ b/host/include/uhd/cal/iq_cal.hpp
@@ -0,0 +1,78 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_CAL_IQ_DATA_HPP
+#define INCLUDED_LIBUHD_CAL_IQ_DATA_HPP
+
+#include <uhd/cal/container.hpp>
+#include <uhd/cal/interpolation.hpp>
+#include <uhd/config.hpp>
+#include <complex>
+#include <memory>
+#include <string>
+
+namespace uhd { namespace usrp { namespace cal {
+
+/*! Class that stores IQ cal data per frequency
+ *
+ * The following calibrations use this:
+ * - Gen-2 and Gen-3 TX DC Offset
+ * - Gen-2 and Gen-3 RX,TX IQ Imbalance
+ */
+class UHD_API iq_cal : public container
+{
+public:
+ using sptr = std::shared_ptr<iq_cal>;
+
+ //! Choose interpolation mode
+ //
+ // This class supports two kinds of interpolation: Nearest-neighbour, and
+ // linear.
+ //
+ // \param interp The new interpolation mode
+ // \throws uhd::value_error if the given interpolation mode is not
+ // supported.
+ virtual void set_interp_mode(const interp_mode interp) = 0;
+
+ //! Return a calibration coefficient for a given frequency
+ //
+ // This function will interpolate to return a valid coefficient for any
+ // given frequency.
+ virtual std::complex<double> get_cal_coeff(const double freq) const = 0;
+
+ //! Update / set a calbration coefficient
+ //
+ // This usually only needs to called by calibration utilities.
+ //
+ // \param freq The frequency at which this coefficient is measured
+ // \param coeff The value that is stored
+ // \param suppression_abs The amount of impairment suppression this
+ // coefficient provides, in dB.
+ // \param suppression_delta The difference of impairment power between
+ // applying this coefficient and applying none, in
+ // dB.
+ virtual void set_cal_coeff(const double freq,
+ const std::complex<double> coeff,
+ const double suppression_abs = 0,
+ const double suppression_delta = 0) = 0;
+
+ //! Clear the list of coefficients
+ //
+ // This can be useful in order to drop existing cal data, and load an
+ // entirely new set with deserialize().
+ virtual void clear() = 0;
+
+ //! Factory for new cal data sets
+ static sptr make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp);
+
+ //! Default factory
+ static sptr make();
+};
+
+}}} // namespace uhd::usrp::cal
+
+#endif /* INCLUDED_LIBUHD_CAL_IQ_DATA_HPP */
diff --git a/host/include/uhd/cal/iq_cal_generated.h b/host/include/uhd/cal/iq_cal_generated.h
new file mode 100644
index 000000000..f0dfa0b82
--- /dev/null
+++ b/host/include/uhd/cal/iq_cal_generated.h
@@ -0,0 +1,162 @@
+// automatically generated by the FlatBuffers compiler, do not modify
+
+
+#ifndef FLATBUFFERS_GENERATED_IQCAL_UHD_USRP_CAL_H_
+#define FLATBUFFERS_GENERATED_IQCAL_UHD_USRP_CAL_H_
+
+#include "flatbuffers/flatbuffers.h"
+
+#include "cal_metadata_generated.h"
+
+namespace uhd {
+namespace usrp {
+namespace cal {
+
+struct IQCalCoeff;
+
+struct IQCalCoeffs;
+
+FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(8) IQCalCoeff FLATBUFFERS_FINAL_CLASS {
+ private:
+ double freq_;
+ double coeff_real_;
+ double coeff_imag_;
+ double suppression_abs_;
+ double suppression_delta_;
+
+ public:
+ IQCalCoeff() {
+ memset(static_cast<void *>(this), 0, sizeof(IQCalCoeff));
+ }
+ IQCalCoeff(double _freq, double _coeff_real, double _coeff_imag, double _suppression_abs, double _suppression_delta)
+ : freq_(flatbuffers::EndianScalar(_freq)),
+ coeff_real_(flatbuffers::EndianScalar(_coeff_real)),
+ coeff_imag_(flatbuffers::EndianScalar(_coeff_imag)),
+ suppression_abs_(flatbuffers::EndianScalar(_suppression_abs)),
+ suppression_delta_(flatbuffers::EndianScalar(_suppression_delta)) {
+ }
+ double freq() const {
+ return flatbuffers::EndianScalar(freq_);
+ }
+ double coeff_real() const {
+ return flatbuffers::EndianScalar(coeff_real_);
+ }
+ double coeff_imag() const {
+ return flatbuffers::EndianScalar(coeff_imag_);
+ }
+ double suppression_abs() const {
+ return flatbuffers::EndianScalar(suppression_abs_);
+ }
+ double suppression_delta() const {
+ return flatbuffers::EndianScalar(suppression_delta_);
+ }
+};
+FLATBUFFERS_STRUCT_END(IQCalCoeff, 40);
+
+struct IQCalCoeffs FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
+ VT_METADATA = 4,
+ VT_COEFFS = 6
+ };
+ const Metadata *metadata() const {
+ return GetPointer<const Metadata *>(VT_METADATA);
+ }
+ const flatbuffers::Vector<const IQCalCoeff *> *coeffs() const {
+ return GetPointer<const flatbuffers::Vector<const IQCalCoeff *> *>(VT_COEFFS);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyOffset(verifier, VT_METADATA) &&
+ verifier.VerifyTable(metadata()) &&
+ VerifyOffset(verifier, VT_COEFFS) &&
+ verifier.VerifyVector(coeffs()) &&
+ verifier.EndTable();
+ }
+};
+
+struct IQCalCoeffsBuilder {
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_metadata(flatbuffers::Offset<Metadata> metadata) {
+ fbb_.AddOffset(IQCalCoeffs::VT_METADATA, metadata);
+ }
+ void add_coeffs(flatbuffers::Offset<flatbuffers::Vector<const IQCalCoeff *>> coeffs) {
+ fbb_.AddOffset(IQCalCoeffs::VT_COEFFS, coeffs);
+ }
+ explicit IQCalCoeffsBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ IQCalCoeffsBuilder &operator=(const IQCalCoeffsBuilder &);
+ flatbuffers::Offset<IQCalCoeffs> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<IQCalCoeffs>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<IQCalCoeffs> CreateIQCalCoeffs(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ flatbuffers::Offset<flatbuffers::Vector<const IQCalCoeff *>> coeffs = 0) {
+ IQCalCoeffsBuilder builder_(_fbb);
+ builder_.add_coeffs(coeffs);
+ builder_.add_metadata(metadata);
+ return builder_.Finish();
+}
+
+inline flatbuffers::Offset<IQCalCoeffs> CreateIQCalCoeffsDirect(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ flatbuffers::Offset<Metadata> metadata = 0,
+ const std::vector<IQCalCoeff> *coeffs = nullptr) {
+ auto coeffs__ = coeffs ? _fbb.CreateVectorOfStructs<IQCalCoeff>(*coeffs) : 0;
+ return uhd::usrp::cal::CreateIQCalCoeffs(
+ _fbb,
+ metadata,
+ coeffs__);
+}
+
+inline const uhd::usrp::cal::IQCalCoeffs *GetIQCalCoeffs(const void *buf) {
+ return flatbuffers::GetRoot<uhd::usrp::cal::IQCalCoeffs>(buf);
+}
+
+inline const uhd::usrp::cal::IQCalCoeffs *GetSizePrefixedIQCalCoeffs(const void *buf) {
+ return flatbuffers::GetSizePrefixedRoot<uhd::usrp::cal::IQCalCoeffs>(buf);
+}
+
+inline const char *IQCalCoeffsIdentifier() {
+ return "IQ/f";
+}
+
+inline bool IQCalCoeffsBufferHasIdentifier(const void *buf) {
+ return flatbuffers::BufferHasIdentifier(
+ buf, IQCalCoeffsIdentifier());
+}
+
+inline bool VerifyIQCalCoeffsBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifyBuffer<uhd::usrp::cal::IQCalCoeffs>(IQCalCoeffsIdentifier());
+}
+
+inline bool VerifySizePrefixedIQCalCoeffsBuffer(
+ flatbuffers::Verifier &verifier) {
+ return verifier.VerifySizePrefixedBuffer<uhd::usrp::cal::IQCalCoeffs>(IQCalCoeffsIdentifier());
+}
+
+inline void FinishIQCalCoeffsBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::IQCalCoeffs> root) {
+ fbb.Finish(root, IQCalCoeffsIdentifier());
+}
+
+inline void FinishSizePrefixedIQCalCoeffsBuffer(
+ flatbuffers::FlatBufferBuilder &fbb,
+ flatbuffers::Offset<uhd::usrp::cal::IQCalCoeffs> root) {
+ fbb.FinishSizePrefixed(root, IQCalCoeffsIdentifier());
+}
+
+} // namespace cal
+} // namespace usrp
+} // namespace uhd
+
+#endif // FLATBUFFERS_GENERATED_IQCAL_UHD_USRP_CAL_H_
diff --git a/host/lib/cal/CMakeLists.txt b/host/lib/cal/CMakeLists.txt
index 5c2c8a617..fb1f4b0e3 100644
--- a/host/lib/cal/CMakeLists.txt
+++ b/host/lib/cal/CMakeLists.txt
@@ -7,7 +7,9 @@
########################################################################
# This file included, use CMake directory variables
########################################################################
+include_directories(${CMAKE_SOURCE_DIR}/lib/deps/flatbuffers/include)
LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/database.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/iq_cal.cpp
)
diff --git a/host/lib/cal/cal_python.hpp b/host/lib/cal/cal_python.hpp
index 0fe87046f..39a13d94c 100644
--- a/host/lib/cal/cal_python.hpp
+++ b/host/lib/cal/cal_python.hpp
@@ -8,6 +8,8 @@
#define INCLUDED_UHD_CAL_PYTHON_HPP
#include <uhd/cal/database.hpp>
+#include <uhd/cal/interpolation.hpp>
+#include <uhd/cal/iq_cal.hpp>
std::vector<uint8_t> pybytes_to_vector(const py::bytes& data)
{
@@ -56,6 +58,42 @@ void export_cal(py::module& m)
[](const std::string& key, const std::string& serial, const py::bytes data) {
database::write_cal_data(key, serial, pybytes_to_vector(data));
});
+
+ py::enum_<interp_mode>(m, "interp_mode")
+ .value("NEAREST_NEIGHBOR", interp_mode::NEAREST_NEIGHBOR)
+ .value("LINEAR", interp_mode::LINEAR);
+
+ py::class_<container, std::shared_ptr<container>>(m, "container")
+ .def("get_name", &container::get_name)
+ .def("get_serial", &container::get_serial)
+ .def("get_timestamp", &container::get_name)
+ .def("serialize",
+ [](std::shared_ptr<container>& self) {
+ return vector_to_pybytes(self->serialize());
+ })
+ .def("deserialize", [](std::shared_ptr<container>& self, const py::bytes data) {
+ self->deserialize(pybytes_to_vector(data));
+ });
+
+ py::class_<iq_cal, container, iq_cal::sptr>(m, "iq_cal")
+ .def(py::init([](const std::string& name,
+ const std::string& serial,
+ const uint64_t timestamp) {
+ return iq_cal::make(name, serial, timestamp);
+ }))
+ .def(py::init([]() { return iq_cal::make(); }))
+ .def(py::init([](const py::bytes data) {
+ return container::make<iq_cal>(pybytes_to_vector(data));
+ }))
+ .def("set_interp_mode", &iq_cal::set_interp_mode)
+ .def("get_cal_coeff", &iq_cal::get_cal_coeff)
+ .def("set_cal_coeff",
+ &iq_cal::set_cal_coeff,
+ py::arg("freq"),
+ py::arg("coeff"),
+ py::arg("suppression_abs") = 0,
+ py::arg("suppression_delta") = 0)
+ .def("clear", &iq_cal::clear);
}
#endif /* INCLUDED_UHD_CAL_PYTHON_HPP */
diff --git a/host/lib/cal/iq_cal.cpp b/host/lib/cal/iq_cal.cpp
new file mode 100644
index 000000000..e1ed8c9cb
--- /dev/null
+++ b/host/lib/cal/iq_cal.cpp
@@ -0,0 +1,197 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/iq_cal.hpp>
+#include <uhd/cal/iq_cal_generated.h>
+#include <uhd/exception.hpp>
+#include <uhd/utils/math.hpp>
+#include <map>
+#include <string>
+
+using namespace uhd::usrp::cal;
+
+constexpr int VERSION_MAJOR = 1;
+constexpr int VERSION_MINOR = 0;
+
+/***********************************************************************
+ * Helper routines
+ **********************************************************************/
+class iq_cal_impl : public iq_cal
+{
+public:
+ iq_cal_impl(const std::string& name = "",
+ const std::string& serial = "",
+ const uint64_t timestamp = 0)
+ : _name(name)
+ , _serial(serial)
+ , _timestamp(timestamp)
+ , _interp(interp_mode::LINEAR)
+ {
+ }
+
+ std::string get_name() const
+ {
+ return _name;
+ }
+
+ //! Return the name of this calibration table
+ std::string get_serial() const
+ {
+ return _serial;
+ }
+
+ //! Timestamp of acquisition time
+ uint64_t get_timestamp() const
+ {
+ return _timestamp;
+ }
+
+ void set_interp_mode(const interp_mode interp)
+ {
+ UHD_ASSERT_THROW(
+ interp == interp_mode::LINEAR || interp == interp_mode::NEAREST_NEIGHBOR);
+ _interp = interp;
+ }
+
+ std::complex<double> get_cal_coeff(const double freq) const
+ {
+ UHD_ASSERT_THROW(!_coeffs.empty());
+ // Find the first coefficient in the map that maps to a larger frequency
+ // than freq (or equal)
+ auto next_coeff = _coeffs.lower_bound(freq);
+ if (next_coeff == _coeffs.end()) {
+ // This means freq is larger than our biggest key, and thus we
+ // can't interpolate. We return the coeffs of the largest key.
+ return _coeffs.rbegin()->second;
+ }
+ if (next_coeff == _coeffs.begin()) {
+ // This means freq is smaller than our smallest key, and thus we
+ // can't interpolate. We return the coeffs of the smallest key.
+ return _coeffs.begin()->second;
+ }
+ // Stash away freqs and coeffs for easier code
+ const auto hi_freq = next_coeff->first;
+ const auto hi_coeff = next_coeff->second;
+ next_coeff--;
+ const auto lo_coeff = next_coeff->second;
+ const auto lo_freq = next_coeff->first; // lo == low, not LO
+ // Now, we're guaranteed to be between two points
+ if (_interp == interp_mode::NEAREST_NEIGHBOR) {
+ return (hi_freq - freq) < (freq - lo_freq) ? hi_coeff : lo_coeff;
+ }
+ using uhd::math::linear_interp;
+ return std::complex<double>(
+ linear_interp<double>(
+ freq, lo_freq, lo_coeff.real(), hi_freq, hi_coeff.real()),
+ linear_interp<double>(
+ freq, lo_freq, lo_coeff.imag(), hi_freq, hi_coeff.imag()));
+ }
+
+ void set_cal_coeff(const double freq,
+ const std::complex<double> coeff,
+ const double suppression_abs = 0,
+ const double suppression_delta = 0)
+ {
+ _coeffs[freq] = coeff;
+ _supp[freq] = {suppression_abs, suppression_delta};
+ }
+
+ void clear()
+ {
+ _coeffs.clear();
+ _supp.clear();
+ }
+
+ /**************************************************************************
+ * Container API (Serialization/Deserialization)
+ *************************************************************************/
+ std::vector<uint8_t> serialize()
+ {
+ // This is a magic value to estimate the amount of space the builder will
+ // have to reserve on top of the coeff data.
+ // Worst case is we get this too low, and the builder will have to do a
+ // single reallocation later.
+ constexpr size_t RESERVE_HDR_BYTES = 20;
+ const size_t initial_size_bytes =
+ sizeof(IQCalCoeff) * _coeffs.size() + RESERVE_HDR_BYTES;
+ flatbuffers::FlatBufferBuilder builder(initial_size_bytes);
+ // Convert the coefficients to a vector of IQCalCoeff
+ std::vector<IQCalCoeff> fb_coeffs;
+ fb_coeffs.reserve(_coeffs.size());
+ std::transform(_coeffs.cbegin(),
+ _coeffs.cend(),
+ std::back_inserter(fb_coeffs),
+ [&](const coeffs_type::value_type& coeff) {
+ const double freq = coeff.first;
+ const auto this_coeff = coeff.second;
+ return IQCalCoeff(freq,
+ this_coeff.real(),
+ this_coeff.imag(),
+ _supp.count(freq) ? _supp.at(freq).first : 0.0,
+ _supp.count(freq) ? _supp.at(freq).second : 0.0);
+ });
+ // Now load it all into the FlatBuffer
+ const auto metadata = CreateMetadataDirect(builder,
+ _name.c_str(),
+ _serial.c_str(),
+ _timestamp,
+ VERSION_MAJOR,
+ VERSION_MINOR);
+ auto cal_table = CreateIQCalCoeffsDirect(builder, metadata, &fb_coeffs);
+ FinishIQCalCoeffsBuffer(builder, cal_table);
+ const size_t table_size = builder.GetSize();
+ const uint8_t* table = builder.GetBufferPointer();
+ return std::vector<uint8_t>(table, table + table_size);
+ }
+
+ // This will amend the existing table. If that's not desired, then it is
+ // necessary to call clear() ahead of time.
+ void deserialize(const std::vector<uint8_t>& data)
+ {
+ auto verifier = flatbuffers::Verifier(data.data(), data.size());
+ if (!VerifyIQCalCoeffsBuffer(verifier)) {
+ throw uhd::runtime_error("iq_cal: Invalid data provided!");
+ }
+ auto cal_table = GetIQCalCoeffs(static_cast<const void*>(data.data()));
+ // TODO we can handle this more nicely
+ UHD_ASSERT_THROW(cal_table->metadata()->version_major() == VERSION_MAJOR);
+ _name = std::string(cal_table->metadata()->name()->c_str());
+ _serial = std::string(cal_table->metadata()->serial()->c_str());
+ _timestamp = cal_table->metadata()->timestamp();
+ auto coeffs = cal_table->coeffs();
+ for (auto it = coeffs->begin(); it != coeffs->end(); ++it) {
+ _coeffs[it->freq()] = {it->coeff_real(), it->coeff_imag()};
+ // Suppression levels are really not necessary for runtime.
+ // TODO: Come up with a way to skip this step when loading data for
+ // runtime (and not future storage)
+ _supp[it->freq()] = {it->suppression_abs(), it->suppression_delta()};
+ }
+ }
+
+
+private:
+ std::string _name;
+ std::string _serial;
+ uint64_t _timestamp;
+ using coeffs_type = std::map<double, std::complex<double>>;
+ coeffs_type _coeffs;
+ // Abs suppression, delta suppression
+ std::map<double, std::pair<double, double>> _supp;
+
+ interp_mode _interp;
+};
+
+
+iq_cal::sptr iq_cal::make()
+{
+ return std::make_shared<iq_cal_impl>();
+}
+
+iq_cal::sptr iq_cal::make(
+ const std::string& name, const std::string& serial, const uint64_t timestamp)
+{
+ return std::make_shared<iq_cal_impl>(name, serial, timestamp);
+}
diff --git a/host/python/pyuhd.cpp b/host/python/pyuhd.cpp
index 9ce72c476..5e8c6424c 100644
--- a/host/python/pyuhd.cpp
+++ b/host/python/pyuhd.cpp
@@ -13,6 +13,7 @@
namespace py = pybind11;
+#include "cal/cal_python.hpp"
#include "rfnoc/rfnoc_python.hpp"
#include "stream_python.hpp"
#include "types/filters_python.hpp"
@@ -73,4 +74,8 @@ PYBIND11_MODULE(libpyuhd, m)
// Register RFNoC submodule
auto rfnoc_module = m.def_submodule("rfnoc", "RFNoC Objects");
export_rfnoc(rfnoc_module);
+
+ // Register calibration submodule
+ auto cal_module = m.def_submodule("cal", "Calibration Objects");
+ export_cal(cal_module);
}
diff --git a/host/python/uhd/usrp/__init__.py b/host/python/uhd/usrp/__init__.py
index c7a630258..6f76066ef 100644
--- a/host/python/uhd/usrp/__init__.py
+++ b/host/python/uhd/usrp/__init__.py
@@ -15,3 +15,5 @@ from .multi_usrp import MultiUSRP
# pylint: disable=wildcard-import
from .libtypes import *
# pylint: enable=wildcard-import
+
+from . import cal
diff --git a/host/python/uhd/usrp/cal/__init__.py b/host/python/uhd/usrp/cal/__init__.py
new file mode 100644
index 000000000..77cc3ca35
--- /dev/null
+++ b/host/python/uhd/usrp/cal/__init__.py
@@ -0,0 +1,16 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Python UHD Module: Calibration sub-module
+"""
+
+# Disable PyLint because the entire libtypes modules is a list of renames. It is
+# thus less redundant to do a wildcard import, even if generally discouraged.
+# We could also paste the contents of libtypes.py into here, but by leaving it
+# separate we avoid importing the lib module in this __init__ file.
+# pylint: disable=wildcard-import
+from .libtypes import *
+# pylint: enable=wildcard-import
diff --git a/host/python/uhd/usrp/cal/libtypes.py b/host/python/uhd/usrp/cal/libtypes.py
index 754028c24..b75165965 100644
--- a/host/python/uhd/usrp/cal/libtypes.py
+++ b/host/python/uhd/usrp/cal/libtypes.py
@@ -16,4 +16,6 @@ from ... import libpyuhd as lib
# database is a class, but we treat it like a namespace, i.e., a submodule
database = lib.cal.database
Source = lib.cal.source
+IQCal = lib.cal.iq_cal
+InterpMode = lib.cal.interp_mode
# pylint: enable=invalid-name
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index f987aa194..2742c0385 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -26,6 +26,7 @@ set(test_sources
byteswap_test.cpp
cast_test.cpp
cal_database_test.cpp
+ cal_data_iq_test.cpp
chdr_test.cpp
constrained_device_args_test.cpp
convert_test.cpp
diff --git a/host/tests/cal_data_iq_test.cpp b/host/tests/cal_data_iq_test.cpp
new file mode 100644
index 000000000..97b35a331
--- /dev/null
+++ b/host/tests/cal_data_iq_test.cpp
@@ -0,0 +1,81 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/cal/iq_cal.hpp>
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::usrp::cal;
+
+BOOST_AUTO_TEST_CASE(test_iq_cal_api)
+{
+ const std::string name = "Mock IQ Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+
+ auto iq_cal_data = iq_cal::make(name, serial, timestamp);
+ BOOST_CHECK_EQUAL(iq_cal_data->get_name(), name);
+ BOOST_CHECK_EQUAL(iq_cal_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(iq_cal_data->get_timestamp(), timestamp);
+ // Nothing there yet
+ BOOST_REQUIRE_THROW(iq_cal_data->get_cal_coeff(0.0), uhd::assertion_error);
+
+ iq_cal_data->set_cal_coeff(1.0, {1.0, 1.0});
+ iq_cal_data->set_cal_coeff(2.0, {2.0, 2.0});
+
+ // We're in linear interpolation mode
+ auto lo_coeff = iq_cal_data->get_cal_coeff(0.5);
+ auto hi_coeff = iq_cal_data->get_cal_coeff(2.5);
+ BOOST_CHECK_EQUAL(lo_coeff, std::complex<double>(1.0, 1.0));
+ BOOST_CHECK_EQUAL(hi_coeff, std::complex<double>(2.0, 2.0));
+ auto mid_coeff = iq_cal_data->get_cal_coeff(1.75);
+ BOOST_CHECK_EQUAL(mid_coeff, std::complex<double>(1.75, 1.75));
+
+ iq_cal_data->set_interp_mode(interp_mode::NEAREST_NEIGHBOR);
+ lo_coeff = iq_cal_data->get_cal_coeff(0.5);
+ hi_coeff = iq_cal_data->get_cal_coeff(2.5);
+ BOOST_CHECK_EQUAL(lo_coeff, std::complex<double>(1.0, 1.0));
+ BOOST_CHECK_EQUAL(hi_coeff, std::complex<double>(2.0, 2.0));
+ mid_coeff = iq_cal_data->get_cal_coeff(1.75);
+ BOOST_CHECK_EQUAL(mid_coeff, std::complex<double>(2.0, 2.0));
+
+ iq_cal_data->clear();
+ BOOST_REQUIRE_THROW(iq_cal_data->get_cal_coeff(0.0), uhd::assertion_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_iq_cal_serdes)
+{
+ const std::string name = "Mock IQ Data";
+ const std::string serial = "ABC1234";
+ const uint64_t timestamp = 0x12340000;
+
+ auto iq_cal_data_blueprint = iq_cal::make(name, serial, timestamp);
+ for (double d = 0; d < 5.0; d += 1.0) {
+ iq_cal_data_blueprint->set_cal_coeff(d, {d, d}, d * 10, d * 20);
+ }
+
+ const auto serialized = iq_cal_data_blueprint->serialize();
+
+ auto iq_cal_data = container::make<iq_cal>(serialized);
+
+ BOOST_CHECK_EQUAL(iq_cal_data->get_name(), name);
+ BOOST_CHECK_EQUAL(iq_cal_data->get_serial(), serial);
+ BOOST_CHECK_EQUAL(iq_cal_data->get_timestamp(), timestamp);
+
+ iq_cal_data->set_interp_mode(interp_mode::NEAREST_NEIGHBOR);
+ for (double d = 0; d < 5.0; d += 1.0) {
+ BOOST_CHECK_EQUAL(iq_cal_data->get_cal_coeff(d), std::complex<double>(d, d));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_iq_cal_des_fail)
+{
+ std::vector<uint8_t> not_actual_data(42, 23);
+
+ // FIXME: Re-enable this test when the verification works
+ // BOOST_REQUIRE_THROW(container::make<iq_cal>(not_actual_data), uhd::runtime_error);
+}