mirror of https://github.com/llvm/circt.git
[PyCDE] Add ESI metadata to modules (#6597)
Users can either explicitly specify metadata or it can be automatically generated.
This commit is contained in:
parent
01022f7f7b
commit
d0879a7663
|
@ -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()
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue