From 106b3a25d47097d5e804ba5f76f9f41c1c566544 Mon Sep 17 00:00:00 2001 From: John Demme Date: Fri, 10 Nov 2023 13:48:58 -0800 Subject: [PATCH] [ESI][Runtime] Wire up services and ports into the design tree (#6406) Create ports in the design hierarchy and wire them up to the correct service. Patch is a bit larger than it probably should be since it includes some not-unrelated NFC refactoring and `TraceAccelerator` support for writing to trace files. --- .../Dialect/ESI/runtime/loopback.mlir.py | 11 +- lib/Dialect/ESI/ESIOps.cpp | 1 + lib/Dialect/ESI/runtime/CMakeLists.txt | 2 + .../ESI/runtime/cpp/include/esi/Accelerator.h | 77 ++++- .../ESI/runtime/cpp/include/esi/Design.h | 55 +++- .../ESI/runtime/cpp/include/esi/Manifest.h | 22 +- .../ESI/runtime/cpp/include/esi/StdServices.h | 3 + .../ESI/runtime/cpp/include/esi/Types.h | 3 +- .../ESI/runtime/cpp/include/esi/Utils.h | 29 ++ .../runtime/cpp/include/esi/backends/Cosim.h | 6 +- .../runtime/cpp/include/esi/backends/Trace.h | 8 +- .../ESI/runtime/cpp/lib/Accelerator.cpp | 25 +- lib/Dialect/ESI/runtime/cpp/lib/Design.cpp | 41 +++ lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp | 299 ++++++++++++++++-- .../ESI/runtime/cpp/lib/StdServices.cpp | 4 + lib/Dialect/ESI/runtime/cpp/lib/Utils.cpp | 51 +++ .../ESI/runtime/cpp/lib/backends/Cosim.cpp | 19 +- .../ESI/runtime/cpp/lib/backends/Trace.cpp | 155 +++++++-- .../ESI/runtime/python/esi/esiCppAccel.cpp | 42 ++- .../ESI/runtime/python/esi/esiCppAccel.pyi | 34 +- test/Dialect/ESI/manifest.mlir | 18 +- 21 files changed, 813 insertions(+), 92 deletions(-) create mode 100644 lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h create mode 100644 lib/Dialect/ESI/runtime/cpp/lib/Design.cpp create mode 100644 lib/Dialect/ESI/runtime/cpp/lib/Utils.cpp diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index ee7404a69d0e..11af576e23bd 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -1,7 +1,8 @@ import esi import sys -acc = esi.Accelerator(sys.argv[1], sys.argv[2]) +platform = sys.argv[1] +acc = esi.Accelerator(platform, sys.argv[2]) assert acc.sysinfo().esi_version() == 1 m = acc.manifest() @@ -14,3 +15,11 @@ print(appid) assert appid.name == "loopback_inst" assert appid.idx == 0 + +# Services are only hooked up for the trace backend currently. +if platform == "trace": + d.children[0].ports[0].getWrite("recv").write([45] * 128) + d.children[0].ports[0].getWrite("recv").write([24]) + d.children[0].ports[0].getWrite("recv").write([24, 45, 138]) + + d.children[0].ports[1].getRead("send").read(8) diff --git a/lib/Dialect/ESI/ESIOps.cpp b/lib/Dialect/ESI/ESIOps.cpp index 68cc81a927e6..60986c4db85a 100644 --- a/lib/Dialect/ESI/ESIOps.cpp +++ b/lib/Dialect/ESI/ESIOps.cpp @@ -689,6 +689,7 @@ void ServiceRequestRecordOp::getDetails( getDirectionAttrName(), StringAttr::get(ctxt, stringifyBundleDirection(getDirection()))); results.emplace_back(getBundleTypeAttrName(), getBundleTypeAttr()); + results.emplace_back(getServicePortAttrName(), getServicePortAttr()); } StringRef SymbolMetadataOp::getManifestClass() { return "sym_info"; } diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index 6916492bec0c..4ad3da712512 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -44,8 +44,10 @@ include_directories(cpp/include) set(ESIRuntimeSources cpp/lib/Accelerator.cpp + cpp/lib/Design.cpp cpp/lib/Manifest.cpp cpp/lib/StdServices.cpp + cpp/lib/Utils.cpp cpp/lib/backends/Trace.cpp ) diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index 3924f87aa724..435db3d58a7f 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -21,6 +21,10 @@ #ifndef ESI_ACCELERATOR_H #define ESI_ACCELERATOR_H +#include "esi/Design.h" +#include "esi/Manifest.h" + +#include #include #include #include @@ -36,6 +40,32 @@ constexpr uint32_t MagicNumberHi = 0x207D98E5; constexpr uint32_t VersionNumberOffset = MagicNumOffset + 8; constexpr uint32_t ExpectedVersionNumber = 0; +/// Unidirectional channels are the basic communication primitive between the +/// host and accelerator. A 'ChannelPort' is the host side of a channel. It can +/// be either read or write but not both. At this level, channels are untyped -- +/// just streams of bytes. They are not intended to be used directly by users +/// but used by higher level APIs which add types. +class ChannelPort { +public: + virtual ~ChannelPort() = default; +}; + +/// A ChannelPort which sends data to the accelerator. +class WriteChannelPort : public ChannelPort { +public: + /// A very basic write API. Will likely change for performance reasons. + virtual void write(const void *data, size_t size) = 0; +}; + +/// A ChannelPort which reads data from the accelerator. +class ReadChannelPort : public ChannelPort { +public: + /// Specify a buffer to read into and a maximum size to read. Returns the + /// number of bytes read, or -1 on error. Basic API, will likely change for + /// performance reasons. + virtual ssize_t read(void *data, size_t maxSize) = 0; +}; + namespace services { /// Parent class of all APIs modeled as 'services'. May or may not map to a /// hardware side 'service'. @@ -43,6 +73,30 @@ class Service { public: using Type = const std::type_info &; virtual ~Service() = default; + + virtual std::string getServiceSymbol() const = 0; +}; + +/// A service for which there are no standard services registered. Requires +/// ports be added to the design hierarchy instead of high level interfaces like +/// the ones in StdServices.h. +class CustomService : public Service { +public: + CustomService(AppIDPath idPath, const ServiceImplDetails &details, + const HWClientDetails &clients); + virtual ~CustomService() = default; + + virtual std::string getServiceSymbol() const { return _serviceSymbol; } + + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type and direction + /// of the bundle port. + virtual std::map + requestChannelsFor(AppIDPath, const BundleType &, + BundlePort::Direction portDir) = 0; + +private: + std::string _serviceSymbol; }; } // namespace services @@ -51,28 +105,35 @@ class Accelerator { public: virtual ~Accelerator() = default; + using Service = services::Service; /// 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 - ServiceClass *getService() { - return dynamic_cast(getServiceImpl(typeid(ServiceClass))); + ServiceClass *getService(AppIDPath id = {}, ServiceImplDetails details = {}, + HWClientDetails clients = {}) { + return dynamic_cast( + getService(typeid(ServiceClass), id, details, clients)); } + /// Calls `createService` and caches the result. Subclasses can override if + /// they want to use their own caching mechanism. + virtual Service *getService(Service::Type service, AppIDPath id = {}, + ServiceImplDetails details = {}, + HWClientDetails clients = {}); 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); + virtual Service *createService(Service::Type service, AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) = 0; private: /// Cache services via a unique_ptr so they get free'd automatically when /// Accelerator objects get deconstructed. - std::map> serviceCache; + using ServiceCacheKey = std::tuple; + std::map> serviceCache; }; namespace registry { diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h index d4a5c9ee8115..77b02b131b1e 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h @@ -25,7 +25,6 @@ #ifndef ESI_DESIGN_H #define ESI_DESIGN_H -#include "esi/Accelerator.h" #include "esi/Manifest.h" #include @@ -35,21 +34,65 @@ namespace esi { // Forward declarations. class Instance; +class ChannelPort; +class WriteChannelPort; +class ReadChannelPort; +namespace services { +class Service; +} + +/// Services provide connections to 'bundles' -- collections of named, +/// unidirectional communication channels. This class provides access to those +/// ChannelPorts. +class BundlePort { +public: + /// Direction of a bundle. This -- combined with the channel direction in the + /// bundle -- can be used to determine if a channel should be writing to or + /// reading from the accelerator. + enum Direction { ToServer, ToClient }; + + /// Compute the direction of a channel given the bundle direction and the + /// bundle port's direction. + static bool isWrite(BundleType::Direction bundleDir, Direction svcDir) { + if (svcDir == Direction::ToClient) + return bundleDir == BundleType::Direction::To; + return bundleDir == BundleType::Direction::From; + } + + /// Construct a port. + BundlePort(AppID id, std::map channels); + + /// Get access to the raw byte streams of a channel. Intended for internal + /// usage and binding to other languages (e.g. Python) which have their own + /// message serialization code. + WriteChannelPort &getRawWrite(const std::string &name) const; + ReadChannelPort &getRawRead(const std::string &name) const; + +private: + AppID _id; + std::map _channels; +}; class Design { public: Design(std::optional info, - std::vector> children) - : _info(info), _children(std::move(children)) {} + std::vector> children, + std::vector services, + std::vector ports) + : _info(info), _children(std::move(children)), _services(services), + _ports(ports) {} std::optional info() const { return _info; } const std::vector> &children() const { return _children; } + const std::vector &getPorts() const { return _ports; } protected: const std::optional _info; const std::vector> _children; + const std::vector _services; + const std::vector _ports; }; class Instance : public Design { @@ -58,8 +101,10 @@ class Instance : public Design { Instance(const Instance &) = delete; ~Instance() = default; Instance(AppID id, std::optional info, - std::vector> children) - : Design(info, std::move(children)), id_(id) {} + std::vector> children, + std::vector services, + std::vector ports) + : Design(info, std::move(children), services, ports), id_(id) {} const AppID id() const { return id_; } diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h index 30e1ad46ca15..49b1c5ad4980 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -37,8 +38,15 @@ namespace esi { struct AppID { const std::string name; const std::optional idx; + + bool operator==(const AppID &other) const { + return name == other.name && idx == other.idx; + } + bool operator!=(const AppID &other) const { return !(*this == other); } }; using AppIDPath = std::vector; +bool operator<(const AppIDPath &a, const AppIDPath &b); +std::ostream &operator<<(std::ostream &, const AppIDPath &); struct ModuleInfo { const std::optional name; @@ -49,11 +57,23 @@ struct ModuleInfo { const std::map extra; }; -struct ServicePort { +/// A description of a service port. Used pretty exclusively in setting up the +/// design. +struct ServicePortDesc { std::string name; std::string portName; }; +/// A description of a hardware client. Used pretty exclusively in setting up +/// the design. +struct HWClientDetail { + AppIDPath path; + ServicePortDesc port; + std::map implOptions; +}; +using HWClientDetails = std::vector; +using ServiceImplDetails = std::map; + //===----------------------------------------------------------------------===// // Manifest parsing and API creation. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/StdServices.h b/lib/Dialect/ESI/runtime/cpp/include/esi/StdServices.h index 83b421fabaee..4ac4fd42d053 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/StdServices.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/StdServices.h @@ -32,6 +32,8 @@ class SysInfo : public Service { public: virtual ~SysInfo() = default; + virtual std::string getServiceSymbol() const; + /// Get the ESI version number to check version compatibility. virtual uint32_t esiVersion() const = 0; @@ -47,6 +49,7 @@ class MMIO : public Service { virtual ~MMIO() = default; virtual uint64_t read(uint32_t addr) const = 0; virtual void write(uint32_t addr, uint64_t data) = 0; + virtual std::string getServiceSymbol() const; }; /// Implement the SysInfo API for a standard MMIO protocol. diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h index fc7077dd7d66..c6a4b9246a9d 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h @@ -33,6 +33,7 @@ class Type { public: using ID = std::string; Type(const ID &id) : id(id) {} + virtual ~Type() = default; ID getID() { return id; } @@ -54,7 +55,7 @@ class BundleType : public Type { BundleType(const ID &id, const ChannelVector &channels) : Type(id), channels(channels) {} - const ChannelVector &getChannels() { return channels; } + const ChannelVector &getChannels() const { return channels; } protected: ChannelVector channels; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h new file mode 100644 index 000000000000..fe59c806f39e --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h @@ -0,0 +1,29 @@ +//===- Utils.h - ESI runtime utility code -----------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// 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_UTILS_H +#define ESI_UTILS_H + +#include +#include + +namespace esi { +namespace utils { +// Very basic base64 encoding. +void encodeBase64(const void *data, size_t size, std::string &out); +} // namespace utils +} // namespace esi + +#endif // ESI_UTILS_H diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h index 71c7cfdd9948..5c5b91ac802d 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h @@ -12,7 +12,7 @@ // // 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/esi.h). +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp). // //===----------------------------------------------------------------------===// @@ -35,7 +35,9 @@ class CosimAccelerator : public esi::Accelerator { static std::unique_ptr connect(std::string connectionString); protected: - virtual Service *createService(Service::Type service) override; + virtual Service *createService(Service::Type service, AppIDPath path, + const ServiceImplDetails &details, + const HWClientDetails &clients) override; private: struct Impl; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h index 870234b09a88..fbe04563c311 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -57,11 +57,15 @@ class TraceAccelerator : public esi::Accelerator { /// ":[:]". static std::unique_ptr connect(std::string connectionString); + /// Internal implementation. + struct Impl; + protected: - virtual Service *createService(Service::Type service) override; + virtual Service *createService(Service::Type service, AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) override; private: - struct Impl; std::unique_ptr impl; }; diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index 87855d39e1f8..1a0e6ea88705 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -8,8 +8,7 @@ // // 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). +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/). // //===----------------------------------------------------------------------===// @@ -18,13 +17,29 @@ #include #include +using namespace esi; +using namespace esi::services; + +CustomService::CustomService(AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) { + _serviceSymbol = std::any_cast(details.at("service")); + // Strip off initial '@'. + _serviceSymbol = _serviceSymbol.substr(1); +} + namespace esi { -services::Service *Accelerator::getServiceImpl(Service::Type svcType) { - std::unique_ptr &cacheEntry = serviceCache[&svcType]; +services::Service *Accelerator::getService(Service::Type svcType, AppIDPath id, + ServiceImplDetails details, + HWClientDetails clients) { + std::unique_ptr &cacheEntry = + serviceCache[std::make_tuple(&svcType, id)]; if (cacheEntry == nullptr) - cacheEntry = std::unique_ptr(createService(svcType)); + cacheEntry = + std::unique_ptr(createService(svcType, id, details, clients)); return cacheEntry.get(); } + namespace registry { namespace internal { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp new file mode 100644 index 000000000000..ee53612b966f --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp @@ -0,0 +1,41 @@ +//===- Design.cpp - Implementation of dynamic API -------------------------===// +// +// 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/). +// +//===----------------------------------------------------------------------===// + +#include "esi/Design.h" +#include "esi/Accelerator.h" + +using namespace esi; + +BundlePort::BundlePort(AppID id, std::map channels) + : _id(id), _channels(channels) {} + +WriteChannelPort &BundlePort::getRawWrite(const std::string &name) const { + auto f = _channels.find(name); + if (f == _channels.end()) + throw std::runtime_error("Channel '" + name + "' not found"); + auto *write = dynamic_cast(&f->second); + if (!write) + throw std::runtime_error("Channel '" + name + "' is not a write channel"); + return *write; +} + +ReadChannelPort &BundlePort::getRawRead(const std::string &name) const { + auto f = _channels.find(name); + if (f == _channels.end()) + throw std::runtime_error("Channel '" + name + "' not found"); + auto *read = dynamic_cast(&f->second); + if (!read) + throw std::runtime_error("Channel '" + name + "' is not a read channel"); + return *read; +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 90448531dacf..001dfcbea879 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -8,8 +8,7 @@ // // 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). +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/). // //===----------------------------------------------------------------------===// @@ -25,6 +24,12 @@ using namespace esi; namespace esi { namespace internal { +// While building the design, keep around a map of active services indexed by +// the service name. When a new service is encountered during descent, add it to +// the table (perhaps overwriting one). Modifications to the table only apply to +// the current branch, so copy this and update it at each level of the tree. +using ServiceTable = std::map; + // This is a proxy class to the manifest JSON. It is used to avoid having to // include the JSON parser in the header. Forward references don't work since // nlohmann::json is a rather complex template. @@ -40,14 +45,39 @@ class ManifestProxy { auto at(const std::string &key) const { return manifestJson.at(key); } // Get the module info (if any) for the module instance in 'json'. - std::optional getModInfo(const nlohmann::json &json) const; - - // Build the 'Instance' recursively for the instance in 'json'. - Instance getInstance(const nlohmann::json &json) const; - - /// Build the set of child instances for the module instance in 'json'. + std::optional getModInfo(const nlohmann::json &) const; + + /// Get a Service for the service specified in 'json'. Update the + /// activeServices table. + services::Service *getService(AppIDPath idPath, Accelerator &, + const nlohmann::json &, + ServiceTable &activeServices) const; + + /// Get all the services in the description of an instance. Update the active + /// services table. + std::vector + getServices(AppIDPath idPath, Accelerator &, const nlohmann::json &, + ServiceTable &activeServices) const; + + /// Get the bundle ports for the instance at 'idPath' and specified in + /// 'instJson'. Look them up in 'activeServies'. + std::vector getBundlePorts(AppIDPath idPath, + const ServiceTable &activeServices, + const nlohmann::json &instJson) const; + + /// Build the set of child instances (recursively) for the module instance + /// description. std::vector> - getChildInstances(const nlohmann::json &json) const; + getChildInstances(AppIDPath idPath, Accelerator &acc, + const ServiceTable &activeServices, + const nlohmann::json &instJson) const; + + /// Get a single child instance. Implicitly copy the active services table so + /// that it can be safely updated for the child's branch of the tree. + std::unique_ptr + getChildInstance(AppIDPath idPath, Accelerator &acc, + ServiceTable activeServices, + const nlohmann::json &childJson) const; /// Parse all the types and populate the types table. void populateTypes(const nlohmann::json &typesJson); @@ -57,6 +87,13 @@ class ManifestProxy { return _typeTable; } + // Forwarded from Manifest. + std::optional> getType(Type::ID id) const { + if (auto f = _types.find(id); f != _types.end()) + return *f->second; + return std::nullopt; + } + /// Build a dynamic API for the Accelerator connection 'acc' based on the /// manifest stored herein. std::unique_ptr buildDesign(Accelerator &acc) const; @@ -88,23 +125,57 @@ static AppID parseID(const nlohmann::json &jsonID) { return AppID{jsonID.at("name").get(), idx}; } -static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { - auto getAny = [](const nlohmann::json &value) -> std::any { - if (value.is_string()) - return value.get(); - else if (value.is_number_integer()) - return value.get(); - else if (value.is_number_unsigned()) - return value.get(); - else if (value.is_number_float()) - return value.get(); - else if (value.is_boolean()) - return value.get(); - else if (value.is_null()) - return value.get(); - else - throw std::runtime_error("Unknown type in manifest: " + value.dump(2)); +static AppIDPath parseIDPath(const nlohmann::json &jsonIDPath) { + AppIDPath ret; + for (auto &id : jsonIDPath) + ret.push_back(parseID(id)); + return ret; +} + +static ServicePortDesc parseServicePort(const nlohmann::json &jsonPort) { + return ServicePortDesc{jsonPort.at("outer_sym").get(), + jsonPort.at("inner").get()}; +} + +/// Convert the json value to a 'std::any', which can be exposed outside of this +/// file. +static std::any getAny(const nlohmann::json &value) { + auto getObject = [](const nlohmann::json &json) { + std::map ret; + for (auto &e : json.items()) + ret[e.key()] = getAny(e.value()); + return ret; + }; + + auto getArray = [](const nlohmann::json &json) { + std::vector ret; + for (auto &e : json) + ret.push_back(getAny(e)); + return ret; }; + + if (value.is_string()) + return value.get(); + else if (value.is_number_integer()) + return value.get(); + else if (value.is_number_unsigned()) + return value.get(); + else if (value.is_number_float()) + return value.get(); + else if (value.is_boolean()) + return value.get(); + else if (value.is_null()) + return value.get(); + else if (value.is_object()) + return getObject(value); + else if (value.is_array()) + return getArray(value); + else + throw std::runtime_error("Unknown type in manifest: " + value.dump(2)); +} + +static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { + std::map extras; for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && @@ -138,9 +209,16 @@ internal::ManifestProxy::ManifestProxy(const std::string &manifestStr) { std::unique_ptr internal::ManifestProxy::buildDesign(Accelerator &acc) const { auto designJson = manifestJson.at("design"); - std::vector> children = - getChildInstances(designJson); - return std::make_unique(getModInfo(designJson), std::move(children)); + + // Get the initial active services table. Update it as we descend down. + ServiceTable activeSvcs; + std::vector services = + getServices({}, acc, designJson, activeSvcs); + + return std::make_unique( + getModInfo(designJson), + getChildInstances({}, acc, activeSvcs, designJson), services, + getBundlePorts({}, activeSvcs, designJson)); } std::optional @@ -155,15 +233,136 @@ internal::ManifestProxy::getModInfo(const nlohmann::json &json) const { } std::vector> -internal::ManifestProxy::getChildInstances(const nlohmann::json &json) const { +internal::ManifestProxy::getChildInstances( + AppIDPath idPath, Accelerator &acc, const ServiceTable &activeServices, + const nlohmann::json &instJson) const { std::vector> ret; - auto childrenIter = json.find("children"); - if (childrenIter == json.end()) + auto childrenIter = instJson.find("children"); + if (childrenIter == instJson.end()) return ret; - for (auto &child : childrenIter.value()) { - auto children = getChildInstances(child); - ret.emplace_back(std::make_unique( - parseID(child.at("app_id")), getModInfo(child), std::move(children))); + for (auto &child : childrenIter.value()) + ret.emplace_back(getChildInstance(idPath, acc, activeServices, child)); + return ret; +} +std::unique_ptr +internal::ManifestProxy::getChildInstance(AppIDPath idPath, Accelerator &acc, + ServiceTable activeServices, + const nlohmann::json &child) const { + AppID childID = parseID(child.at("app_id")); + idPath.push_back(childID); + + std::vector services = + getServices(idPath, acc, child, activeServices); + + auto children = getChildInstances(idPath, acc, activeServices, child); + return std::make_unique( + parseID(child.at("app_id")), getModInfo(child), std::move(children), + services, getBundlePorts(idPath, activeServices, child)); +} + +services::Service * +internal::ManifestProxy::getService(AppIDPath idPath, Accelerator &acc, + const nlohmann::json &svcJson, + ServiceTable &activeServices) const { + + AppID id = parseID(svcJson.at("appID")); + idPath.push_back(id); + + // Get all the client info, including the implementation details. + HWClientDetails clientDetails; + for (auto &client : svcJson.at("client_details")) { + HWClientDetail clientDetail; + for (auto &detail : client.items()) { + if (detail.key() == "relAppIDPath") + clientDetail.path = parseIDPath(detail.value()); + else if (detail.key() == "port") + clientDetail.port = parseServicePort(detail.value()); + else + clientDetail.implOptions[detail.key()] = getAny(detail.value()); + } + } + + // Get the implementation details. + ServiceImplDetails svcDetails; + for (auto &detail : svcJson.items()) + if (detail.key() != "appID" && detail.key() != "client_details") + svcDetails[detail.key()] = getAny(detail.value()); + + // Create the service. + // TODO: Add support for 'standard' services. + auto svc = acc.getService(idPath, svcDetails, + clientDetails); + if (!svc) + throw std::runtime_error("Could not create service for "); + + // Update the active services table. + activeServices[svc->getServiceSymbol()] = svc; + return svc; +} + +std::vector +internal::ManifestProxy::getServices(AppIDPath idPath, Accelerator &acc, + const nlohmann::json &svcsJson, + ServiceTable &activeServices) const { + std::vector ret; + auto contentsIter = svcsJson.find("contents"); + if (contentsIter == svcsJson.end()) + return ret; + + for (auto &content : contentsIter.value()) + if (content.at("class") == "service") + ret.emplace_back(getService(idPath, acc, content, activeServices)); + return ret; +} + +std::vector +internal::ManifestProxy::getBundlePorts(AppIDPath idPath, + const ServiceTable &activeServices, + const nlohmann::json &instJson) const { + std::vector ret; + auto contentsIter = instJson.find("contents"); + if (contentsIter == instJson.end()) + return ret; + + for (auto &content : contentsIter.value()) { + if (content.at("class") != "client_port") + continue; + + // Lookup the requested service in the active services table. + ServicePortDesc port = parseServicePort(content.at("servicePort")); + auto svc = activeServices.find(port.name); + if (svc == activeServices.end()) + throw std::runtime_error( + "Malformed manifest: could not find active service '" + port.name + + "'"); + + std::string typeName = content.at("bundleType").at("circt_name"); + auto type = getType(typeName); + if (!type) + throw std::runtime_error( + "Malformed manifest: could not find port type '" + typeName + "'"); + const BundleType &bundleType = + dynamic_cast(type->get()); + + BundlePort::Direction portDir; + std::string dirStr = content.at("direction"); + if (dirStr == "toClient") + portDir = BundlePort::Direction::ToClient; + else if (dirStr == "toServer") + portDir = BundlePort::Direction::ToServer; + else + throw std::runtime_error("Malformed manifest: unknown direction '" + + dirStr + "'"); + + idPath.push_back(parseID(content.at("appID"))); + std::map portChannels; + // If we need to have custom ports (because of a custom service), add them. + if (auto *customSvc = dynamic_cast(svc->second)) + portChannels = customSvc->requestChannelsFor(idPath, bundleType, portDir); + ret.emplace_back(idPath.back(), portChannels); + // Since we share idPath between iterations, pop the last element before the + // next iteration. + idPath.pop_back(); } return ret; } @@ -303,3 +502,33 @@ std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { } return os; } + +namespace esi { +bool operator<(const AppID &a, const AppID &b) { + if (a.name != b.name) + return a.name < b.name; + return a.idx < b.idx; +} +bool operator<(const AppIDPath &a, const AppIDPath &b) { + if (a.size() != b.size()) + return a.size() < b.size(); + for (size_t i = 0, e = a.size(); i < e; ++i) + if (a[i] != b[i]) + return a[i] < b[i]; + return false; +} +std::ostream &operator<<(std::ostream &os, const AppID &id) { + os << id.name; + if (id.idx) + os << "[" << *id.idx << "]"; + return os; +} +std::ostream &operator<<(std::ostream &os, const AppIDPath &path) { + for (size_t i = 0, e = path.size(); i < e; ++i) { + if (i > 0) + os << '.'; + os << path[i]; + } + return os; +} +} // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/lib/StdServices.cpp b/lib/Dialect/ESI/runtime/cpp/lib/StdServices.cpp index a089792963b6..94cee898a337 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/StdServices.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/StdServices.cpp @@ -23,6 +23,8 @@ using namespace esi; using namespace esi::services; +std::string SysInfo::getServiceSymbol() const { return "__builtin_SysInfo"; } + // 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. @@ -38,6 +40,8 @@ std::string SysInfo::jsonManifest() const { return std::string(reinterpret_cast(dst.data()), dstSize); } +std::string MMIO::getServiceSymbol() const { return "__builtin_MMIO"; } + MMIOSysInfo::MMIOSysInfo(const MMIO *mmio) : mmio(mmio) {} uint32_t MMIOSysInfo::esiVersion() const { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Utils.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Utils.cpp new file mode 100644 index 000000000000..2e908f484442 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/Utils.cpp @@ -0,0 +1,51 @@ +//===- Utils.cpp - implementations ESI utility code -----------------------===// +// +// 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/Utils.h" + +static constexpr char Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +// Copied then modified slightly from llvm/lib/Support/Base64.cpp. +void esi::utils::encodeBase64(const void *dataIn, size_t size, + std::string &buffer) { + const char *data = static_cast(dataIn); + buffer.resize(((size + 2) / 3) * 4); + + size_t i = 0, j = 0; + for (size_t n = size / 3 * 3; i < n; i += 3, j += 4) { + uint32_t x = ((unsigned char)data[i] << 16) | + ((unsigned char)data[i + 1] << 8) | (unsigned char)data[i + 2]; + buffer[j + 0] = Table[(x >> 18) & 63]; + buffer[j + 1] = Table[(x >> 12) & 63]; + buffer[j + 2] = Table[(x >> 6) & 63]; + buffer[j + 3] = Table[x & 63]; + } + if (i + 1 == size) { + uint32_t x = ((unsigned char)data[i] << 16); + buffer[j + 0] = Table[(x >> 18) & 63]; + buffer[j + 1] = Table[(x >> 12) & 63]; + buffer[j + 2] = '='; + buffer[j + 3] = '='; + } else if (i + 2 == size) { + uint32_t x = + ((unsigned char)data[i] << 16) | ((unsigned char)data[i + 1] << 8); + buffer[j + 0] = Table[(x >> 18) & 63]; + buffer[j + 1] = Table[(x >> 12) & 63]; + buffer[j + 2] = Table[(x >> 6) & 63]; + buffer[j + 3] = '='; + } +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp index 3dda809ba8d0..d6312fb697bd 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp @@ -130,12 +130,29 @@ class CosimSysInfo : public SysInfo { }; } // namespace -Service *CosimAccelerator::createService(Service::Type svcType) { +namespace { +class CosimCustomService : public services::CustomService { +public: + using CustomService::CustomService; + + virtual std::map + requestChannelsFor(AppIDPath, const BundleType &, + BundlePort::Direction portDir) override { + return {}; + } +}; +} // namespace + +Service *CosimAccelerator::createService(Service::Type svcType, AppIDPath id, + const ServiceImplDetails &details, + const HWClientDetails &clients) { if (svcType == typeid(MMIO)) return new CosimMMIO(impl->lowLevel, impl->waitScope); else if (svcType == typeid(SysInfo)) // return new MMIOSysInfo(getService()); return new CosimSysInfo(impl->cosim, impl->waitScope); + else if (svcType == typeid(CustomService)) + return new CosimCustomService(id, details, clients); return nullptr; } diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp index 2140f248c256..b49feb65daad 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp @@ -13,7 +13,9 @@ //===----------------------------------------------------------------------===// #include "esi/backends/Trace.h" +#include "esi/Design.h" #include "esi/StdServices.h" +#include "esi/Utils.h" #include #include @@ -28,6 +30,62 @@ using namespace esi::backends::trace; // We only support v1. constexpr uint32_t ESIVersion = 1; +namespace { +class TraceChannelPort; +} + +struct esi::backends::trace::TraceAccelerator::Impl { + Impl(Mode mode, std::filesystem::path manifestJson, + std::filesystem::path traceFile) + : manifestJson(manifestJson), traceFile(traceFile) { + if (!filesystem::exists(manifestJson)) + throw runtime_error("manifest file '" + manifestJson.string() + + "' does not exist"); + + if (mode == Write) { + // Open the trace file for writing. + traceWrite = new ofstream(traceFile); + if (!traceWrite->is_open()) + throw runtime_error("failed to open trace file '" + traceFile.string() + + "'"); + } else { + assert(false && "not implemented"); + } + } + + ~Impl() { + if (traceWrite) { + traceWrite->close(); + delete traceWrite; + } + } + + Service *createService(Service::Type svcType, AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients); + + void adoptChannelPort(ChannelPort *port) { channels.emplace_back(port); } + + void write(const AppIDPath &id, const std::string &portName, const void *data, + size_t size); + +private: + ofstream *traceWrite; + std::filesystem::path manifestJson; + std::filesystem::path traceFile; + vector> channels; +}; + +void TraceAccelerator::Impl::write(const AppIDPath &id, + const std::string &portName, + const void *data, size_t size) { + std::string b64data; + utils::encodeBase64(data, size, b64data); + + *traceWrite << "write " << id << '.' << portName << ": " << b64data + << std::endl; +} + unique_ptr TraceAccelerator::connect(string connectionString) { string modeStr; string manifestPath; @@ -58,6 +116,18 @@ unique_ptr TraceAccelerator::connect(string connectionString) { mode, filesystem::path(manifestPath), filesystem::path(traceFile)); } +TraceAccelerator::TraceAccelerator(Mode mode, + std::filesystem::path manifestJson, + std::filesystem::path traceFile) { + impl = std::make_unique(mode, manifestJson, traceFile); +} + +Service *TraceAccelerator::createService(Service::Type svcType, + AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) { + return impl->createService(svcType, idPath, details, clients); +} namespace { class TraceSysInfo : public SysInfo { public: @@ -87,37 +157,78 @@ class TraceSysInfo : public SysInfo { }; } // namespace -struct esi::backends::trace::TraceAccelerator::Impl { - Impl(Mode mode, std::filesystem::path manifestJson, - std::filesystem::path traceFile) - : mode(mode), manifestJson(manifestJson), traceFile(traceFile) { - if (!filesystem::exists(manifestJson)) - throw runtime_error("manifest file '" + manifestJson.string() + - "' does not exist"); +namespace { +class WriteTraceChannelPort : public WriteChannelPort { +public: + WriteTraceChannelPort(TraceAccelerator::Impl &impl, const AppIDPath &id, + const std::string &portName) + : impl(impl), id(id), portName(portName) {} + + virtual void write(const void *data, size_t size) override { + impl.write(id, portName, data, size); } - Service *createService(Service::Type svcType); +protected: + TraceAccelerator::Impl &impl; + AppIDPath id; + std::string portName; +}; +} // namespace + +namespace { +class ReadTraceChannelPort : public ReadChannelPort { +public: + ReadTraceChannelPort(TraceAccelerator::Impl &impl) {} + + virtual ssize_t read(void *data, size_t maxSize) override; +}; +} // namespace + +ssize_t ReadTraceChannelPort::read(void *data, size_t maxSize) { + uint8_t *dataPtr = reinterpret_cast(data); + for (size_t i = 0; i < maxSize; ++i) + dataPtr[i] = rand() % 256; + return maxSize; +} + +namespace { +class TraceCustomService : public CustomService { +public: + TraceCustomService(TraceAccelerator::Impl &impl, AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) + : CustomService(idPath, details, clients), impl(impl) {} + + virtual std::map + requestChannelsFor(AppIDPath idPath, const BundleType &bundleType, + BundlePort::Direction svcDir) override { + std::map channels; + for (auto [name, dir, type] : bundleType.getChannels()) { + ChannelPort *port; + if (BundlePort::isWrite(dir, svcDir)) + port = new WriteTraceChannelPort(impl, idPath, name); + else + port = new ReadTraceChannelPort(impl); + channels.emplace(name, *port); + impl.adoptChannelPort(port); + } + return channels; + } private: - Mode mode; - std::filesystem::path manifestJson; - std::filesystem::path traceFile; + TraceAccelerator::Impl &impl; }; +} // namespace -Service *TraceAccelerator::Impl::createService(Service::Type svcType) { +Service * +TraceAccelerator::Impl::createService(Service::Type svcType, AppIDPath idPath, + const ServiceImplDetails &details, + const HWClientDetails &clients) { if (svcType == typeid(SysInfo)) return new TraceSysInfo(manifestJson); + if (svcType == typeid(CustomService)) + return new TraceCustomService(*this, idPath, details, clients); return nullptr; } -TraceAccelerator::TraceAccelerator(Mode mode, - std::filesystem::path manifestJson, - std::filesystem::path traceFile) { - impl = std::make_unique(mode, manifestJson, traceFile); -} - -Service *TraceAccelerator::createService(Service::Type svcType) { - return impl->createService(svcType); -} - REGISTER_ACCELERATOR("trace", TraceAccelerator); diff --git a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp index 362bc5ceff36..143a438585a1 100644 --- a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp @@ -68,10 +68,34 @@ PYBIND11_MODULE(esiCppAccel, m) { return ret; }); + py::class_(m, "ChannelPort"); + py::class_(m, "WriteChannelPort") + .def("write", [](WriteChannelPort &p, std::vector data) { + p.write(data.data(), data.size()); + }); + py::class_(m, "ReadChannelPort") + .def("read", [](ReadChannelPort &p, size_t maxSize) { + std::vector data(maxSize); + ssize_t size = p.read(data.data(), data.size()); + if (size < 0) + throw std::runtime_error("read failed"); + data.resize(size); + return data; + }); + + py::class_(m, "BundlePort") + .def("getWrite", &BundlePort::getRawWrite, + py::return_value_policy::reference) + .def("getRead", &BundlePort::getRawRead, + py::return_value_policy::reference); + // Store this variable (not commonly done) as the "children" method needs for // "Instance" to be defined first. - auto design = py::class_(m, "Design") - .def_property_readonly("info", &Design::info); + auto design = + py::class_(m, "Design") + .def_property_readonly("info", &Design::info) + .def_property_readonly("ports", &Design::getPorts, + py::return_value_policy::reference_internal); // In order to inherit methods from "Design", it needs to be defined first. py::class_(m, "Instance") @@ -91,10 +115,16 @@ PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Accelerator") .def(py::init(®istry::connect)) - .def("sysinfo", &Accelerator::getService, - py::return_value_policy::reference_internal) - .def("get_service_mmio", &Accelerator::getService, - py::return_value_policy::reference_internal); + .def( + "sysinfo", + [](Accelerator &acc) { + return acc.getService({}); + }, + py::return_value_policy::reference_internal) + .def( + "get_service_mmio", + [](Accelerator &acc) { return acc.getService({}); }, + py::return_value_policy::reference_internal); py::class_(m, "Type") .def_property_readonly("id", &Type::getID) diff --git a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi index 4ea844258eeb..7a602c1bc7a6 100644 --- a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi +++ b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi @@ -7,8 +7,9 @@ from __future__ import annotations import typing __all__ = [ - 'Accelerator', 'AppID', 'Design', 'Instance', 'MMIO', 'Manifest', - 'ModuleInfo', 'SysInfo', 'Type' + 'Accelerator', 'AppID', 'BundlePort', 'ChannelPort', 'Design', 'Instance', + 'MMIO', 'Manifest', 'ModuleInfo', 'ReadChannelPort', 'SysInfo', 'Type', + 'WriteChannelPort' ] @@ -38,6 +39,19 @@ class AppID: ... +class BundlePort: + + def getRead(self, arg0: str) -> ReadChannelPort: + ... + + def getWrite(self, arg0: str) -> WriteChannelPort: + ... + + +class ChannelPort: + pass + + class Design: @property @@ -48,6 +62,10 @@ class Design: def info(self) -> ModuleInfo | None: ... + @property + def ports(self) -> list[BundlePort]: + ... + class Instance(Design): @@ -108,6 +126,12 @@ class ModuleInfo: ... +class ReadChannelPort(ChannelPort): + + def read(self, arg0: int) -> list[int]: + ... + + class SysInfo: def esi_version(self) -> int: @@ -125,3 +149,9 @@ class Type: @property def id(self) -> str: ... + + +class WriteChannelPort(ChannelPort): + + def write(self, arg0: list[int]) -> None: + ... diff --git a/test/Dialect/ESI/manifest.mlir b/test/Dialect/ESI/manifest.mlir index f2b96c60559e..71ab03299807 100644 --- a/test/Dialect/ESI/manifest.mlir +++ b/test/Dialect/ESI/manifest.mlir @@ -51,7 +51,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // HIER: } // HW-LABEL: hw.module @top -// HW: hw.instance "__manifest" @Cosim_Manifest(compressed_manifest: %{{.+}}: !hw.uarray<780xi8>) -> () +// HW: hw.instance "__manifest" @Cosim_Manifest(compressed_manifest: %{{.+}}: !hw.uarray<{{.+}}xi8>) -> () // HW-LABEL: hw.module.extern @Cosim_Manifest(in %compressed_manifest : !hw.uarray<#hw.param.decl.ref<"COMPRESSED_MANIFEST_SIZE">xi8>) attributes {verilogName = "Cosim_Manifest"} // CHECK: { @@ -169,6 +169,10 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" +// CHECK-NEXT: }, +// CHECK-NEXT: "servicePort": { +// CHECK-NEXT: "inner": "Recv", +// CHECK-NEXT: "outer_sym": "HostComms" // CHECK-NEXT: } // CHECK-NEXT: }, // CHECK-NEXT: { @@ -179,6 +183,10 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: }, +// CHECK-NEXT: "servicePort": { +// CHECK-NEXT: "inner": "Send", +// CHECK-NEXT: "outer_sym": "HostComms" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], @@ -199,6 +207,10 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" +// CHECK-NEXT: }, +// CHECK-NEXT: "servicePort": { +// CHECK-NEXT: "inner": "Recv", +// CHECK-NEXT: "outer_sym": "HostComms" // CHECK-NEXT: } // CHECK-NEXT: }, // CHECK-NEXT: { @@ -209,6 +221,10 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: }, +// CHECK-NEXT: "servicePort": { +// CHECK-NEXT: "inner": "Send", +// CHECK-NEXT: "outer_sym": "HostComms" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ],