mirror of
https://git.rwth-aachen.de/acs/public/villas/node/
synced 2025-03-09 00:00:00 +01:00
Merge branch 'feature/memory-manager-rebased' into 'develop'
Graph-based memory-manager for translating between addressspaces See merge request acs/public/villas/VILLASfpga-code!4
This commit is contained in:
commit
231050d734
31 changed files with 1175 additions and 702 deletions
|
@ -28,74 +28,114 @@
|
|||
"ips": {
|
||||
"bram_0_axi_bram_ctrl_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_bram_ctrl:4.0",
|
||||
"s_axi_baseaddr": 0,
|
||||
"s_axi_highaddr": 8191,
|
||||
"size": 8192
|
||||
},
|
||||
"hier_0_axi_dma_axi_dma_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_dma:7.1",
|
||||
"memory-view": {
|
||||
"SG": {
|
||||
"M_AXI_SG": {
|
||||
"bram_0_axi_bram_ctrl_0": {
|
||||
"baseaddr": 0,
|
||||
"highaddr": 8191
|
||||
"Mem0": {
|
||||
"baseaddr": 0,
|
||||
"highaddr": 8191,
|
||||
"size": 8192
|
||||
}
|
||||
},
|
||||
"hier_0_axi_dma_axi_dma_1": {
|
||||
"baseaddr": 8192,
|
||||
"highaddr": 12287
|
||||
"Reg": {
|
||||
"baseaddr": 8192,
|
||||
"highaddr": 12287,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axi_dma_axi_dma_0": {
|
||||
"baseaddr": 12288,
|
||||
"highaddr": 16383
|
||||
"Reg": {
|
||||
"baseaddr": 12288,
|
||||
"highaddr": 16383,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"timer_0_axi_timer_0": {
|
||||
"baseaddr": 16384,
|
||||
"highaddr": 20479
|
||||
"Reg": {
|
||||
"baseaddr": 16384,
|
||||
"highaddr": 20479,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axis_interconnect_0_axis_interconnect_0_xbar": {
|
||||
"baseaddr": 20480,
|
||||
"highaddr": 24575
|
||||
"Reg": {
|
||||
"baseaddr": 20480,
|
||||
"highaddr": 24575,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axi_fifo_mm_s_0": {
|
||||
"baseaddr": 49152,
|
||||
"highaddr": 57343
|
||||
"Mem0": {
|
||||
"baseaddr": 24576,
|
||||
"highaddr": 28671,
|
||||
"size": 4096
|
||||
},
|
||||
"Mem1": {
|
||||
"baseaddr": 49152,
|
||||
"highaddr": 57343,
|
||||
"size": 8192
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_reset_0": {
|
||||
"baseaddr": 28672,
|
||||
"highaddr": 32767
|
||||
"Reg": {
|
||||
"baseaddr": 28672,
|
||||
"highaddr": 32767,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_rtds_axis_0": {
|
||||
"baseaddr": 32768,
|
||||
"highaddr": 36863
|
||||
"reg0": {
|
||||
"baseaddr": 32768,
|
||||
"highaddr": 36863,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_hls_dft_0": {
|
||||
"baseaddr": 36864,
|
||||
"highaddr": 40959
|
||||
"Reg": {
|
||||
"baseaddr": 36864,
|
||||
"highaddr": 40959,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_intc_0": {
|
||||
"baseaddr": 45056,
|
||||
"highaddr": 49151
|
||||
"Reg": {
|
||||
"baseaddr": 45056,
|
||||
"highaddr": 49151,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"baseaddr": 268435456,
|
||||
"highaddr": 536870911
|
||||
"CTL0": {
|
||||
"baseaddr": 268435456,
|
||||
"highaddr": 536870911,
|
||||
"size": 268435456
|
||||
}
|
||||
}
|
||||
},
|
||||
"MM2S": {
|
||||
"M_AXI_MM2S": {
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295
|
||||
"BAR0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295,
|
||||
"size": 2147483648
|
||||
}
|
||||
}
|
||||
},
|
||||
"S2MM": {
|
||||
"M_AXI_S2MM": {
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295
|
||||
"BAR0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295,
|
||||
"size": 2147483648
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"baseaddr": 12288,
|
||||
"highaddr": 16383,
|
||||
"ports": [
|
||||
{
|
||||
"role": "initiator",
|
||||
|
@ -112,21 +152,25 @@
|
|||
"hier_0_axi_dma_axi_dma_1": {
|
||||
"vlnv": "xilinx.com:ip:axi_dma:7.1",
|
||||
"memory-view": {
|
||||
"MM2S": {
|
||||
"M_AXI_MM2S": {
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295
|
||||
"BAR0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295,
|
||||
"size": 2147483648
|
||||
}
|
||||
}
|
||||
},
|
||||
"S2MM": {
|
||||
"M_AXI_S2MM": {
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295
|
||||
"BAR0": {
|
||||
"baseaddr": 2147483648,
|
||||
"highaddr": 4294967295,
|
||||
"size": 2147483648
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"baseaddr": 8192,
|
||||
"highaddr": 12287,
|
||||
"ports": [
|
||||
{
|
||||
"role": "initiator",
|
||||
|
@ -142,10 +186,6 @@
|
|||
},
|
||||
"hier_0_axi_fifo_mm_s_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_fifo_mm_s:4.1",
|
||||
"baseaddr": 24576,
|
||||
"highaddr": 28671,
|
||||
"axi4_baseaddr": 49152,
|
||||
"axi4_highaddr": 57343,
|
||||
"ports": [
|
||||
{
|
||||
"role": "master",
|
||||
|
@ -164,8 +204,6 @@
|
|||
},
|
||||
"hier_0_axis_interconnect_0_axis_interconnect_0_xbar": {
|
||||
"vlnv": "xilinx.com:ip:axis_switch:1.1",
|
||||
"baseaddr": 20480,
|
||||
"highaddr": 24575,
|
||||
"ports": [
|
||||
{
|
||||
"role": "initiator",
|
||||
|
@ -188,12 +226,10 @@
|
|||
"name": "S04_AXIS"
|
||||
}
|
||||
],
|
||||
"num_ports": 14
|
||||
"num_ports": 7
|
||||
},
|
||||
"hier_0_hls_dft_0": {
|
||||
"vlnv": "acs.eonerc.rwth-aachen.de:hls:hls_dft:1.0",
|
||||
"s_axi_ctrl_baseaddr": 36864,
|
||||
"s_axi_ctrl_highaddr": 40959,
|
||||
"ports": [
|
||||
{
|
||||
"role": "master",
|
||||
|
@ -209,8 +245,6 @@
|
|||
},
|
||||
"hier_0_rtds_axis_0": {
|
||||
"vlnv": "acs.eonerc.rwth-aachen.de:user:rtds_axis:1.0",
|
||||
"baseaddr": 32768,
|
||||
"highaddr": 36863,
|
||||
"ports": [
|
||||
{
|
||||
"role": "master",
|
||||
|
@ -229,20 +263,103 @@
|
|||
"irq_case": "pcie_0_axi_pcie_intc_0:7"
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_pcie:2.8",
|
||||
"memory-view": {
|
||||
"M_AXI": {
|
||||
"bram_0_axi_bram_ctrl_0": {
|
||||
"Mem0": {
|
||||
"baseaddr": 0,
|
||||
"highaddr": 8191,
|
||||
"size": 8192
|
||||
}
|
||||
},
|
||||
"hier_0_axi_dma_axi_dma_1": {
|
||||
"Reg": {
|
||||
"baseaddr": 8192,
|
||||
"highaddr": 12287,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axi_dma_axi_dma_0": {
|
||||
"Reg": {
|
||||
"baseaddr": 12288,
|
||||
"highaddr": 16383,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"timer_0_axi_timer_0": {
|
||||
"Reg": {
|
||||
"baseaddr": 16384,
|
||||
"highaddr": 20479,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axis_interconnect_0_axis_interconnect_0_xbar": {
|
||||
"Reg": {
|
||||
"baseaddr": 20480,
|
||||
"highaddr": 24575,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_axi_fifo_mm_s_0": {
|
||||
"Mem0": {
|
||||
"baseaddr": 24576,
|
||||
"highaddr": 28671,
|
||||
"size": 4096
|
||||
},
|
||||
"Mem1": {
|
||||
"baseaddr": 49152,
|
||||
"highaddr": 57343,
|
||||
"size": 8192
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_reset_0": {
|
||||
"Reg": {
|
||||
"baseaddr": 28672,
|
||||
"highaddr": 32767,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_rtds_axis_0": {
|
||||
"reg0": {
|
||||
"baseaddr": 32768,
|
||||
"highaddr": 36863,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"hier_0_hls_dft_0": {
|
||||
"Reg": {
|
||||
"baseaddr": 36864,
|
||||
"highaddr": 40959,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_intc_0": {
|
||||
"Reg": {
|
||||
"baseaddr": 45056,
|
||||
"highaddr": 49151,
|
||||
"size": 4096
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_0": {
|
||||
"CTL0": {
|
||||
"baseaddr": 268435456,
|
||||
"highaddr": 536870911,
|
||||
"size": 268435456
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"pcie_0_axi_pcie_intc_0": {
|
||||
"vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0",
|
||||
"baseaddr": 45056,
|
||||
"highaddr": 49151
|
||||
"vlnv": "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:1.0"
|
||||
},
|
||||
"pcie_0_axi_reset_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_gpio:2.0",
|
||||
"baseaddr": 28672,
|
||||
"highaddr": 32767
|
||||
"vlnv": "xilinx.com:ip:axi_gpio:2.0"
|
||||
},
|
||||
"timer_0_axi_timer_0": {
|
||||
"vlnv": "xilinx.com:ip:axi_timer:2.0",
|
||||
"baseaddr": 16384,
|
||||
"highaddr": 20479,
|
||||
"irqs": {
|
||||
"generateout0": "pcie_0_axi_pcie_intc_0:0"
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
#ifndef VILLAS_DEPENDENCY_GRAPH_HPP
|
||||
#define VILLAS_DEPENDENCY_GRAPH_HPP
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace utils {
|
||||
|
||||
|
||||
template<typename T>
|
||||
class DependencyGraph {
|
||||
public:
|
||||
using NodeList = std::list<T>;
|
||||
|
||||
/// Create a node without dependencies if it not yet exists, return if a new
|
||||
/// node has been created.
|
||||
bool addNode(const T& node);
|
||||
|
||||
/// Remove a node and all other nodes that depend on it
|
||||
void removeNode(const T& node);
|
||||
|
||||
/// Add a dependency to a node. Will create the node if it not yet exists
|
||||
void addDependency(const T& node, const T& dependency);
|
||||
|
||||
void dump();
|
||||
|
||||
/// Return a sequential evaluation order list. If a circular dependency has been
|
||||
/// detected, all nodes involved will not be part of that list.
|
||||
NodeList getEvaluationOrder() const;
|
||||
|
||||
private:
|
||||
/// Return whether a node already exists or not
|
||||
bool nodeExists(const T& node)
|
||||
{ return graph.find(node) != graph.end(); }
|
||||
|
||||
static bool
|
||||
nodeInList(const NodeList& list, const T& node)
|
||||
{ return list.end() != std::find(list.begin(), list.end(), node); }
|
||||
|
||||
private:
|
||||
using Graph = std::map<T, NodeList>;
|
||||
|
||||
Graph graph;
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
} // namespace villas
|
||||
|
||||
#include "dependency_graph_impl.hpp"
|
||||
|
||||
#endif // VILLAS_DEPENDENCY_GRAPH_HPP
|
|
@ -1,111 +0,0 @@
|
|||
#ifndef VILLAS_DEPENDENCY_GRAPH_HPP
|
||||
#error "Do not include this file directly, please include depedency_graph.hpp"
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include "dependency_graph.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
static auto logger = loggerGetOrCreate("DependencyGraph");
|
||||
|
||||
namespace villas {
|
||||
namespace utils {
|
||||
|
||||
template<typename T>
|
||||
bool
|
||||
DependencyGraph<T>::addNode(const T &node)
|
||||
{
|
||||
bool existedBefore = nodeExists(node);
|
||||
|
||||
// accessing is enough to create if not exists
|
||||
graph[node];
|
||||
|
||||
return existedBefore;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
DependencyGraph<T>::removeNode(const T &node)
|
||||
{
|
||||
graph.erase(node);
|
||||
|
||||
// check if other nodes depend on this one
|
||||
for(auto& [key, dependencies] : graph) {
|
||||
if(nodeInList(dependencies, node)) {
|
||||
// remove other node that depends on the one to delete
|
||||
removeNode(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
DependencyGraph<T>::addDependency(const T &node, const T &dependency)
|
||||
{
|
||||
NodeList& dependencies = graph[node];
|
||||
if(not nodeInList(dependencies, dependency))
|
||||
dependencies.push_back(dependency);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void
|
||||
DependencyGraph<T>::dump() {
|
||||
for(auto& node : graph) {
|
||||
std::stringstream ss;
|
||||
for(auto& dep : node.second) {
|
||||
ss << dep << " ";
|
||||
}
|
||||
logger->info("{}: {}", node.first, ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename DependencyGraph<T>::NodeList
|
||||
DependencyGraph<T>::getEvaluationOrder() const
|
||||
{
|
||||
// copy graph to preserve information (we have to delete entries later)
|
||||
Graph graph = this->graph;
|
||||
|
||||
// output list
|
||||
NodeList out;
|
||||
|
||||
while(graph.size() > 0) {
|
||||
int added = 0;
|
||||
|
||||
// look for nodes with no dependencies
|
||||
for(auto& [key, dependencies] : graph) {
|
||||
|
||||
for(auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) {
|
||||
if(nodeInList(out, *dep)) {
|
||||
// dependency has been pushed to list in last round
|
||||
dep = dependencies.erase(dep);
|
||||
}
|
||||
}
|
||||
|
||||
// nodes with no dependencies can be pushed to list
|
||||
if(dependencies.empty()) {
|
||||
out.push_back(key);
|
||||
graph.erase(key);
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
// if a round doesn't add any elements and is not the last, then
|
||||
// there is a circular dependency
|
||||
if(added == 0 and graph.size() > 0) {
|
||||
logger->error("Circular dependency detected! IPs not available:");
|
||||
for(auto& [key, value] : graph) {
|
||||
(void) value;
|
||||
logger->error(" {}", key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace villas
|
|
@ -71,6 +71,7 @@ public:
|
|||
|
||||
using VertexIdentifier = Vertex::Identifier;
|
||||
using EdgeIdentifier = Edge::Identifier;
|
||||
using Path = std::list<EdgeIdentifier>;
|
||||
|
||||
DirectedGraph(const std::string& name = "DirectedGraph") :
|
||||
lastVertexId(0), lastEdgeId(0)
|
||||
|
@ -88,6 +89,18 @@ public:
|
|||
return vertices.at(vertexId);
|
||||
}
|
||||
|
||||
template<class UnaryPredicate>
|
||||
VertexIdentifier findVertex(UnaryPredicate p)
|
||||
{
|
||||
for(auto& [vertexId, vertex] : vertices) {
|
||||
if(p(vertex)) {
|
||||
return vertexId;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::out_of_range("vertex not found");
|
||||
}
|
||||
|
||||
std::shared_ptr<EdgeType> getEdge(EdgeIdentifier edgeId) const
|
||||
{
|
||||
if(edgeId < 0 or edgeId >= lastEdgeId)
|
||||
|
@ -194,8 +207,9 @@ public:
|
|||
vertexGetEdges(VertexIdentifier vertexId) const
|
||||
{ return getVertex(vertexId)->edges; }
|
||||
|
||||
bool getPath(VertexIdentifier fromVertexId, VertexIdentifier toVertexId,
|
||||
std::list<EdgeIdentifier>& path)
|
||||
bool getPath(VertexIdentifier fromVertexId,
|
||||
VertexIdentifier toVertexId,
|
||||
Path& path)
|
||||
{
|
||||
if(fromVertexId == toVertexId) {
|
||||
// arrived at the destination
|
||||
|
|
|
@ -45,6 +45,8 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
#define PCI_FILTER_DEFAULT_FPGA { \
|
||||
.id = { \
|
||||
.vendor = FPGA_PCI_VID_XILINX, \
|
||||
|
@ -75,7 +77,8 @@ public:
|
|||
bool reset() { return true; }
|
||||
void dump() { }
|
||||
|
||||
ip::IpCore* lookupIp(std::string name) const;
|
||||
ip::IpCore* lookupIp(const std::string& name) const;
|
||||
ip::IpCore* lookupIp(const Vlnv& vlnv) const;
|
||||
|
||||
ip::IpCoreList ips; ///< IPs located on this FPGA card
|
||||
|
||||
|
@ -91,7 +94,9 @@ public:
|
|||
::vfio_container *vfio_container;
|
||||
struct vfio_device vfio_device; /**< VFIO device handle. */
|
||||
|
||||
char *map; /**< PCI BAR0 mapping for register access */
|
||||
/// Address space identifier of the master address space of this FPGA card.
|
||||
/// This will be used for address resolution of all IPs on this card.
|
||||
MemoryManager::AddressSpaceId addrSpaceId;
|
||||
|
||||
size_t maplen;
|
||||
size_t dmalen;
|
||||
|
|
|
@ -41,14 +41,23 @@
|
|||
|
||||
#include <jansson.h>
|
||||
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
|
||||
// forward declaration
|
||||
class PCIeCard;
|
||||
|
||||
namespace ip {
|
||||
|
||||
// forward declarations
|
||||
class IpCore;
|
||||
class IpCoreFactory;
|
||||
class InterruptController;
|
||||
|
||||
using IpCoreList = std::list<std::unique_ptr<IpCore>>;
|
||||
|
||||
|
||||
class IpIdentifier {
|
||||
public:
|
||||
|
@ -58,55 +67,109 @@ public:
|
|||
IpIdentifier(std::string vlnvString, std::string name = "") :
|
||||
vlnv(vlnvString), name(name) {}
|
||||
|
||||
const std::string&
|
||||
getName() const
|
||||
{ return name; }
|
||||
|
||||
const Vlnv&
|
||||
getVlnv() const
|
||||
{ return vlnv; }
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const IpIdentifier& id)
|
||||
{ return stream << TXT_BOLD(id.name) << " vlnv=" << id.vlnv; }
|
||||
|
||||
bool
|
||||
operator==(const IpIdentifier& otherId) const {
|
||||
const bool vlnvWildcard = otherId.getVlnv() == Vlnv::getWildcard();
|
||||
const bool nameWildcard = this->getName().empty() or otherId.getName().empty();
|
||||
|
||||
const bool vlnvMatch = vlnvWildcard or this->getVlnv() == otherId.getVlnv();
|
||||
const bool nameMatch = nameWildcard or this->getName() == otherId.getName();
|
||||
|
||||
return vlnvMatch and nameMatch;
|
||||
}
|
||||
|
||||
bool
|
||||
operator!=(const IpIdentifier& otherId) const
|
||||
{ return !(*this == otherId); }
|
||||
|
||||
private:
|
||||
Vlnv vlnv;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
using IpDependency = std::pair<std::string, Vlnv>;
|
||||
|
||||
// forward declarations
|
||||
class IpCoreFactory;
|
||||
|
||||
class IpCore {
|
||||
public:
|
||||
|
||||
friend IpCoreFactory;
|
||||
|
||||
IpCore() : card(nullptr), baseaddr(0) {}
|
||||
virtual ~IpCore() {}
|
||||
public:
|
||||
IpCore() : card(nullptr) {}
|
||||
virtual ~IpCore() = default;
|
||||
|
||||
// IPs can implement this interface
|
||||
public:
|
||||
/* Generic management interface for IPs */
|
||||
|
||||
/// Runtime setup of IP, should access and initialize hardware
|
||||
virtual bool init()
|
||||
{ return true; }
|
||||
|
||||
/// Runtime check of IP, should verify basic functionality
|
||||
virtual bool check() { return true; }
|
||||
virtual bool init() { return true; }
|
||||
|
||||
/// Generic disabling of IP, meaning may depend on IP
|
||||
virtual bool stop() { return true; }
|
||||
|
||||
/// Reset the IP, it should behave like freshly initialized afterwards
|
||||
virtual bool reset() { return true; }
|
||||
|
||||
/// Print some debug information about the IP
|
||||
virtual void dump();
|
||||
|
||||
bool
|
||||
operator== (const IpIdentifier& otherId) {
|
||||
const bool vlnvMatch = id.vlnv == otherId.vlnv;
|
||||
const bool nameWildcard = id.name.empty() or otherId.name.empty();
|
||||
protected:
|
||||
/// Each IP can declare via this function which memory blocks it requires
|
||||
virtual std::list<std::string>
|
||||
getMemoryBlocks() const
|
||||
{ return {}; }
|
||||
|
||||
return vlnvMatch and (nameWildcard or id.name == otherId.name);
|
||||
}
|
||||
public:
|
||||
const std::string&
|
||||
getInstanceName() const
|
||||
{ return id.getName(); }
|
||||
|
||||
/* Operators */
|
||||
|
||||
bool
|
||||
operator!= (const IpIdentifier& otherId) {
|
||||
return !(*this == otherId);
|
||||
}
|
||||
operator==(const Vlnv& otherVlnv) const
|
||||
{ return id.getVlnv() == otherVlnv; }
|
||||
|
||||
bool
|
||||
operator== (const Vlnv& otherVlnv)
|
||||
{ return id.vlnv == otherVlnv; }
|
||||
operator!=(const Vlnv& otherVlnv) const
|
||||
{ return id.getVlnv() != otherVlnv; }
|
||||
|
||||
bool
|
||||
operator== (const std::string& otherName)
|
||||
{ return id.name == otherName; }
|
||||
operator==(const IpIdentifier& otherId) const
|
||||
{ return this->id == otherId; }
|
||||
|
||||
bool
|
||||
operator!=(const IpIdentifier& otherId) const
|
||||
{ return this->id != otherId; }
|
||||
|
||||
bool
|
||||
operator==(const std::string& otherName) const
|
||||
{ return getInstanceName() == otherName; }
|
||||
|
||||
bool
|
||||
operator!=(const std::string& otherName) const
|
||||
{ return getInstanceName() != otherName; }
|
||||
|
||||
bool
|
||||
operator==(const IpCore& otherIp) const
|
||||
{ return this->id == otherIp.id; }
|
||||
|
||||
bool
|
||||
operator!=(const IpCore& otherIp) const
|
||||
{ return this->id != otherIp.id; }
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const IpCore& ip)
|
||||
|
@ -114,33 +177,39 @@ public:
|
|||
|
||||
protected:
|
||||
uintptr_t
|
||||
getBaseaddr() const
|
||||
{ return getAddrMapped(this->baseaddr); }
|
||||
getBaseAddr(const std::string& block) const;
|
||||
|
||||
uintptr_t
|
||||
getAddrMapped(uintptr_t address) const;
|
||||
getLocalAddr(const std::string& block, uintptr_t address) const;
|
||||
|
||||
SpdLogger
|
||||
getLogger() { return loggerGetOrCreate(id.name); }
|
||||
getLogger() const
|
||||
{ return loggerGetOrCreate(getInstanceName()); }
|
||||
|
||||
InterruptController*
|
||||
getInterruptController(const std::string& interruptName) const;
|
||||
|
||||
protected:
|
||||
struct IrqPort {
|
||||
int num;
|
||||
std::string controllerName;
|
||||
InterruptController* irqController;
|
||||
std::string description;
|
||||
};
|
||||
|
||||
protected:
|
||||
// populated by FpgaIpFactory
|
||||
PCIeCard* card; ///< FPGA card this IP is instantiated on
|
||||
IpIdentifier id; ///< VLNV and name defined in JSON config
|
||||
uintptr_t baseaddr; ///< The baseadress of this IP component
|
||||
std::map<std::string, IrqPort> irqs; ///< Interrupts of this IP component
|
||||
std::map<std::string, IpCore*> dependencies; ///< dependencies on other IPs
|
||||
/// FPGA card this IP is instantiated on (populated by FpgaIpFactory)
|
||||
PCIeCard* card;
|
||||
|
||||
/// Identifier of this IP with its instance name and VLNV
|
||||
IpIdentifier id;
|
||||
|
||||
/// All interrupts of this IP with their associated interrupt controller
|
||||
std::map<std::string, IrqPort> irqs;
|
||||
|
||||
/// Cached translations from the process address space to each memory block
|
||||
std::map<std::string, MemoryTranslation> addressTranslations;
|
||||
};
|
||||
|
||||
|
||||
using IpCoreList = std::list<std::unique_ptr<IpCore>>;
|
||||
|
||||
|
||||
class IpCoreFactory : public Plugin {
|
||||
public:
|
||||
|
@ -154,7 +223,8 @@ public:
|
|||
|
||||
protected:
|
||||
SpdLogger
|
||||
getLogger() { return loggerGetOrCreate(getName()); }
|
||||
getLogger() const
|
||||
{ return loggerGetOrCreate(getName()); }
|
||||
|
||||
private:
|
||||
/// Create a concrete IP instance
|
||||
|
@ -164,11 +234,9 @@ private:
|
|||
virtual bool configureJson(IpCore& /* ip */, json_t* /* json */)
|
||||
{ return true; }
|
||||
|
||||
|
||||
virtual Vlnv getCompatibleVlnv() const = 0;
|
||||
virtual std::string getName() const = 0;
|
||||
virtual std::string getDescription() const = 0;
|
||||
virtual std::list<IpDependency> getDependencies() const { return {}; }
|
||||
|
||||
protected:
|
||||
static SpdLogger
|
||||
|
|
|
@ -50,19 +50,21 @@ public:
|
|||
size_t read(void* buf, size_t len);
|
||||
|
||||
private:
|
||||
static constexpr char registerMemory[] = "Mem0";
|
||||
static constexpr char axi4Memory[] = "Mem1";
|
||||
static constexpr char irqName[] = "interrupt";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
{ return { registerMemory, axi4Memory }; }
|
||||
|
||||
XLlFifo xFifo;
|
||||
uintptr_t baseaddr_axi4;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FifoFactory : public IpNodeFactory {
|
||||
public:
|
||||
FifoFactory() :
|
||||
IpNodeFactory(getName())
|
||||
{}
|
||||
|
||||
bool configureJson(IpCore& ip, json_t *json_ip);
|
||||
FifoFactory();
|
||||
|
||||
IpCore* create()
|
||||
{ return new Fifo; }
|
||||
|
@ -77,9 +79,6 @@ public:
|
|||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return {"xilinx.com:ip:axi_fifo_mm_s:"}; }
|
||||
|
||||
std::list<IpDependency> getDependencies() const
|
||||
{ return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
|
|
|
@ -61,6 +61,13 @@ public:
|
|||
{ return waitForInterrupt(irq.num); }
|
||||
|
||||
private:
|
||||
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
|
||||
struct Interrupt {
|
||||
int eventFd; /**< Event file descriptor */
|
||||
int number; /**< Interrupt number from /proc/interrupts */
|
||||
|
@ -82,6 +89,10 @@ public:
|
|||
IpCoreFactory(getName())
|
||||
{}
|
||||
|
||||
static constexpr const char*
|
||||
getCompatibleVlnvString()
|
||||
{ return "acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"; }
|
||||
|
||||
IpCore* create()
|
||||
{ return new InterruptController; }
|
||||
|
||||
|
@ -94,7 +105,10 @@ public:
|
|||
{ return "Xilinx's programmable interrupt controller"; }
|
||||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:"); }
|
||||
{ return Vlnv(getCompatibleVlnvString()); }
|
||||
|
||||
// std::list<IpDependency> getDependencies() const
|
||||
// { return { {"pcie", Vlnv("xilinx.com:ip:axi_pcie:") } }; }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
|
|
80
fpga/include/villas/fpga/ips/pcie.hpp
Normal file
80
fpga/include/villas/fpga/ips/pcie.hpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
/** AXI Stream interconnect related helper functions
|
||||
*
|
||||
* These functions present a simpler interface to Xilinx' AXI Stream switch driver (XAxis_Switch_*)
|
||||
*
|
||||
* @file
|
||||
* @author Steffen Vogel <stvogel@eonerc.rwth-aachen.de>
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @copyright 2017, Steffen Vogel
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASfpga
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
/** @addtogroup fpga VILLASfpga
|
||||
* @{
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include <jansson.h>
|
||||
#include <xilinx/xaxis_switch.h>
|
||||
|
||||
#include "fpga/ip_node.hpp"
|
||||
#include "fpga/vlnv.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
class AxiPciExpressBridge : public IpCore {
|
||||
public:
|
||||
friend class AxiPciExpressBridgeFactory;
|
||||
|
||||
bool init();
|
||||
};
|
||||
|
||||
|
||||
class AxiPciExpressBridgeFactory : public IpCoreFactory {
|
||||
public:
|
||||
AxiPciExpressBridgeFactory() :
|
||||
IpCoreFactory(getName()) {}
|
||||
|
||||
static constexpr const char*
|
||||
getCompatibleVlnvString()
|
||||
{ return "xilinx.com:ip:axi_pcie:"; }
|
||||
|
||||
IpCore* create()
|
||||
{ return new AxiPciExpressBridge; }
|
||||
|
||||
std::string getName() const
|
||||
{ return "AxiPciExpressBridge"; }
|
||||
|
||||
std::string getDescription() const
|
||||
{ return "Xilinx's AXI-PCIe Bridge"; }
|
||||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return Vlnv(getCompatibleVlnvString()); }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
||||
/** @} */
|
|
@ -56,6 +56,11 @@ public:
|
|||
private:
|
||||
static constexpr int PORT_DISABLED = -1;
|
||||
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
struct Path {
|
||||
IpCore* masterOut;
|
||||
IpCore* slaveIn;
|
||||
|
@ -72,6 +77,10 @@ public:
|
|||
AxiStreamSwitchFactory() :
|
||||
IpNodeFactory(getName()) {}
|
||||
|
||||
static constexpr const char*
|
||||
getCompatibleVlnvString()
|
||||
{ return "xilinx.com:ip:axis_switch:"; }
|
||||
|
||||
bool configureJson(IpCore& ip, json_t *json_ip);
|
||||
|
||||
IpCore* create()
|
||||
|
@ -84,7 +93,7 @@ public:
|
|||
{ return "Xilinx's AXI4-Stream switch"; }
|
||||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return Vlnv("xilinx.com:ip:axis_switch:"); }
|
||||
{ return Vlnv(getCompatibleVlnvString()); }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
|
|
|
@ -43,10 +43,10 @@ namespace ip {
|
|||
|
||||
class Timer : public IpCore
|
||||
{
|
||||
friend class TimerFactory;
|
||||
public:
|
||||
bool init();
|
||||
|
||||
|
||||
bool start(uint32_t ticks);
|
||||
bool wait();
|
||||
uint32_t remaining();
|
||||
|
@ -62,8 +62,14 @@ public:
|
|||
{ return FPGA_AXI_HZ; }
|
||||
|
||||
private:
|
||||
|
||||
std::list<std::string> getMemoryBlocks() const
|
||||
{ return { registerMemory }; }
|
||||
|
||||
static constexpr char irqName[] = "generateout0";
|
||||
static constexpr char registerMemory[] = "Reg";
|
||||
|
||||
XTmrCtr xTmr;
|
||||
InterruptController* intc;
|
||||
};
|
||||
|
||||
|
||||
|
@ -88,9 +94,6 @@ public:
|
|||
|
||||
Vlnv getCompatibleVlnv() const
|
||||
{ return {"xilinx.com:ip:axi_timer:"}; }
|
||||
|
||||
std::list<IpDependency> getDependencies() const
|
||||
{ return { {"intc", Vlnv("acs.eonerc.rwth-aachen.de:user:axi_pcie_intc:") } }; }
|
||||
};
|
||||
|
||||
} // namespace ip
|
||||
|
|
|
@ -34,10 +34,8 @@
|
|||
namespace villas {
|
||||
namespace fpga {
|
||||
|
||||
|
||||
class Vlnv {
|
||||
public:
|
||||
|
||||
static constexpr char delimiter = ':';
|
||||
|
||||
Vlnv() :
|
||||
|
@ -52,20 +50,15 @@ public:
|
|||
{ return Vlnv(); }
|
||||
|
||||
std::string
|
||||
toString() const
|
||||
{
|
||||
std::stringstream stream;
|
||||
std::string string;
|
||||
|
||||
stream << *this;
|
||||
stream >> string;
|
||||
|
||||
return string;
|
||||
}
|
||||
toString() const;
|
||||
|
||||
bool
|
||||
operator==(const Vlnv& other) const;
|
||||
|
||||
bool
|
||||
operator!=(const Vlnv& other) const
|
||||
{ return !(*this == other); }
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const Vlnv& vlnv)
|
||||
{
|
||||
|
|
|
@ -104,6 +104,9 @@ void vfio_dump(struct vfio_container *c);
|
|||
/** Map a device memory region to the application address space (e.g. PCI BARs) */
|
||||
void * vfio_map_region(struct vfio_device *d, int idx);
|
||||
|
||||
/** Get the size of a device memory region */
|
||||
size_t vfio_region_size(struct vfio_device *d, int idx);
|
||||
|
||||
/** Map VM to an IOVA, which is accessible by devices in the container */
|
||||
int vfio_map_dma(struct vfio_container *c, uint64_t virt, uint64_t phys, size_t len);
|
||||
|
||||
|
|
|
@ -2,96 +2,207 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "directed_graph.hpp"
|
||||
|
||||
namespace villas {
|
||||
|
||||
|
||||
class Mapping : public graph::Edge {
|
||||
friend class MemoryManager;
|
||||
|
||||
/**
|
||||
* @brief Translation between a local (master) to a foreign (slave) address space
|
||||
*
|
||||
* Memory translations can be chained together using the `+=` operator which is
|
||||
* used internally by the MemoryManager to compute a translation through
|
||||
* multiple hops (memory mappings).
|
||||
*/
|
||||
class MemoryTranslation {
|
||||
public:
|
||||
// create mapping here (if needed)
|
||||
Mapping() {}
|
||||
|
||||
// destroy mapping here (if needed)
|
||||
virtual ~Mapping();
|
||||
/**
|
||||
* @brief MemoryTranslation
|
||||
* @param src Base address of local address space
|
||||
* @param dst Base address of foreign address space
|
||||
* @param size Size of "memory window"
|
||||
*/
|
||||
MemoryTranslation(uintptr_t src, uintptr_t dst, size_t size) :
|
||||
src(src), dst(dst), size(size) {}
|
||||
|
||||
uintptr_t
|
||||
getLocalAddr(uintptr_t addrInForeignAddrSpace) const;
|
||||
|
||||
uintptr_t
|
||||
getForeignAddr(uintptr_t addrInLocalAddrSpace) const;
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const Mapping& mapping)
|
||||
operator<< (std::ostream& stream, const MemoryTranslation& translation)
|
||||
{
|
||||
return stream << static_cast<const Edge&>(mapping) << " = "
|
||||
<< std::hex
|
||||
<< "(src=0x" << mapping.src
|
||||
<< ", dest=0x" << mapping.dest
|
||||
<< ", size=0x" << mapping.size
|
||||
return stream << std::hex
|
||||
<< "(src=0x" << translation.src
|
||||
<< ", dst=0x" << translation.dst
|
||||
<< ", size=0x" << translation.size
|
||||
<< ")";
|
||||
}
|
||||
|
||||
private:
|
||||
uintptr_t src;
|
||||
uintptr_t dest;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
class AddressSpace : public graph::Vertex {
|
||||
friend class MemoryManager;
|
||||
|
||||
public:
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const AddressSpace& addrSpace)
|
||||
{
|
||||
return stream << static_cast<const Vertex&>(addrSpace) << " = "
|
||||
<< addrSpace.name;
|
||||
}
|
||||
/// Merge two MemoryTranslations together
|
||||
MemoryTranslation& operator+=(const MemoryTranslation& other);
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
uintptr_t src; ///< Base address of local address space
|
||||
uintptr_t dst; ///< Base address of foreign address space
|
||||
size_t size; ///< Size of "memory window"
|
||||
};
|
||||
|
||||
|
||||
// is or has a graph
|
||||
/**
|
||||
* @brief Global memory manager to resolve addresses across address spaces
|
||||
*
|
||||
* Every entity in the system has to register its (master) address space and
|
||||
* create mappings to other (slave) address spaces that it can access. A
|
||||
* directed graph is then constructed which allows to traverse addresses spaces
|
||||
* through multiple mappings and resolve addresses through this "tunnel" of
|
||||
* memory mappings.
|
||||
*/
|
||||
class MemoryManager {
|
||||
private:
|
||||
// This is a singleton, so private constructor
|
||||
// This is a singleton, so private constructor ...
|
||||
MemoryManager() :
|
||||
memoryGraph("MemoryGraph") {}
|
||||
memoryGraph("MemoryGraph"),
|
||||
logger(loggerGetOrCreate("MemoryManager")) {}
|
||||
|
||||
// no copying or assigning
|
||||
// ... and no copying or assigning
|
||||
MemoryManager(const MemoryManager&) = delete;
|
||||
MemoryManager& operator=(const MemoryManager&) = delete ;
|
||||
|
||||
/**
|
||||
* @brief Custom edge in memory graph representing a memory mapping
|
||||
*
|
||||
* A memory mapping maps from one address space into another and can only be
|
||||
* traversed in the forward direction which reflects the nature of real
|
||||
* memory mappings.
|
||||
*
|
||||
* Implementation Notes:
|
||||
* The member #src is the address in the "from" address space, where the
|
||||
* destination address space is mapped. The member #dest is the address in
|
||||
* the destination address space, where the mapping points to. Often, #dest
|
||||
* will be zero for mappings to hardware, but consider the example when
|
||||
* mapping FPGA to application memory:
|
||||
* The application allocates a block 1kB at address
|
||||
* 0x843001000 in its address space. The mapping would then have a #dest
|
||||
* address of 0x843001000 and a #size of 1024.
|
||||
*/
|
||||
class Mapping : public graph::Edge {
|
||||
public:
|
||||
std::string name; ///< Human-readable name
|
||||
uintptr_t src; ///< Base address in "from" address space
|
||||
uintptr_t dest; ///< Base address in "to" address space
|
||||
size_t size; ///< Size of the mapping
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const Mapping& mapping)
|
||||
{
|
||||
return stream << static_cast<const Edge&>(mapping) << " = "
|
||||
<< mapping.name
|
||||
<< std::hex
|
||||
<< "(src=0x" << mapping.src
|
||||
<< ", dest=0x" << mapping.dest
|
||||
<< ", size=0x" << mapping.size
|
||||
<< ")";
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Custom vertex in memory graph representing an address space
|
||||
*
|
||||
* Since most information in the memory graph is stored in the edges (memory
|
||||
* mappings), this is just a small extension to the default vertex. It only
|
||||
* associates an additional string #name for human-readability.
|
||||
*/
|
||||
class AddressSpace : public graph::Vertex {
|
||||
public:
|
||||
std::string name; ///< Human-readable name
|
||||
|
||||
friend std::ostream&
|
||||
operator<< (std::ostream& stream, const AddressSpace& addrSpace)
|
||||
{
|
||||
return stream << static_cast<const Vertex&>(addrSpace) << " = "
|
||||
<< addrSpace.name;
|
||||
}
|
||||
};
|
||||
|
||||
/// Memory graph with custom edges and vertices for address resolution
|
||||
using MemoryGraph = graph::DirectedGraph<AddressSpace, Mapping>;
|
||||
|
||||
public:
|
||||
using AddressSpaceId = MemoryGraph::VertexIdentifier;
|
||||
using MappingId = MemoryGraph::EdgeIdentifier;
|
||||
|
||||
static MemoryManager& get();
|
||||
/// Get singleton instance
|
||||
static MemoryManager&
|
||||
get();
|
||||
|
||||
AddressSpaceId
|
||||
getProcessAddressSpace()
|
||||
{ return getOrCreateAddressSpace("villas-fpga"); }
|
||||
|
||||
AddressSpaceId createAddressSpace(std::string name);
|
||||
AddressSpaceId
|
||||
getOrCreateAddressSpace(std::string name);
|
||||
|
||||
/// Create a default mapping
|
||||
MappingId createMapping(uintptr_t src, uintptr_t dest, size_t size,
|
||||
AddressSpaceId fromAddrSpace,
|
||||
AddressSpaceId toAddrSpace);
|
||||
MappingId
|
||||
createMapping(uintptr_t src, uintptr_t dest, size_t size, std::string name,
|
||||
AddressSpaceId fromAddrSpace,
|
||||
AddressSpaceId toAddrSpace);
|
||||
|
||||
/// Add a mapping
|
||||
///
|
||||
/// Can be used to derive from Mapping in order to implement custom
|
||||
/// constructor/destructor.
|
||||
MappingId addMapping(std::shared_ptr<Mapping> mapping,
|
||||
AddressSpaceId fromAddrSpace,
|
||||
AddressSpaceId toAddrSpace);
|
||||
MappingId
|
||||
addMapping(std::shared_ptr<Mapping> mapping,
|
||||
AddressSpaceId fromAddrSpace,
|
||||
AddressSpaceId toAddrSpace);
|
||||
|
||||
void dump()
|
||||
|
||||
AddressSpaceId
|
||||
findAddressSpace(std::string name);
|
||||
|
||||
MemoryTranslation
|
||||
getTranslation(AddressSpaceId fromAddrSpaceId, AddressSpaceId toAddrSpaceId);
|
||||
|
||||
MemoryTranslation
|
||||
getTranslationFromProcess(AddressSpaceId foreignAddrSpaceId)
|
||||
{ return getTranslation(getProcessAddressSpace(), foreignAddrSpaceId); }
|
||||
|
||||
static std::string
|
||||
getSlaveAddrSpaceName(std::string ipInstance, std::string memoryBlock)
|
||||
{ return ipInstance + "/" + memoryBlock; }
|
||||
|
||||
void
|
||||
dump()
|
||||
{ memoryGraph.dump(); }
|
||||
|
||||
|
||||
private:
|
||||
/// Convert a Mapping to MemoryTranslation for calculations
|
||||
static MemoryTranslation
|
||||
getTranslationFromMapping(const Mapping& mapping)
|
||||
{ return MemoryTranslation(mapping.src, mapping.dest, mapping.size); }
|
||||
|
||||
|
||||
private:
|
||||
/// Directed graph that stores address spaces and memory mappings
|
||||
MemoryGraph memoryGraph;
|
||||
|
||||
/// Cache mapping of names to address space ids for fast lookup
|
||||
std::map<std::string, AddressSpaceId> addrSpaceLookup;
|
||||
|
||||
/// Logger for universal access in this class
|
||||
SpdLogger logger;
|
||||
|
||||
/// Static pointer to global instance, because this is a singleton
|
||||
static MemoryManager* instance;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,39 +1,43 @@
|
|||
set(SOURCES
|
||||
ip.cpp
|
||||
ip.c
|
||||
ip_node.cpp
|
||||
vlnv.cpp
|
||||
vlnv.c
|
||||
card.c
|
||||
|
||||
vlnv.cpp
|
||||
card.cpp
|
||||
ip.cpp
|
||||
ip_node.cpp
|
||||
|
||||
ips/timer.c
|
||||
ips/timer.cpp
|
||||
ips/model.c
|
||||
ips/switch.c
|
||||
ips/switch.cpp
|
||||
ips/dft.c
|
||||
ips/fifo.c
|
||||
ips/fifo.cpp
|
||||
ips/dma.c
|
||||
ips/intc.cpp
|
||||
ips/intc.c
|
||||
ips/gpio.c
|
||||
ips/rtds_axis.c
|
||||
|
||||
ips/timer.cpp
|
||||
ips/switch.cpp
|
||||
ips/fifo.cpp
|
||||
ips/intc.cpp
|
||||
ips/pcie.cpp
|
||||
|
||||
kernel/kernel.c
|
||||
kernel/pci.c
|
||||
kernel/vfio.c
|
||||
|
||||
memory_manager.cpp
|
||||
plugin.c
|
||||
plugin.cpp
|
||||
utils.c
|
||||
utils.cpp
|
||||
list.c
|
||||
log.c
|
||||
log_config.c
|
||||
log_helper.c
|
||||
|
||||
plugin.cpp
|
||||
utils.cpp
|
||||
memory_manager.cpp
|
||||
)
|
||||
|
||||
include(FindPkgConfig)
|
||||
|
|
|
@ -20,31 +20,18 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "log_config.h"
|
||||
#include "list.h"
|
||||
#include "utils.h"
|
||||
#include "log.hpp"
|
||||
|
||||
#include "kernel/pci.h"
|
||||
#include "kernel/vfio.h"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "fpga/ip.hpp"
|
||||
#include "fpga/card.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
|
||||
|
@ -101,7 +88,6 @@ fpga::PCIeCardFactory::make(json_t *json, struct pci* pci, ::vfio_container* vc)
|
|||
}
|
||||
|
||||
|
||||
// TODO: currently fails, fix and remove comment
|
||||
if(not card->init()) {
|
||||
logger->warn("Cannot start FPGA card {}", card_name);
|
||||
continue;
|
||||
|
@ -132,7 +118,8 @@ fpga::PCIeCardFactory::create()
|
|||
|
||||
|
||||
ip::IpCore*
|
||||
PCIeCard::lookupIp(std::string name) const {
|
||||
PCIeCard::lookupIp(const std::string& name) const
|
||||
{
|
||||
for(auto& ip : ips) {
|
||||
if(*ip == name) {
|
||||
return ip.get();
|
||||
|
@ -141,14 +128,28 @@ PCIeCard::lookupIp(std::string name) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
ip::IpCore*
|
||||
PCIeCard::lookupIp(const Vlnv& vlnv) const
|
||||
{
|
||||
for(auto& ip : ips) {
|
||||
if(*ip == vlnv) {
|
||||
return ip.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool fpga::PCIeCard::init()
|
||||
|
||||
bool
|
||||
fpga::PCIeCard::init()
|
||||
{
|
||||
int ret;
|
||||
struct pci_device *pdev;
|
||||
|
||||
auto logger = getLogger();
|
||||
|
||||
logger->info("Initializing FPGA card {}", name);
|
||||
|
||||
/* Search for FPGA card */
|
||||
pdev = pci_lookup_device(pci, &filter);
|
||||
if (!pdev) {
|
||||
|
@ -164,12 +165,31 @@ bool fpga::PCIeCard::init()
|
|||
}
|
||||
|
||||
/* Map PCIe BAR */
|
||||
map = (char*) vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX);
|
||||
if (map == MAP_FAILED) {
|
||||
const void* bar0_mapped = vfio_map_region(&vfio_device, VFIO_PCI_BAR0_REGION_INDEX);
|
||||
if (bar0_mapped == MAP_FAILED) {
|
||||
logger->error("Failed to mmap() BAR0");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* Link mapped BAR0 to global memory graph */
|
||||
|
||||
// get the address space of the current application
|
||||
auto villasAddrSpace = MemoryManager::get().getProcessAddressSpace();
|
||||
|
||||
// create a new address space for this FPGA card
|
||||
this->addrSpaceId = MemoryManager::get().getOrCreateAddressSpace(name);
|
||||
|
||||
// determine size of BAR0 region
|
||||
const size_t bar0_size = vfio_region_size(&vfio_device,
|
||||
VFIO_PCI_BAR0_REGION_INDEX);
|
||||
|
||||
// create a mapping from our address space to the FPGA card via vfio
|
||||
MemoryManager::get().createMapping(reinterpret_cast<uintptr_t>(bar0_mapped),
|
||||
0, bar0_size, "VFIO_map",
|
||||
villasAddrSpace, this->addrSpaceId);
|
||||
|
||||
|
||||
/* Enable memory access and PCI bus mastering for DMA */
|
||||
ret = vfio_pci_enable(&vfio_device);
|
||||
if (ret) {
|
||||
|
|
412
fpga/lib/ip.cpp
412
fpga/lib/ip.cpp
|
@ -20,188 +20,107 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include "log_config.h"
|
||||
#include "log.hpp"
|
||||
#include "plugin.h"
|
||||
#include "dependency_graph.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
#include "fpga/ip.hpp"
|
||||
#include "fpga/card.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "log.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
using DependencyGraph = villas::utils::DependencyGraph<std::string>;
|
||||
#include "fpga/ip.hpp"
|
||||
#include "fpga/vlnv.hpp"
|
||||
#include "fpga/card.hpp"
|
||||
|
||||
static
|
||||
std::list<std::string>
|
||||
dependencyTokens = {"irqs"};
|
||||
|
||||
static
|
||||
bool
|
||||
buildDependencyGraph(DependencyGraph& dependencyGraph, json_t* json_ips, std::string name)
|
||||
{
|
||||
const bool nodeExists = dependencyGraph.addNode(name);
|
||||
|
||||
// HACK: just get the right logger
|
||||
auto logger = loggerGetOrCreate("IpCoreFactory");
|
||||
|
||||
// do not add IP multiple times
|
||||
// this happens if more than 1 IP depends on a certain other IP
|
||||
if(nodeExists) {
|
||||
return true;
|
||||
}
|
||||
|
||||
json_t* json_ip = json_object_get(json_ips, name.c_str());
|
||||
if(json_ip == nullptr) {
|
||||
logger->error("IP {} not found in config", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(auto& dependencyToken : dependencyTokens) {
|
||||
json_t* json_dependency = json_object_get(json_ip, dependencyToken.c_str());
|
||||
if(json_dependency == nullptr) {
|
||||
logger->debug("Property {} of {} is not present",
|
||||
dependencyToken, TXT_BOLD(name));
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* irq_name;
|
||||
json_t* json_irq;
|
||||
json_object_foreach(json_dependency, irq_name, json_irq) {
|
||||
const char* value = json_string_value(json_irq);
|
||||
if(value == nullptr) {
|
||||
logger->warn("Property {} of {} is invalid",
|
||||
dependencyToken, TXT_BOLD(name));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto mapping = villas::utils::tokenize(value, ":");
|
||||
|
||||
|
||||
if(mapping.size() != 2) {
|
||||
logger->error("Invalid {} mapping of {}",
|
||||
dependencyToken, TXT_BOLD(name));
|
||||
|
||||
dependencyGraph.removeNode(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(name == mapping[0]) {
|
||||
logger->error("IP {} cannot depend on itself", TXT_BOLD(name));
|
||||
|
||||
dependencyGraph.removeNode(name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// already add dependency, if adding it fails, removing the dependency
|
||||
// will also remove the current one
|
||||
dependencyGraph.addDependency(name, mapping[0]);
|
||||
|
||||
if(not buildDependencyGraph(dependencyGraph, json_ips, mapping[0])) {
|
||||
logger->error("Dependency {} of {} not satisfied",
|
||||
mapping[0], TXT_BOLD(name));
|
||||
|
||||
dependencyGraph.removeNode(mapping[0]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// needed to get VLNVs for initialization order list
|
||||
#include "fpga/ips/pcie.hpp"
|
||||
#include "fpga/ips/intc.hpp"
|
||||
#include "fpga/ips/switch.hpp"
|
||||
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
void IpCore::dump() {
|
||||
auto logger = getLogger();
|
||||
|
||||
logger->info("Base address = {:08x}", baseaddr);
|
||||
for(auto& [num, irq] : irqs) {
|
||||
logger->info("IRQ {}: {}:{}", num, irq.controllerName, irq.num);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IpCoreFactory* IpCoreFactory::lookup(const Vlnv &vlnv)
|
||||
{
|
||||
for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) {
|
||||
IpCoreFactory* ipCoreFactory = dynamic_cast<IpCoreFactory*>(ip);
|
||||
|
||||
if(ipCoreFactory->getCompatibleVlnv() == vlnv)
|
||||
return ipCoreFactory;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
IpCore::getAddrMapped(uintptr_t address) const
|
||||
{
|
||||
assert(card != nullptr);
|
||||
return reinterpret_cast<uintptr_t>(card->map) + address;
|
||||
}
|
||||
// Special IPs that have to be initialized first. Will be initialized in the
|
||||
// same order as they appear in this list, i.e. first here will be initialized
|
||||
// first.
|
||||
static std::list<Vlnv>
|
||||
vlnvInitializationOrder = {
|
||||
Vlnv(AxiPciExpressBridgeFactory::getCompatibleVlnvString()),
|
||||
Vlnv(InterruptControllerFactory::getCompatibleVlnvString()),
|
||||
Vlnv(AxiStreamSwitchFactory::getCompatibleVlnvString()),
|
||||
};
|
||||
|
||||
|
||||
IpCoreList
|
||||
IpCoreFactory::make(PCIeCard* card, json_t *json_ips)
|
||||
{
|
||||
DependencyGraph dependencyGraph;
|
||||
IpCoreList initializedIps;
|
||||
// We only have this logger until we know the factory to build an IP with
|
||||
auto loggerStatic = getStaticLogger();
|
||||
|
||||
std::list<IpIdentifier> allIps; // all IPs available in config
|
||||
std::list<IpIdentifier> orderedIps; // IPs ordered in initialization order
|
||||
|
||||
loggerStatic->debug("Parsing IP dependency graph:");
|
||||
void* iter = json_object_iter(json_ips);
|
||||
while(iter != nullptr) {
|
||||
buildDependencyGraph(dependencyGraph, json_ips, json_object_iter_key(iter));
|
||||
iter = json_object_iter_next(json_ips, iter);
|
||||
}
|
||||
IpCoreList configuredIps; // Successfully configured IPs
|
||||
IpCoreList initializedIps; // Initialized, i.e. ready-to-use IPs
|
||||
|
||||
|
||||
loggerStatic->debug("IP initialization order:");
|
||||
for(auto& ipName : dependencyGraph.getEvaluationOrder()) {
|
||||
loggerStatic->debug(" {}", TXT_BOLD(ipName));
|
||||
}
|
||||
|
||||
|
||||
for(auto& ipName : dependencyGraph.getEvaluationOrder()) {
|
||||
loggerStatic->info("Initializing {}", TXT_BOLD(ipName));
|
||||
|
||||
json_t* json_ip = json_object_get(json_ips, ipName.c_str());
|
||||
|
||||
// extract VLNV from JSON
|
||||
// parse all IP instance names and their VLNV into list `allIps`
|
||||
const char* ipName;
|
||||
json_t* json_ip;
|
||||
json_object_foreach(json_ips, ipName, json_ip) {
|
||||
const char* vlnv;
|
||||
if(json_unpack(json_ip, "{ s: s }", "vlnv", &vlnv) != 0) {
|
||||
loggerStatic->warn("IP {} has no entry 'vlnv'", ipName);
|
||||
loggerStatic->warn("IP {} has no VLNV", ipName);
|
||||
continue;
|
||||
}
|
||||
|
||||
IpIdentifier id(Vlnv(vlnv), ipName);
|
||||
allIps.push_back({vlnv, ipName});
|
||||
}
|
||||
|
||||
// Pick out IPs to be initialized first.
|
||||
//
|
||||
// Reverse order of the initialization order list, because we push to the
|
||||
// front of the output list, so that the first element will also be the
|
||||
// first to be initialized.
|
||||
vlnvInitializationOrder.reverse();
|
||||
|
||||
for(auto& vlnvInitFirst : vlnvInitializationOrder) {
|
||||
// iterate over IPs, if VLNV matches, push to front and remove from list
|
||||
for(auto it = allIps.begin(); it != allIps.end(); ++it) {
|
||||
if(vlnvInitFirst == it->getVlnv()) {
|
||||
orderedIps.push_front(*it);
|
||||
it = allIps.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert all other IPs at the end
|
||||
orderedIps.splice(orderedIps.end(), allIps);
|
||||
|
||||
loggerStatic->debug("IP initialization order:");
|
||||
for(auto& id : orderedIps) {
|
||||
loggerStatic->debug(" {}", TXT_BOLD(id.getName()));
|
||||
}
|
||||
|
||||
// configure all IPs
|
||||
for(auto& id : orderedIps) {
|
||||
loggerStatic->info("Initializing {}", id);
|
||||
|
||||
// find the appropriate factory that can create the specified VLNV
|
||||
// Note:
|
||||
// This is the magic part! Factories automatically register as a
|
||||
// plugin as soon as they are instantiated. If there are multiple
|
||||
// candidates, the first suitable factory will be used.
|
||||
IpCoreFactory* ipCoreFactory = lookup(id.vlnv);
|
||||
IpCoreFactory* ipCoreFactory = lookup(id.getVlnv());
|
||||
|
||||
if(ipCoreFactory == nullptr) {
|
||||
loggerStatic->warn("No plugin found to handle {}", vlnv);
|
||||
loggerStatic->warn("No plugin found to handle {}", id.getVlnv());
|
||||
continue;
|
||||
} else {
|
||||
loggerStatic->debug("Using {} for IP {}",
|
||||
ipCoreFactory->getName(), vlnv);
|
||||
ipCoreFactory->getName(), id.getVlnv());
|
||||
}
|
||||
|
||||
auto logger = ipCoreFactory->getLogger();
|
||||
|
@ -224,25 +143,38 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips)
|
|||
ip->card = card;
|
||||
ip->id = id;
|
||||
|
||||
// extract base address if it has one
|
||||
if(json_unpack(json_ip, "{ s?: i }", "baseaddr", &ip->baseaddr) != 0) {
|
||||
logger->warn("Problem while parsing base address of IP {}",
|
||||
TXT_BOLD(ipName));
|
||||
continue;
|
||||
}
|
||||
json_t* json_ip = json_object_get(json_ips, id.getName().c_str());
|
||||
|
||||
json_t* json_irqs = json_object_get(json_ip, "irqs");
|
||||
if(json_is_object(json_irqs)) {
|
||||
const char* irq_name;
|
||||
logger->debug("Parse IRQs of {}", *ip);
|
||||
|
||||
const char* irqName;
|
||||
json_t* json_irq;
|
||||
json_object_foreach(json_irqs, irq_name, json_irq) {
|
||||
const char* irq = json_string_value(json_irq);
|
||||
json_object_foreach(json_irqs, irqName, json_irq) {
|
||||
const char* irqEntry = json_string_value(json_irq);
|
||||
|
||||
|
||||
auto tokens = utils::tokenize(irq, ":");
|
||||
auto tokens = utils::tokenize(irqEntry, ":");
|
||||
if(tokens.size() != 2) {
|
||||
logger->warn("Cannot parse IRQ '{}' of {}",
|
||||
irq, TXT_BOLD(ipName));
|
||||
irqEntry, TXT_BOLD(id.getName()));
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& irqControllerName = tokens[0];
|
||||
InterruptController* intc = nullptr;
|
||||
|
||||
for(auto& configuredIp : configuredIps) {
|
||||
if(*configuredIp == irqControllerName) {
|
||||
intc = dynamic_cast<InterruptController*>(configuredIp.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(intc == nullptr) {
|
||||
logger->error("Interrupt Controller {} for IRQ {} not found",
|
||||
irqControllerName, irqName);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -250,39 +182,66 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips)
|
|||
try {
|
||||
num = std::stoi(tokens[1]);
|
||||
} catch(const std::invalid_argument&) {
|
||||
logger->warn("IRQ number is not an integer: '{}'", irq);
|
||||
logger->warn("IRQ number is not an integer: '{}'", irqEntry);
|
||||
continue;
|
||||
}
|
||||
logger->debug("IRQ: {} -> {}:{}", irq_name, tokens[0], num);
|
||||
ip->irqs[irq_name] = {num, tokens[0], ""};
|
||||
logger->debug("IRQ: {} -> {}:{}", irqName, irqControllerName, num);
|
||||
ip->irqs[irqName] = {num, intc, ""};
|
||||
}
|
||||
} else {
|
||||
logger->debug("IP has no interrupts");
|
||||
}
|
||||
|
||||
|
||||
bool dependenciesOk = true;
|
||||
for(auto& [depName, depVlnv] : ipCoreFactory->getDependencies()) {
|
||||
// lookup dependency IP core in list of already initialized IPs
|
||||
auto iter = std::find_if(initializedIps.begin(),
|
||||
initializedIps.end(),
|
||||
[&](const std::unique_ptr<IpCore>& ip) {
|
||||
return *ip == depVlnv;
|
||||
});
|
||||
json_t* json_memory_view = json_object_get(json_ip, "memory-view");
|
||||
if(json_is_object(json_memory_view)) {
|
||||
logger->debug("Parse memory view of {}", *ip);
|
||||
|
||||
if(iter == initializedIps.end()) {
|
||||
logger->error("Cannot find '{}' dependency {} of {}",
|
||||
depName, depVlnv, TXT_BOLD(ipName));
|
||||
dependenciesOk = false;
|
||||
break;
|
||||
// create a master address space because this IP has a memory view
|
||||
const MemoryManager::AddressSpaceId myAddrSpaceId =
|
||||
MemoryManager::get().getOrCreateAddressSpace(id.getName());
|
||||
|
||||
// now find all slave address spaces this master can access
|
||||
const char* bus_name;
|
||||
json_t* json_bus;
|
||||
json_object_foreach(json_memory_view, bus_name, json_bus) {
|
||||
|
||||
const char* instance_name;
|
||||
json_t* json_instance;
|
||||
json_object_foreach(json_bus, instance_name, json_instance) {
|
||||
|
||||
const char* block_name;
|
||||
json_t* json_block;
|
||||
json_object_foreach(json_instance, block_name, json_block) {
|
||||
|
||||
int base, high, size;
|
||||
int ret = json_unpack(json_block, "{ s: i, s: i, s: i }",
|
||||
"baseaddr", &base,
|
||||
"highaddr", &high,
|
||||
"size", &size);
|
||||
if(ret != 0) {
|
||||
logger->error("Cannot parse address block {}/{}/{}/{}",
|
||||
ip->getInstanceName(),
|
||||
bus_name, instance_name, block_name);
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
// get or create the slave address space
|
||||
const std::string slaveAddrSpace =
|
||||
MemoryManager::getSlaveAddrSpaceName(instance_name, block_name);
|
||||
|
||||
const MemoryManager::AddressSpaceId slaveAddrSpaceId =
|
||||
MemoryManager::get().getOrCreateAddressSpace(slaveAddrSpace);
|
||||
|
||||
// create a new mapping to the slave address space
|
||||
MemoryManager::get().createMapping(static_cast<uintptr_t>(base),
|
||||
0,
|
||||
static_cast<uintptr_t>(size),
|
||||
bus_name,
|
||||
myAddrSpaceId,
|
||||
slaveAddrSpaceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger->debug("Found dependency IP {}", (*iter)->id);
|
||||
ip->dependencies[depName] = (*iter).get();
|
||||
}
|
||||
|
||||
if(not dependenciesOk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// IP-specific setup via JSON config
|
||||
|
@ -291,24 +250,113 @@ IpCoreFactory::make(PCIeCard* card, json_t *json_ips)
|
|||
continue;
|
||||
}
|
||||
|
||||
// TODO: currently fails, fix and remove comment
|
||||
// IP has been configured now
|
||||
configuredIps.push_back(std::move(ip));
|
||||
}
|
||||
|
||||
// Start and check IPs now
|
||||
for(auto& ip : configuredIps) {
|
||||
|
||||
// Translate all memory blocks that the IP needs to be accessible from
|
||||
// the process and cache in the instance, so this has not to be done at
|
||||
// runtime.
|
||||
for(auto& memoryBlock : ip->getMemoryBlocks()) {
|
||||
// construct the global name of this address block
|
||||
const auto addrSpaceName =
|
||||
MemoryManager::getSlaveAddrSpaceName(ip->getInstanceName(),
|
||||
memoryBlock);
|
||||
|
||||
// retrieve its address space identifier
|
||||
const auto addrSpaceId =
|
||||
MemoryManager::get().findAddressSpace(addrSpaceName);
|
||||
|
||||
// get the translation to the address space
|
||||
const auto& translation =
|
||||
MemoryManager::get().getTranslationFromProcess(addrSpaceId);
|
||||
|
||||
// cache it in the IP instance only with local name
|
||||
ip->addressTranslations.emplace(memoryBlock, translation);
|
||||
}
|
||||
|
||||
if(not ip->init()) {
|
||||
logger->error("Cannot start IP {}", ip->id.name);
|
||||
loggerStatic->error("Cannot start IP {}", *ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(not ip->check()) {
|
||||
logger->error("Checking of IP {} failed", ip->id.name);
|
||||
loggerStatic->error("Checking failed for IP {}", *ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
// will only be reached if the IP successfully was initialized
|
||||
initializedIps.push_back(std::move(ip));
|
||||
}
|
||||
|
||||
|
||||
loggerStatic->debug("Initialized IPs:");
|
||||
for(auto& ip : initializedIps) {
|
||||
loggerStatic->debug(" {}", *ip);
|
||||
}
|
||||
|
||||
return initializedIps;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
IpCore::dump() {
|
||||
auto logger = getLogger();
|
||||
|
||||
logger->info("IP: {}", *this);
|
||||
for(auto& [num, irq] : irqs) {
|
||||
logger->info("IRQ {}: {}:{}",
|
||||
num, irq.irqController->getInstanceName(), irq.num);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IpCoreFactory*
|
||||
IpCoreFactory::lookup(const Vlnv &vlnv)
|
||||
{
|
||||
for(auto& ip : Plugin::lookup(Plugin::Type::FpgaIp)) {
|
||||
IpCoreFactory* ipCoreFactory = dynamic_cast<IpCoreFactory*>(ip);
|
||||
|
||||
if(ipCoreFactory->getCompatibleVlnv() == vlnv)
|
||||
return ipCoreFactory;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
uintptr_t
|
||||
IpCore::getBaseAddr(const std::string& block) const
|
||||
{
|
||||
return getLocalAddr(block, 0);
|
||||
}
|
||||
|
||||
|
||||
uintptr_t
|
||||
IpCore::getLocalAddr(const std::string& block, uintptr_t address) const
|
||||
{
|
||||
// throws exception if block not present
|
||||
auto& translation = addressTranslations.at(block);
|
||||
|
||||
return translation.getLocalAddr(address);
|
||||
}
|
||||
|
||||
|
||||
InterruptController*
|
||||
IpCore::getInterruptController(const std::string& interruptName) const
|
||||
{
|
||||
try {
|
||||
const IrqPort irq = irqs.at(interruptName);
|
||||
return irq.irqController;
|
||||
} catch(const std::out_of_range&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
|
|
@ -40,43 +40,39 @@ namespace ip {
|
|||
// instantiate factory to make available to plugin infrastructure
|
||||
static FifoFactory factory;
|
||||
|
||||
bool
|
||||
FifoFactory::configureJson(IpCore &ip, json_t *json_ip)
|
||||
|
||||
FifoFactory::FifoFactory() :
|
||||
IpNodeFactory(getName())
|
||||
{
|
||||
auto logger = getLogger();
|
||||
|
||||
if(not IpNodeFactory::configureJson(ip, json_ip)) {
|
||||
logger->error("Configuring IpNode failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& fifo = reinterpret_cast<Fifo&>(ip);
|
||||
if(json_unpack(json_ip, "{ s: i }", "axi4_baseaddr", &fifo.baseaddr_axi4) != 0) {
|
||||
logger->warn("Cannot parse property 'axi4_baseaddr'");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
|
||||
bool Fifo::init()
|
||||
{
|
||||
auto logger = getLogger();
|
||||
|
||||
XLlFifo_Config fifo_cfg;
|
||||
|
||||
fifo_cfg.Axi4BaseAddress = getAddrMapped(this->baseaddr_axi4);
|
||||
try {
|
||||
// if this throws an exception, then there's no AXI4 data interface
|
||||
fifo_cfg.Axi4BaseAddress = getBaseAddr(axi4Memory);
|
||||
fifo_cfg.Datainterface = 1;
|
||||
} catch(const std::out_of_range&) {
|
||||
fifo_cfg.Datainterface = 0;
|
||||
}
|
||||
|
||||
// use AXI4 for Data, AXI4-Lite for control
|
||||
fifo_cfg.Datainterface = (this->baseaddr_axi4 != static_cast<size_t>(-1)) ? 1 : 0;
|
||||
|
||||
if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseaddr()) != XST_SUCCESS)
|
||||
if (XLlFifo_CfgInitialize(&xFifo, &fifo_cfg, getBaseAddr(registerMemory)) != XST_SUCCESS)
|
||||
return false;
|
||||
|
||||
if(irqs.find(irqName) == irqs.end()) {
|
||||
logger->error("IRQ '{}' not found but required", irqName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Receive complete IRQ
|
||||
XLlFifo_IntEnable(&xFifo, XLLF_INT_RC_MASK);
|
||||
|
||||
auto intc = reinterpret_cast<InterruptController*>(dependencies["intc"]);
|
||||
intc->enableInterrupt(irqs["interrupt"], false);
|
||||
irqs[irqName].irqController->enableInterrupt(irqs[irqName], false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -85,6 +81,7 @@ bool Fifo::stop()
|
|||
{
|
||||
// Receive complete IRQ
|
||||
XLlFifo_IntDisable(&xFifo, XLLF_INT_RC_MASK);
|
||||
irqs[irqName].irqController->disableInterrupt(irqs[irqName]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -110,10 +107,8 @@ size_t Fifo::read(void *buf, size_t len)
|
|||
size_t nextlen = 0;
|
||||
size_t rxlen;
|
||||
|
||||
auto intc = reinterpret_cast<InterruptController*>(dependencies["intc"]);
|
||||
|
||||
while (!XLlFifo_IsRxDone(&xFifo))
|
||||
intc->waitForInterrupt(irqs["interrupt"].num);
|
||||
irqs[irqName].irqController->waitForInterrupt(irqs[irqName]);
|
||||
|
||||
XLlFifo_IntClear(&xFifo, XLLF_INT_RC_MASK);
|
||||
|
||||
|
@ -127,69 +122,6 @@ size_t Fifo::read(void *buf, size_t len)
|
|||
return nextlen;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
|
||||
ssize_t fifo_write(struct fpga_ip *c, char *buf, size_t len)
|
||||
{
|
||||
struct fifo *fifo = (struct fifo *) c->_vd;
|
||||
|
||||
XLlFifo *xllfifo = &fifo->inst;
|
||||
|
||||
}
|
||||
|
||||
ssize_t fifo_read(struct fpga_ip *c, char *buf, size_t len)
|
||||
{
|
||||
struct fifo *fifo = (struct fifo *) c->_vd;
|
||||
|
||||
XLlFifo *xllfifo = &fifo->inst;
|
||||
|
||||
size_t nextlen = 0;
|
||||
uint32_t rxlen;
|
||||
|
||||
while (!XLlFifo_IsRxDone(xllfifo))
|
||||
intc_wait(c->card->intc, c->irq);
|
||||
XLlFifo_IntClear(xllfifo, XLLF_INT_RC_MASK);
|
||||
|
||||
/* Get length of next frame */
|
||||
rxlen = XLlFifo_RxGetLen(xllfifo);
|
||||
nextlen = MIN(rxlen, len);
|
||||
|
||||
/* Read from FIFO */
|
||||
XLlFifo_Read(xllfifo, buf, nextlen);
|
||||
|
||||
return nextlen;
|
||||
}
|
||||
|
||||
int fifo_parse(struct fpga_ip *c, json_t *cfg)
|
||||
{
|
||||
struct fifo *fifo = (struct fifo *) c->_vd;
|
||||
|
||||
int baseaddr_axi4 = -1, ret;
|
||||
|
||||
json_error_t err;
|
||||
|
||||
fifo->baseaddr_axi4 = -1;
|
||||
|
||||
ret = json_unpack_ex(cfg, &err, 0, "{ s?: i }", "baseaddr_axi4", &baseaddr_axi4);
|
||||
if (ret)
|
||||
jerror(&err, "Failed to parse configuration of FPGA IP '%s'", c->name);
|
||||
|
||||
fifo->baseaddr_axi4 = baseaddr_axi4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fifo_reset(struct fpga_ip *c)
|
||||
{
|
||||
struct fifo *fifo = (struct fifo *) c->_vd;
|
||||
|
||||
XLlFifo_Reset(&fifo->inst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
|
|
@ -48,7 +48,7 @@ InterruptController::~InterruptController()
|
|||
bool
|
||||
InterruptController::init()
|
||||
{
|
||||
const uintptr_t base = getBaseaddr();
|
||||
const uintptr_t base = getBaseAddr(registerMemory);
|
||||
auto logger = getLogger();
|
||||
|
||||
num_irqs = vfio_pci_msi_init(&card->vfio_device, efds);
|
||||
|
@ -86,7 +86,7 @@ bool
|
|||
InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool polling)
|
||||
{
|
||||
auto logger = getLogger();
|
||||
const uintptr_t base = getBaseaddr();
|
||||
const uintptr_t base = getBaseAddr(registerMemory);
|
||||
|
||||
/* Current state of INTC */
|
||||
const uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET);
|
||||
|
@ -120,7 +120,7 @@ InterruptController::enableInterrupt(InterruptController::IrqMaskType mask, bool
|
|||
bool
|
||||
InterruptController::disableInterrupt(InterruptController::IrqMaskType mask)
|
||||
{
|
||||
const uintptr_t base = getBaseaddr();
|
||||
const uintptr_t base = getBaseAddr(registerMemory);
|
||||
uint32_t ier = XIntc_In32(base + XIN_IER_OFFSET);
|
||||
|
||||
XIntc_Out32(base + XIN_IER_OFFSET, ier & ~mask);
|
||||
|
@ -133,7 +133,7 @@ InterruptController::waitForInterrupt(int irq)
|
|||
{
|
||||
assert(irq < maxIrqs);
|
||||
|
||||
const uintptr_t base = getBaseaddr();
|
||||
const uintptr_t base = getBaseAddr(registerMemory);
|
||||
|
||||
if (this->polling[irq]) {
|
||||
uint32_t isr, mask = 1 << irq;
|
||||
|
|
53
fpga/lib/ips/pcie.cpp
Normal file
53
fpga/lib/ips/pcie.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
/** AXI PCIe bridge
|
||||
*
|
||||
* @author Daniel Krebs <github@daniel-krebs.net>
|
||||
* @copyright 2018, RWTH Institute for Automation of Complex Power Systems (ACS)
|
||||
* @license GNU General Public License (version 3)
|
||||
*
|
||||
* VILLASfpga
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
|
||||
#include <limits>
|
||||
#include <jansson.h>
|
||||
|
||||
#include "fpga/ips/pcie.hpp"
|
||||
#include "fpga/card.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
namespace villas {
|
||||
namespace fpga {
|
||||
namespace ip {
|
||||
|
||||
static AxiPciExpressBridgeFactory factory;
|
||||
|
||||
bool
|
||||
AxiPciExpressBridge::init()
|
||||
{
|
||||
// Create an identity mapping from the FPGA card to this IP as an entry
|
||||
// point to all other IPs in the FPGA, because Vivado will generate a
|
||||
// memory view for this bridge that can see all others.
|
||||
auto addrSpace = MemoryManager::get().findAddressSpace(getInstanceName());
|
||||
MemoryManager::get().createMapping(0x00, 0x00, SIZE_MAX, "PCIeBridge",
|
||||
card->addrSpaceId, addrSpace);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ip
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
|
@ -37,13 +37,16 @@ static AxiStreamSwitchFactory factory;
|
|||
|
||||
bool
|
||||
AxiStreamSwitch::init()
|
||||
{
|
||||
{
|
||||
auto logger = getLogger();
|
||||
|
||||
/* Setup AXI-stream switch */
|
||||
XAxis_Switch_Config sw_cfg;
|
||||
sw_cfg.MaxNumMI = num_ports;
|
||||
sw_cfg.MaxNumSI = num_ports;
|
||||
|
||||
if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseaddr()) != XST_SUCCESS) {
|
||||
if(XAxisScr_CfgInitialize(&xSwitch, &sw_cfg, getBaseAddr(registerMemory)) != XST_SUCCESS) {
|
||||
logger->error("Cannot initialize switch");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -57,7 +60,6 @@ AxiStreamSwitch::init()
|
|||
portMapping[portMaster] = PORT_DISABLED;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,28 +44,23 @@ bool Timer::init()
|
|||
XTmrCtr_Config xtmr_cfg;
|
||||
xtmr_cfg.SysClockFreqHz = getFrequency();
|
||||
|
||||
XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseaddr());
|
||||
XTmrCtr_CfgInitialize(&xTmr, &xtmr_cfg, getBaseAddr(registerMemory));
|
||||
XTmrCtr_InitHw(&xTmr);
|
||||
|
||||
if(dependencies.find("intc") == dependencies.end()) {
|
||||
logger->error("No intc");
|
||||
if(irqs.find(irqName) == irqs.end()) {
|
||||
logger->error("IRQ '{}' not found but required", irqName);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(irqs.find("generateout0") == irqs.end()) {
|
||||
logger->error("no irq");
|
||||
return false;
|
||||
}
|
||||
|
||||
intc = reinterpret_cast<InterruptController*>(dependencies["intc"]);
|
||||
intc->disableInterrupt(irqs["generateout0"]);
|
||||
// disable so we don't receive any stray interrupts
|
||||
irqs[irqName].irqController->disableInterrupt(irqs[irqName]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Timer::start(uint32_t ticks)
|
||||
{
|
||||
intc->enableInterrupt(irqs["generateout0"], false);
|
||||
irqs[irqName].irqController->enableInterrupt(irqs[irqName], false);
|
||||
|
||||
XTmrCtr_SetOptions(&xTmr, 0, XTC_EXT_COMPARE_OPTION | XTC_DOWN_COUNT_OPTION);
|
||||
XTmrCtr_SetResetValue(&xTmr, 0, ticks);
|
||||
|
@ -76,8 +71,8 @@ bool Timer::start(uint32_t ticks)
|
|||
|
||||
bool Timer::wait()
|
||||
{
|
||||
int count = intc->waitForInterrupt(irqs["generateout0"]);
|
||||
intc->disableInterrupt(irqs["generateout0"]);
|
||||
int count = irqs[irqName].irqController->waitForInterrupt(irqs[irqName]);
|
||||
irqs[irqName].irqController->disableInterrupt(irqs[irqName]);
|
||||
|
||||
return (count == 1);
|
||||
}
|
||||
|
|
|
@ -574,6 +574,14 @@ void * vfio_map_region(struct vfio_device *d, int idx)
|
|||
return d->mappings[idx];
|
||||
}
|
||||
|
||||
size_t vfio_region_size(struct vfio_device *d, int idx)
|
||||
{
|
||||
assert(d != NULL);
|
||||
assert((size_t)idx < d->info.num_regions);
|
||||
|
||||
return d->regions[idx].size;
|
||||
}
|
||||
|
||||
int vfio_unmap_region(struct vfio_device *d, int idx)
|
||||
{
|
||||
int ret;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <memory>
|
||||
#include <limits>
|
||||
#include <cstdint>
|
||||
|
||||
#include "memory_manager.hpp"
|
||||
|
||||
|
@ -18,20 +20,32 @@ MemoryManager::get()
|
|||
}
|
||||
|
||||
MemoryManager::AddressSpaceId
|
||||
MemoryManager::createAddressSpace(std::string name)
|
||||
MemoryManager::getOrCreateAddressSpace(std::string name)
|
||||
{
|
||||
std::shared_ptr<AddressSpace> addrSpace(new AddressSpace);
|
||||
addrSpace->name = name;
|
||||
try {
|
||||
// try fast lookup
|
||||
return addrSpaceLookup.at(name);
|
||||
} catch (const std::out_of_range&) {
|
||||
// does not yet exist, create
|
||||
std::shared_ptr<AddressSpace> addrSpace(new AddressSpace);
|
||||
addrSpace->name = name;
|
||||
|
||||
return memoryGraph.addVertex(addrSpace);
|
||||
// cache it for the next access
|
||||
addrSpaceLookup[name] = memoryGraph.addVertex(addrSpace);
|
||||
|
||||
return addrSpaceLookup[name];
|
||||
}
|
||||
}
|
||||
|
||||
MemoryManager::MappingId
|
||||
MemoryManager::createMapping(uintptr_t src, uintptr_t dest, size_t size,
|
||||
std::string name,
|
||||
MemoryManager::AddressSpaceId fromAddrSpace,
|
||||
MemoryManager::AddressSpaceId toAddrSpace)
|
||||
{
|
||||
std::shared_ptr<Mapping> mapping(new Mapping);
|
||||
|
||||
mapping->name = name;
|
||||
mapping->src = src;
|
||||
mapping->dest = dest;
|
||||
mapping->size = size;
|
||||
|
@ -47,11 +61,109 @@ MemoryManager::addMapping(std::shared_ptr<Mapping> mapping,
|
|||
return memoryGraph.addEdge(mapping, fromAddrSpace, toAddrSpace);
|
||||
}
|
||||
|
||||
|
||||
Mapping::~Mapping()
|
||||
MemoryManager::AddressSpaceId
|
||||
MemoryManager::findAddressSpace(std::string name)
|
||||
{
|
||||
|
||||
return memoryGraph.findVertex(
|
||||
[&](const std::shared_ptr<AddressSpace>& v) {
|
||||
return v->name == name;
|
||||
});
|
||||
}
|
||||
|
||||
MemoryTranslation
|
||||
MemoryManager::getTranslation(MemoryManager::AddressSpaceId fromAddrSpaceId,
|
||||
MemoryManager::AddressSpaceId toAddrSpaceId)
|
||||
{
|
||||
// find a path through the memory graph
|
||||
MemoryGraph::Path path;
|
||||
if(not memoryGraph.getPath(fromAddrSpaceId, toAddrSpaceId, path)) {
|
||||
auto fromAddrSpace = memoryGraph.getVertex(fromAddrSpaceId);
|
||||
auto toAddrSpace = memoryGraph.getVertex(toAddrSpaceId);
|
||||
|
||||
logger->error("No translation found from ({}) to ({})",
|
||||
*fromAddrSpace, *toAddrSpace);
|
||||
|
||||
throw std::out_of_range("no translation found");
|
||||
}
|
||||
|
||||
// start with an identity mapping
|
||||
MemoryTranslation translation(0, 0, SIZE_MAX);
|
||||
|
||||
// iterate through path and merge all mappings into a single translation
|
||||
for(auto& mappingId : path) {
|
||||
auto mapping = memoryGraph.getEdge(mappingId);
|
||||
translation += getTranslationFromMapping(*mapping);
|
||||
}
|
||||
|
||||
return translation;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
MemoryTranslation::getLocalAddr(uintptr_t addrInForeignAddrSpace) const
|
||||
{
|
||||
assert(addrInForeignAddrSpace >= dst);
|
||||
assert(addrInForeignAddrSpace < (dst + size));
|
||||
return src + addrInForeignAddrSpace - dst;
|
||||
}
|
||||
|
||||
uintptr_t
|
||||
MemoryTranslation::getForeignAddr(uintptr_t addrInLocalAddrSpace) const
|
||||
{
|
||||
assert(addrInLocalAddrSpace >= src);
|
||||
assert(addrInLocalAddrSpace < (src + size));
|
||||
return dst + addrInLocalAddrSpace - src;
|
||||
}
|
||||
|
||||
MemoryTranslation&
|
||||
MemoryTranslation::operator+=(const MemoryTranslation& other)
|
||||
{
|
||||
auto logger = loggerGetOrCreate("MemoryTranslation");
|
||||
// set level to debug to enable debug output
|
||||
logger->set_level(spdlog::level::info);
|
||||
|
||||
const uintptr_t this_dst_high = this->dst + this->size;
|
||||
const uintptr_t other_src_high = other.src + other.size;
|
||||
|
||||
// make sure there is a common memory area
|
||||
assert(other.src < this_dst_high);
|
||||
assert(this->dst < other_src_high);
|
||||
|
||||
const uintptr_t hi = std::max(this_dst_high, other_src_high);
|
||||
const uintptr_t lo = std::min(this->dst, other.src);
|
||||
|
||||
const uintptr_t diff_hi = (this_dst_high > other_src_high)
|
||||
? (this_dst_high - other_src_high)
|
||||
: (other_src_high - this_dst_high);
|
||||
|
||||
const uintptr_t diff_lo = (this->dst > other.src)
|
||||
? (this->dst - other.src)
|
||||
: (other.src - this->dst);
|
||||
|
||||
const size_t size = (hi - lo) - diff_hi - diff_lo;
|
||||
|
||||
logger->debug("this->src: 0x{:x}", this->src);
|
||||
logger->debug("this->dst: 0x{:x}", this->dst);
|
||||
logger->debug("this->size: 0x{:x}", this->size);
|
||||
logger->debug("other.src: 0x{:x}", other.src);
|
||||
logger->debug("other.dst: 0x{:x}", other.dst);
|
||||
logger->debug("other.size: 0x{:x}", other.size);
|
||||
logger->debug("this_dst_high: 0x{:x}", this_dst_high);
|
||||
logger->debug("other_src_high: 0x{:x}", other_src_high);
|
||||
logger->debug("hi: 0x{:x}", hi);
|
||||
logger->debug("lo: 0x{:x}", lo);
|
||||
logger->debug("diff_hi: 0x{:x}", diff_hi);
|
||||
logger->debug("diff_hi: 0x{:x}", diff_lo);
|
||||
logger->debug("size: 0x{:x}", size);
|
||||
|
||||
this->src += other.src;
|
||||
this->dst += other.dst;
|
||||
this->size = size;
|
||||
|
||||
logger->debug("result src: 0x{:x}", this->src);
|
||||
logger->debug("result dst: 0x{:x}", this->dst);
|
||||
logger->debug("result size: 0x{:x}", this->size);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace villas
|
||||
|
|
|
@ -66,5 +66,17 @@ Vlnv::parseFromString(std::string vlnv)
|
|||
if(version == "*") version = "";
|
||||
}
|
||||
|
||||
std::string
|
||||
Vlnv::toString() const
|
||||
{
|
||||
std::stringstream stream;
|
||||
std::string string;
|
||||
|
||||
stream << *this;
|
||||
stream >> string;
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
} // namespace fpga
|
||||
} // namespace villas
|
||||
|
|
|
@ -34,7 +34,8 @@ whitelist = [
|
|||
[ 'acs.eonerc.rwth-aachen.de', 'hls' ],
|
||||
[ 'acs.eonerc.rwth-aachen.de', 'sysgen' ],
|
||||
[ 'xilinx.com', 'ip', 'axi_gpio' ],
|
||||
[ 'xilinx.com', 'ip', 'axi_bram_ctrl' ]
|
||||
[ 'xilinx.com', 'ip', 'axi_bram_ctrl' ],
|
||||
[ 'xilinx.com', 'ip', 'axi_pcie' ]
|
||||
]
|
||||
|
||||
# List of VLNI ids of AXI4-Stream infrastructure IP cores which do not alter data
|
||||
|
@ -140,29 +141,19 @@ for module in modules:
|
|||
|
||||
mem = ips[instance].setdefault('memory-view', {})
|
||||
for mrange in mmap:
|
||||
mem_interface = remove_prefix(mrange.get('MASTERBUSINTERFACE'), 'M_AXI_')
|
||||
mem_interface = mrange.get('MASTERBUSINTERFACE')
|
||||
mem_instance = mrange.get('INSTANCE')
|
||||
mem_block = mrange.get('ADDRESSBLOCK')
|
||||
|
||||
entry = mem.setdefault(mem_interface, {}).setdefault(mem_instance, {})
|
||||
_interface = mem.setdefault(mem_interface, {})
|
||||
_instance = _interface.setdefault(mem_instance, {})
|
||||
_block = _instance.setdefault(mem_block, {})
|
||||
|
||||
entry['baseaddr'] = int(mrange.get('BASEVALUE'), 16);
|
||||
entry['highaddr'] = int(mrange.get('HIGHVALUE'), 16);
|
||||
_block['baseaddr'] = int(mrange.get('BASEVALUE'), 16)
|
||||
_block['highaddr'] = int(mrange.get('HIGHVALUE'), 16)
|
||||
_block['size'] = _block['highaddr'] - _block['baseaddr'] + 1
|
||||
|
||||
|
||||
|
||||
# find PCI-e module to extract memory map
|
||||
pcie = root.find('.//MODULE[@MODTYPE="axi_pcie"]')
|
||||
mmap = pcie.find('.//MEMORYMAP')
|
||||
for mrange in mmap:
|
||||
instance = mrange.get('INSTANCE')
|
||||
|
||||
if instance in ips:
|
||||
base_name = remove_prefix(mrange.get('BASENAME'), 'C_').lower()
|
||||
high_name = remove_prefix(mrange.get('HIGHNAME'), 'C_').lower()
|
||||
|
||||
ips[instance][base_name] = int(mrange.get('BASEVALUE'), 16);
|
||||
ips[instance][high_name] = int(mrange.get('HIGHVALUE'), 16);
|
||||
|
||||
# find AXI-Stream switch port mapping
|
||||
switch = root.find('.//MODULE[@MODTYPE="axis_switch"]')
|
||||
busifs = switch.find('.//BUSINTERFACES')
|
||||
|
@ -197,7 +188,7 @@ for busif in busifs:
|
|||
ports[-1]['name'] = sanitize_name(busif_ep.get('NAME'))
|
||||
|
||||
# set number of master/slave port pairs for switch
|
||||
ips[switch.get('INSTANCE')]['num_ports'] = switch_ports / 2
|
||||
ips[switch.get('INSTANCE')]['num_ports'] = int(switch_ports / 2)
|
||||
|
||||
|
||||
# find Interrupt assignments
|
||||
|
|
|
@ -34,6 +34,7 @@ Test(fpga, fifo, .description = "FIFO")
|
|||
{
|
||||
ssize_t len;
|
||||
char src[255], dst[255];
|
||||
size_t count = 0;
|
||||
|
||||
auto logger = loggerGetOrCreate("unittest:fifo");
|
||||
|
||||
|
@ -46,12 +47,14 @@ Test(fpga, fifo, .description = "FIFO")
|
|||
|
||||
auto fifo = reinterpret_cast<villas::fpga::ip::Fifo&>(*ip);
|
||||
|
||||
if(not fifo.loopbackPossible()) {
|
||||
logger->info("Loopback test not possible for {}", *ip);
|
||||
if(not fifo.connectLoopback()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(not fifo.connectLoopback()) {
|
||||
count++;
|
||||
|
||||
if(not fifo.loopbackPossible()) {
|
||||
logger->info("Loopback test not possible for {}", *ip);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -80,4 +83,6 @@ Test(fpga, fifo, .description = "FIFO")
|
|||
|
||||
logger->info(TXT_GREEN("Passed"));
|
||||
}
|
||||
|
||||
cr_assert(count > 0, "No fifo found");
|
||||
}
|
||||
|
|
|
@ -51,6 +51,9 @@ static void init()
|
|||
FILE *f;
|
||||
json_error_t err;
|
||||
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
spdlog::set_pattern("[%T] [%l] [%n] %v");
|
||||
|
||||
villas::Plugin::dumpList();
|
||||
|
||||
ret = pci_init(&pci);
|
||||
|
|
|
@ -3,18 +3,26 @@
|
|||
#include <criterion/criterion.h>
|
||||
#include <villas/directed_graph.hpp>
|
||||
#include <villas/log.hpp>
|
||||
#include <villas/memory_manager.hpp>
|
||||
|
||||
static void init_graph()
|
||||
{
|
||||
spdlog::set_pattern("[%T] [%l] [%n] %v");
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
}
|
||||
|
||||
TestSuite(graph,
|
||||
.description = "Graph library"
|
||||
.description = "Graph library",
|
||||
.init = init_graph
|
||||
);
|
||||
|
||||
Test(graph, basic, .description = "DirectedGraph")
|
||||
{
|
||||
auto logger = loggerGetOrCreate("test:graph:basic");
|
||||
logger->info("Testing basic graph construction and modification");
|
||||
|
||||
villas::graph::DirectedGraph<> g("test:graph:basic");
|
||||
|
||||
logger->info("Testing basic graph construction and modification");
|
||||
|
||||
std::shared_ptr<villas::graph::Vertex> v1(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v2(new villas::graph::Vertex);
|
||||
std::shared_ptr<villas::graph::Vertex> v3(new villas::graph::Vertex);
|
||||
|
@ -37,6 +45,8 @@ Test(graph, basic, .description = "DirectedGraph")
|
|||
g.dump();
|
||||
cr_assert(g.getVertexCount() == 2);
|
||||
cr_assert(g.vertexGetEdges(v2id).size() == 0);
|
||||
|
||||
logger->info(TXT_GREEN("Passed"));
|
||||
}
|
||||
|
||||
Test(graph, path, .description = "Find path")
|
||||
|
@ -101,4 +111,27 @@ Test(graph, path, .description = "Find path")
|
|||
for(auto& edge : path4) {
|
||||
logger->info(" -> edge {}", edge);
|
||||
}
|
||||
|
||||
logger->info(TXT_GREEN("Passed"));
|
||||
}
|
||||
|
||||
Test(graph, memory_manager, .description = "Global Memory Manager")
|
||||
{
|
||||
auto logger = loggerGetOrCreate("test:graph:mm");
|
||||
auto& mm = villas::MemoryManager::get();
|
||||
|
||||
logger->info("Create address spaces");
|
||||
auto dmaRegs = mm.getOrCreateAddressSpace("DMA Registers");
|
||||
auto pcieBridge = mm.getOrCreateAddressSpace("PCIe Bridge");
|
||||
|
||||
logger->info("Create a mapping");
|
||||
mm.createMapping(0x1000, 0, 0x1000, "Testmapping", dmaRegs, pcieBridge);
|
||||
|
||||
logger->info("Find address space by name");
|
||||
auto vertex = mm.findAddressSpace("PCIe Bridge");
|
||||
logger->info(" found: {}", vertex);
|
||||
|
||||
mm.dump();
|
||||
|
||||
logger->info(TXT_GREEN("Passed"));
|
||||
}
|
||||
|
|
|
@ -63,9 +63,10 @@ ReportHook(PRE_ALL)(struct criterion_test_set *tests)
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
spdlog::set_pattern("[%T] [%l] [%n] %v");
|
||||
|
||||
/* Run criterion tests */
|
||||
auto tests = criterion_initialize();
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ Test(fpga, timer, .description = "Timer Counter")
|
|||
{
|
||||
auto logger = loggerGetOrCreate("unittest:timer");
|
||||
|
||||
size_t count = 0;
|
||||
|
||||
for(auto& ip : state.cards.front()->ips) {
|
||||
// skip non-timer IPs
|
||||
if(*ip != villas::fpga::Vlnv("xilinx.com:ip:axi_timer:")) {
|
||||
|
@ -42,6 +44,8 @@ Test(fpga, timer, .description = "Timer Counter")
|
|||
|
||||
logger->info("Testing {}", *ip);
|
||||
|
||||
count++;
|
||||
|
||||
auto timer = reinterpret_cast<villas::fpga::ip::Timer&>(*ip);
|
||||
|
||||
logger->info("Test simple waiting");
|
||||
|
@ -68,5 +72,5 @@ Test(fpga, timer, .description = "Timer Counter")
|
|||
logger->info(TXT_GREEN("Passed"));
|
||||
}
|
||||
|
||||
return;
|
||||
cr_assert(count > 0, "No timer found");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue