From 1ae324575c203dc5fdb7ac4833562fd8c9860235 Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 22 Jun 2018 14:50:19 +0100 Subject: RFNoC: Add FIR, Null, and Window block controllers --- host/include/uhd/rfnoc/fir_block_ctrl.hpp | 46 ++++++++++++ host/include/uhd/rfnoc/null_block_ctrl.hpp | 70 ++++++++++++++++++ host/include/uhd/rfnoc/window_block_ctrl.hpp | 54 ++++++++++++++ host/lib/rfnoc/CMakeLists.txt | 4 + host/lib/rfnoc/fir_block_ctrl_impl.cpp | 77 +++++++++++++++++++ host/lib/rfnoc/null_block_ctrl_impl.cpp | 107 +++++++++++++++++++++++++++ host/lib/rfnoc/window_block_ctrl_impl.cpp | 94 +++++++++++++++++++++++ 7 files changed, 452 insertions(+) create mode 100644 host/include/uhd/rfnoc/fir_block_ctrl.hpp create mode 100644 host/include/uhd/rfnoc/null_block_ctrl.hpp create mode 100644 host/include/uhd/rfnoc/window_block_ctrl.hpp create mode 100644 host/lib/rfnoc/fir_block_ctrl_impl.cpp create mode 100644 host/lib/rfnoc/null_block_ctrl_impl.cpp create mode 100644 host/lib/rfnoc/window_block_ctrl_impl.cpp diff --git a/host/include/uhd/rfnoc/fir_block_ctrl.hpp b/host/include/uhd/rfnoc/fir_block_ctrl.hpp new file mode 100644 index 000000000..bfed5e067 --- /dev/null +++ b/host/include/uhd/rfnoc/fir_block_ctrl.hpp @@ -0,0 +1,46 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_fir_block_ctrl_HPP +#define INCLUDED_LIBUHD_RFNOC_fir_block_ctrl_HPP + +#include +#include + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for the standard FIR RFNoC block. + * + * The standard FIR has the following features: + * - One input- and output-port + * - Configurable taps, but fixed number of taps + * - Supports data type sc16 (16-Bit fix-point complex samples) + * + * This block requires packets to be the same size as the FFT length. + * It will perform one FFT operation per incoming packet, treating it + * as a vector of samples. + */ +class UHD_RFNOC_API fir_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + UHD_RFNOC_BLOCK_OBJECT(fir_block_ctrl) + + //! Configure the filter taps. + // + // The length of \p taps must correspond the number of taps + // in this block. If it's shorter, zeros will be padded. + // If it's longer, throws a uhd::value_error. + virtual void set_taps(const std::vector &taps) = 0; + + //! Returns the number of filter taps in this block. + virtual size_t get_n_taps() const = 0; +}; /* class fir_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_fir_block_ctrl_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/null_block_ctrl.hpp b/host/include/uhd/rfnoc/null_block_ctrl.hpp new file mode 100644 index 000000000..8a982b3e0 --- /dev/null +++ b/host/include/uhd/rfnoc/null_block_ctrl.hpp @@ -0,0 +1,70 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_NULL_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_NULL_BLOCK_CTRL_HPP + +#include +#include + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to a 'null block'. + * + * A 'null block' is a specific block, which comes with a couple + * of features useful for testing: + * - It can produce data at a given line rate, with a configurable + * packet size. + * - It can be used to dump packets ("null sink", "bit bucket") + * + * This block also serves as an example of how to create your own + * C++ classes to control your block. + * + * As a true source, it understands the following stream commands: + * - STREAM_MODE_START_CONTINUOUS + * - STREAM_MODE_STOP_CONTINUOUS + * + * Other stream commands are not understood and issue_stream_cmd() + * will throw if it receives them. + */ +class null_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + // This macro must always be at the top of the public section in an RFNoC block class + UHD_RFNOC_BLOCK_OBJECT(null_block_ctrl) + + //! Set this register to number of lines per packet + static const uint32_t SR_LINES_PER_PACKET = 129; + //! Set this register to number of cycles between producing a line + static const uint32_t SR_LINE_RATE = 130; + //! Set this register to non-zero to start producing data + static const uint32_t SR_ENABLE_STREAM = 131; + + static const size_t DEFAULT_LINES_PER_PACKET = 32; + static const size_t BYTES_PER_LINE = 8; + + //! Custom function to set the rate at which data is produced. + // Note: This is 'cycles per line', so the bit rate is actually + // 64 times this value (byte/s is 8*rate etc.) + // + // Equivalent to writing to line_rate/value in the property tree. + // + // \param The rate you want to set this to + // \param The clock rate of this block's clock domain + // \returns the actual line rate (will find closest possible). + virtual double set_line_rate(double rate, double clock_rate=166.6e6) = 0; + + //! Return the current line rate. Equivalent to reading line_rate/value + // from the property tree. + virtual double get_line_rate(double clock_rate=166.6e6) const = 0; + +}; /* class null_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_NULL_BLOCK_CTRL_HPP */ +// vim: sw=4 et: diff --git a/host/include/uhd/rfnoc/window_block_ctrl.hpp b/host/include/uhd/rfnoc/window_block_ctrl.hpp new file mode 100644 index 000000000..093b34e67 --- /dev/null +++ b/host/include/uhd/rfnoc/window_block_ctrl.hpp @@ -0,0 +1,54 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_RFNOC_WINDOW_BLOCK_CTRL_HPP +#define INCLUDED_LIBUHD_RFNOC_WINDOW_BLOCK_CTRL_HPP + +#include +#include + +namespace uhd { + namespace rfnoc { + +/*! \brief Block controller for the standard windowing RFNoC block. + * + * The standard windowing block has the following features: + * - One input- and output-port + * - Configurable window length and coefficients + * - Supports data type sc16 (16-Bit fix-point complex samples) + * + * This block requires packets to be the same size as the downstream FFT length. + * It will perform one window operation per incoming packet, treating it + * as a vector of samples. + */ +class UHD_RFNOC_API window_block_ctrl : public source_block_ctrl_base, public sink_block_ctrl_base +{ +public: + UHD_RFNOC_BLOCK_OBJECT(window_block_ctrl) + + static const size_t MAX_COEFF_VAL = 32767; + static const uint32_t SR_WINDOW_LEN = 131; // Note: AXI config bus uses 129 & 130 + static const uint32_t RB_MAX_WINDOW_LEN = 0; + static const uint32_t AXIS_WINDOW_LOAD = AXIS_CONFIG_BUS+0; // 2*0+0 + static const uint32_t AXIS_WINDOW_LOAD_TLAST = AXIS_CONFIG_BUS+1; // 2*0+1 + + //! Configure the window coefficients + // + // \p coeffs size determines the window length. If it longer than + // the maximum window length, throws a uhd::value_error. + virtual void set_window(const std::vector &coeffs) = 0; + + //! Returns the maximum window length. + virtual size_t get_max_len() const = 0; + + //! Returns the current window length. + virtual size_t get_window_len() const = 0; + +}; /* class window_block_ctrl*/ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_WINDOW_BLOCK_CTRL_HPP */ diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index 9ee4970a3..527c99e14 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -34,7 +34,11 @@ LIBUHD_APPEND_SOURCES( # Default block control classes: ${CMAKE_CURRENT_SOURCE_DIR}/ddc_block_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/duc_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fir_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/null_block_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/window_block_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/siggen_block_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_block_ctrl_impl.cpp ) diff --git a/host/lib/rfnoc/fir_block_ctrl_impl.cpp b/host/lib/rfnoc/fir_block_ctrl_impl.cpp new file mode 100644 index 000000000..4267e0b22 --- /dev/null +++ b/host/lib/rfnoc/fir_block_ctrl_impl.cpp @@ -0,0 +1,77 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include +#include +#include + +using namespace uhd::rfnoc; + +class fir_block_ctrl_impl : public fir_block_ctrl +{ +public: + static const uint32_t RB_NUM_TAPS = 0; + static const uint32_t SR_RELOAD = 128; + static const uint32_t SR_RELOAD_TLAST = 129; + static const uint32_t SR_CONFIG = 130; + + UHD_RFNOC_BLOCK_CONSTRUCTOR(fir_block_ctrl), + _item_type("sc16") // We only support sc16 in this block + { + _n_taps = uint32_t(user_reg_read64(RB_NUM_TAPS)); + UHD_LOGGER_DEBUG(unique_id()) + << "fir_block::fir_block() n_taps ==" << _n_taps << std::endl; + UHD_ASSERT_THROW(_n_taps); + + // Default to Dirac impulse + std::vector default_taps(1, 20000); + set_taps(default_taps); + } + + void set_taps(const std::vector &taps_) + { + UHD_LOGGER_TRACE(unique_id()) << "fir_block::set_taps()" << std::endl; + if (taps_.size() > _n_taps) { + throw uhd::value_error(str( + boost::format("FIR block: Too many filter coefficients! Provided %d, FIR allows %d.\n") + % taps_.size() % _n_taps + )); + } + for (size_t i = 0; i < taps_.size(); i++) { + if (taps_[i] > 32767 || taps_[i] < -32768) { + throw uhd::value_error(str( + boost::format("FIR block: Coefficient %d out of range! Value %d, Allowed range [-32768,32767].\n") + % i % taps_[i])); + } + } + std::vector taps = taps_; + if (taps.size() < _n_taps) { + taps.resize(_n_taps, 0); + } + + // Write taps via the reload bus + for (size_t i = 0; i < taps.size() - 1; i++) { + sr_write(SR_RELOAD, uint32_t(taps[i])); + } + // Assert tlast when sending the spinal tap (haha, it's actually the final tap). + sr_write(SR_RELOAD_TLAST, uint32_t(taps.back())); + // Send the configuration word to replace the existing coefficients with the new ones. + // Note: This configuration bus does not require tlast + sr_write(SR_CONFIG, 0); + } + + //! Returns the number of filter taps in this block. + size_t get_n_taps() const + { + return _n_taps; + } + +private: + const std::string _item_type; + size_t _n_taps; +}; + +UHD_RFNOC_BLOCK_REGISTER(fir_block_ctrl, "FIR"); diff --git a/host/lib/rfnoc/null_block_ctrl_impl.cpp b/host/lib/rfnoc/null_block_ctrl_impl.cpp new file mode 100644 index 000000000..7e62a2b3e --- /dev/null +++ b/host/lib/rfnoc/null_block_ctrl_impl.cpp @@ -0,0 +1,107 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include +#include +#include +#include + +using namespace uhd::rfnoc; + +class null_block_ctrl_impl : public null_block_ctrl +{ +public: + UHD_RFNOC_BLOCK_CONSTRUCTOR(null_block_ctrl) + { + // Register hooks for line_rate: + _tree->access(_root_path / "args" / 0 / "line_rate" / "value") + .add_coerced_subscriber([this](const int delay){ + this->set_line_delay_cycles(delay); + }) + .update() + ; + // Register hooks for bpp: + _tree->access(_root_path / "args" / 0 / "bpp" / "value") + .add_coerced_subscriber([this](const int bpp){ + this->set_bytes_per_packet(bpp); + }) + .update() + ; + } + + void set_line_delay_cycles(int cycles) + { + sr_write(SR_LINE_RATE, uint32_t(cycles)); + } + + void set_bytes_per_packet(int bpp) + { + sr_write(SR_LINES_PER_PACKET, uint32_t(bpp / BYTES_PER_LINE)); + } + + double set_line_rate(double rate, double clock_rate) + { + int cycs_between_lines = clock_rate / rate - 1; + if (cycs_between_lines > 0xFFFF) { + cycs_between_lines = 0xFFFF; + UHD_LOGGER_WARNING(unique_id()) + << str(boost::format("Requested rate %f is larger than possible " + "with the current clock rate (%.2f MHz).") + % rate % (clock_rate / 1e6)) + << std::endl; + } + cycs_between_lines = std::max(0, cycs_between_lines); + set_arg("line_rate", cycs_between_lines); + return _line_rate_from_reg_val(cycs_between_lines, clock_rate); + } + + double get_line_rate(double clock_rate) const + { + return _line_rate_from_reg_val(get_arg("line_rate"), clock_rate); + } + + double _line_rate_from_reg_val(uint32_t reg_val, double clock_rate) const + { + return clock_rate / (reg_val + 1); + } + + void issue_stream_cmd(const uhd::stream_cmd_t &stream_cmd, const size_t) + { + if (not stream_cmd.stream_now) { + throw uhd::not_implemented_error("null_block does not support timed commands."); + } + switch (stream_cmd.stream_mode) { + case uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS: + sr_write(SR_ENABLE_STREAM, true); + break; + + case uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS: + sr_write(SR_ENABLE_STREAM, false); + break; + + case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE: + case uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_MORE: + throw uhd::not_implemented_error("null_block does not support streaming modes other than CONTINUOUS"); + + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_destination( + uint32_t next_address, + size_t output_block_port + ) { + uhd::sid_t sid(next_address); + if (sid.get_src() == 0) { + sid.set_src(get_address()); + } + sr_write(SR_NEXT_DST_SID, sid.get(), output_block_port); + } +}; + +UHD_RFNOC_BLOCK_REGISTER(null_block_ctrl, "NullSrcSink"); + diff --git a/host/lib/rfnoc/window_block_ctrl_impl.cpp b/host/lib/rfnoc/window_block_ctrl_impl.cpp new file mode 100644 index 000000000..74ebd146d --- /dev/null +++ b/host/lib/rfnoc/window_block_ctrl_impl.cpp @@ -0,0 +1,94 @@ +// +// Copyright 2014-2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include +#include +#include + +using namespace uhd::rfnoc; + +class window_block_ctrl_impl : public window_block_ctrl +{ +public: + UHD_RFNOC_BLOCK_CONSTRUCTOR(window_block_ctrl), + _item_type("sc16"), // We only support sc16 in this block + _bpi(uhd::convert::get_bytes_per_item("sc16")) + { + _max_len = uint32_t(user_reg_read64(RB_MAX_WINDOW_LEN)); + UHD_LOGGER_DEBUG(unique_id()) + << "window_block::window_block() max_len ==" << _max_len << std::endl; + UHD_ASSERT_THROW(_max_len); + + // TODO we need a coercer to check that spp on the prop tree doesn't get set to anything invalid + _set_default_window(std::min(get_arg("spp"), _max_len)); + } + + //! Set window coefficients and length + void set_window(const std::vector &coeffs) + { + UHD_LOGGER_TRACE(unique_id()) + << "window_block::set_window()" << std::endl; + if (coeffs.size() > _max_len) { + throw uhd::value_error(str( + boost::format("window_block::set_window(): Too many window " + "coefficients! Provided %d, window allows up to %d.\n") + % coeffs.size() % _max_len + )); + } + + size_t window_len = coeffs.size(); + + // Window block can take complex coefficients in sc16 format, but typical usage is + // to have real(coeffs) == imag(coeffs) + std::vector coeffs_; + for (size_t i = 0; i < window_len - 1; i++) { + if (coeffs[i] > 32767 || coeffs[i] < -32768) { + throw uhd::value_error(str( + boost::format("window_block::set_window(): Coefficient %d " + "(index %d) outside coefficient range [-32768,32767].\n") + % coeffs[i] % i)); + } + coeffs_.push_back(coeffs[i]); + } + + // Write coefficients via the load bus + for (size_t i = 0; i < window_len - 1; i++) { + sr_write(AXIS_WINDOW_LOAD, coeffs_[i]); + } + // Assert tlast when sending the final coefficient (sorry, no joke here) + sr_write(AXIS_WINDOW_LOAD_TLAST, coeffs_.back()); + // Set the window length + sr_write(SR_WINDOW_LEN, window_len); + + // This block requires spp to match the window length: + set_arg("spp", int(window_len)); + } + + //! Returns the maximum window length of this block. + size_t get_max_len() const + { + return _max_len; + } + + size_t get_window_len() const + { + return size_t(get_arg("spp")); + } + + +private: + const std::string _item_type; + const size_t _bpi; + size_t _max_len; + + //! Default is a rectangular window + void _set_default_window(size_t window_len) { + std::vector default_coeffs(window_len, (1 << 15)-1); + set_window(default_coeffs); + } +}; + +UHD_RFNOC_BLOCK_REGISTER(window_block_ctrl, "Window"); -- cgit v1.2.3