diff options
-rw-r--r-- | host/include/uhd/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/null_block_control.hpp | 88 | ||||
-rw-r--r-- | host/lib/rfnoc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/rfnoc/null_block_control.cpp | 182 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/tests/rfnoc_blocks_test.cpp | 87 |
6 files changed, 360 insertions, 0 deletions
diff --git a/host/include/uhd/rfnoc/CMakeLists.txt b/host/include/uhd/rfnoc/CMakeLists.txt index 6251a555d..f5cb4006b 100644 --- a/host/include/uhd/rfnoc/CMakeLists.txt +++ b/host/include/uhd/rfnoc/CMakeLists.txt @@ -41,6 +41,7 @@ if(ENABLE_RFNOC) duc_block_ctrl.hpp fir_block_ctrl.hpp null_block_ctrl.hpp + null_block_control.hpp radio_ctrl.hpp radio_control.hpp replay_block_ctrl.hpp diff --git a/host/include/uhd/rfnoc/null_block_control.hpp b/host/include/uhd/rfnoc/null_block_control.hpp new file mode 100644 index 000000000..74f6db2a3 --- /dev/null +++ b/host/include/uhd/rfnoc/null_block_control.hpp @@ -0,0 +1,88 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#ifndef INCLUDED_LIBUHD_NULL_BLOCK_CONTROL_HPP +#define INCLUDED_LIBUHD_NULL_BLOCK_CONTROL_HPP + +#include <uhd/config.hpp> +#include <uhd/rfnoc/noc_block_base.hpp> +#include <uhd/types/stream_cmd.hpp> + +namespace uhd { namespace rfnoc { + +class UHD_API null_block_control : public noc_block_base +{ +public: + RFNOC_DECLARE_BLOCK(null_block_control) + + enum port_type_t { SINK, SOURCE, LOOP }; + enum count_type_t { LINES, PACKETS }; + + static const uint32_t REG_CTRL_STATUS; + static const uint32_t REG_SRC_LINES_PER_PKT; + static const uint32_t REG_SRC_BYTES_PER_PKT; + static const uint32_t REG_SRC_THROTTLE_CYC; + static const uint32_t REG_SNK_LINE_CNT_LO; + static const uint32_t REG_SNK_LINE_CNT_HI; + static const uint32_t REG_SNK_PKT_CNT_LO; + static const uint32_t REG_SNK_PKT_CNT_HI; + static const uint32_t REG_SRC_LINE_CNT_LO; + static const uint32_t REG_SRC_LINE_CNT_HI; + static const uint32_t REG_SRC_PKT_CNT_LO; + static const uint32_t REG_SRC_PKT_CNT_HI; + static const uint32_t REG_LOOP_LINE_CNT_LO; + static const uint32_t REG_LOOP_LINE_CNT_HI; + static const uint32_t REG_LOOP_PKT_CNT_LO; + static const uint32_t REG_LOOP_PKT_CNT_HI; + + /*! Start/stop the null source (port 0) + */ + virtual void issue_stream_cmd(const uhd::stream_cmd_t& stream_cmd) = 0; + + /*! Reset the counters + */ + virtual void reset_counters() = 0; + + /*! Set bytes per packet + * + * Note: This sets the entire packet size, including header. + */ + virtual void set_bytes_per_packet(const uint32_t bpp) = 0; + + /*! Set throttle cycles + * + * The block will wait this many cycles between packets. Useful for reducing + * the data output. + */ + virtual void set_throttle_cycles(const uint32_t cycs) = 0; + + /*! Get lines per packet (including the header!) + */ + virtual uint32_t get_lines_per_packet() = 0; + + /*! Get bytes per packet + */ + virtual uint32_t get_bytes_per_packet() = 0; + + /*! Get throttle cycles + */ + virtual uint32_t get_throttle_cycles() = 0; + + /*! Returns the number of lines that have been consumed on port 0 since the + * last reset + * + * \param port_type The port for which the counter value should be returned, + * sink, source, or loop. + * \param count_type If we want the packet count, or the line count + */ + virtual uint64_t get_count( + const port_type_t port_type, const count_type_t count_type) = 0; +}; + +}} // namespace uhd::rfnoc + +#endif /* INCLUDED_LIBUHD_NULL_BLOCK_CONTROL_HPP */ + diff --git a/host/lib/rfnoc/CMakeLists.txt b/host/lib/rfnoc/CMakeLists.txt index d62489484..1b627d304 100644 --- a/host/lib/rfnoc/CMakeLists.txt +++ b/host/lib/rfnoc/CMakeLists.txt @@ -57,6 +57,7 @@ LIBUHD_APPEND_SOURCES( ${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}/null_block_control.cpp ${CMAKE_CURRENT_SOURCE_DIR}/window_block_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_control_impl.cpp diff --git a/host/lib/rfnoc/null_block_control.cpp b/host/lib/rfnoc/null_block_control.cpp new file mode 100644 index 000000000..be0738b76 --- /dev/null +++ b/host/lib/rfnoc/null_block_control.cpp @@ -0,0 +1,182 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: GPL-3.0-or-later +// + +#include <uhd/rfnoc/null_block_control.hpp> +#include <uhd/rfnoc/registry.hpp> +#include <uhd/rfnoc/defaults.hpp> +#include <atomic> + +namespace { + +} // namespace + +using namespace uhd::rfnoc; +using uhd::stream_cmd_t; + + +const uint32_t null_block_control::REG_CTRL_STATUS = 0x00; +const uint32_t null_block_control::REG_SRC_LINES_PER_PKT = 0x04; +const uint32_t null_block_control::REG_SRC_BYTES_PER_PKT = 0x08; +const uint32_t null_block_control::REG_SRC_THROTTLE_CYC = 0x0C; +const uint32_t null_block_control::REG_SNK_LINE_CNT_LO = 0x10; +const uint32_t null_block_control::REG_SNK_LINE_CNT_HI = 0x14; +const uint32_t null_block_control::REG_SNK_PKT_CNT_LO = 0x18; +const uint32_t null_block_control::REG_SNK_PKT_CNT_HI = 0x1C; +const uint32_t null_block_control::REG_SRC_LINE_CNT_LO = 0x20; +const uint32_t null_block_control::REG_SRC_LINE_CNT_HI = 0x24; +const uint32_t null_block_control::REG_SRC_PKT_CNT_LO = 0x28; +const uint32_t null_block_control::REG_SRC_PKT_CNT_HI = 0x2C; +const uint32_t null_block_control::REG_LOOP_LINE_CNT_LO = 0x30; +const uint32_t null_block_control::REG_LOOP_LINE_CNT_HI = 0x34; +const uint32_t null_block_control::REG_LOOP_PKT_CNT_LO = 0x38; +const uint32_t null_block_control::REG_LOOP_PKT_CNT_HI = 0x3C; + + +class null_block_control_impl : public null_block_control +{ +public: + RFNOC_BLOCK_CONSTRUCTOR(null_block_control) + { + uint32_t initial_state = regs().peek32(REG_CTRL_STATUS); + _streaming = initial_state & 0x2; + _nipc = (initial_state >> 24) & 0xFF; + _item_width = (initial_state >> 16) & 0xFF; + reset_counters(); + register_issue_stream_cmd(); + } + + void issue_stream_cmd(const stream_cmd_t& stream_cmd) + { + if (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_START_CONTINUOUS) { + RFNOC_LOG_TRACE("Received start stream request!"); + regs().poke32(REG_CTRL_STATUS, 0x2); + _streaming = true; + } else if (stream_cmd.stream_mode == stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS) { + RFNOC_LOG_TRACE("Received stop stream request!"); + regs().poke32(REG_CTRL_STATUS, 0x0); + _streaming = false; + } else { + throw uhd::runtime_error("Null source can only do continuous streaming!"); + } + } + + void reset_counters() + { + const uint32_t streaming_flag = _streaming ? 0x2 : 0x0; + regs().poke32(REG_CTRL_STATUS, streaming_flag | 0x1); + regs().poke32(REG_CTRL_STATUS, streaming_flag | 0x0); + } + + void set_lines_per_packet(const uint32_t lpp) + { + if (lpp < 2) { + throw uhd::value_error("Null source lines per packet must be at " + "least one line in the payload!"); + } + if (lpp > 0xFFF) { + throw uhd::value_error("Null source lines per packet cannot exceed 12 bits!"); + } + // The register value is decreased by one line for the header, and one + // line for encoding (if we write a 0 here, the payload will still be at + // least one line long). + regs().poke32(REG_SRC_LINES_PER_PKT, lpp - 2); + } + + void set_bytes_per_packet(const uint32_t bpp) + { + if (bpp > 0xFFFF) { + throw uhd::value_error("Null source lines per packet cannot exceed 16 bits!"); + } + regs().poke32(REG_SRC_BYTES_PER_PKT, bpp); + const uint32_t bytes_per_line = (_item_width * _nipc) / 8; + // If bpp is not an integer multiple of bytes_per_line, then we add a + // full additional line! + const uint32_t lpp = bpp / bytes_per_line + (bpp % bytes_per_line ? 1 : 0); + set_lines_per_packet(lpp); + } + + void set_throttle_cycles(const uint32_t cycs) + { + if (cycs > 0x3FF) { + throw uhd::value_error("Null source throttle cycles cannot exceed 10 bits!"); + } + regs().poke32(REG_SRC_THROTTLE_CYC, cycs); + } + + uint32_t get_lines_per_packet() + { + return regs().peek32(REG_SRC_LINES_PER_PKT) + 2; + } + + uint32_t get_bytes_per_packet() + { + return regs().peek32(REG_SRC_BYTES_PER_PKT); + } + + uint32_t get_throttle_cycles() + { + return regs().peek32(REG_SRC_THROTTLE_CYC); + } + + uint64_t get_count( + const port_type_t port_type, const count_type_t count_type) + { + const uint32_t count_addr_lo = [&](){ + switch (port_type) { + case SOURCE: + return count_type == LINES ? REG_SRC_LINE_CNT_LO : REG_SRC_PKT_CNT_LO; + case SINK: + return count_type == LINES ? REG_SNK_LINE_CNT_LO : REG_SNK_PKT_CNT_LO; + case LOOP: + return count_type == LINES ? REG_LOOP_LINE_CNT_LO : REG_LOOP_PKT_CNT_LO; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + }(); + return regs().peek64(count_addr_lo); + } + +private: + + /*! Action API: Register a handler for stream commands + */ + void register_issue_stream_cmd() + { + register_action_handler(ACTION_KEY_STREAM_CMD, + [this](const res_source_info& src, action_info::sptr action) { + stream_cmd_action_info::sptr stream_cmd_action = + std::dynamic_pointer_cast<stream_cmd_action_info>(action); + if (!stream_cmd_action) { + throw uhd::runtime_error( + "Received stream_cmd of invalid action type!"); + } + if (src.instance != 0 || src.type != res_source_info::OUTPUT_EDGE) { + throw uhd::runtime_error( + "The null source can only stream from output port 0!"); + } + RFNOC_LOG_DEBUG("Received stream command action request!"); + issue_stream_cmd(stream_cmd_action->stream_cmd); + }); + } + + /************************************************************************** + * FPGA communication (register IO) + *************************************************************************/ + + /************************************************************************** + * Attributes + *************************************************************************/ + //! True if the source port 0 is producing data + std::atomic_bool _streaming{false}; + + //! Number of items per clock + uint32_t _nipc; + + //! Bits per item + uint32_t _item_width; +}; + +UHD_RFNOC_BLOCK_REGISTER_DIRECT(null_block_control, 0x00000001, "NullSrcSink") diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index 484eeb903..df891b7e2 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -250,6 +250,7 @@ UHD_ADD_NONAPI_TEST( ${CMAKE_SOURCE_DIR}/lib/rfnoc/block_id.cpp ${CMAKE_SOURCE_DIR}/lib/utils/compat_check.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/ddc_block_control.cpp + ${CMAKE_SOURCE_DIR}/lib/rfnoc/null_block_control.cpp ${CMAKE_SOURCE_DIR}/lib/rfnoc/radio_control_impl.cpp ${CMAKE_SOURCE_DIR}/lib/usrp/cores/dsp_core_utils.cpp ) diff --git a/host/tests/rfnoc_blocks_test.cpp b/host/tests/rfnoc_blocks_test.cpp index 4b393eb36..1832fc6ae 100644 --- a/host/tests/rfnoc_blocks_test.cpp +++ b/host/tests/rfnoc_blocks_test.cpp @@ -5,10 +5,13 @@ // #include "rfnoc_mock_reg_iface.hpp" +#include <uhd/rfnoc/null_block_control.hpp> #include <uhd/rfnoc/ddc_block_control.hpp> +#include <uhd/rfnoc/actions.hpp> #include <uhdlib/rfnoc/clock_iface.hpp> #include <uhdlib/rfnoc/node_accessor.hpp> #include <uhdlib/rfnoc/clock_iface.hpp> +#include <uhdlib/utils/narrow.hpp> #include <boost/test/unit_test.hpp> #include <iostream> @@ -38,8 +41,92 @@ noc_block_base::make_args_ptr make_make_args(noc_block_base::noc_id_t noc_id, uhd::rfnoc::noc_block_base::sptr BLOCK_NAME##_make( \ uhd::rfnoc::noc_block_base::make_args_ptr make_args); +MOCK_REGISTER(null_block_control) MOCK_REGISTER(ddc_block_control) +BOOST_AUTO_TEST_CASE(test_null_block) +{ + node_accessor_t node_accessor{}; + constexpr size_t num_chans = 2; + constexpr uint32_t nipc = 2; + constexpr uint32_t item_width = 32; + constexpr noc_block_base::noc_id_t mock_id = 0x7E570000; + + auto make_args = make_make_args(mock_id, "0/NullSrcSink#0", num_chans, num_chans); + auto reg_iface = std::dynamic_pointer_cast<mock_reg_iface_t>(make_args->reg_iface); + auto set_mem = [&](const uint32_t addr, const uint32_t data) { + reg_iface->read_memory[addr] = data; + }; + auto get_mem = [&](const uint32_t addr) { return reg_iface->write_memory[addr]; }; + auto copy_mem = [&](const uint32_t addr) { set_mem(addr, get_mem(addr)); }; + + set_mem(null_block_control::REG_CTRL_STATUS, (nipc << 24) | (item_width << 16)); + auto test_null = std::dynamic_pointer_cast<null_block_control>( + null_block_control_make(std::move(make_args))); + + using uhd::stream_cmd_t; + node_accessor.init_props(test_null.get()); + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + test_null->issue_stream_cmd(stream_cmd); + stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE; + BOOST_REQUIRE_THROW(test_null->issue_stream_cmd(stream_cmd), uhd::runtime_error); + + constexpr uint64_t snk_count = 1000000000; + constexpr uint64_t snk_count_pkts = 5; + constexpr uint64_t src_count = 2323232323; + constexpr uint64_t loop_count = 4242424242; + set_mem(null_block_control::REG_SNK_LINE_CNT_LO, + uhd::narrow_cast<uint32_t>(snk_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_LINE_CNT_HI, + uhd::narrow_cast<uint32_t>((snk_count >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_PKT_CNT_LO, + uhd::narrow_cast<uint32_t>(snk_count_pkts & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SNK_PKT_CNT_HI, + uhd::narrow_cast<uint32_t>((snk_count_pkts >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SRC_LINE_CNT_LO, + uhd::narrow_cast<uint32_t>(src_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_SRC_LINE_CNT_HI, + uhd::narrow_cast<uint32_t>((src_count >> 32) & 0xFFFFFFFF)); + set_mem(null_block_control::REG_LOOP_LINE_CNT_LO, + uhd::narrow_cast<uint32_t>(loop_count & 0xFFFFFFFF)); + set_mem(null_block_control::REG_LOOP_LINE_CNT_HI, + uhd::narrow_cast<uint32_t>((loop_count >> 32) & 0xFFFFFFFF)); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SINK, null_block_control::LINES), + snk_count); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SINK, null_block_control::PACKETS), + snk_count_pkts); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::SOURCE, null_block_control::LINES), + src_count); + BOOST_CHECK_EQUAL( + test_null->get_count(null_block_control::LOOP, null_block_control::LINES), + loop_count); + + constexpr uint32_t lpp = 3; + constexpr uint32_t bpp = nipc * item_width / 8 * lpp; + test_null->set_bytes_per_packet(bpp); + copy_mem(null_block_control::REG_SRC_LINES_PER_PKT); + copy_mem(null_block_control::REG_SRC_BYTES_PER_PKT); + BOOST_CHECK_EQUAL(test_null->get_lines_per_packet(), lpp); + BOOST_CHECK_EQUAL(test_null->get_bytes_per_packet(), bpp); + + auto sca = stream_cmd_action_info::make(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + node_accessor.send_action(test_null.get(), {res_source_info::OUTPUT_EDGE, 0}, sca); + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); + BOOST_REQUIRE_THROW( + node_accessor.send_action(test_null.get(), {res_source_info::OUTPUT_EDGE, 1}, sca), + uhd::runtime_error); + BOOST_REQUIRE_THROW( + node_accessor.send_action(test_null.get(), {res_source_info::INPUT_EDGE, 0}, sca), + uhd::runtime_error); + + stream_cmd.stream_mode = stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + test_null->issue_stream_cmd(stream_cmd); + BOOST_CHECK_EQUAL(get_mem(null_block_control::REG_CTRL_STATUS) & 0x2, 0x2); +} + BOOST_AUTO_TEST_CASE(test_ddc_block) { node_accessor_t node_accessor{}; |