diff options
-rw-r--r-- | host/include/uhd/rfnoc/multichan_register_iface.hpp | 294 | ||||
-rw-r--r-- | host/include/uhd/rfnoc/register_iface.hpp | 10 | ||||
-rw-r--r-- | host/tests/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/tests/multichan_register_iface_test.cpp | 180 |
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); + } +} |