host: Complete X4xx GPIO SPI support

Update the X4xx SPI core, radio-control integration, examples, and
tests for transactions up to 64 bits and the revised register map.
This commit is contained in:
Max Becker 2026-09-15 11:21:32 +02:00 committed by Frank Dietze
parent b1e4016c2b
commit 3ed25c39c7
8 changed files with 339 additions and 89 deletions

View File

@ -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}")

View File

@ -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 <uhd/utils/safe_main.hpp>
#include <stdlib.h>
#include <boost/program_options.hpp>
#include <cstdint>
#include <iostream>
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<size_t>(&cs)->default_value(SPI_DEFAULT_CS_PIN), "number of pin for chip select")
("payload", po::value<std::string>(&payload_str)->default_value(SPI_DEFAULT_PAYLOAD), "payload as integer value")
("length", po::value<size_t>(&payload_length)->default_value(SPI_DEFAULT_PAYLOAD_LENGTH), "payload length in bits")
("clk-div", po::value<size_t>(&clk_divider)->default_value(SPI_DEFAULT_CLK_DIVIDER), "clock divider for SPI")
("clk-div", po::value<size_t>(&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<uint32_t>(payload), payload_length, true);
std::cout << "Data read: 0x" << std::hex << read_data << std::endl;
return EXIT_SUCCESS;

View File

@ -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<spi_core_4000>;
using poke32_fn_t = std::function<void(uint32_t, uint32_t)>;
using poke64_fn_t = std::function<void(uint32_t, uint64_t)>;
using peek32_fn_t = std::function<uint32_t(uint32_t)>;
using poll32_fn_t =
std::function<uint32_t(uint32_t, uint32_t, uint32_t, double, bool)>;
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.

View File

@ -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 <uhd/exception.hpp>
#include <uhd/features/spi_getter_iface.hpp>
#include <uhdlib/usrp/cores/spi_core_4000.hpp>
#include <algorithm>
#include <chrono>
#include <memory>
#include <mutex>
#include <thread>
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<uint32_t>(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<std::mutex> 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<uint32_t>(
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<uint32_t>((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<uint32_t> _periph_ctrl_cache;
uint32_t _transaction_cfg_cache = 0;
// Initialize with invalid value.
uint32_t _transaction_cfg_cache = std::numeric_limits<uint32_t>::max();
std::mutex _mutex;
std::vector<uhd::features::spi_periph_config_t> _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<spi_core_4000_impl>(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);
}

View File

@ -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<size_t> MPM_COMPAT_NUM = {7, 0};
const std::vector<size_t> 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

View File

@ -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<x400_spi_getter>(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.");
}
}

View File

@ -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

View File

@ -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<uint32_t>(num_bits) & 0x3F) << 20;
periph_ctrl |= (static_cast<uint32_t>(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<uint32_t>(data & 0xFFFFFFFF));
writes.emplace_back(addr + 4, static_cast<uint32_t>(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<uhd::features::spi_periph_config_t> 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<uhd::features::spi_periph_config_t> 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<uint32_t>(data & 0xFFFFFFFF));
writes.emplace_back(addr + 4, static_cast<uint32_t>(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<uhd::features::spi_periph_config_t> 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<reg_write_t> writes;
std::vector<uint32_t> 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<uint32_t>(data & 0xFFFFFFFF));
writes.emplace_back(addr + 4, static_cast<uint32_t>(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<uhd::cores::spi_core_4000>(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<uint32_t> 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);
}