2023-09-04 12:21:37 +02:00
|
|
|
/* Communicate with VILLASfpga Xilinx FPGA boards.
|
2020-06-15 22:21:56 +02:00
|
|
|
*
|
2023-12-12 14:15:55 +01:00
|
|
|
* Author: Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
|
|
|
|
* SPDX-FileCopyrightText: 2023 Niklas Eiling <niklas.eiling@eonerc.rwth-aachen.de>
|
2022-07-04 18:20:03 +02:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
2020-06-15 22:21:56 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <iostream>
|
2023-01-13 11:28:53 +01:00
|
|
|
#include <memory>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2023-01-13 11:28:53 +01:00
|
|
|
|
2020-06-15 22:21:56 +02:00
|
|
|
#include <jansson.h>
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/exceptions.hpp>
|
|
|
|
#include <villas/log.hpp>
|
2020-06-15 22:21:56 +02:00
|
|
|
#include <villas/nodes/fpga.hpp>
|
2021-08-10 10:12:48 -04:00
|
|
|
#include <villas/sample.hpp>
|
2020-06-15 22:21:56 +02:00
|
|
|
#include <villas/super_node.hpp>
|
2023-09-07 11:46:39 +02:00
|
|
|
#include <villas/utils.hpp>
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2024-01-12 12:05:45 +01:00
|
|
|
#include <villas/fpga/ips/switch.hpp>
|
2023-12-12 14:15:55 +01:00
|
|
|
#include <villas/fpga/utils.hpp>
|
2020-06-15 22:21:56 +02:00
|
|
|
|
|
|
|
using namespace villas;
|
|
|
|
using namespace villas::node;
|
2021-02-24 15:33:47 +01:00
|
|
|
using namespace villas::fpga;
|
2020-06-15 22:21:56 +02:00
|
|
|
using namespace villas::utils;
|
|
|
|
|
|
|
|
// Global state
|
2023-12-12 14:15:55 +01:00
|
|
|
static std::list<std::shared_ptr<fpga::Card>> cards;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
static std::shared_ptr<kernel::vfio::Container> vfioContainer;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
FpgaNode::FpgaNode(const uuid_t &id, const std::string &name)
|
2024-02-26 11:20:02 +01:00
|
|
|
: Node(id, name), cardName(""), card(nullptr), dma(), blockRx(), blockTx() {
|
|
|
|
}
|
2021-02-24 15:33:47 +01:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
FpgaNode::~FpgaNode() {}
|
2021-02-24 15:33:47 +01:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
int FpgaNode::prepare() {
|
|
|
|
if (card == nullptr) {
|
2024-02-26 11:20:02 +01:00
|
|
|
for (auto &fpgaCard : cards) {
|
|
|
|
if (fpgaCard->name == cardName) {
|
|
|
|
card = fpgaCard;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (card == nullptr) {
|
|
|
|
throw ConfigError(config, "node-config-fpga",
|
|
|
|
"There is no FPGA card with the name: {}", cardName);
|
|
|
|
}
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
|
|
|
|
2024-01-12 12:05:45 +01:00
|
|
|
auto axisswitch = std::dynamic_pointer_cast<fpga::ip::AxiStreamSwitch>(
|
|
|
|
card->lookupIp(fpga::Vlnv("xilinx.com:ip:axis_switch:")));
|
2023-12-12 14:15:55 +01:00
|
|
|
|
2024-01-12 12:05:45 +01:00
|
|
|
for (auto ports : axisswitch->getMasterPorts()) {
|
|
|
|
logger->info("Port: {}, {}", ports.first, *ports.second);
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
2024-01-12 12:05:45 +01:00
|
|
|
for (auto ports : axisswitch->getSlavePorts()) {
|
|
|
|
logger->info("Port: {}, {}", ports.first, *ports.second);
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
|
|
|
// Configure Crossbar switch
|
|
|
|
for (std::string str : connectStrings) {
|
|
|
|
const fpga::ConnectString parsedConnectString(str);
|
2024-01-12 12:05:45 +01:00
|
|
|
parsedConnectString.configCrossBar(card);
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
|
|
|
|
2024-02-26 11:20:02 +01:00
|
|
|
dma = std::dynamic_pointer_cast<fpga::ip::Dma>(
|
|
|
|
card->lookupIp(fpga::Vlnv("xilinx.com:ip:axi_dma:")));
|
|
|
|
if (dma == nullptr) {
|
|
|
|
logger->error("No DMA found on FPGA ");
|
|
|
|
throw std::runtime_error("No DMA found on FPGA");
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
auto &alloc = HostRam::getAllocator();
|
|
|
|
|
|
|
|
blockRx[0] = alloc.allocateBlock(0x200 * sizeof(float));
|
|
|
|
blockRx[1] = alloc.allocateBlock(0x200 * sizeof(float));
|
|
|
|
blockTx = alloc.allocateBlock(0x200 * sizeof(float));
|
|
|
|
villas::MemoryAccessor<float> memRx[] = {*(blockRx[0]), *(blockRx[1])};
|
|
|
|
villas::MemoryAccessor<float> memTx = *blockTx;
|
|
|
|
|
|
|
|
dma->makeAccesibleFromVA(blockRx[0]);
|
|
|
|
dma->makeAccesibleFromVA(blockRx[1]);
|
|
|
|
dma->makeAccesibleFromVA(blockTx);
|
|
|
|
|
2024-02-26 11:20:02 +01:00
|
|
|
MemoryManager::get().printGraph();
|
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
return Node::prepare();
|
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int FpgaNode::parse(json_t *json) {
|
|
|
|
int ret = Node::parse(json);
|
2024-02-26 11:20:02 +01:00
|
|
|
if (ret) {
|
2023-09-07 11:46:39 +02:00
|
|
|
return ret;
|
2024-02-26 11:20:02 +01:00
|
|
|
}
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
json_error_t err;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2024-02-26 11:20:02 +01:00
|
|
|
json_t *jsonCard = nullptr;
|
2023-12-12 14:15:55 +01:00
|
|
|
json_t *jsonConnectStrings = nullptr;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2024-02-26 11:20:02 +01:00
|
|
|
if (vfioContainer == nullptr) {
|
|
|
|
vfioContainer = std::make_shared<kernel::vfio::Container>();
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = json_unpack_ex(json, &err, 0, "{ s: o, s?: o}", "card", &jsonCard,
|
2023-12-12 14:15:55 +01:00
|
|
|
"connect", &jsonConnectStrings);
|
|
|
|
if (ret) {
|
2023-09-07 11:46:39 +02:00
|
|
|
throw ConfigError(json, err, "node-config-fpga",
|
|
|
|
"Failed to parse configuration of node {}",
|
|
|
|
this->getName());
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
2024-02-26 11:20:02 +01:00
|
|
|
if (json_is_string(jsonCard)) {
|
|
|
|
cardName = std::string(json_string_value(jsonCard));
|
|
|
|
} else if (json_is_object(jsonCard)) {
|
|
|
|
json_t *jsonCardName = json_object_get(jsonCard, "name");
|
|
|
|
if (jsonCardName != nullptr && !json_is_string(jsonCardName)) {
|
|
|
|
cardName = std::string(json_string_value(jsonCardName));
|
|
|
|
} else {
|
|
|
|
cardName = "anonymous Card";
|
|
|
|
}
|
2024-02-26 18:42:02 +01:00
|
|
|
auto searchPath = configPath.substr(0, configPath.rfind("/"));
|
2024-02-26 11:53:49 +01:00
|
|
|
card = createCard(jsonCard, searchPath, vfioContainer, cardName);
|
2024-02-26 11:20:02 +01:00
|
|
|
if (card == nullptr) {
|
|
|
|
throw ConfigError(jsonCard, "node-config-fpga", "Failed to create card");
|
2024-02-26 11:53:49 +01:00
|
|
|
} else {
|
|
|
|
cards.push_back(card);
|
2024-02-26 11:20:02 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw ConfigError(jsonCard, "node-config-fpga",
|
|
|
|
"Failed to parse card name");
|
|
|
|
}
|
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
if (jsonConnectStrings != nullptr && json_is_array(jsonConnectStrings)) {
|
|
|
|
for (size_t i = 0; i < json_array_size(jsonConnectStrings); i++) {
|
|
|
|
json_t *jsonConnectString = json_array_get(jsonConnectStrings, i);
|
|
|
|
if (jsonConnectString == nullptr || !json_is_string(jsonConnectString)) {
|
|
|
|
throw ConfigError(jsonConnectString, "node-config-fpga",
|
|
|
|
"Failed to parse connect string");
|
|
|
|
}
|
|
|
|
connectStrings.push_back(
|
|
|
|
std::string(json_string_value(jsonConnectString)));
|
|
|
|
}
|
|
|
|
}
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return 0;
|
2020-06-15 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
const std::string &FpgaNode::getDetails() {
|
|
|
|
if (details.empty()) {
|
|
|
|
auto &name = card ? card->name : cardName;
|
2022-11-03 07:51:23 -04:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
const char *const delim = ", ";
|
|
|
|
|
|
|
|
std::ostringstream imploded;
|
|
|
|
std::copy(connectStrings.begin(), connectStrings.end(),
|
|
|
|
std::ostream_iterator<std::string>(imploded, delim));
|
|
|
|
|
|
|
|
details = fmt::format("fpga={}, connect={}", name, imploded.str());
|
2023-09-07 11:46:39 +02:00
|
|
|
}
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return details;
|
2020-06-15 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int FpgaNode::check() { return 0; }
|
2021-02-24 15:33:47 +01:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
int FpgaNode::start() {
|
|
|
|
// enque first read
|
2024-03-14 11:53:19 +01:00
|
|
|
// dma->read(*(blockRx[0]), blockRx[0]->getSize());
|
2023-12-12 14:15:55 +01:00
|
|
|
return Node::start();
|
2022-11-03 07:51:23 -04:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int FpgaNode::_read(Sample *smps[], unsigned cnt) {
|
2024-03-14 11:53:19 +01:00
|
|
|
static size_t cur = 0, next = 0;
|
2023-09-07 11:46:39 +02:00
|
|
|
unsigned read;
|
|
|
|
Sample *smp = smps[0];
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
assert(cnt == 1);
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
dma->read(*(blockRx[next]), blockRx[next]->getSize()); // TODO: calc size
|
|
|
|
auto c = dma->readComplete();
|
2024-02-26 11:20:02 +01:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
read = c.bytes / sizeof(float);
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
if (c.interrupts > 1) {
|
|
|
|
logger->warn("Missed {} interrupts", c.interrupts - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto mem = MemoryAccessor<float>(*(blockRx[cur]));
|
2022-11-03 07:51:23 -04:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
smp->length = 0;
|
|
|
|
for (unsigned i = 0; i < MIN(read, smp->capacity); i++) {
|
2024-02-26 11:20:02 +01:00
|
|
|
smp->data[i].f = static_cast<double>(mem[i]);
|
2023-12-12 14:15:55 +01:00
|
|
|
smp->length++;
|
|
|
|
}
|
|
|
|
smp->flags = (int)SampleFlags::HAS_DATA;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
smp->signals = in.signals;
|
2024-03-14 11:53:19 +01:00
|
|
|
//cur = next;
|
|
|
|
//next = (next + 1) % (sizeof(blockRx) / sizeof(blockRx[0]));
|
2020-07-06 15:07:05 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
return 1;
|
2020-06-15 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int FpgaNode::_write(Sample *smps[], unsigned cnt) {
|
2023-12-12 14:15:55 +01:00
|
|
|
unsigned int written;
|
2023-09-07 11:46:39 +02:00
|
|
|
Sample *smp = smps[0];
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
assert(cnt == 1 && smps != nullptr && smps[0] != nullptr);
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2024-02-26 11:20:02 +01:00
|
|
|
auto mem = MemoryAccessor<uint32_t>(*blockTx);
|
|
|
|
float scaled;
|
2022-11-03 07:51:23 -04:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
for (unsigned i = 0; i < smps[0]->length; i++) {
|
2024-02-26 11:20:02 +01:00
|
|
|
scaled = smps[0]->data[i].f;
|
|
|
|
if (scaled > 10.) {
|
|
|
|
scaled = 10.;
|
|
|
|
} else if (scaled < -10.) {
|
|
|
|
scaled = -10.;
|
|
|
|
}
|
|
|
|
mem[i] = (scaled + 10.) * ((float)0xFFFF / 20.);
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
2021-08-10 10:12:48 -04:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
bool state = dma->write(*blockTx, smp->length * sizeof(float));
|
2023-09-07 11:46:39 +02:00
|
|
|
if (!state)
|
|
|
|
return -1;
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
written = dma->writeComplete().bytes /
|
|
|
|
sizeof(float); // The number of samples written
|
|
|
|
|
|
|
|
if (written != smp->length) {
|
|
|
|
logger->warn("Wrote {} samples, but {} were expected", written,
|
|
|
|
smp->length);
|
|
|
|
}
|
2020-06-15 22:21:56 +02:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
return 1;
|
2020-06-15 22:21:56 +02:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
std::vector<int> FpgaNode::getPollFDs() {
|
2023-12-12 14:15:55 +01:00
|
|
|
return card->vfioDevice->getEventfdList();
|
2022-11-03 07:51:23 -04:00
|
|
|
}
|
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
int FpgaNodeFactory::start(SuperNode *sn) {
|
2024-02-26 11:20:02 +01:00
|
|
|
if (vfioContainer == nullptr) {
|
|
|
|
vfioContainer = std::make_shared<kernel::vfio::Container>();
|
|
|
|
}
|
2022-11-03 07:51:23 -04:00
|
|
|
|
2023-12-12 14:15:55 +01:00
|
|
|
if (cards.empty()) {
|
2024-02-26 18:42:02 +01:00
|
|
|
auto searchPath =
|
|
|
|
sn->getConfigPath().substr(0, sn->getConfigPath().rfind("/"));
|
2024-03-01 14:11:55 +01:00
|
|
|
createCards(sn->getConfig(), cards, searchPath, vfioContainer);
|
2023-12-12 14:15:55 +01:00
|
|
|
}
|
2022-11-03 07:51:23 -04:00
|
|
|
|
2023-09-07 11:46:39 +02:00
|
|
|
return NodeFactory::start(sn);
|
2022-11-03 07:51:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static FpgaNodeFactory p;
|