[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.esi import DeclareRandomAccessMemory, ServiceDecl
from pycde.bsp import cosim, xrt
from pycde.module import Metadata
from pycde.types import Bits
import sys
@ -22,9 +23,19 @@ class MemComms:
read = ServiceDecl.From(RamI64x8.read.type)
class Dummy(Module):
"""To test completely automated metadata collection."""
@generator
def construct(ports):
pass
class MemWriter(Module):
"""Write to address 3 the contents of address 2."""
metadata = Metadata(version="0.1", misc={"numWriters": 1, "style": "stupid"})
clk = Clock()
rst = Input(Bits(1))
@ -54,7 +65,8 @@ def Top(xrt: bool):
@generator
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.
if not xrt:
@ -82,5 +94,6 @@ if __name__ == "__main__":
s = pycde.System(bsp(Top(is_xrt)),
name="ESIMem",
output_directory=sys.argv[1])
s.generate()
s.compile()
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.connect()
# Baseline
m = acc.manifest()
if (platform == "cosim"):
# Baseline
m = acc.manifest()
# MMIO method
acc.cpp_accel.set_manifest_method(esi.esiCppAccel.ManifestMMIO)
m_alt = acc.manifest()
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:
mem_read_addr.write([addr])

View File

@ -3,7 +3,8 @@
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
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 .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
from contextvars import ContextVar
import inspect
import os
import sys
# A memoization table for module parameterization function calls.
@ -396,9 +398,69 @@ class ModuleBuilder(ModuleLikeBuilderBase):
return sys._create_circt_mod(self)
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):
"""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 hasattr(self, "parameters") and self.parameters is not None:
self.attributes["pycde.parameters"] = self.parameters
@ -617,6 +679,20 @@ class modparams:
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):
"""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.constructs import Wire
from pycde.esi import MMIO
from pycde.module import Metadata
from pycde.types import (Bits, Bundle, BundledChannel, Channel,
ChannelDirection, ChannelSignaling, UInt, ClockType)
from pycde.testing import unittestmodule
@ -27,6 +28,9 @@ class HostComms:
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: 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"]>
@ -39,6 +43,15 @@ class LoopbackInOutTop(Module):
clk = Clock()
rst = Input(types.i1)
metadata = Metadata(
name="LoopbackInOut",
version="0.1",
misc={
"foo": 1,
"bar": "baz"
},
)
@generator
def construct(self):
# 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())
if (extra.key() != "name" && extra.key() != "summary" &&
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());
auto value = [&](const string &key) -> optional<string> {
@ -187,8 +187,8 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) {
return nullopt;
return f.value();
};
return ModuleInfo{value("name"), value("summary"), value("version"),
value("repo"), value("commit_hash"), extras};
return ModuleInfo{value("name"), value("summary"), value("version"),
value("repo"), value("commitHash"), extras};
}
//===----------------------------------------------------------------------===//
@ -575,7 +575,8 @@ ostream &operator<<(ostream &os, const ModuleInfo &m) {
os << ")";
}
if (m.summary)
os << ": " << *m.summary << "\n";
os << ": " << *m.summary;
os << "\n";
if (!m.extra.empty()) {
os << " Extra metadata:\n";

View File

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

View File

@ -213,5 +213,6 @@ PYBIND11_MODULE(esiCppAccel, m) {
.def_property_readonly("api_version", &Manifest::getApiVersion)
.def("build_accelerator", &Manifest::buildAccelerator,
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.
from __future__ import annotations
from ast import Mod
import typing
__all__ = [
@ -284,6 +285,10 @@ class Manifest:
def type_table(self) -> list[Type]:
...
@property
def module_infos(self) -> list[ModuleInfo]:
...
class ModuleInfo: