[PyCDE] Add ESI metadata to modules (#6597)

Users can either explicitly specify metadata or it can be automatically generated.
This commit is contained in:
John Demme 2024-01-19 17:28:54 -08:00 committed by GitHub
parent 01022f7f7b
commit d0879a7663
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 125 additions and 12 deletions

View File

@ -8,6 +8,7 @@ import pycde
from pycde import (AppID, Clock, Input, Module, generator) from pycde import (AppID, Clock, Input, Module, generator)
from pycde.esi import DeclareRandomAccessMemory, ServiceDecl from pycde.esi import DeclareRandomAccessMemory, ServiceDecl
from pycde.bsp import cosim, xrt from pycde.bsp import cosim, xrt
from pycde.module import Metadata
from pycde.types import Bits from pycde.types import Bits
import sys import sys
@ -22,9 +23,19 @@ class MemComms:
read = ServiceDecl.From(RamI64x8.read.type) read = ServiceDecl.From(RamI64x8.read.type)
class Dummy(Module):
"""To test completely automated metadata collection."""
@generator
def construct(ports):
pass
class MemWriter(Module): class MemWriter(Module):
"""Write to address 3 the contents of address 2.""" """Write to address 3 the contents of address 2."""
metadata = Metadata(version="0.1", misc={"numWriters": 1, "style": "stupid"})
clk = Clock() clk = Clock()
rst = Input(Bits(1)) rst = Input(Bits(1))
@ -54,7 +65,8 @@ def Top(xrt: bool):
@generator @generator
def construct(ports): def construct(ports):
MemWriter(clk=ports.clk, rst=ports.rst) Dummy(appid=AppID("dummy"))
MemWriter(clk=ports.clk, rst=ports.rst, appid=AppID("mem_writer"))
# We don't have support for host--device channel communication on XRT yet. # We don't have support for host--device channel communication on XRT yet.
if not xrt: if not xrt:
@ -82,5 +94,6 @@ if __name__ == "__main__":
s = pycde.System(bsp(Top(is_xrt)), s = pycde.System(bsp(Top(is_xrt)),
name="ESIMem", name="ESIMem",
output_directory=sys.argv[1]) output_directory=sys.argv[1])
s.generate()
s.compile() s.compile()
s.package() s.package()

View File

@ -15,15 +15,18 @@ mem_read_addr.connect()
mem_read_data = d.ports[esi.AppID("read")].read_port("data") mem_read_data = d.ports[esi.AppID("read")].read_port("data")
mem_read_data.connect() mem_read_data.connect()
# Baseline
m = acc.manifest()
if (platform == "cosim"): if (platform == "cosim"):
# Baseline
m = acc.manifest()
# MMIO method # MMIO method
acc.cpp_accel.set_manifest_method(esi.esiCppAccel.ManifestMMIO) acc.cpp_accel.set_manifest_method(esi.esiCppAccel.ManifestMMIO)
m_alt = acc.manifest() m_alt = acc.manifest()
assert len(m.type_table) == len(m_alt.type_table) assert len(m.type_table) == len(m_alt.type_table)
info = m.module_infos
assert len(info) == 3
assert info[1].name == "Dummy"
def read(addr: int) -> bytearray: def read(addr: int) -> bytearray:
mem_read_addr.write([addr]) mem_read_addr.write([addr])

View File

@ -3,7 +3,8 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
from __future__ import annotations from __future__ import annotations
from typing import List, Optional, Set, Tuple, Dict from dataclasses import dataclass
from typing import Any, List, Optional, Set, Tuple, Dict
from .common import (AppID, Clock, Input, Output, PortError, _PyProxy, Reset) from .common import (AppID, Clock, Input, Output, PortError, _PyProxy, Reset)
from .support import (get_user_loc, _obj_to_attribute, create_type_string, from .support import (get_user_loc, _obj_to_attribute, create_type_string,
@ -18,6 +19,7 @@ from .circt.support import BackedgeBuilder, attribute_to_var
import builtins import builtins
from contextvars import ContextVar from contextvars import ContextVar
import inspect import inspect
import os
import sys import sys
# A memoization table for module parameterization function calls. # A memoization table for module parameterization function calls.
@ -396,9 +398,69 @@ class ModuleBuilder(ModuleLikeBuilderBase):
return sys._create_circt_mod(self) return sys._create_circt_mod(self)
return ret return ret
def add_metadata(self, sys, symbol: str, meta: Optional[Metadata]):
"""Add the metadata to the IR so it potentially gets included in the
manifest. (It'll only be included if one of the instances has an appid.) If
user did not specify the metadata (or components thereof), attempt to fill
them in automatically:
- Name defaults to the module name.
- Summary defaults to the module docstring.
- If GitPython is installed, the commit hash and repo are automatically
generated if neither are specified.
"""
from .dialects.esi import esi
if meta is None:
meta = Metadata()
elif not isinstance(meta, Metadata):
raise TypeError("Module metadata must be of type Metadata")
if meta.name is None:
meta.name = self.modcls.__name__
try:
# Attempt to automatically generate repo and commit hash using GitPython.
if meta.repo is None and meta.commit_hash is None:
import git
import inspect
modclsmodule = inspect.getmodule(self.modcls)
if modclsmodule is not None:
r = git.Repo(os.path.dirname(modclsmodule.__file__),
search_parent_directories=True)
if r is not None:
meta.repo = r.remotes.origin.url
meta.commit_hash = r.head.object.hexsha
except Exception:
pass
if meta.summary is None and self.modcls.__doc__ is not None:
meta.summary = self.modcls.__doc__
with ir.InsertionPoint(sys.mod.body):
meta_op = esi.SymbolMetadataOp(
symbolRef=ir.FlatSymbolRefAttr.get(symbol),
name=ir.StringAttr.get(meta.name),
repo=ir.StringAttr.get(meta.repo) if meta.repo is not None else None,
commitHash=ir.StringAttr.get(meta.commit_hash)
if meta.commit_hash is not None else None,
version=ir.StringAttr.get(meta.version)
if meta.version is not None else None,
summary=ir.StringAttr.get(meta.summary)
if meta.summary is not None else None)
if meta.misc is not None:
for k, v in meta.misc.items():
meta_op.attributes[k] = _obj_to_attribute(v)
def create_op(self, sys, symbol): def create_op(self, sys, symbol):
"""Callback for creating a module op.""" """Callback for creating a module op."""
if hasattr(self.modcls, "metadata"):
meta = self.modcls.metadata
self.add_metadata(sys, symbol, meta)
else:
self.add_metadata(sys, symbol, None)
if len(self.generators) > 0: if len(self.generators) > 0:
if hasattr(self, "parameters") and self.parameters is not None: if hasattr(self, "parameters") and self.parameters is not None:
self.attributes["pycde.parameters"] = self.parameters self.attributes["pycde.parameters"] = self.parameters
@ -617,6 +679,20 @@ class modparams:
return cls return cls
@dataclass
class Metadata:
"""Metadata for a module. This is used to provide information about a module
in the ESI manifest. Set the classvar 'metadata' to an instance of this class
to provide metadata for a module."""
name: Optional[str] = None
repo: Optional[str] = None
commit_hash: Optional[str] = None
version: Optional[str] = None
summary: Optional[str] = None
misc: Optional[Dict[str, Any]] = None
class ImportedModSpec(ModuleBuilder): class ImportedModSpec(ModuleBuilder):
"""Specialization to support imported CIRCT modules.""" """Specialization to support imported CIRCT modules."""

View File

@ -7,6 +7,7 @@ from pycde import esi
from pycde.common import AppID, Output, RecvBundle, SendBundle from pycde.common import AppID, Output, RecvBundle, SendBundle
from pycde.constructs import Wire from pycde.constructs import Wire
from pycde.esi import MMIO from pycde.esi import MMIO
from pycde.module import Metadata
from pycde.types import (Bits, Bundle, BundledChannel, Channel, from pycde.types import (Bits, Bundle, BundledChannel, Channel,
ChannelDirection, ChannelSignaling, UInt, ClockType) ChannelDirection, ChannelSignaling, UInt, ClockType)
from pycde.testing import unittestmodule from pycde.testing import unittestmodule
@ -27,6 +28,9 @@ class HostComms:
from_host = TestFromBundle from_host = TestFromBundle
# CHECK: esi.manifest.sym @LoopbackInOutTop name "LoopbackInOut" repo "{{.+}}" commit "{{.+}}" version "0.1" {bar = "baz", foo = 1 : i64}
# CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1) # CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1)
# CHECK: esi.service.instance #esi.appid<"cosim"[0]> svc @HostComms impl as "cosim"(%clk, %rst) : (!seq.clock, i1) -> () # CHECK: esi.service.instance #esi.appid<"cosim"[0]> svc @HostComms impl as "cosim"(%clk, %rst) : (!seq.clock, i1) -> ()
# CHECK: %bundle, %req = esi.bundle.pack %chanOutput : !esi.bundle<[!esi.channel<i16> to "resp", !esi.channel<i24> from "req"]> # CHECK: %bundle, %req = esi.bundle.pack %chanOutput : !esi.bundle<[!esi.channel<i16> to "resp", !esi.channel<i24> from "req"]>
@ -39,6 +43,15 @@ class LoopbackInOutTop(Module):
clk = Clock() clk = Clock()
rst = Input(types.i1) rst = Input(types.i1)
metadata = Metadata(
name="LoopbackInOut",
version="0.1",
misc={
"foo": 1,
"bar": "baz"
},
)
@generator @generator
def construct(self): def construct(self):
# Use Cosim to implement the 'HostComms' service. # Use Cosim to implement the 'HostComms' service.

View File

@ -178,7 +178,7 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) {
for (auto &extra : mod.items()) for (auto &extra : mod.items())
if (extra.key() != "name" && extra.key() != "summary" && if (extra.key() != "name" && extra.key() != "summary" &&
extra.key() != "version" && extra.key() != "repo" && extra.key() != "version" && extra.key() != "repo" &&
extra.key() != "commit_hash" && extra.key() != "symbolRef") extra.key() != "commitHash" && extra.key() != "symbolRef")
extras[extra.key()] = getAny(extra.value()); extras[extra.key()] = getAny(extra.value());
auto value = [&](const string &key) -> optional<string> { auto value = [&](const string &key) -> optional<string> {
@ -187,8 +187,8 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) {
return nullopt; return nullopt;
return f.value(); return f.value();
}; };
return ModuleInfo{value("name"), value("summary"), value("version"), return ModuleInfo{value("name"), value("summary"), value("version"),
value("repo"), value("commit_hash"), extras}; value("repo"), value("commitHash"), extras};
} }
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
@ -575,7 +575,8 @@ ostream &operator<<(ostream &os, const ModuleInfo &m) {
os << ")"; os << ")";
} }
if (m.summary) if (m.summary)
os << ": " << *m.summary << "\n"; os << ": " << *m.summary;
os << "\n";
if (!m.extra.empty()) { if (!m.extra.empty()) {
os << " Extra metadata:\n"; os << " Extra metadata:\n";

View File

@ -73,12 +73,13 @@ void printInfo(ostream &os, AcceleratorConnection &acc) {
Manifest m(jsonManifest); Manifest m(jsonManifest);
os << "API version: " << m.getApiVersion() << endl << endl; os << "API version: " << m.getApiVersion() << endl << endl;
os << "********************************" << endl; os << "********************************" << endl;
os << "* Design information" << endl; os << "* Module information" << endl;
os << "********************************" << endl; os << "********************************" << endl;
os << endl; os << endl;
for (ModuleInfo mod : m.getModuleInfos()) for (ModuleInfo mod : m.getModuleInfos())
os << mod << endl; os << "- " << mod;
os << endl;
os << "********************************" << endl; os << "********************************" << endl;
os << "* Type table" << endl; os << "* Type table" << endl;
os << "********************************" << endl; os << "********************************" << endl;

View File

@ -213,5 +213,6 @@ PYBIND11_MODULE(esiCppAccel, m) {
.def_property_readonly("api_version", &Manifest::getApiVersion) .def_property_readonly("api_version", &Manifest::getApiVersion)
.def("build_accelerator", &Manifest::buildAccelerator, .def("build_accelerator", &Manifest::buildAccelerator,
py::return_value_policy::take_ownership) py::return_value_policy::take_ownership)
.def_property_readonly("type_table", &Manifest::getTypeTable); .def_property_readonly("type_table", &Manifest::getTypeTable)
.def_property_readonly("module_infos", &Manifest::getModuleInfos);
} }

View File

@ -4,6 +4,7 @@
# None yet. Though we're assuming that we will have some at some point. # None yet. Though we're assuming that we will have some at some point.
from __future__ import annotations from __future__ import annotations
from ast import Mod
import typing import typing
__all__ = [ __all__ = [
@ -284,6 +285,10 @@ class Manifest:
def type_table(self) -> list[Type]: def type_table(self) -> list[Type]:
... ...
@property
def module_infos(self) -> list[ModuleInfo]:
...
class ModuleInfo: class ModuleInfo: