mirror of https://github.com/llvm/circt.git
[ESI][Runtime] MMIO service support (#6034)
Service C++ interface, Python wrapping, and cosim support for MMIO. Also adds the basic service framework.
This commit is contained in:
parent
d345d1ffbd
commit
28bd7f1521
|
@ -1,5 +0,0 @@
|
|||
# REQUIRES: esi-runtime
|
||||
# RUN: %PYTHON% %s
|
||||
|
||||
# Just test that we can load the python library.
|
||||
import esiaccel
|
|
@ -0,0 +1,94 @@
|
|||
// REQUIRES: esi-cosim, esi-runtime
|
||||
// RUN: esi-cosim-runner.py --exec %s.py %s
|
||||
|
||||
// Test the low level cosim MMIO functionality. This test has 1024 64-bit
|
||||
// registers as a memory. It is an error to write to register 0.
|
||||
|
||||
import Cosim_DpiPkg::*;
|
||||
|
||||
module top(
|
||||
input logic clk,
|
||||
input logic rst
|
||||
);
|
||||
|
||||
// MMIO read: address channel.
|
||||
logic arvalid;
|
||||
logic arready;
|
||||
logic [31:0] araddr;
|
||||
|
||||
// MMIO read: data response channel.
|
||||
reg rvalid;
|
||||
logic rready;
|
||||
reg [31:0] rdata;
|
||||
reg [1:0] rresp;
|
||||
|
||||
// MMIO write: address channel.
|
||||
logic awvalid;
|
||||
reg awready;
|
||||
logic [31:0] awaddr;
|
||||
|
||||
// MMIO write: data channel.
|
||||
logic wvalid;
|
||||
reg wready;
|
||||
logic [31:0] wdata;
|
||||
|
||||
// MMIO write: write response channel.
|
||||
reg bvalid;
|
||||
logic bready;
|
||||
reg [1:0] bresp;
|
||||
|
||||
Cosim_MMIO mmio (
|
||||
.clk(clk),
|
||||
.rst(rst),
|
||||
.arvalid(arvalid),
|
||||
.arready(arready),
|
||||
.araddr(araddr),
|
||||
.rvalid(rvalid),
|
||||
.rready(rready),
|
||||
.rdata(rdata),
|
||||
.rresp(rresp),
|
||||
.awvalid(awvalid),
|
||||
.awready(awready),
|
||||
.awaddr(awaddr),
|
||||
.wvalid(wvalid),
|
||||
.wready(wready),
|
||||
.wdata(wdata),
|
||||
.bvalid(bvalid),
|
||||
.bready(bready),
|
||||
.bresp(bresp)
|
||||
);
|
||||
|
||||
reg [31:0] regs [1023:0];
|
||||
|
||||
assign arready = 1;
|
||||
assign rdata = regs[araddr >> 3];
|
||||
assign rresp = araddr == 0 ? 3 : 0;
|
||||
always@(posedge clk) begin
|
||||
if (rst) begin
|
||||
rvalid <= 0;
|
||||
end else begin
|
||||
if (arvalid)
|
||||
rvalid <= 1;
|
||||
if (rready && rvalid)
|
||||
rvalid <= 0;
|
||||
end
|
||||
end
|
||||
|
||||
wire write = awvalid && wvalid && !bvalid;
|
||||
assign awready = write;
|
||||
assign wready = write;
|
||||
always@(posedge clk) begin
|
||||
if (rst) begin
|
||||
bvalid <= 0;
|
||||
end else begin
|
||||
if (bvalid && bready)
|
||||
bvalid <= 0;
|
||||
if (write) begin
|
||||
bvalid <= 1;
|
||||
bresp <= awaddr == 0 ? 3 : 0;
|
||||
regs[awaddr >> 3] <= wdata;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
|
@ -0,0 +1,32 @@
|
|||
import esiaccel
|
||||
import os
|
||||
import sys
|
||||
|
||||
conn = f"{sys.argv[1]}:{sys.argv[2]}"
|
||||
|
||||
acc = esiaccel.Accelerator.connect("cosim", conn)
|
||||
mmio = acc.get_service_mmio()
|
||||
|
||||
r = mmio.read(40)
|
||||
print(f"data resp: 0x{r:x}")
|
||||
|
||||
try:
|
||||
mmio.read(0)
|
||||
assert False, "above should have thrown exception"
|
||||
except Exception:
|
||||
print("caught expected exception")
|
||||
|
||||
mmio.write(32, 86)
|
||||
r = mmio.read(32)
|
||||
print(f"data resp: 0x{r:x}")
|
||||
assert r == 86
|
||||
|
||||
try:
|
||||
mmio.write(0, 44)
|
||||
assert False, "above should have thrown exception"
|
||||
except Exception:
|
||||
print("caught expected exception")
|
||||
|
||||
# Crashes with "magic num not found", which is expected since this isn't
|
||||
# supported yet.
|
||||
# acc.sysinfo().esi_version()
|
|
@ -0,0 +1,2 @@
|
|||
# Don't treat the Python files in this directory as tests.
|
||||
config.suffixes.remove('.py')
|
|
@ -84,10 +84,12 @@ set(ESI_RUNTIME_SRCS
|
|||
|
||||
runtime/CMakeLists.txt
|
||||
runtime/cpp/include/esi/Accelerator.h
|
||||
runtime/cpp/include/esi/StdServices.h
|
||||
runtime/cpp/include/esi/backends/Cosim.h
|
||||
runtime/cpp/include/esi/backends/capnp.h
|
||||
runtime/cpp/include/esi/esi.h
|
||||
runtime/cpp/lib/Accelerator.cpp
|
||||
runtime/cpp/lib/StdServices.cpp
|
||||
runtime/cpp/lib/backends/Cosim.cpp
|
||||
runtime/cpp/tools/esiquery.cpp
|
||||
runtime/python/esi/esiaccel.cpp
|
||||
|
|
|
@ -33,6 +33,7 @@ include_directories(cpp/include)
|
|||
|
||||
set(ESIRuntimeSources
|
||||
cpp/lib/Accelerator.cpp
|
||||
cpp/lib/StdServices.cpp
|
||||
)
|
||||
set(ESIRuntimeLinkLibraries)
|
||||
|
||||
|
|
|
@ -23,30 +23,56 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
|
||||
namespace esi {
|
||||
class SysInfo;
|
||||
|
||||
constexpr uint32_t MagicNumOffset = 16;
|
||||
constexpr uint32_t MagicNumberLo = 0xE5100E51;
|
||||
constexpr uint32_t MagicNumberHi = 0x207D98E5;
|
||||
constexpr uint32_t VersionNumberOffset = MagicNumOffset + 8;
|
||||
constexpr uint32_t ExpectedVersionNumber = 0;
|
||||
|
||||
namespace services {
|
||||
/// Parent class of all APIs modeled as 'services'. May or may not map to a
|
||||
/// hardware side 'service'.
|
||||
class Service {
|
||||
public:
|
||||
using Type = const std::type_info &;
|
||||
virtual ~Service() = default;
|
||||
};
|
||||
} // namespace services
|
||||
|
||||
/// An ESI accelerator system.
|
||||
class Accelerator {
|
||||
public:
|
||||
virtual ~Accelerator() = default;
|
||||
|
||||
virtual const SysInfo &sysInfo() = 0;
|
||||
};
|
||||
/// Get a typed reference to a particular service type. Caller does *not* take
|
||||
/// ownership of the returned pointer -- the Accelerator object owns it.
|
||||
/// Pointer lifetime ends with the Accelerator lifetime.
|
||||
template <typename ServiceClass>
|
||||
ServiceClass *getService() {
|
||||
return dynamic_cast<ServiceClass *>(getServiceImpl(typeid(ServiceClass)));
|
||||
}
|
||||
|
||||
/// Information about the Accelerator system.
|
||||
class SysInfo {
|
||||
public:
|
||||
virtual ~SysInfo() = default;
|
||||
protected:
|
||||
using Service = services::Service;
|
||||
/// Called by `getServiceImpl` exclusively. It wraps the pointer returned by
|
||||
/// this in a unique_ptr and caches it. Separate this from the
|
||||
/// wrapping/caching since wrapping/caching is an implementation detail.
|
||||
virtual Service *createService(Service::Type service) = 0;
|
||||
/// Calls `createService` and caches the result. Subclasses can override if
|
||||
/// they want to use their own caching mechanism.
|
||||
virtual Service *getServiceImpl(Service::Type service);
|
||||
|
||||
/// Get the ESI version number to check version compatibility.
|
||||
virtual uint32_t esiVersion() const = 0;
|
||||
|
||||
/// Return the JSON-formatted system manifest.
|
||||
virtual std::string rawJsonManifest() const = 0;
|
||||
private:
|
||||
/// Cache services via a unique_ptr so they get free'd automatically when
|
||||
/// Accelerator objects get deconstructed.
|
||||
std::map<const std::type_info *, std::unique_ptr<Service>> serviceCache;
|
||||
};
|
||||
|
||||
namespace registry {
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//===- StdServices.h - ESI standard services C++ API ------------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// The APIs in this backend are all optionally implemented. The lower level
|
||||
// ones, however, are strongly recommended. 'Services' here refers to ESI
|
||||
// services. These are standard APIs into the standard ESI services.
|
||||
//
|
||||
// DO NOT EDIT!
|
||||
// This file is distributed as part of an ESI package. The source for this file
|
||||
// should always be modified within CIRCT.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// NOLINTNEXTLINE(llvm-header-guard)
|
||||
#ifndef ESI_RUNTIME_STDSERVICES_H
|
||||
#define ESI_RUNTIME_STDSERVICES_H
|
||||
|
||||
#include "esi/Accelerator.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esi {
|
||||
namespace services {
|
||||
|
||||
/// Information about the Accelerator system.
|
||||
class SysInfo : public Service {
|
||||
public:
|
||||
virtual ~SysInfo() = default;
|
||||
|
||||
/// Get the ESI version number to check version compatibility.
|
||||
virtual uint32_t esiVersion() const = 0;
|
||||
|
||||
/// Return the JSON-formatted system manifest.
|
||||
virtual std::string rawJsonManifest() const = 0;
|
||||
};
|
||||
|
||||
class MMIO : public Service {
|
||||
public:
|
||||
virtual ~MMIO() = default;
|
||||
virtual uint64_t read(uint32_t addr) const = 0;
|
||||
virtual void write(uint32_t addr, uint64_t data) = 0;
|
||||
};
|
||||
|
||||
/// Implement the SysInfo API for a standard MMIO protocol.
|
||||
class MMIOSysInfo final : public SysInfo {
|
||||
public:
|
||||
MMIOSysInfo(const MMIO *);
|
||||
|
||||
/// Get the ESI version number to check version compatibility.
|
||||
uint32_t esiVersion() const override;
|
||||
|
||||
/// Return the JSON-formatted system manifest.
|
||||
std::string rawJsonManifest() const override;
|
||||
|
||||
private:
|
||||
const MMIO *mmio;
|
||||
};
|
||||
|
||||
} // namespace services
|
||||
} // namespace esi
|
||||
|
||||
#endif // ESI_RUNTIME_STDSERVICES_H
|
|
@ -22,22 +22,24 @@
|
|||
|
||||
#include "esi/Accelerator.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace esi {
|
||||
namespace backends {
|
||||
namespace cosim {
|
||||
class CosimSysInfo;
|
||||
|
||||
/// Connect to an ESI simulation.
|
||||
class CosimAccelerator : public esi::Accelerator {
|
||||
public:
|
||||
CosimAccelerator(std::string hostname, uint16_t port);
|
||||
~CosimAccelerator();
|
||||
static std::unique_ptr<Accelerator> connect(std::string connectionString);
|
||||
|
||||
const SysInfo &sysInfo() override;
|
||||
protected:
|
||||
virtual Service *createService(Service::Type service) override;
|
||||
|
||||
private:
|
||||
CosimSysInfo *info;
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace cosim
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
#include <stdexcept>
|
||||
|
||||
namespace esi {
|
||||
services::Service *Accelerator::getServiceImpl(Service::Type svcType) {
|
||||
std::unique_ptr<Service> &cacheEntry = serviceCache[&svcType];
|
||||
if (cacheEntry == nullptr)
|
||||
cacheEntry = std::unique_ptr<Service>(createService(svcType));
|
||||
return cacheEntry.get();
|
||||
}
|
||||
namespace registry {
|
||||
namespace internal {
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
//===- StdServices.cpp - implementations of std services ------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// DO NOT EDIT!
|
||||
// This file is distributed as part of an ESI package. The source for this file
|
||||
// should always be modified within CIRCT
|
||||
// (lib/dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp).
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "esi/StdServices.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace esi;
|
||||
using namespace esi::services;
|
||||
|
||||
MMIOSysInfo::MMIOSysInfo(const MMIO *mmio) : mmio(mmio) {}
|
||||
|
||||
uint32_t MMIOSysInfo::esiVersion() const {
|
||||
uint32_t hi = mmio->read(MagicNumOffset);
|
||||
uint32_t lo = mmio->read(MagicNumOffset + 4);
|
||||
if (hi != MagicNumberHi || lo != MagicNumberLo)
|
||||
throw std::runtime_error("ESI magic number not found");
|
||||
return mmio->read(VersionNumberOffset);
|
||||
}
|
||||
|
||||
std::string MMIOSysInfo::rawJsonManifest() const { return ""; }
|
|
@ -14,6 +14,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "esi/backends/Cosim.h"
|
||||
#include "esi/StdServices.h"
|
||||
|
||||
#include "CosimDpi.capnp.h"
|
||||
#include <capnp/ez-rpc.h>
|
||||
|
@ -22,30 +23,9 @@
|
|||
#include <iostream>
|
||||
|
||||
using namespace esi;
|
||||
using namespace esi::services;
|
||||
using namespace esi::backends::cosim;
|
||||
|
||||
namespace esi::backends::cosim {
|
||||
|
||||
/// Implement the SysInfo API for cosimulation.
|
||||
class CosimSysInfo final : public esi::SysInfo {
|
||||
private:
|
||||
friend class CosimAccelerator;
|
||||
CosimSysInfo() = default;
|
||||
|
||||
public:
|
||||
/// Get the ESI version number to check version compatibility.
|
||||
uint32_t esiVersion() const override;
|
||||
|
||||
/// Return the JSON-formatted system manifest.
|
||||
std::string rawJsonManifest() const override;
|
||||
};
|
||||
} // namespace esi::backends::cosim
|
||||
|
||||
// For now, just return dummy values since these are not yet supported by the
|
||||
// hardware.
|
||||
uint32_t CosimSysInfo::esiVersion() const { return -1; }
|
||||
std::string CosimSysInfo::rawJsonManifest() const { return ""; }
|
||||
|
||||
/// Parse the connection string and instantiate the accelerator. Support the
|
||||
/// traditional 'host:port' syntax and a path to 'cosim.cfg' which is output by
|
||||
/// the cosimulation when it starts (which is useful when it chooses its own
|
||||
|
@ -80,21 +60,57 @@ CosimAccelerator::connect(std::string connectionString) {
|
|||
return std::make_unique<CosimAccelerator>(host, port);
|
||||
}
|
||||
|
||||
struct esi::backends::cosim::CosimAccelerator::Impl {
|
||||
capnp::EzRpcClient rpcClient;
|
||||
kj::WaitScope &waitScope;
|
||||
CosimDpiServer::Client cosim;
|
||||
EsiLowLevel::Client lowLevel;
|
||||
|
||||
Impl(std::string hostname, uint16_t port)
|
||||
: rpcClient(hostname, port), waitScope(rpcClient.getWaitScope()),
|
||||
cosim(rpcClient.getMain<CosimDpiServer>()), lowLevel(nullptr) {
|
||||
auto llReq = cosim.openLowLevelRequest();
|
||||
auto llPromise = llReq.send();
|
||||
lowLevel = llPromise.wait(waitScope).getLowLevel();
|
||||
}
|
||||
};
|
||||
|
||||
/// Construct and connect to a cosim server.
|
||||
// TODO: Implement this.
|
||||
CosimAccelerator::CosimAccelerator(std::string hostname, uint16_t port)
|
||||
: info(nullptr) {
|
||||
std::cout << hostname << ":" << port << std::endl;
|
||||
}
|
||||
CosimAccelerator::~CosimAccelerator() {
|
||||
if (info)
|
||||
delete info;
|
||||
CosimAccelerator::CosimAccelerator(std::string hostname, uint16_t port) {
|
||||
impl = std::make_unique<Impl>(hostname, port);
|
||||
}
|
||||
|
||||
const SysInfo &CosimAccelerator::sysInfo() {
|
||||
if (info == nullptr)
|
||||
info = new CosimSysInfo();
|
||||
return *info;
|
||||
namespace {
|
||||
class CosimMMIO : public MMIO {
|
||||
public:
|
||||
CosimMMIO(EsiLowLevel::Client &llClient, kj::WaitScope &waitScope)
|
||||
: llClient(llClient), waitScope(waitScope) {}
|
||||
|
||||
uint64_t read(uint32_t addr) const override {
|
||||
auto req = llClient.readMMIORequest();
|
||||
req.setAddress(addr);
|
||||
return req.send().wait(waitScope).getData();
|
||||
}
|
||||
void write(uint32_t addr, uint64_t data) override {
|
||||
auto req = llClient.writeMMIORequest();
|
||||
req.setAddress(addr);
|
||||
req.setData(data);
|
||||
req.send().wait(waitScope);
|
||||
}
|
||||
|
||||
private:
|
||||
EsiLowLevel::Client &llClient;
|
||||
kj::WaitScope &waitScope;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Service *CosimAccelerator::createService(Service::Type svcType) {
|
||||
if (svcType == typeid(MMIO))
|
||||
return new CosimMMIO(impl->lowLevel, impl->waitScope);
|
||||
else if (svcType == typeid(SysInfo))
|
||||
return new MMIOSysInfo(getService<MMIO>());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "esi/Accelerator.h"
|
||||
#include "esi/StdServices.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
|
|
@ -11,21 +11,30 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "esi/Accelerator.h"
|
||||
#include "esi/StdServices.h"
|
||||
|
||||
// pybind11 includes
|
||||
#include "pybind11/pybind11.h"
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace esi;
|
||||
using namespace esi::services;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
PYBIND11_MODULE(esiaccel, m) {
|
||||
py::class_<Accelerator>(m, "Accelerator")
|
||||
.def_static("connect", ®istry::connect,
|
||||
py::return_value_policy::take_ownership)
|
||||
.def("sysinfo", &Accelerator::sysInfo,
|
||||
.def("sysinfo", &Accelerator::getService<SysInfo>,
|
||||
py::return_value_policy::reference_internal)
|
||||
.def("get_service_mmio", &Accelerator::getService<services::MMIO>,
|
||||
py::return_value_policy::reference_internal);
|
||||
|
||||
py::class_<SysInfo>(m, "SysInfo")
|
||||
.def("esi_version", &SysInfo::esiVersion)
|
||||
.def("raw_json_manifest", &SysInfo::rawJsonManifest);
|
||||
|
||||
py::class_<services::MMIO>(m, "MMIO")
|
||||
.def("read", &services::MMIO::read)
|
||||
.def("write", &services::MMIO::write);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue