diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir index f5eec8e5e390..58d039724549 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -3,6 +3,7 @@ // 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 --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir // RUN: cd .. +// RUN: esiquery trace w:%t6/esi_system_manifest.json info | FileCheck %s --check-prefix=QUERY-INFO // RUN: esiquery trace w:%t6/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER // RUN: %python %s.py trace w:%t6/esi_system_manifest.json // RUN: esi-cosim.py --source %t6 --top top -- %python %s.py cosim env @@ -95,6 +96,7 @@ hw.module @CallableFunc1() { } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} +esi.manifest.constants @Loopback {depth=5:ui32} 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) -> () @@ -107,6 +109,16 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { hw.instance "loopback_array" @LoopbackArray() -> () } +// QUERY-INFO: API version: 0 +// QUERY-INFO: ******************************** +// QUERY-INFO: * Module information +// QUERY-INFO: ******************************** +// QUERY-INFO: - LoopbackIP v0.0 : IP which simply echos bytes +// QUERY-INFO: Constants: +// QUERY-INFO: depth: 5 +// QUERY-INFO: Extra metadata: +// QUERY-INFO: foo: 1 + // QUERY-HIER: ******************************** // QUERY-HIER: * Design hierarchy // QUERY-HIER: ******************************** diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index a942a45b232b..091b02511599 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -21,6 +21,13 @@ for esiType in m.type_table: print(f"{esiType}") +for info in m.module_infos: + print(f"{info.name}") + for const_name, const in info.constants.items(): + print(f" {const_name}: {const.value} {const.type}") + if info.name == "LoopbackIP" and const_name == "depth": + assert const.value == 5 + d = acc.build_accelerator() loopback = d.children[esiaccel.AppID("loopback_inst", 0)] diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index ee502c72f774..a47e2c0e76dd 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -25,6 +25,7 @@ #include namespace esi { +class Type; //===----------------------------------------------------------------------===// // Common accelerator description types. @@ -53,12 +54,18 @@ class AppIDPath : public std::vector { }; bool operator<(const AppIDPath &a, const AppIDPath &b); +struct Constant { + std::any value; + std::optional type; +}; + struct ModuleInfo { std::optional name; std::optional summary; std::optional version; std::optional repo; std::optional commitHash; + std::map constants; std::map extra; }; diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 0640538eb91f..56e7986179e4 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -107,6 +107,10 @@ class Manifest::Impl { return ctxt.getType(id); } + std::any getAny(const nlohmann::json &value) const; + void parseModuleMetadata(ModuleInfo &info, const nlohmann::json &mod) const; + void parseModuleConsts(ModuleInfo &info, const nlohmann::json &mod) const; + // The parsed json. nlohmann::json manifestJson; // Cache the module info for each symbol. @@ -138,42 +142,50 @@ static ServicePortDesc parseServicePort(const nlohmann::json &jsonPort) { /// 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::any Manifest::Impl::getAny(const nlohmann::json &value) const { + auto getObject = [this](const nlohmann::json &json) -> std::any { std::map ret; for (auto &e : json.items()) ret[e.key()] = getAny(e.value()); return ret; }; - auto getArray = [](const nlohmann::json &json) { + auto getArray = [this](const nlohmann::json &json) -> std::any { 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)); + auto getValue = [&](const nlohmann::json &innerValue) -> std::any { + if (innerValue.is_string()) + return innerValue.get(); + else if (innerValue.is_number_integer()) + return innerValue.get(); + else if (innerValue.is_number_unsigned()) + return innerValue.get(); + else if (innerValue.is_number_float()) + return innerValue.get(); + else if (innerValue.is_boolean()) + return innerValue.get(); + else if (innerValue.is_null()) + return innerValue.get(); + else if (innerValue.is_object()) + return getObject(innerValue); + else if (innerValue.is_array()) + return getArray(innerValue); + else + throw std::runtime_error("Unknown type in manifest: " + + innerValue.dump(2)); + }; + + if (!value.is_object() || !value.contains("type") || !value.contains("value")) + return getValue(value); + return Constant{getValue(value.at("value")), getType(value.at("type"))}; } -static void parseModuleInfo(ModuleInfo &info, const nlohmann::json &mod) { +void Manifest::Impl::parseModuleMetadata(ModuleInfo &info, + const nlohmann::json &mod) const { for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && extra.key() != "version" && extra.key() != "repo" && @@ -193,6 +205,19 @@ static void parseModuleInfo(ModuleInfo &info, const nlohmann::json &mod) { info.commitHash = value("commitHash"); } +void Manifest::Impl::parseModuleConsts(ModuleInfo &info, + const nlohmann::json &mod) const { + for (auto &item : mod.items()) { + std::any value = getAny(item.value()); + auto *c = std::any_cast(&value); + if (c) + info.constants[item.key()] = *c; + else + // If the value isn't a "proper" constant, present it as one with no type. + info.constants[item.key()] = Constant{value, std::nullopt}; + } +} + //===----------------------------------------------------------------------===// // Manifest::Impl class implementation. //===----------------------------------------------------------------------===// @@ -209,7 +234,9 @@ Manifest::Impl::Impl(Context &ctxt, const std::string &manifestStr) for (auto &mod : manifestJson.at("symbols")) { ModuleInfo info; if (mod.contains("sym_info")) - parseModuleInfo(info, mod); + parseModuleMetadata(info, mod.at("sym_info")); + if (mod.contains("sym_consts")) + parseModuleConsts(info, mod.at("sym_consts")); symbolInfoCache.insert(make_pair(mod.at("symbol"), info)); } } catch (const std::exception &e) { @@ -577,6 +604,9 @@ const std::vector &Manifest::getTypeTable() const { // Print a module info, including the extra metadata. std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { auto printAny = [&os](std::any a) { + if (auto *c = std::any_cast(&a)) + a = std::any_cast(a).value; + const std::type_info &t = a.type(); if (t == typeid(std::string)) os << std::any_cast(a); @@ -610,6 +640,15 @@ std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { os << ": " << *m.summary; os << "\n"; + if (!m.constants.empty()) { + os << " Constants:\n"; + for (auto &e : m.constants) { + os << " " << e.first << ": "; + printAny(e.second); + os << "\n"; + } + } + if (!m.extra.empty()) { os << " Extra metadata:\n"; for (auto &e : m.extra) { diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index ab8b94e81d67..051106ecc5e2 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -42,8 +42,45 @@ struct polymorphic_type_hook { return port; } }; + +namespace detail { +/// Pybind11 doesn't have a built-in type caster for std::any +/// (https://github.com/pybind/pybind11/issues/1590). We must provide one which +/// knows about all of the potential types which the any might be. +template <> +struct type_caster { +public: + PYBIND11_TYPE_CASTER(std::any, const_name("object")); + + static handle cast(std::any src, return_value_policy /* policy */, + handle /* parent */) { + const std::type_info &t = src.type(); + if (t == typeid(std::string)) + return py::str(std::any_cast(src)); + else if (t == typeid(int64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(uint64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(double)) + return py::float_(std::any_cast(src)); + else if (t == typeid(bool)) + return py::bool_(std::any_cast(src)); + else if (t == typeid(std::nullptr_t)) + return py::none(); + return py::none(); + } +}; +} // namespace detail } // namespace pybind11 +/// Resolve a Type to the Python wrapper object. +py::object getPyType(std::optional t) { + py::object typesModule = py::module_::import("esiaccel.types"); + if (!t) + return py::none(); + return typesModule.attr("_get_esi_type")(*t); +} + // NOLINTNEXTLINE(readability-identifier-naming) PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Type") @@ -75,6 +112,12 @@ PYBIND11_MODULE(esiCppAccel, m) { py::return_value_policy::reference) .def_property_readonly("size", &ArrayType::getSize); + py::class_(m, "Constant") + .def_property_readonly("value", [](Constant &c) { return c.value; }) + .def_property_readonly( + "type", [](Constant &c) { return getPyType(*c.type); }, + py::return_value_policy::reference); + py::class_(m, "ModuleInfo") .def_property_readonly("name", [](ModuleInfo &info) { return info.name; }) .def_property_readonly("summary", @@ -84,6 +127,8 @@ PYBIND11_MODULE(esiCppAccel, m) { .def_property_readonly("repo", [](ModuleInfo &info) { return info.repo; }) .def_property_readonly("commit_hash", [](ModuleInfo &info) { return info.commitHash; }) + .def_property_readonly("constants", + [](ModuleInfo &info) { return info.constants; }) // TODO: "extra" field. .def("__repr__", [](ModuleInfo &info) { std::string ret;