[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:
John Demme 2023-09-06 13:11:18 -07:00 committed by GitHub
parent d345d1ffbd
commit 28bd7f1521
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 341 additions and 55 deletions

View File

@ -1,5 +0,0 @@
# REQUIRES: esi-runtime
# RUN: %PYTHON% %s
# Just test that we can load the python library.
import esiaccel

View File

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

View File

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

View File

@ -0,0 +1,2 @@
# Don't treat the Python files in this directory as tests.
config.suffixes.remove('.py')

View File

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

View File

@ -33,6 +33,7 @@ include_directories(cpp/include)
set(ESIRuntimeSources
cpp/lib/Accelerator.cpp
cpp/lib/StdServices.cpp
)
set(ESIRuntimeLinkLibraries)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
//===----------------------------------------------------------------------===//
#include "esi/Accelerator.h"
#include "esi/StdServices.h"
#include <iostream>
#include <map>

View File

@ -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", &registry::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);
}