aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--host/include/uhd/rfnoc/multichan_register_iface.hpp294
-rw-r--r--host/include/uhd/rfnoc/register_iface.hpp10
-rw-r--r--host/tests/CMakeLists.txt1
-rw-r--r--host/tests/multichan_register_iface_test.cpp180
4 files changed, 480 insertions, 5 deletions
diff --git a/host/include/uhd/rfnoc/multichan_register_iface.hpp b/host/include/uhd/rfnoc/multichan_register_iface.hpp
new file mode 100644
index 000000000..9517dde6e
--- /dev/null
+++ b/host/include/uhd/rfnoc/multichan_register_iface.hpp
@@ -0,0 +1,294 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#ifndef INCLUDED_LIBUHD_RFNOC_MULTICHAN_REGISTER_IFACE_HPP
+#define INCLUDED_LIBUHD_RFNOC_MULTICHAN_REGISTER_IFACE_HPP
+
+#include <uhd/rfnoc/register_iface.hpp>
+#include <uhd/rfnoc/register_iface_holder.hpp>
+#include <uhd/types/device_addr.hpp>
+#include <uhd/types/time_spec.hpp>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace uhd { namespace rfnoc {
+
+/*! A software interface to access low-level registers in a NoC block, which
+ * automatically handles address translation between multiple consecutive
+ * instances of the block. (For instance, accessing registers from multiple
+ * channels in hardware).
+ *
+ * This interface supports the following:
+ * - Writing and reading registers
+ * - Hardware timed delays (for time sequencing operations)
+ *
+ */
+class multichan_register_iface
+{
+public:
+ using sptr = std::shared_ptr<multichan_register_iface>;
+
+ multichan_register_iface(register_iface_holder& reg_iface_holder,
+ const uint32_t block_base_addr,
+ const size_t block_size)
+ : _reg_iface_holder(reg_iface_holder)
+ , _block_base_addr(block_base_addr)
+ , _block_size(block_size)
+ {
+ }
+
+ ~multichan_register_iface() = default;
+
+ /*! Write a 32-bit register implemented in the NoC block.
+ *
+ * \param addr The byte address of the register to write to (truncated to 20 bits).
+ * \param data New value of this register.
+ * \param instance The index of the block of registers to which the write applies
+ * \param time The time at which the transaction should be executed.
+ * \param ack Should transaction completion be acknowledged?
+ *
+ * \throws op_failed if an ACK is requested and the transaction fails
+ * \throws op_timeout if an ACK is requested and no response is received
+ * \throws op_seqerr if an ACK is requested and a sequence error occurs
+ * \throws op_timeerr if an ACK is requested and a time error occurs (late command)
+ */
+ inline void poke32(uint32_t addr,
+ uint32_t data,
+ const size_t instance = 0,
+ uhd::time_spec_t time = uhd::time_spec_t::ASAP,
+ bool ack = false)
+ {
+ _reg_iface_holder.regs().poke32(_get_addr(addr, instance), data, time, ack);
+ }
+
+ /*! Write two consecutive 32-bit registers implemented in the NoC block from
+ * one 64-bit value.
+ *
+ * Note: This is a convenience call, because all register pokes are 32-bits.
+ * This will concatenate two pokes in a block poke, and then return the
+ * combined result of the two pokes.
+ *
+ * \param addr The byte address of the lower 32-bit register to read from
+ * (truncated to 20 bits).
+ * \param data New value of the register(s).
+ * \param instance The index of the block of registers to which the writes apply.
+ * \param time The time at which the transaction should be executed.
+ * \param ack Should transaction completion be acknowledged?
+ *
+ * \throws op_failed if the transaction fails
+ * \throws op_timeout if no response is received
+ * \throws op_seqerr if a sequence error occurs
+ */
+ inline void poke64(uint32_t addr,
+ uint64_t data,
+ const size_t instance = 0,
+ time_spec_t time = uhd::time_spec_t::ASAP,
+ bool ack = false)
+ {
+ _reg_iface_holder.regs().poke64(_get_addr(addr, instance), data, time, ack);
+ }
+
+ /*! Write multiple 32-bit registers implemented in the NoC block.
+ *
+ * This method should be called when multiple writes need to happen that are
+ * at non-consecutive addresses. For consecutive writes, cf. block_poke32().
+ *
+ * \param addrs The byte addresses of the registers to write to
+ * (each truncated to 20 bits).
+ * \param data New values of these registers. The lengths of data and addr
+ * must match.
+ * \param instance The index of the block of registers to which the writes apply.
+ * \param time The time at which the first transaction should be executed.
+ * \param ack Should transaction completion be acknowledged?
+
+ * \throws uhd::value_error if lengths of data and addr don't match
+ * \throws op_failed if an ACK is requested and the transaction fails
+ * \throws op_timeout if an ACK is requested and no response is received
+ * \throws op_seqerr if an ACK is requested and a sequence error occurs
+ * \throws op_timeerr if an ACK is requested and a time error occurs (late command)
+ */
+ inline void multi_poke32(const std::vector<uint32_t> addrs,
+ const std::vector<uint32_t> data,
+ const size_t instance = 0,
+ uhd::time_spec_t time = uhd::time_spec_t::ASAP,
+ bool ack = false)
+ {
+ std::vector<uint32_t> abs_addrs(addrs.size());
+ std::transform(addrs.begin(),
+ addrs.end(),
+ abs_addrs.begin(),
+ [this, instance](
+ uint32_t addr) -> uint32_t { return _get_addr(addr, instance); });
+ _reg_iface_holder.regs().multi_poke32(abs_addrs, data, time, ack);
+ }
+
+ /*! Write multiple consecutive 32-bit registers implemented in the NoC block.
+ *
+ * This function will only allow writes to adjacent registers, in increasing
+ * order. If addr is set to 0, and the length of data is 8, then this method
+ * triggers eight writes, in order, to addresses 0, 4, 8, 12, 16, 20, 24, 28.
+ * For arbitrary addresses, cf. multi_poke32().
+ *
+ * Note: There is no guarantee that under the hood, the implementation won't
+ * separate the writes.
+ *
+ * \param first_addr The byte addresses of the first register to write
+ * \param data New values of these registers
+ * \param instance The index of the block of registers to which the writes apply.
+ * \param time The time at which the first transaction should be executed.
+ * \param ack Should transaction completion be acknowledged?
+ *
+ * \throws op_failed if an ACK is requested and the transaction fails
+ * \throws op_timeout if an ACK is requested and no response is received
+ * \throws op_seqerr if an ACK is requested and a sequence error occurs
+ * \throws op_timeerr if an ACK is requested and a time error occurs (late command)
+ */
+ inline void block_poke32(uint32_t first_addr,
+ const std::vector<uint32_t> data,
+ const size_t instance = 0,
+ uhd::time_spec_t time = uhd::time_spec_t::ASAP,
+ bool ack = false)
+ {
+ _reg_iface_holder.regs().block_poke32(
+ _get_addr(first_addr, instance), data, time, ack);
+ }
+
+ /*! Read a 32-bit register implemented in the NoC block.
+ *
+ * \param addr The byte address of the register to read from (truncated to 20 bits).
+ * \param instance The index of the block of registers to which the read applies.
+ * \param time The time at which the transaction should be executed.
+ *
+ * \throws op_failed if the transaction fails
+ * \throws op_timeout if no response is received
+ * \throws op_seqerr if a sequence error occurs
+ */
+ inline uint32_t peek32(uint32_t addr,
+ const size_t instance = 0,
+ time_spec_t time = uhd::time_spec_t::ASAP)
+ {
+ return _reg_iface_holder.regs().peek32(_get_addr(addr, instance), time);
+ }
+
+ /*! Read two consecutive 32-bit registers implemented in the NoC block
+ * and return them as one 64-bit value.
+ *
+ * Note: This is a convenience call, because all register peeks are 32-bits.
+ * This will concatenate two peeks in a block peek, and then return the
+ * combined result of the two peeks.
+ *
+ * \param addr The byte address of the lower 32-bit register to read from
+ * (truncated to 20 bits).
+ * \param instance The index of the block of registers to which the reads apply.
+ * \param time The time at which the transaction should be executed.
+ *
+ * \throws op_failed if the transaction fails
+ * \throws op_timeout if no response is received
+ * \throws op_seqerr if a sequence error occurs
+ */
+ inline uint64_t peek64(uint32_t addr,
+ const size_t instance = 0,
+ time_spec_t time = uhd::time_spec_t::ASAP)
+ {
+ return _reg_iface_holder.regs().peek64(_get_addr(addr, instance), time);
+ }
+
+ /*! Read multiple 32-bit consecutive registers implemented in the NoC block.
+ *
+ * \param first_addr The byte address of the first register to read from
+ * (truncated to 20 bits).
+ * \param length The number of 32-bit values to read
+ * \param instance The index of the block of registers to which the reads apply.
+ * \param time The time at which the transaction should be executed.
+ * \return data New value of this register.
+ *
+ * Example: If \p first_addr is set to 0, and length is 8, then this
+ * function will return a vector of length 8, with the content of registers
+ * at addresses 0, 4, 8, 12, 16, 20, 24, and 28 respectively.
+ *
+ * Note: There is no guarantee that under the hood, the implementation won't
+ * separate the reads.
+ *
+ * \throws op_failed if the transaction fails
+ * \throws op_timeout if no response is received
+ * \throws op_seqerr if a sequence error occurs
+ */
+ inline std::vector<uint32_t> block_peek32(uint32_t first_addr,
+ size_t length,
+ const size_t instance = 0,
+ time_spec_t time = uhd::time_spec_t::ASAP)
+ {
+ return _reg_iface_holder.regs().block_peek32(
+ _get_addr(first_addr, instance), length, time);
+ }
+
+ /*! Poll a 32-bit register until its value for all bits in mask match data&mask
+ *
+ * This will insert a command into the command queue to wait until a
+ * register is of a certain value. This can be used, e.g., to poll for a
+ * lock pin before executing the next command. It is related to sleep(),
+ * except it has a condition to wait on, rather than an unconditional stall
+ * duration. The timeout is hardware-timed.
+ * If the register does not attain the requested value within the requested
+ * duration, ${something bad happens}.
+ *
+ * Example: Assume readback register 16 is a status register, and bit 0
+ * indicates a lock is in place (i.e., we want it to be 1) and bit 1 is an
+ * error flag (i.e., we want it to be 0). The previous command can modify
+ * the state of the block, so we give it 1ms to settle. In that case, the
+ * call would be thus:
+ *
+ * ~~~{.cpp}
+ * // iface is a multichan_register_iface::sptr:
+ * iface->poll32(16, 0x1, 0x3, 1e-3);
+ * ~~~
+ *
+ * \param addr The byte address of the register to read from (truncated to 20 bits).
+ * \param data The values that the register must have
+ * \param mask The bitmask that is applied before checking the readback
+ * value
+ * \param timeout The max duration that the register is allowed to take
+ * before reaching its new state.
+ * \param instance The index of the block of registers to which the poll applies.
+ * \param time When the poll should be executed
+ * \param ack Should transaction completion be acknowledged? This is
+ * typically only necessary if the software needs a condition to
+ * be fulfilled before continueing, or during debugging.
+ *
+ * \throws op_failed if an ACK is requested and the transaction fails
+ * \throws op_timeout if an ACK is requested and no response is received
+ * \throws op_seqerr if an ACK is requested and a sequence error occurs
+ * \throws op_timeerr if an ACK is requested and a time error occurs (late command)
+ */
+ inline void poll32(uint32_t addr,
+ uint32_t data,
+ uint32_t mask,
+ time_spec_t timeout,
+ const size_t instance = 0,
+ time_spec_t time = uhd::time_spec_t::ASAP,
+ bool ack = false)
+ {
+ _reg_iface_holder.regs().poll32(
+ _get_addr(addr, instance), data, mask, timeout, time, ack);
+ }
+
+private:
+ register_iface_holder& _reg_iface_holder;
+ uint32_t _block_base_addr;
+ size_t _block_size;
+
+ inline uint32_t _get_addr(const uint32_t reg_offset, const size_t instance) const
+ {
+ return _block_base_addr + reg_offset + _block_size * instance;
+ }
+
+}; // class multichan_register_iface
+
+}} /* namespace uhd::rfnoc */
+
+#endif /* INCLUDED_LIBUHD_RFNOC_MULTICHAN_REGISTER_IFACE_HPP */
diff --git a/host/include/uhd/rfnoc/register_iface.hpp b/host/include/uhd/rfnoc/register_iface.hpp
index 5d95e6aae..e0849f1c3 100644
--- a/host/include/uhd/rfnoc/register_iface.hpp
+++ b/host/include/uhd/rfnoc/register_iface.hpp
@@ -127,8 +127,8 @@ public:
/*! Write multiple consecutive 32-bit registers implemented in the NoC block.
*
* This function will only allow writes to adjacent registers, in increasing
- * order. If addr is set to 0, and the length of data is 4, then this method
- * will trigger four writes, in order, to addresses 0, 4, 8, 12.
+ * order. If addr is set to 0, and the length of data is 8, then this method
+ * triggers eight writes, in order, to addresses 0, 4, 8, 12, 16, 20, 24, 28.
* For arbitrary addresses, cf. multi_poke32().
*
* Note: There is no guarantee that under the hood, the implementation won't
@@ -189,9 +189,9 @@ public:
* \param time The time at which the transaction should be executed.
* \return data New value of this register.
*
- * Example: If \p first_addr is set to 0, and length is 4, then this
- * function will return a vector of length 4, with the content of registers
- * at addresses 0, 4, 8, and 12, respectively.
+ * Example: If \p first_addr is set to 0, and length is 8, then this
+ * function will return a vector of length 8, with the content of registers
+ * at addresses 0, 4, 8, 12, 16, 20, 24, and 28 respectively.
*
* Note: There is no guarantee that under the hood, the implementation won't
* separate the reads.
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 2742c0385..3665a9d17 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -59,6 +59,7 @@ set(test_sources
tx_streamer_test.cpp
block_id_test.cpp
rfnoc_property_test.cpp
+ multichan_register_iface_test.cpp
)
#turn each test cpp file into an executable with an int main() function
diff --git a/host/tests/multichan_register_iface_test.cpp b/host/tests/multichan_register_iface_test.cpp
new file mode 100644
index 000000000..3a97d56cd
--- /dev/null
+++ b/host/tests/multichan_register_iface_test.cpp
@@ -0,0 +1,180 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include <uhd/rfnoc/mock_block.hpp>
+#include <uhd/rfnoc/multichan_register_iface.hpp>
+#include <boost/test/unit_test.hpp>
+#include <algorithm>
+#include <iostream>
+#include <vector>
+
+using namespace uhd::rfnoc;
+
+namespace {
+
+constexpr uint32_t BASE_ADDR = 0x8000;
+constexpr size_t INSTANCE_SIZE = 0x1000;
+
+inline uint32_t get_addr_translation(uint32_t offset, size_t instance)
+{
+ return offset + BASE_ADDR + INSTANCE_SIZE * instance;
+}
+
+} // namespace
+
+BOOST_AUTO_TEST_CASE(test_poke32)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x100;
+ uint32_t data = 0x1230;
+ block_reg_iface.poke32(addr, data);
+ uint32_t abs_addr = get_addr_translation(addr, 0);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data);
+ for (size_t instance = 0; instance < 4; instance++) {
+ data = 0xabc0 | instance;
+ block_reg_iface.poke32(addr, data, instance);
+ abs_addr = get_addr_translation(addr, instance);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_peek32)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x200;
+ for (size_t instance = 0; instance < 4; instance++) {
+ uint32_t data = 0xdef0 | instance;
+ uint32_t abs_addr = get_addr_translation(addr, instance);
+ mock_reg_iface->read_memory[abs_addr] = data;
+ if (instance == 0) {
+ BOOST_CHECK_EQUAL(block_reg_iface.peek32(addr), data);
+ }
+ BOOST_CHECK_EQUAL(block_reg_iface.peek32(addr, instance), data);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_poke32)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ std::vector<uint32_t> addrs = {0, 4, 8, 12, 16, 20, 24, 28};
+ std::vector<uint32_t> data = {
+ 0x0000, 0x0010, 0x0200, 0x3000, 0x0004, 0x0050, 0x0600, 0x7000};
+ block_reg_iface.multi_poke32(addrs, data);
+ for (size_t i = 0; i < addrs.size(); i++) {
+ uint32_t abs_addr = get_addr_translation(addrs[i], 0);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data[i]);
+ }
+ std::reverse(data.begin(), data.end());
+ for (size_t instance = 0; instance < 4; instance++) {
+ block_reg_iface.multi_poke32(addrs, data, instance);
+ for (size_t i = 0; i < addrs.size(); i++) {
+ uint32_t abs_addr = get_addr_translation(addrs[i], instance);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data[i]);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_block_poke32)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x100;
+ std::vector<uint32_t> data = {
+ 0x0000, 0x0010, 0x0200, 0x3000, 0x0004, 0x0050, 0x0600, 0x7000};
+ block_reg_iface.block_poke32(addr, data);
+ for (size_t i = 0; i < data.size(); i++) {
+ uint32_t abs_addr = get_addr_translation(addr + i * sizeof(uint32_t), 0);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data[i]);
+ }
+ std::reverse(data.begin(), data.end());
+ for (size_t instance = 0; instance < 4; instance++) {
+ block_reg_iface.block_poke32(addr, data, instance);
+ for (size_t i = 0; i < data.size(); i++) {
+ uint32_t abs_addr =
+ get_addr_translation(addr + i * sizeof(uint32_t), instance);
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr], data[i]);
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_block_peek32)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x200;
+ std::vector<uint32_t> data = {
+ 0x0008, 0x0090, 0x0a00, 0xb000, 0x000c, 0x00d0, 0x0e00, 0xf000};
+ for (size_t instance = 0; instance < 4; instance++) {
+ for (size_t i = 0; i < data.size(); i++) {
+ uint32_t abs_addr =
+ get_addr_translation(addr + i * sizeof(uint32_t), instance);
+ mock_reg_iface->read_memory[abs_addr] = data[i];
+ }
+ std::vector<uint32_t> peek_data =
+ block_reg_iface.block_peek32(addr, data.size(), instance);
+ BOOST_CHECK_EQUAL(peek_data.size(), data.size());
+ for (size_t i = 0; i < data.size(); i++) {
+ BOOST_CHECK_EQUAL(peek_data[i], data[i]);
+ }
+ if (instance == 0) {
+ peek_data = block_reg_iface.block_peek32(addr, data.size());
+ BOOST_CHECK_EQUAL(peek_data.size(), data.size());
+ for (size_t i = 0; i < data.size(); i++) {
+ BOOST_CHECK_EQUAL(peek_data[i], data[i]);
+ }
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_poke64)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x100;
+ uint64_t data = 0xabcdef12;
+ block_reg_iface.poke64(addr, data);
+ uint32_t abs_addr = get_addr_translation(addr, 0);
+ BOOST_CHECK_EQUAL(
+ mock_reg_iface->write_memory[abs_addr], uint32_t(data & 0xFFFFFFFF));
+ BOOST_CHECK_EQUAL(
+ mock_reg_iface->write_memory[abs_addr + 4], uint32_t((data >> 32) & 0xFFFFFFFF));
+ for (size_t instance = 0; instance < 4; instance++) {
+ data = 0x12345670 | instance;
+ block_reg_iface.poke64(addr, data, instance);
+ abs_addr = get_addr_translation(addr, instance);
+ BOOST_CHECK_EQUAL(
+ mock_reg_iface->write_memory[abs_addr], uint32_t(data & 0xFFFFFFFF));
+ BOOST_CHECK_EQUAL(mock_reg_iface->write_memory[abs_addr + 4],
+ uint32_t((data >> 32) & 0xFFFFFFFF));
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_peek64)
+{
+ auto mock_reg_iface = std::make_shared<mock_reg_iface_t>();
+ register_iface_holder mock_holder{mock_reg_iface};
+ multichan_register_iface block_reg_iface{mock_holder, BASE_ADDR, INSTANCE_SIZE};
+ uint32_t addr = 0x200;
+ for (size_t instance = 0; instance < 4; instance++) {
+ uint64_t data = 0x9abcdef0 | instance;
+ uint32_t abs_addr = get_addr_translation(addr, instance);
+ mock_reg_iface->read_memory[abs_addr] = uint32_t(data & 0xFFFFFFFF);
+ mock_reg_iface->read_memory[abs_addr + 4] = uint32_t((data >> 32) & 0xFFFFFFFF);
+ if (instance == 0) {
+ BOOST_CHECK_EQUAL(block_reg_iface.peek64(addr), data);
+ }
+ BOOST_CHECK_EQUAL(block_reg_iface.peek64(addr, instance), data);
+ }
+}