[ESI] Basic Python runtime for manifests (#6371)

Infrastructure for Python runtime. Loads and parses the manifest.
This commit is contained in:
John Demme 2023-11-02 11:50:35 -07:00 committed by GitHub
parent 3583c43bea
commit 39217a0ae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 209 additions and 40 deletions

View File

@ -32,8 +32,8 @@ get_target_property(ESI_COLLATERAL_PATH esi-collateral BINARY_DIR)
llvm_canonicalize_cmake_booleans(ESI_RUNTIME)
if (ESI_RUNTIME)
list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS
esiaccel
ESIRuntime
ESIPythonRuntime
)
endif()

View File

@ -1,10 +1,10 @@
import esiaccel
import esi
import os
import sys
conn = f"{sys.argv[1]}:{sys.argv[2]}"
acc = esiaccel.Accelerator.connect("cosim", conn)
acc = esi.Accelerator("cosim", conn)
mmio = acc.get_service_mmio()
r = mmio.read(40)
@ -26,7 +26,3 @@ try:
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,29 @@
// REQUIRES: esi-cosim
// RUN: rm -rf %t6 && mkdir %t6 && cd %t6
// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir
// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --export-split-verilog -o %t3.mlir
// RUN: cd ..
// RUN: esi-cosim-runner.py --exec %s.py %t6/*.sv
!sendI8 = !esi.bundle<[!esi.channel<i8> to "send"]>
!recvI8 = !esi.bundle<[!esi.channel<i8> to "recv"]>
esi.service.decl @HostComms {
esi.service.to_server @Send : !sendI8
esi.service.to_client @Recv : !recvI8
}
hw.module @Loopback (in %clk: !seq.clock) {
%dataInBundle = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) {esi.appid=#esi.appid<"loopback_tohw">} : !recvI8
%dataOut = esi.bundle.unpack from %dataInBundle : !recvI8
%dataOutBundle = esi.bundle.pack %dataOut : !sendI8
esi.service.req.to_server %dataOutBundle -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8
}
esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1}
hw.module @top(in %clk: !seq.clock, in %rst: i1) {
esi.service.instance #esi.appid<"cosim"> svc @HostComms impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> ()
hw.instance "m1" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[0]>}
hw.instance "m2" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[1]>}
}

View File

@ -0,0 +1,10 @@
import esi
import os
import sys
conn = f"{sys.argv[1]}:{sys.argv[2]}"
acc = esi.Accelerator("cosim", conn)
assert acc.sysinfo().esi_version() == 1
assert acc.manifest._manifest['api_version'] == 1

View File

@ -67,11 +67,6 @@ config.test_exec_root = os.path.join(config.circt_obj_root, 'integration_test')
# Tweak the PATH to include the tools dir.
llvm_config.with_environment('PATH', config.llvm_tools_dir, append_path=True)
# Substitute '%l' with the path to the build lib dir.
# Tweak the PYTHONPATH to include the lib dir. Some pybind11 modules there.
llvm_config.with_environment('PYTHONPATH', [config.llvm_lib_dir],
append_path=True)
# Tweak the PYTHONPATH to include the binary dir.
if config.bindings_python_enabled:
@ -178,6 +173,10 @@ if ieee_sims and ieee_sims[-1][1] == config.iverilog_path:
if config.esi_runtime == "1":
config.available_features.add('esi-runtime')
llvm_config.with_environment('PYTHONPATH',
[f"{config.esi_runtime_path}/python/"],
append_path=True)
# Enable ESI cosim tests if they have been built.
if config.esi_cosim_path != "":
config.available_features.add('esi-cosim')

View File

@ -50,6 +50,7 @@ config.clang_tidy_path = "@CLANG_TIDY_PATH@"
config.have_systemc = "@HAVE_SYSTEMC@"
config.esi_capnp = "@ESI_CAPNP@"
config.esi_runtime = "@ESI_RUNTIME@"
config.esi_runtime_path = "@ESIRuntimePath@"
config.esi_collateral_path = "@ESI_COLLATERAL_PATH@"
config.bindings_python_enabled = @CIRCT_BINDINGS_PYTHON_ENABLED@
config.bindings_tcl_enabled = @CIRCT_BINDINGS_TCL_ENABLED@

View File

