Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ESI][Runtime] Parse and expose manifest constants #7492

Merged
merged 3 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/circt/Dialect/ESI/ESIInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ def IsManifestData : OpInterface<"IsManifestData"> {
"Get the class name for this op.",
"StringRef", "getManifestClass", (ins)
>,
InterfaceMethod<
"Get the symbol to which this manifest data is referring, if any.",
"FlatSymbolRefAttr", "getSymbolRefAttr", (ins),
/*methodBody=*/"",
/*defaultImplementation=*/[{
return FlatSymbolRefAttr();
}]
>,
InterfaceMethod<
"Populate results with the manifest data.",
"void", "getDetails", (ins "SmallVectorImpl<NamedAttribute>&":$results),
Expand Down
17 changes: 17 additions & 0 deletions include/circt/Dialect/ESI/ESIManifest.td
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,23 @@ def AppIDHierNodeOp : ESI_Op<"manifest.hier_node", [
}];
}

def SymbolConstantsOp : ESI_Op<"manifest.consts", [
DeclareOpInterfaceMethods<IsManifestData>]> {
let summary = "Constant values associated with a symbol";

let arguments = (ins FlatSymbolRefAttr:$symbolRef,
DictionaryAttr:$constants);
let assemblyFormat = [{
$symbolRef $constants attr-dict
}];

let extraClassDeclaration = [{
// Get information which needs to appear in the manifest for the host to
// connect to this service.
void getDetails(SmallVectorImpl<NamedAttribute> &results);
}];
}

