test: Add spi_core_4000 test

This adds a unit test for the spi_core_4000 which checks for the
validation of configurations and arguments and verifies the correct
registers are written during a SPI transaction.
This commit is contained in:
Martin Anderseck 2026-07-24 14:20:35 +02:00 committed by mkoop-ni
parent 62aec9a0a0
commit 2951e32013
2 changed files with 179 additions and 0 deletions

View File

@ -679,6 +679,12 @@ UHD_ADD_NONAPI_TEST(
${UHD_SOURCE_DIR}/lib/usrp/cores/complex_gain_3000.cpp
)
UHD_ADD_NONAPI_TEST(
TARGET "spi_core_4000_test.cpp"
EXTRA_SOURCES
${UHD_SOURCE_DIR}/lib/usrp/cores/spi_core_4000.cpp
)
########################################################################
# demo of a loadable module
########################################################################

View File

@ -0,0 +1,173 @@
//
// Copyright 2026 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include <boost/test/unit_test.hpp>
#include <uhd/exception.hpp>
#include <uhd/features/spi_getter_iface.hpp>
#include <uhdlib/usrp/cores/spi_core_4000.hpp>
#include <cstdint>
#include <utility>
#include <vector>
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 num_peripherals = 2;
using reg_write_t = std::pair<uint32_t, uint32_t>;
uhd::features::spi_periph_config_t make_periph_cfg(
uint8_t cs, uint8_t sdi, uint8_t sdo, uint8_t clk)
{
return {cs, sdi, sdo, clk};
}
uint32_t calc_periph_ctrl_expected(const uhd::spi_config_t& config,
const uhd::features::spi_periph_config_t& periph_cfg,
size_t num_bits)
{
auto get_bitfield = [](uint32_t pin, uint32_t offset) {
const uint32_t adjusted_pin =
pin
+ ((pin >= uhd::cores::NUM_PINS_PER_PORT) ? static_cast<uint32_t>(
uhd::cores::PORT_NUMBER_OFFSET - uhd::cores::NUM_PINS_PER_PORT)
: 0u);
return (adjusted_pin & 0x1F) << offset;
};
uint32_t periph_ctrl = 0;
if (config.mosi_edge == uhd::spi_config_t::EDGE_FALL) {
periph_ctrl |= (1u << 27);
}
if (config.miso_edge == uhd::spi_config_t::EDGE_RISE) {
periph_ctrl |= (1u << 26);
}
periph_ctrl |= (static_cast<uint32_t>(num_bits) & 0x3F) << 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);
periph_ctrl |= get_bitfield(periph_cfg.periph_clk, 0);
return periph_ctrl;
}
} // namespace
BOOST_AUTO_TEST_CASE(spi_core_4000_validates_configuration_and_arguments)
{
std::vector<reg_write_t> writes;
auto poke = [&](uint32_t addr, uint32_t data) { writes.emplace_back(addr, data); };
auto peek = [&](uint32_t addr) -> uint32_t {
if (addr == SPI_CTRL_INFO_ADDR) {
return num_peripherals;
}
if (addr == SPI_STATUS_ADDR) {
return (1u << 24) | 0x0055AA;
}
return 0;
};
auto spi = uhd::cores::spi_core_4000::make(std::move(poke),
std::move(peek),
SPI_PERIPH_CFG_ADDR,
SPI_TRANSACTION_CFG_ADDR,
SPI_TRANSACTION_GO_ADDR,
SPI_STATUS_ADDR,
SPI_CTRL_INFO_ADDR);
uhd::spi_config_t config;
BOOST_CHECK_THROW(spi->transact_spi(0, config, 0, 8, false), uhd::value_error);
std::vector<uhd::features::spi_periph_config_t> too_many_cfgs = {
make_periph_cfg(0, 1, 2, 3),
make_periph_cfg(4, 5, 6, 7),
make_periph_cfg(8, 9, 10, 11),
};
BOOST_CHECK_THROW(spi->set_spi_periph_config(too_many_cfgs), uhd::value_error);
std::vector<uhd::features::spi_periph_config_t> valid_cfgs = {
make_periph_cfg(0, 1, 2, 3),
make_periph_cfg(12, 13, 14, 15),
};
spi->set_spi_periph_config(valid_cfgs);
uhd::spi_config_t invalid_divider_config;
invalid_divider_config.divider = 0x10000;
BOOST_CHECK_THROW(
spi->transact_spi(0, invalid_divider_config, 0x1, 1, false), uhd::value_error);
}
BOOST_AUTO_TEST_CASE(spi_core_4000_transact_writes_expected_register_values_and_caches)
{
std::vector<reg_write_t> writes;
auto poke = [&](uint32_t addr, uint32_t data) { writes.emplace_back(addr, data); };
auto peek = [&](uint32_t addr) -> uint32_t {
if (addr == SPI_CTRL_INFO_ADDR) {
return num_peripherals;
}
if (addr == SPI_STATUS_ADDR) {
return (1u << 24) | 0x00ABCDEF;
}
return 0;
};
auto spi = uhd::cores::spi_core_4000::make(std::move(poke),
std::move(peek),
SPI_PERIPH_CFG_ADDR,
SPI_TRANSACTION_CFG_ADDR,
SPI_TRANSACTION_GO_ADDR,
SPI_STATUS_ADDR,
SPI_CTRL_INFO_ADDR);
std::vector<uhd::features::spi_periph_config_t> cfgs = {
make_periph_cfg(0, 1, 2, 3),
// Use port-B range pins to verify 12..15 become 16..19 in hardware bitfield
make_periph_cfg(12, 13, 14, 15),
};
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;
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_REQUIRE_EQUAL(writes.size(), 3);
const uint32_t expected_periph_ctrl =
calc_periph_ctrl_expected(config, cfgs.at(1), tx_bits);
BOOST_CHECK_EQUAL(writes.at(0).first, SPI_PERIPH_CFG_ADDR + 0x4);
BOOST_CHECK_EQUAL(writes.at(0).second, expected_periph_ctrl);
const uint32_t expected_transaction_cfg = (1u << 16) | 0x1234;
BOOST_CHECK_EQUAL(writes.at(1).first, SPI_TRANSACTION_CFG_ADDR);
BOOST_CHECK_EQUAL(writes.at(1).second, expected_transaction_cfg);
const uint32_t expected_data_out = tx_data << (32 - tx_bits);
BOOST_CHECK_EQUAL(writes.at(2).first, SPI_TRANSACTION_GO_ADDR);
BOOST_CHECK_EQUAL(writes.at(2).second, expected_data_out);
// 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_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);
}