@ -80,7 +80,7 @@ set(ESI_RUNTIME_SRCS
runtime/cpp/lib/StdServices.cpp
runtime/cpp/lib/backends/Cosim.cpp
runtime/cpp/tools/esiquery.cpp
runtime/python/esi/esiaccel.cpp
runtime/python/esi/esiCppAccel.cpp
runtime/cosim/CMakeLists.txt
runtime/cosim/Cosim_DpiPkg.sv
runtime/cosim/Cosim_Endpoint.sv

View File

@ -29,13 +29,21 @@ cmake_minimum_required(VERSION 3.20)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
find_package(ZLIB REQUIRED)
include_directories(cpp/include)
set(ESIRuntimeSources
cpp/lib/Accelerator.cpp
cpp/lib/StdServices.cpp
)
set(ESIRuntimeLinkLibraries)
set(ESIRuntimeLinkLibraries
ZLIB::ZLIB
)
set(ESIPythonRuntimeSources
python/esi/__init__.py
python/esi/accelerator.py
)
# If Cap'nProto hasn't been explicitly disabled, find it.
option(CAPNP_DISABLE "Disable Cap'nProto (needed for cosimulation).")
@ -74,6 +82,7 @@ if(CapnProto_FOUND)
)
endif()
# The core API. For now, compile the backends into it directly.
# TODO: make this a plugin architecture.
add_library(ESIRuntime SHARED
@ -87,6 +96,10 @@ add_executable(esiquery
)
target_link_libraries(esiquery PRIVATE ESIRuntime)
# Global variable for the path to the ESI runtime for use by tests.
set(ESIRuntimePath "${CMAKE_CURRENT_BINARY_DIR}"
CACHE INTERNAL "Path to ESI runtime" FORCE)
# Pybind11 is used to wrap the ESIRuntime APIs.
find_package(Python COMPONENTS Interpreter Development)
if(Python_FOUND)
@ -113,7 +126,35 @@ if(Python_FOUND)
if (NOT pybind11_FOUND)
message (STATUS "Could not find pybind11. Disabling Python API.")
else()
pybind11_add_module(esiaccel python/esi/esiaccel.cpp)
target_link_libraries(esiaccel PRIVATE ESIRuntime)
# Compile Pybind11 module and copy to the correct python directory.
pybind11_add_module(esiCppAccel python/esi/esiCppAccel.cpp)
target_link_libraries(esiCppAccel PRIVATE ESIRuntime)
add_custom_command(
TARGET esiCppAccel
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:esiCppAccel>
"${CMAKE_CURRENT_BINARY_DIR}/python/esi/$<TARGET_FILE_NAME:esiCppAccel>"
)
# Copy each of the Python sources to the build dir.
foreach(pysrc ${ESIPythonRuntimeSources})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${pysrc}
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${pysrc}
${CMAKE_CURRENT_BINARY_DIR}/${pysrc}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${pysrc}
)
endforeach()
# Custom target for the Python runtime just aggregates the python sources
# and Pybind11 module.
add_custom_target(ESIPythonRuntime
DEPENDS
${ESIPythonRuntimeSources}
esiCppAccel
)
endif()
endif()

View File

@ -16,7 +16,7 @@
@0x9fd65fec6e2d2779;
# The primary interface exposed by an ESI cosim simulation.
interface CosimDpiServer @0x85e029b5352bcdb5 {
interface CosimDpiServer @0xe3d7f70c7065c46a {
# List all the registered endpoints.
list @0 () -> (ifaces :List(EsiDpiInterfaceDesc));
# Open one of them. Specify both the send and recv data types if want type
@ -24,7 +24,7 @@ interface CosimDpiServer @0x85e029b5352bcdb5 {
open @1 [S, T] (iface :EsiDpiInterfaceDesc) -> (iface :EsiDpiEndpoint);
# Get the zlib-compressed JSON system manifest.
getCompressedManifest @2 () -> (compressedManifest :Data);
getCompressedManifest @2 () -> (version :Int32, compressedManifest :Data);
# Create a low level interface into the simulation.
openLowLevel @3 () -> (lowLevel :EsiLowLevel);

View File

@ -78,6 +78,7 @@ import "DPI-C" sv2cCosimserverEpTryGet =
import "DPI-C" sv2cCosimserverSetManifest =
function void cosim_set_manifest(
input int unsigned esi_version,
input byte unsigned compressed_manifest[]
);

View File

@ -15,12 +15,13 @@ import Cosim_DpiPkg::*;
module Cosim_Manifest
#(
parameter int COMPRESSED_MANIFEST_SIZE = 0
parameter int COMPRESSED_MANIFEST_SIZE = 0,
parameter int unsigned ESI_VERSION = 1
)(
input byte unsigned compressed_manifest[COMPRESSED_MANIFEST_SIZE]
);
always@(compressed_manifest)
cosim_set_manifest(compressed_manifest);
cosim_set_manifest(ESI_VERSION, compressed_manifest);
endmodule

View File

@ -28,7 +28,6 @@ using namespace esi::cosim;
/// If non-null, log to this file. Protected by 'serverMutex`.
static FILE *logFile;
static RpcServer *server = nullptr;
static std::vector<uint8_t> manifest;
static std::mutex serverMutex;
// ---- Helper functions ----
@ -261,8 +260,10 @@ DPI int sv2cCosimserverInit() {
// ---- Manifest DPI entry points ----
DPI void
sv2cCosimserverSetManifest(const svOpenArrayHandle compressedManifest) {
sv2cCosimserverInit();
sv2cCosimserverSetManifest(unsigned int esiVersion,
const svOpenArrayHandle compressedManifest) {
if (server == nullptr)
sv2cCosimserverInit();
if (validateSvOpenArray(compressedManifest, sizeof(int8_t)) != 0) {
printf("ERROR: DPI-func=%s line=%d event=invalid-sv-array\n", __func__,
@ -276,7 +277,9 @@ sv2cCosimserverSetManifest(const svOpenArrayHandle compressedManifest) {
for (int i = 0; i < size; ++i) {
blob[i] = *(char *)svGetArrElemPtr1(compressedManifest, i);
}
server->setManifest(blob);
printf("[cosim] Setting manifest (esiVersion=%d, size=%d)\n", esiVersion,
size);
server->setManifest(esiVersion, blob);
}
// ---- Low-level cosim DPI entry points ----

View File

@ -87,10 +87,12 @@ class CosimServer final : public CosimDpiServer::Server {
/// The registry of endpoints. The RpcServer class owns this.
EndpointRegistry &reg;
LowLevel &lowLevelBridge;
const unsigned int &esiVersion;
const std::vector<uint8_t> &compressedManifest;
public:
CosimServer(EndpointRegistry &reg, LowLevel &lowLevelBridge,
const unsigned int &esiVersion,
const std::vector<uint8_t> &compressedManifest);
/// List all the registered interfaces.
@ -194,9 +196,12 @@ kj::Promise<void> LowLevelServer::writeMMIO(WriteMMIOContext context) {
/// ----- CosimServer definitions.
CosimServer::CosimServer(EndpointRegistry &reg, LowLevel &lowLevelBridge,
const unsigned int &esiVersion,
const std::vector<uint8_t> &compressedManifest)
: reg(reg), lowLevelBridge(lowLevelBridge),
compressedManifest(compressedManifest) {}
: reg(reg), lowLevelBridge(lowLevelBridge), esiVersion(esiVersion),
compressedManifest(compressedManifest) {
printf("version: %d\n", esiVersion);
}
kj::Promise<void> CosimServer::list(ListContext context) {
auto ifaces = context.getResults().initIfaces((unsigned int)reg.size());
@ -224,6 +229,7 @@ kj::Promise<void> CosimServer::open(OpenContext ctxt) {
kj::Promise<void>
CosimServer::getCompressedManifest(GetCompressedManifestContext ctxt) {
ctxt.getResults().setVersion(esiVersion);
ctxt.getResults().setCompressedManifest(
Data::Reader(compressedManifest.data(), compressedManifest.size()));
return kj::READY_NOW;
@ -250,9 +256,10 @@ static void writePort(uint16_t port) {
}
void RpcServer::mainLoop(uint16_t port) {
capnp::EzRpcServer rpcServer(
kj::heap<CosimServer>(endpoints, lowLevelBridge, compressedManifest),
/* bindAddress */ "*", port);
capnp::EzRpcServer rpcServer(kj::heap<CosimServer>(endpoints, lowLevelBridge,
esiVersion,
compressedManifest),
/* bindAddress */ "*", port);
auto &waitScope = rpcServer.getWaitScope();
// If port is 0, ExRpcSever selects one and we have to wait to get the port.
if (port == 0) {

View File

@ -39,7 +39,9 @@ public:
void run(uint16_t port);
void stop();
void setManifest(const std::vector<uint8_t> &manifest) {
void setManifest(unsigned int esiVersion,
const std::vector<uint8_t> &manifest) {
this->esiVersion = esiVersion;
compressedManifest = manifest;
}
@ -53,6 +55,7 @@ private:
volatile bool stopSig;
std::mutex m;
unsigned int esiVersion;
std::vector<uint8_t> compressedManifest;
};

View File

@ -47,7 +47,8 @@ DPI int sv2cCosimserverInit();
DPI void sv2cCosimserverFinish();
/// Set the system zlib-compressed manifest.
DPI void sv2cCosimserverSetManifest(const svOpenArrayHandle compressedManifest);
DPI void sv2cCosimserverSetManifest(unsigned int esiVersion,
const svOpenArrayHandle compressedManifest);
/// Register an MMIO module. Just checks that there is only one instantiated.
DPI int sv2cCosimserverMMIORegister();

View File

@ -36,7 +36,10 @@ public:
virtual uint32_t esiVersion() const = 0;
/// Return the JSON-formatted system manifest.
virtual std::string rawJsonManifest() const = 0;
virtual std::string jsonManifest() const;
/// Return the zlib compressed JSON system manifest.
virtual std::vector<uint8_t> compressedManifest() const = 0;
};
class MMIO : public Service {
@ -54,8 +57,8 @@ 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;
/// Return the zlib compressed JSON system manifest.
virtual std::vector<uint8_t> compressedManifest() const override;
private:
const MMIO *mmio;

View File

@ -15,11 +15,29 @@
#include "esi/StdServices.h"
#include "zlib.h"
#include <cassert>
#include <stdexcept>
using namespace esi;
using namespace esi::services;
// Allocate 10MB for the uncompressed manifest. This should be plenty.
constexpr uint32_t MAX_MANIFEST_SIZE = 10 << 20;
/// Get the compressed manifest, uncompress, and return it.
std::string SysInfo::jsonManifest() const {
std::vector<uint8_t> compressed = compressedManifest();
std::vector<Bytef> dst(MAX_MANIFEST_SIZE);
uLongf dstSize = MAX_MANIFEST_SIZE;
int rc =
uncompress(dst.data(), &dstSize, compressed.data(), compressed.size());
if (rc != Z_OK)
throw std::runtime_error("zlib uncompress failed with rc=" +
std::to_string(rc));
return std::string(reinterpret_cast<char *>(dst.data()), dstSize);
}
MMIOSysInfo::MMIOSysInfo(const MMIO *mmio) : mmio(mmio) {}
uint32_t MMIOSysInfo::esiVersion() const {
@ -30,4 +48,6 @@ uint32_t MMIOSysInfo::esiVersion() const {
return mmio->read(VersionNumberOffset);
}
std::string MMIOSysInfo::rawJsonManifest() const { return ""; }
std::vector<uint8_t> MMIOSysInfo::compressedManifest() const {
assert(false && "Not implemented");
}

View File

@ -105,11 +105,37 @@ private:
};
} // namespace
namespace {
class CosimSysInfo : public SysInfo {
public:
CosimSysInfo(CosimDpiServer::Client &client, kj::WaitScope &waitScope)
: client(client), waitScope(waitScope) {}
uint32_t esiVersion() const override {
auto maniResp =
client.getCompressedManifestRequest().send().wait(waitScope);
return maniResp.getVersion();
}
std::vector<uint8_t> compressedManifest() const override {
auto maniResp =
client.getCompressedManifestRequest().send().wait(waitScope);
capnp::Data::Reader data = maniResp.getCompressedManifest();
return std::vector<uint8_t>(data.begin(), data.end());
}
private:
CosimDpiServer::Client &client;
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 new MMIOSysInfo(getService<MMIO>());
return new CosimSysInfo(impl->cosim, impl->waitScope);
return nullptr;
}

View File

@ -0,0 +1,5 @@
# 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
from .accelerator import Accelerator

View File

@ -0,0 +1,24 @@
# 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
from .esiCppAccel import Accelerator as CppAccelerator
import json
class Accelerator(CppAccelerator):
"""A connection to an ESI accelerator."""
@property
def manifest(self) -> "AcceleratorManifest":
"""Get and parse the accelerator manifest."""
return AcceleratorManifest(self)
class AcceleratorManifest:
"""An accelerator manifest. Essential for interacting with an accelerator."""
def __init__(self, accel: Accelerator) -> None:
self.accel = accel
self._manifest = json.loads(accel.sysinfo().json_manifest())

View File

@ -21,10 +21,9 @@ using namespace esi;
using namespace esi::services;
// NOLINTNEXTLINE(readability-identifier-naming)
PYBIND11_MODULE(esiaccel, m) {
PYBIND11_MODULE(esiCppAccel, m) {
py::class_<Accelerator>(m, "Accelerator")
.def_static("connect", &registry::connect,
py::return_value_policy::take_ownership)
.def(py::init(&registry::connect))
.def("sysinfo", &Accelerator::getService<SysInfo>,
py::return_value_policy::reference_internal)
.def("get_service_mmio", &Accelerator::getService<services::MMIO>,
@ -32,7 +31,7 @@ PYBIND11_MODULE(esiaccel, m) {
py::class_<SysInfo>(m, "SysInfo")
.def("esi_version", &SysInfo::esiVersion)
.def("raw_json_manifest", &SysInfo::rawJsonManifest);
.def("json_manifest", &SysInfo::jsonManifest);
py::class_<services::MMIO>(m, "MMIO")
.def("read", &services::MMIO::read)