def SymbolMetadataOp : ESI_Op<"manifest.sym", [
DeclareOpInterfaceMethods<IsManifestData>]> {
let summary = "Metadata about a symbol";
Expand Down
12 changes: 12 additions & 0 deletions integration_test/Dialect/ESI/runtime/loopback.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.consts @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) -> ()
Expand All @@ -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: ********************************
Expand Down
7 changes: 7 additions & 0 deletions integration_test/Dialect/ESI/runtime/loopback.mlir.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
6 changes: 6 additions & 0 deletions lib/Dialect/ESI/ESIOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,12 @@ void ServiceRequestRecordOp::getDetails(

StringRef SymbolMetadataOp::getManifestClass() { return "sym_info"; }

StringRef SymbolConstantsOp::getManifestClass() { return "sym_consts"; }
void SymbolConstantsOp::getDetails(SmallVectorImpl<NamedAttribute> &results) {
for (auto &attr : getConstantsAttr())
results.push_back(attr);
}

#define GET_OP_CLASSES
#include "circt/Dialect/ESI/ESI.cpp.inc"

Expand Down
192 changes: 117 additions & 75 deletions lib/Dialect/ESI/Passes/ESIBuildManifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ struct ESIBuildManifestPass
void gatherFilters(Operation *);
void gatherFilters(Attribute);

/// Get a JSON representation of a type.
llvm::json::Value json(Operation *errorOp, Type);
/// Get a JSON representation of a type.
llvm::json::Value json(Operation *errorOp, Attribute);
/// Get a JSON representation of a type. 'useTable' indicates whether to use
/// the type table to determine if the type should be emitted as a reference
/// if it already exists in the type table.
llvm::json::Value json(Operation *errorOp, Type, bool useTable = true);
/// Get a JSON representation of a type. 'elideType' indicates to not print
/// the type if it would have been printed.
llvm::json::Value json(Operation *errorOp, Attribute, bool elideType = false);

// Output a node in the appid hierarchy.
void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp);
Expand Down Expand Up @@ -88,6 +91,12 @@ void ESIBuildManifestPass::runOnOperation() {
// scraping unnecessary types.
appidRoot->walk([&](Operation *op) { gatherFilters(op); });

// Also gather types from the manifest data.
for (Region &region : mod->getRegions())
for (Block &block : region)
for (auto manifestInfo : block.getOps<IsManifestData>())
gatherFilters(manifestInfo);

// JSONify the manifest.
std::string jsonManifest = json();

Expand Down Expand Up @@ -165,15 +174,34 @@ std::string ESIBuildManifestPass::json() {
j.attribute("api_version", esiApiVersion);

j.attributeArray("symbols", [&]() {
for (auto symInfo : mod.getBody()->getOps<SymbolMetadataOp>()) {
if (!symbols.contains(symInfo.getSymbolRefAttr()))
// First, gather all of the manifest data for each symbol.
DenseMap<SymbolRefAttr, SmallVector<IsManifestData>> symbolInfoLookup;
for (auto symInfo : mod.getBody()->getOps<IsManifestData>()) {
FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr();
if (!sym || !symbols.contains(sym))
continue;
symbolInfoLookup[sym].push_back(symInfo);
}

// Now, emit a JSON object for each symbol.
for (const auto &symNameInfo : symbolInfoLookup) {
j.object([&] {
SmallVector<NamedAttribute, 4> attrs;
symInfo.getDetails(attrs);
for (auto attr : attrs)
j.attribute(attr.getName().getValue(),
json(symInfo, attr.getValue()));
j.attribute("symbol", json(symNameInfo.second.front(),
symNameInfo.first, /*elideType=*/true));
for (auto symInfo : symNameInfo.second) {
j.attributeBegin(symInfo.getManifestClass());
j.object([&] {
SmallVector<NamedAttribute, 4> attrs;
symInfo.getDetails(attrs);
for (auto attr : attrs) {
if (attr.getName().getValue() == "symbolRef")
continue;
j.attribute(attr.getName().getValue(),
json(symInfo, attr.getValue()));
}
});
j.attributeEnd();
}
});
}
});
Expand Down Expand Up @@ -215,7 +243,7 @@ std::string ESIBuildManifestPass::json() {

j.attributeArray("types", [&]() {
for (auto type : types) {
j.value(json(mod, type));
j.value(json(mod, type, /*useTable=*/false));
}
});
j.objectEnd();
Expand Down Expand Up @@ -245,6 +273,7 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) {
// This is far from complete. Build out as necessary.
TypeSwitch<Attribute>(attr)
.Case([&](TypeAttr a) { addType(a.getValue()); })
.Case([&](IntegerAttr a) { addType(a.getType()); })
.Case([&](FlatSymbolRefAttr a) { symbols.insert(a); })
.Case([&](hw::InnerRefAttr a) { symbols.insert(a.getModuleRef()); })
.Case([&](ArrayAttr a) {
Expand All @@ -259,18 +288,28 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) {

/// Get a JSON representation of a type.
// NOLINTNEXTLINE(misc-no-recursion)
llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {
llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type,
bool useTable) {
using llvm::json::Array;
using llvm::json::Object;
using llvm::json::Value;

if (useTable && typeLookup.contains(type)) {
// If the type is in the type table, it'll be present in the types
// section. Just give the circt type name, which is guaranteed to
// uniquely identify the type.
std::string typeName;
llvm::raw_string_ostream(typeName) << type;
return typeName;
}

std::string m;
Object o =
// This is not complete. Build out as necessary.
TypeSwitch<Type, Object>(type)
.Case([&](ChannelType t) {
m = "channel";
return Object({{"inner", json(errorOp, t.getInner())}});
return Object({{"inner", json(errorOp, t.getInner(), useTable)}});
})
.Case([&](ChannelBundleType t) {
m = "bundle";
Expand All @@ -279,7 +318,7 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {
channels.push_back(Object(
{{"name", field.name.getValue()},
{"direction", stringifyChannelDirection(field.direction)},
{"type", json(errorOp, field.type)}}));
{"type", json(errorOp, field.type, useTable)}}));
return Object({{"channels", Value(std::move(channels))}});
})
.Case([&](AnyType t) {
Expand All @@ -288,25 +327,29 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {
})
.Case([&](ListType t) {
m = "list";
return Object({{"element", json(errorOp, t.getElementType())}});
return Object(
{{"element", json(errorOp, t.getElementType(), useTable)}});
})
.Case([&](hw::ArrayType t) {
m = "array";
return Object({{"size", t.getNumElements()},
{"element", json(errorOp, t.getElementType())}});
return Object(
{{"size", t.getNumElements()},
{"element", json(errorOp, t.getElementType(), useTable)}});
})
.Case([&](hw::StructType t) {
m = "struct";
Array fields;
for (auto field : t.getElements())
fields.push_back(Object({{"name", field.name.getValue()},
{"type", json(errorOp, field.type)}}));
fields.push_back(
Object({{"name", field.name.getValue()},
{"type", json(errorOp, field.type, useTable)}}));
return Object({{"fields", Value(std::move(fields))}});
})
.Case([&](hw::TypeAliasType t) {
m = "alias";
return Object({{"name", t.getTypeDecl(symCache).getPreferredName()},
{"inner", json(errorOp, t.getInnerType())}});
return Object(
{{"name", t.getTypeDecl(symCache).getPreferredName()},
{"inner", json(errorOp, t.getInnerType(), useTable)}});
})
.Case([&](IntegerType t) {
m = "int";
Expand All @@ -322,9 +365,9 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {
});

// Common metadata.
std::string circtName;
llvm::raw_string_ostream(circtName) << type;
o["circt_name"] = circtName;
std::string typeID;
llvm::raw_string_ostream(typeID) << type;
o["id"] = typeID;

int64_t width = hw::getBitWidth(type);
if (auto chanType = dyn_cast<ChannelType>(type))
Expand All @@ -340,59 +383,58 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {

// Serialize an attribute to a JSON value.
// NOLINTNEXTLINE(misc-no-recursion)
llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp,
Attribute attr) {
llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Attribute attr,
bool elideType) {

// This is far from complete. Build out as necessary.
using llvm::json::Object;
using llvm::json::Value;
return TypeSwitch<Attribute, Value>(attr)
.Case([&](StringAttr a) { return a.getValue(); })
.Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); })
.Case([&](TypeAttr a) {
Type t = a.getValue();

llvm::json::Object typeMD;
if (typeLookup.contains(t)) {
// If the type is in the type table, it'll be present in the types
// section. Just give the circt type name, which is guaranteed to
// uniquely identify the type.
std::string buff;
llvm::raw_string_ostream(buff) << a;
typeMD["circt_name"] = buff;
return typeMD;
}
Value value =
TypeSwitch<Attribute, Value>(attr)
.Case([&](StringAttr a) { return a.getValue(); })
.Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); })
.Case([&](TypeAttr a) { return json(errorOp, a.getValue()); })
.Case([&](ArrayAttr a) {
return llvm::json::Array(llvm::map_range(
a, [&](Attribute a) { return json(errorOp, a); }));
})
.Case([&](DictionaryAttr a) {
llvm::json::Object dict;
for (const auto &entry : a.getValue())
dict[entry.getName().getValue()] =
json(errorOp, entry.getValue());
return dict;
})
.Case([&](hw::InnerRefAttr ref) {
llvm::json::Object dict;
dict["outer_sym"] = ref.getModule().getValue();
dict["inner"] = ref.getName().getValue();
return dict;
})
.Case([&](AppIDAttr appid) {
llvm::json::Object dict;
dict["name"] = appid.getName().getValue();
auto idx = appid.getIndex();
if (idx)
dict["index"] = *idx;
return dict;
})
.Default([&](Attribute a) {
std::string value;
llvm::raw_string_ostream(value) << a;
return value;
});

typeMD["type"] = json(errorOp, t);
return typeMD;
})
.Case([&](ArrayAttr a) {
return llvm::json::Array(
llvm::map_range(a, [&](Attribute a) { return json(errorOp, a); }));
})
.Case([&](DictionaryAttr a) {
llvm::json::Object dict;
for (const auto &entry : a.getValue())
dict[entry.getName().getValue()] = json(errorOp, entry.getValue());
return dict;
})
.Case([&](hw::InnerRefAttr ref) {
llvm::json::Object dict;
dict["outer_sym"] = ref.getModule().getValue();
dict["inner"] = ref.getName().getValue();
return dict;
})
.Case([&](AppIDAttr appid) {
llvm::json::Object dict;
dict["name"] = appid.getName().getValue();
auto idx = appid.getIndex();
if (idx)
dict["index"] = *idx;
return dict;
})
.Default([&](Attribute a) {
std::string buff;
llvm::raw_string_ostream(buff) << a;
return buff;
});
// Don't print the type if it's None or we're eliding it.
auto typedAttr = llvm::dyn_cast<TypedAttr>(attr);
if (elideType || !typedAttr || isa<NoneType>(typedAttr.getType()))
return value;

// Otherwise, return an object with the value and type.
Object dict;
dict["value"] = value;
dict["type"] = json(errorOp, typedAttr.getType());
return dict;
}

std::unique_ptr<OperationPass<ModuleOp>>
Expand Down
Loading
Loading