diff --git a/host/examples/python/spi.py b/host/examples/python/spi.py index 0883fe2fb..954c656fb 100644 --- a/host/examples/python/spi.py +++ b/host/examples/python/spi.py @@ -29,7 +29,7 @@ SPI_DEFAULT_SDO_PIN = 2 SPI_DEFAULT_CS_PIN = 3 SPI_DEFAULT_PAYLOAD_LENGTH = 32 SPI_DEFAULT_PAYLOAD = "0xfefe" -SPI_DEFAULT_CLK_DIVIDER = 4 +SPI_DEFAULT_CLK_DIVIDER = None SPI_DEFAULT_MOSI_EDGE = uhd.types.SPIEdge.EDGE_RISE SPI_DEFAULT_MISO_EDGE = uhd.types.SPIEdge.EDGE_FALL @@ -61,17 +61,18 @@ def parse_args(): "--clk-div", default=SPI_DEFAULT_CLK_DIVIDER, type=int, help="Clock divider for SPI" ) parser.add_argument( - "--miso-edge", default=SPI_DEFAULT_MISO_EDGE, type=spi_edge_type, help="SPI MISO edge" + "--miso-edge", + default=SPI_DEFAULT_MISO_EDGE, + type=spi_edge_type, + help="SPI Sample MISO at clk edge type", ) parser.add_argument( - "--mosi-edge", default=SPI_DEFAULT_MOSI_EDGE, type=spi_edge_type, help="SPI MOSI edge" - ) - parser.add_argument( - "--use-custom-divider", - default=False, - action="store_true", - help="Use custom clock divider for SPI", + "--mosi-edge", + default=SPI_DEFAULT_MOSI_EDGE, + type=spi_edge_type, + help="SPI Output MOSI at clk edge type", ) + return parser.parse_args() @@ -138,24 +139,34 @@ print(f" CS = {cfg.periph_cs}") print() payload = int(args.payload, 16) -if not 1 <= args.length <= 32: - print("ERROR: --length must be between 1 and 32 bits.") +max_payload_bits = 64 +if not 1 <= args.length <= max_payload_bits: + print(f"ERROR: --length must be between 1 and {max_payload_bits} bits.") sys.exit(1) -if not 0 <= payload <= 0xFFFFFFFF: - print("ERROR: --payload must fit in 32 bits (0 .. 0xFFFFFFFF).") +if not 0 <= payload < (1 << max_payload_bits): + print(f"ERROR: --payload must fit in {max_payload_bits} bits.") sys.exit(1) -mask = (1 << args.length) - 1 if args.length < 32 else 0xFFFFFFFF +mask = (1 << args.length) - 1 +if payload & ~mask: + print( + f"WARNING: Payload {payload:#x} has bits set outside of the specified " + f"length {args.length} bits. Remaining payload {(payload & mask):#x}." + ) payload &= mask print(f"Writing payload {payload:#x} with length {args.length} bits.") # The spi_config_t holds items like the clock divider and the SDI and SDO edges spi_cfg = uhd.types.SPIConfig() -spi_cfg.divider = args.clk_div -spi_cfg.use_custom_divider = args.use_custom_divider +if args.clk_div is not None: + spi_cfg.divider = args.clk_div + spi_cfg.use_custom_divider = True spi_cfg.miso_edge = args.miso_edge spi_cfg.mosi_edge = args.mosi_edge # Do the SPI transaction. There are write() and read() methods available, too. print("Performing SPI transaction...") -read_data = spi.transact_spi(0, spi_cfg, payload, args.length, True) +if args.length > 32: + read_data = spi.transact_spi64(0, spi_cfg, payload, args.length, True) +else: + read_data = spi.transact_spi32(0, spi_cfg, payload, args.length, True) print(f"Data read: {read_data:#x}") diff --git a/host/examples/spi.cpp b/host/examples/spi.cpp index d0936e3da..df57e4446 100644 --- a/host/examples/spi.cpp +++ b/host/examples/spi.cpp @@ -1,5 +1,5 @@ // -// Copyright 2022 Ettus Research, a National Instruments Brand +// Copyright 2026 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -14,6 +14,7 @@ #include #include #include +#include #include static const size_t SPI_DEFAULT_CLK_PIN = 0; @@ -22,7 +23,6 @@ static const size_t SPI_DEFAULT_SDO_PIN = 2; static const size_t SPI_DEFAULT_CS_PIN = 3; static const size_t SPI_DEFAULT_PAYLOAD_LENGTH = 32; static const std::string SPI_DEFAULT_PAYLOAD = "0xfefe"; -static const size_t SPI_DEFAULT_CLK_DIVIDER = 4; namespace po = boost::program_options; @@ -37,7 +37,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[]) size_t payload_length; size_t clk_divider; std::string payload_str; - uint32_t payload; + uint64_t payload; // setup the program options po::options_description desc("Allowed options"); @@ -52,7 +52,7 @@ int UHD_SAFE_MAIN(int argc, char* argv[]) ("cs", po::value(&cs)->default_value(SPI_DEFAULT_CS_PIN), "number of pin for chip select") ("payload", po::value(&payload_str)->default_value(SPI_DEFAULT_PAYLOAD), "payload as integer value") ("length", po::value(&payload_length)->default_value(SPI_DEFAULT_PAYLOAD_LENGTH), "payload length in bits") - ("clk-div", po::value(&clk_divider)->default_value(SPI_DEFAULT_CLK_DIVIDER), "clock divider for SPI") + ("clk-div", po::value(&clk_divider), "clock divider for SPI") ; // clang-format on po::variables_map vm; @@ -118,19 +118,40 @@ int UHD_SAFE_MAIN(int argc, char* argv[]) << " CS = " << (int)(periph_cfg.periph_cs) << std::endl << std::endl; - payload = strtoul(payload_str.c_str(), NULL, 0); + payload = strtoull(payload_str.c_str(), NULL, 0); + const size_t max_payload_bits = 64; + if (payload_length == 0 || payload_length > max_payload_bits) { + std::cerr << "Error: --length must be between 1 and " << max_payload_bits + << " bits." << std::endl; + return EXIT_FAILURE; + } + const uint64_t payload_mask = + payload_length == 64 ? UINT64_MAX : (UINT64_C(1) << payload_length) - UINT64_C(1); + if (payload & ~payload_mask) { + std::cout << "WARNING: Payload 0x" << std::hex << payload + << " has bits set outside of the specified length " << std::dec + << payload_length << " bits. Remaining payload 0x" << std::hex + << (payload & payload_mask) << std::endl; + payload &= payload_mask; + } std::cout << "Writing payload: 0x" << std::hex << payload << " with length " << std::dec << payload_length << " bits" << std::endl; // The spi_config_t holds items like the clock divider and the SDI and SDO edges uhd::spi_config_t config; - config.divider = clk_divider; - config.use_custom_divider = true; - config.mosi_edge = config.EDGE_RISE; - config.miso_edge = config.EDGE_FALL; + if (vm.count("clk-div")) { + config.divider = clk_divider; + config.use_custom_divider = true; + } + config.mosi_edge = config.EDGE_RISE; + config.miso_edge = config.EDGE_FALL; // Do the SPI transaction. There are write() and read() methods available, too. std::cout << "Performing SPI transaction..." << std::endl; - uint32_t read_data = spi_ref->transact_spi(0, config, payload, payload_length, true); + const uint64_t read_data = + payload_length > 32 + ? spi_ref->transact_spi64(0, config, payload, payload_length, true) + : spi_ref->transact_spi32( + 0, config, static_cast(payload), payload_length, true); std::cout << "Data read: 0x" << std::hex << read_data << std::endl; return EXIT_SUCCESS; diff --git a/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp b/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp index f1140f12e..24ff97edd 100644 --- a/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp +++ b/host/lib/include/uhdlib/usrp/cores/spi_core_4000.hpp @@ -1,5 +1,5 @@ // -// Copyright 2021 Ettus Research, a National Instruments Brand +// Copyright 2026 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -26,7 +26,10 @@ class spi_core_4000 : uhd::noncopyable, public uhd::spi_iface public: using sptr = std::shared_ptr; using poke32_fn_t = std::function; + using poke64_fn_t = std::function; using peek32_fn_t = std::function; + using poll32_fn_t = + std::function; virtual ~spi_core_4000(void) = default; @@ -35,11 +38,15 @@ public: //! makes a new spi core from register iface and peripheral static sptr make(poke32_fn_t&& poke32_fn, - peek32_fn_t&& peek_fn, + poke64_fn_t&& poke64_fn, + peek32_fn_t&& peek32_fn, + poll32_fn_t&& poll32_fn, const size_t spi_periph_cfg, const size_t spi_transaction_cfg, const size_t spi_transaction_go, + const size_t spi_transaction_lsb, const size_t spi_status, + const size_t spi_full_response_msb, const size_t spi_controller_info); //! Configures the SPI transaction. The vector index refers to the peripheral number. diff --git a/host/lib/usrp/cores/spi_core_4000.cpp b/host/lib/usrp/cores/spi_core_4000.cpp index e6179564b..75ce83aa8 100644 --- a/host/lib/usrp/cores/spi_core_4000.cpp +++ b/host/lib/usrp/cores/spi_core_4000.cpp @@ -1,5 +1,5 @@ // -// Copyright 2021 Ettus Research, a National Instruments Brand +// Copyright 2026 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -7,30 +7,59 @@ #include #include #include +#include #include #include #include #include +namespace { +constexpr uint32_t SPI_PERIPH_MOSI_EDGE_MASK = 1u << 28; +constexpr uint32_t SPI_PERIPH_MISO_EDGE_MASK = 1u << 27; +constexpr uint32_t SPI_PERIPH_NUM_BITS_MASK = 0x7F; +constexpr uint32_t SPI_PERIPH_NUM_BITS_SHIFT = 20; +constexpr uint32_t SPI_PERIPH_PIN_MASK = 0x1F; +constexpr uint32_t SPI_PERIPH_CS_SHIFT = 15; +constexpr uint32_t SPI_PERIPH_SDI_SHIFT = 10; +constexpr uint32_t SPI_PERIPH_SDO_SHIFT = 5; +constexpr uint32_t SPI_PERIPH_CLK_SHIFT = 0; + +constexpr uint32_t SPI_TRANSACTION_PERIPH_MASK = 0x3; +constexpr uint32_t SPI_TRANSACTION_PERIPH_SHIFT = 16; +constexpr uint32_t SPI_TRANSACTION_DIVIDER_MASK = 0xFFFF; +constexpr uint32_t SPI_TRANSACTION_DIVIDER_SHIFT = 0; + +constexpr uint32_t SPI_STATUS_READY_MASK = 1u << 31; +constexpr uint32_t SPI_STATUS_RESPONSE_MASK = 0x7FFFFFFF; +} // namespace + namespace uhd { namespace cores { class spi_core_4000_impl : public spi_core_4000 { public: spi_core_4000_impl(poke32_fn_t&& poke32_fn, + poke64_fn_t&& poke64_fn, peek32_fn_t&& peek32_fn, + poll32_fn_t&& poll32_fn, const size_t spi_periph_cfg, const size_t spi_transaction_cfg, const size_t spi_transaction_go, + const size_t spi_transaction_lsb, const size_t spi_status, + const size_t spi_full_response_msb, const size_t spi_controller_info) : _poke32(std::move(poke32_fn)) + , _poke64(std::move(poke64_fn)) , _peek32(std::move(peek32_fn)) + , _poll32(std::move(poll32_fn)) , _spi_periph_cfg(spi_periph_cfg) , _spi_transaction_cfg(spi_transaction_cfg) , _spi_transaction_go(spi_transaction_go) + , _spi_transaction_lsb(spi_transaction_lsb) , _spi_status(spi_status) + , _spi_full_response_msb(spi_full_response_msb) , _spi_ctrl_info(spi_controller_info) { } @@ -56,23 +85,40 @@ public: const uint32_t data, const size_t num_bits, const bool readback) override + { + return transact_spi64(which_periph, config, data, num_bits, readback) + & 0xFFFFFFFF; + } + + uint64_t transact_spi64(const int which_periph, + const spi_config_t& config, + const uint64_t data, + const size_t num_bits, + const bool readback) override { if (static_cast(which_periph) >= _spi_periph_config.size()) { throw uhd::value_error( "No configuration given for requested SPI peripheral."); } - if (config.divider > 0xFFFF) { + size_t divider = 4; + if (config.use_custom_divider) { + divider = config.divider; + } + if (divider > 0xFFFF) { throw uhd::value_error("Clock divider exceeds maximum value (65535)."); } + if (num_bits > 64 || num_bits == 0) { + throw uhd::value_error("Number of bits must be between 1 and 64."); + } std::lock_guard lock(_mutex); uint32_t periph_ctrl = 0; if (config.mosi_edge == spi_config_t::EDGE_FALL) { - periph_ctrl |= (1 << 27); + periph_ctrl |= SPI_PERIPH_MOSI_EDGE_MASK; } if (config.miso_edge == spi_config_t::EDGE_RISE) { - periph_ctrl |= (1 << 26); + periph_ctrl |= SPI_PERIPH_MISO_EDGE_MASK; } - periph_ctrl |= ((num_bits & 0x3F) << 20); + periph_ctrl |= (num_bits & SPI_PERIPH_NUM_BITS_MASK) << SPI_PERIPH_NUM_BITS_SHIFT; /* In the FPGA, the offset between Port A and B is 16, but we only have 12 pins * per port. To align this with the rest of the GPIO API, we need to adjust the @@ -83,16 +129,20 @@ public: + ((pin >= NUM_PINS_PER_PORT) ? static_cast( PORT_NUMBER_OFFSET - NUM_PINS_PER_PORT) : 0u); - return (adj & 0x1F) << offset; + return (adj & SPI_PERIPH_PIN_MASK) << offset; }; // periph_cs (which GPIO line for CS signal) - periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_cs, 15); + periph_ctrl |= + get_bitfield(_spi_periph_config[which_periph].periph_cs, SPI_PERIPH_CS_SHIFT); // periph_sdi (which GPIO line for serial data in from peripheral) - periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_sdi, 10); + periph_ctrl |= get_bitfield( + _spi_periph_config[which_periph].periph_sdi, SPI_PERIPH_SDI_SHIFT); // periph_sdo (which GPIO line for serial data out to peripheral) - periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_sdo, 5); + periph_ctrl |= get_bitfield( + _spi_periph_config[which_periph].periph_sdo, SPI_PERIPH_SDO_SHIFT); // periph_clk (which GPIO line for clk signal) - periph_ctrl |= get_bitfield(_spi_periph_config[which_periph].periph_clk, 0); + periph_ctrl |= get_bitfield( + _spi_periph_config[which_periph].periph_clk, SPI_PERIPH_CLK_SHIFT); // conditionally send peripheral control if (_periph_ctrl_cache[which_periph] != periph_ctrl) { @@ -102,9 +152,11 @@ public: uint32_t transaction_config = 0; // SPI chip select - transaction_config |= ((which_periph & 0x3) << 16); + transaction_config |= (which_periph & SPI_TRANSACTION_PERIPH_MASK) + << SPI_TRANSACTION_PERIPH_SHIFT; // SPI clock divider - transaction_config |= ((config.divider & 0xFFFF) << 0); + transaction_config |= (divider & SPI_TRANSACTION_DIVIDER_MASK) + << SPI_TRANSACTION_DIVIDER_SHIFT; // conditionally send transaction config if (_transaction_cfg_cache != transaction_config) { @@ -112,28 +164,41 @@ public: _transaction_cfg_cache = transaction_config; } - // load data word (in upper bits) - const uint32_t data_out = data << (32 - num_bits); - - // send data word - _poke32(_spi_transaction_go, data_out); + if (num_bits > 32) { + _poke64(_spi_transaction_lsb, data << (64 - num_bits)); + } else { + // load data word (in upper bits) + const uint32_t data_out = + static_cast((data << (32 - num_bits)) & 0xFFFFFFFF); + _poke32(_spi_transaction_go, data_out); + } // conditional readback if (readback) { - uint32_t spi_response = 0; - bool spi_ready = false; - // Poll the SPI status until we get a SPI Ready flag - std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now(); - while (!spi_ready) { - spi_response = _peek32(_spi_status); - spi_ready = spi_ready_bit(spi_response); - if (spi_timeout(t1, 5)) { - throw uhd::io_error( - "SPI Read did not receive a SPI Ready within 5 seconds"); - return 0; - } + uint64_t response = 0; + try { + response = _poll32(_spi_status, + SPI_STATUS_READY_MASK, + SPI_STATUS_READY_MASK, + 5.0, + true) + & SPI_STATUS_RESPONSE_MASK; + } catch (uhd::op_failed&) { + throw uhd::io_error( + "SPI Read did not receive a SPI Ready within 5 seconds"); + return 0; } - return (0xFFFFFF & spi_response); + + if (num_bits > 31) { + // Read and filter bits 32 to 63 of the response if applicable + const size_t msb_bits = std::min(num_bits - 31, size_t(32)); + const uint64_t msb_mask = (UINT64_C(1) << msb_bits) - 1; + response |= (_peek32(_spi_full_response_msb) & msb_mask) << 31; + } else { + // Filter the response to only include the valid number of bits + response &= (UINT64_C(1) << num_bits) - 1; + } + return response; } return 0; @@ -141,23 +206,22 @@ public: private: poke32_fn_t _poke32; + poke64_fn_t _poke64; peek32_fn_t _peek32; + poll32_fn_t _poll32; const size_t _spi_periph_cfg; const size_t _spi_transaction_cfg; const size_t _spi_transaction_go; + const size_t _spi_transaction_lsb; const size_t _spi_status; + const size_t _spi_full_response_msb; const size_t _spi_ctrl_info; std::vector _periph_ctrl_cache; - uint32_t _transaction_cfg_cache = 0; + // Initialize with invalid value. + uint32_t _transaction_cfg_cache = std::numeric_limits::max(); std::mutex _mutex; std::vector _spi_periph_config; - /*! Gets the SPI_READY flag */ - bool spi_ready_bit(uint32_t spi_response) - { - return (spi_response >> 24) & 0x1; - } - /*! Find out if we timed out */ bool spi_timeout(std::chrono::steady_clock::time_point start, uint32_t timeout_s) { @@ -167,19 +231,27 @@ private: }; spi_core_4000::sptr spi_core_4000::make(spi_core_4000::poke32_fn_t&& poke32_fn, + spi_core_4000::poke64_fn_t&& poke64_fn, spi_core_4000::peek32_fn_t&& peek32_fn, + spi_core_4000::poll32_fn_t&& poll32_fn, const size_t spi_periph_cfg, const size_t spi_transaction_cfg, const size_t spi_transaction_go, + const size_t spi_transaction_lsb, const size_t spi_status, + const size_t spi_full_response_msb, const size_t spi_controller_info) { return std::make_shared(std::move(poke32_fn), + std::move(poke64_fn), std::move(peek32_fn), + std::move(poll32_fn), spi_periph_cfg, spi_transaction_cfg, spi_transaction_go, + spi_transaction_lsb, spi_status, + spi_full_response_msb, spi_controller_info); } diff --git a/host/lib/usrp/mpmd/mpmd_impl.cpp b/host/lib/usrp/mpmd/mpmd_impl.cpp index c032e5ab4..59c41021a 100644 --- a/host/lib/usrp/mpmd/mpmd_impl.cpp +++ b/host/lib/usrp/mpmd/mpmd_impl.cpp @@ -33,7 +33,7 @@ namespace { //! Most pessimistic time for a CHDR query to go to device and back const double MPMD_CHDR_MAX_RTT = 0.02; //! MPM Compatibility number {MAJOR, MINOR} -const std::vector MPM_COMPAT_NUM = {7, 0}; +const std::vector MPM_COMPAT_NUM = {8, 0}; /*! Default number of motherboards to initialize in parallel at once. * * This caps the number of threads spawned during parallel initialization. It diff --git a/host/lib/usrp/x400/x400_radio_control.cpp b/host/lib/usrp/x400/x400_radio_control.cpp index c5f1cf5a9..f329c66d9 100644 --- a/host/lib/usrp/x400/x400_radio_control.cpp +++ b/host/lib/usrp/x400/x400_radio_control.cpp @@ -1,5 +1,5 @@ // -// Copyright 2020 Ettus Research, a National Instruments Brand +// Copyright 2026 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -225,13 +225,32 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args) [this](const uint32_t addr, const uint32_t data) { regs().poke32(addr, data, get_command_time(0)); }, + [this](const uint32_t addr, const uint64_t data) { + regs().poke64(addr, data, get_command_time(0)); + }, [this](const uint32_t addr) { return regs().peek32(addr, get_command_time(0)); }, + [this](const uint32_t addr, + const uint32_t data, + const uint32_t mask, + const double timeout, + const bool ack) { + return regs() + .poll32(addr, + data, + mask, + uhd::time_spec_t(timeout), + get_command_time(0), + ack) + .value(); + }, x400_regs::SPI_PERIPH_CFG, x400_regs::SPI_TRANSACTION_CFG_REG, x400_regs::SPI_TRANSACTION_GO_REG, + x400_regs::SPI_TRANSACTION_LSB_REG, x400_regs::SPI_STATUS_REG, + x400_regs::SPI_FULL_RESPONSE_MSB_REG, x400_regs::SPI_CONTROLLER_INFO_REG); _spi_getter_iface = std::make_shared(spicore); @@ -239,7 +258,7 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args) } else { UHD_LOG_INFO("x400_radio_control", "SPI functionality not available in this FPGA image. Please update to at " - "least version 7.7 to use SPI."); + "least version 12.0 to use SPI."); } } diff --git a/host/lib/usrp/x400/x400_radio_control.hpp b/host/lib/usrp/x400/x400_radio_control.hpp index 4a0136901..8dfa0c142 100644 --- a/host/lib/usrp/x400/x400_radio_control.hpp +++ b/host/lib/usrp/x400/x400_radio_control.hpp @@ -1,5 +1,5 @@ // -// Copyright 2020 Ettus Research, a National Instruments Brand +// Copyright 2026 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // @@ -48,14 +48,20 @@ constexpr uint32_t SPI_PERIPH_CFG = //! Base address for SPI_TRANSACTION_CONFIG Register constexpr uint32_t SPI_TRANSACTION_CFG_REG = SPI_PERIPH_CFG + 0x0010; +//! Base address for SPI_TRANSACTION_LSB Register +constexpr uint32_t SPI_TRANSACTION_LSB_REG = SPI_PERIPH_CFG + 0x0014; + //! Base address for SPI_TRANSACTION_GO Register -constexpr uint32_t SPI_TRANSACTION_GO_REG = SPI_PERIPH_CFG + 0x0014; +constexpr uint32_t SPI_TRANSACTION_GO_REG = SPI_PERIPH_CFG + 0x0018; //! Base address for SPI_STATUS Register -constexpr uint32_t SPI_STATUS_REG = SPI_PERIPH_CFG + 0x0018; +constexpr uint32_t SPI_STATUS_REG = SPI_PERIPH_CFG + 0x001C; + +//! Base address for SPI_FULL_RESPONSE_MSB Register +constexpr uint32_t SPI_FULL_RESPONSE_MSB_REG = SPI_PERIPH_CFG + 0x0020; //! Base address for SPI_CONTROLLER_INFO Register -constexpr uint32_t SPI_CONTROLLER_INFO_REG = SPI_PERIPH_CFG + 0x001C; +constexpr uint32_t SPI_CONTROLLER_INFO_REG = SPI_PERIPH_CFG + 0x0024; } // namespace x400_regs diff --git a/host/tests/spi_core_4000_test.cpp b/host/tests/spi_core_4000_test.cpp index 0a67a8e6d..df14fadf5 100644 --- a/host/tests/spi_core_4000_test.cpp +++ b/host/tests/spi_core_4000_test.cpp @@ -16,11 +16,13 @@ namespace { -constexpr uint32_t SPI_PERIPH_CFG_ADDR = 0x100; -constexpr uint32_t SPI_TRANSACTION_CFG_ADDR = 0x200; -constexpr uint32_t SPI_TRANSACTION_GO_ADDR = 0x204; -constexpr uint32_t SPI_STATUS_ADDR = 0x208; -constexpr uint32_t SPI_CTRL_INFO_ADDR = 0x20C; +constexpr uint32_t SPI_PERIPH_CFG_ADDR = 0x100; +constexpr uint32_t SPI_TRANSACTION_CFG_ADDR = 0x200; +constexpr uint32_t SPI_TRANSACTION_LSB_ADDR = 0x204; +constexpr uint32_t SPI_TRANSACTION_GO_ADDR = 0x208; +constexpr uint32_t SPI_STATUS_ADDR = 0x20C; +constexpr uint32_t SPI_FULL_RESPONSE_MSB_ADDR = 0x210; +constexpr uint32_t SPI_CTRL_INFO_ADDR = 0x214; constexpr uint32_t num_peripherals = 2; @@ -47,12 +49,12 @@ uint32_t calc_periph_ctrl_expected(const uhd::spi_config_t& config, uint32_t periph_ctrl = 0; if (config.mosi_edge == uhd::spi_config_t::EDGE_FALL) { - periph_ctrl |= (1u << 27); + periph_ctrl |= (1u << 28); } if (config.miso_edge == uhd::spi_config_t::EDGE_RISE) { - periph_ctrl |= (1u << 26); + periph_ctrl |= (1u << 27); } - periph_ctrl |= (static_cast(num_bits) & 0x3F) << 20; + periph_ctrl |= (static_cast(num_bits) & 0x7F) << 20; periph_ctrl |= get_bitfield(periph_cfg.periph_cs, 15); periph_ctrl |= get_bitfield(periph_cfg.periph_sdi, 10); periph_ctrl |= get_bitfield(periph_cfg.periph_sdo, 5); @@ -72,21 +74,37 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments) return num_peripherals; } if (addr == SPI_STATUS_ADDR) { - return (1u << 24) | 0x0055AA; + return (1u << 31) | 0x0055AA; // SPI_READY + dummy response } return 0; }; + auto poke64 = [&](uint32_t addr, uint64_t data) { + writes.emplace_back(addr, static_cast(data & 0xFFFFFFFF)); + writes.emplace_back(addr + 4, static_cast(data >> 32)); + }; + auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) { + const uint32_t value = peek(addr); + if ((value & mask) != data) { + throw uhd::op_timeout("SPI status did not match the poll mask"); + } + return value; + }; auto spi = uhd::cores::spi_core_4000::make(std::move(poke), + std::move(poke64), std::move(peek), + std::move(poll32), SPI_PERIPH_CFG_ADDR, SPI_TRANSACTION_CFG_ADDR, SPI_TRANSACTION_GO_ADDR, + SPI_TRANSACTION_LSB_ADDR, SPI_STATUS_ADDR, + SPI_FULL_RESPONSE_MSB_ADDR, SPI_CTRL_INFO_ADDR); uhd::spi_config_t config; + // Expected to throw because unconfigured peripheral BOOST_CHECK_THROW(spi->transact_spi(0, config, 0, 8, false), uhd::value_error); std::vector too_many_cfgs = { @@ -94,6 +112,7 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments) make_periph_cfg(4, 5, 6, 7), make_periph_cfg(8, 9, 10, 11), }; + // Expected to throw because more than num_peripherals peripherals BOOST_CHECK_THROW(spi->set_spi_periph_config(too_many_cfgs), uhd::value_error); std::vector valid_cfgs = { @@ -102,8 +121,12 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments) }; spi->set_spi_periph_config(valid_cfgs); + // Expected to throw because num_bits > 64 + BOOST_CHECK_THROW(spi->transact_spi(0, config, 0, 65, false), uhd::value_error); + uhd::spi_config_t invalid_divider_config; - invalid_divider_config.divider = 0x10000; + invalid_divider_config.divider = 0x10000; + invalid_divider_config.use_custom_divider = true; BOOST_CHECK_THROW( spi->transact_spi(0, invalid_divider_config, 0x1, 1, false), uhd::value_error); } @@ -118,17 +141,32 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_ return num_peripherals; } if (addr == SPI_STATUS_ADDR) { - return (1u << 24) | 0x00ABCDEF; + return (1u << 31) | 0x00ABCDEF; // SPI_READY + dummy response } return 0; }; + auto poke64 = [&](uint32_t addr, uint64_t data) { + writes.emplace_back(addr, static_cast(data & 0xFFFFFFFF)); + writes.emplace_back(addr + 4, static_cast(data >> 32)); + }; + auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) { + const uint32_t value = peek(addr); + if ((value & mask) != data) { + throw uhd::op_timeout("SPI status did not match the poll mask"); + } + return value; + }; auto spi = uhd::cores::spi_core_4000::make(std::move(poke), + std::move(poke64), std::move(peek), + std::move(poll32), SPI_PERIPH_CFG_ADDR, SPI_TRANSACTION_CFG_ADDR, SPI_TRANSACTION_GO_ADDR, + SPI_TRANSACTION_LSB_ADDR, SPI_STATUS_ADDR, + SPI_FULL_RESPONSE_MSB_ADDR, SPI_CTRL_INFO_ADDR); std::vector cfgs = { @@ -139,15 +177,16 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_ spi->set_spi_periph_config(cfgs); uhd::spi_config_t config; - config.mosi_edge = uhd::spi_config_t::EDGE_FALL; - config.miso_edge = uhd::spi_config_t::EDGE_RISE; - config.divider = 0x1234; + config.mosi_edge = uhd::spi_config_t::EDGE_FALL; + config.miso_edge = uhd::spi_config_t::EDGE_RISE; + config.divider = 0x1234; + config.use_custom_divider = true; const uint32_t tx_data = 0xA5; const size_t tx_bits = 8; const uint32_t response = spi->transact_spi(1, config, tx_data, tx_bits, true); - BOOST_CHECK_EQUAL(response, 0x00ABCDEF); + BOOST_CHECK_EQUAL(response, 0xEF); BOOST_REQUIRE_EQUAL(writes.size(), 3); @@ -166,8 +205,83 @@ BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_ // Same transaction should only issue GO write due to periph/transaction caching. const uint32_t second_response = spi->transact_spi(1, config, tx_data, tx_bits, true); - BOOST_CHECK_EQUAL(second_response, 0x00ABCDEF); + BOOST_CHECK_EQUAL(second_response, 0xEF); BOOST_REQUIRE_EQUAL(writes.size(), 4); BOOST_CHECK_EQUAL(writes.at(3).first, SPI_TRANSACTION_GO_ADDR); BOOST_CHECK_EQUAL(writes.at(3).second, expected_data_out); } + +BOOST_AUTO_TEST_CASE(spi_core_4000_transact_spi64_writes_split_data) +{ + std::vector writes; + std::vector reads; + + auto poke = [&](uint32_t addr, uint32_t data) { writes.emplace_back(addr, data); }; + auto peek = [&](uint32_t addr) -> uint32_t { + reads.push_back(addr); + if (addr == SPI_CTRL_INFO_ADDR) { + return num_peripherals; + } + if (addr == SPI_STATUS_ADDR) { + return (1u << 31) | 0x3456789A; // SPI_READY + response bits 30:0 + } + if (addr == SPI_FULL_RESPONSE_MSB_ADDR) { + return 0x24; // Response bits 39:31 + } + throw uhd::value_error("Unexpected 32-bit SPI register read"); + }; + auto poke64 = [&](uint32_t addr, uint64_t data) { + writes.emplace_back(addr, static_cast(data & 0xFFFFFFFF)); + writes.emplace_back(addr + 4, static_cast(data >> 32)); + }; + auto poll32 = [&](uint32_t addr, uint32_t data, uint32_t mask, double, bool) { + const uint32_t value = peek(addr); + if ((value & mask) != data) { + throw uhd::op_timeout("SPI status did not match the poll mask"); + } + return value; + }; + + uhd::spi_iface::sptr spi = uhd::cores::spi_core_4000::make(std::move(poke), + std::move(poke64), + std::move(peek), + std::move(poll32), + SPI_PERIPH_CFG_ADDR, + SPI_TRANSACTION_CFG_ADDR, + SPI_TRANSACTION_GO_ADDR, + SPI_TRANSACTION_LSB_ADDR, + SPI_STATUS_ADDR, + SPI_FULL_RESPONSE_MSB_ADDR, + SPI_CTRL_INFO_ADDR); + + auto spi_core = std::dynamic_pointer_cast(spi); + BOOST_REQUIRE(spi_core); + const auto periph_cfg = make_periph_cfg(0, 1, 2, 3); + spi_core->set_spi_periph_config({periph_cfg}); + + uhd::spi_config_t config; + // Different divider than previous test -> Expect TRANSACTION_CFG_ADDR + config.divider = 1; + config.use_custom_divider = true; + const uint64_t tx_data = UINT64_C(0x123456789A); + const size_t tx_bits = 40; + + const uint64_t response = spi->transact_spi64(0, config, tx_data, tx_bits, true); + BOOST_CHECK_EQUAL(response, UINT64_C(0x123456789A)); + + const std::vector expected_reads = { + SPI_CTRL_INFO_ADDR, SPI_STATUS_ADDR, SPI_FULL_RESPONSE_MSB_ADDR}; + BOOST_CHECK_EQUAL_COLLECTIONS( + reads.begin(), reads.end(), expected_reads.begin(), expected_reads.end()); + + BOOST_REQUIRE_EQUAL(writes.size(), 4); + BOOST_CHECK_EQUAL(writes.at(0).first, SPI_PERIPH_CFG_ADDR); + BOOST_CHECK_EQUAL( + writes.at(0).second, calc_periph_ctrl_expected(config, periph_cfg, tx_bits)); + BOOST_CHECK_EQUAL(writes.at(1).first, SPI_TRANSACTION_CFG_ADDR); + BOOST_CHECK_EQUAL(writes.at(1).second, 1u); + BOOST_CHECK_EQUAL(writes.at(2).first, SPI_TRANSACTION_LSB_ADDR); + BOOST_CHECK_EQUAL(writes.at(2).second, 0x9A000000u); + BOOST_CHECK_EQUAL(writes.at(3).first, SPI_TRANSACTION_GO_ADDR); + BOOST_CHECK_EQUAL(writes.at(3).second, 0x12345678u); +}