mirror of https://github.com/llvm/circt.git
[ESI] Start of system manifest: types (#6290)
An ESI system manifest is intended to describe the parts of a particular accelerator which may be relevant to software. It will eventually replace the services metadata json. This first PR only outputs types on cosim ports.
This commit is contained in:
parent
285980c15d
commit
77ab38628c
|
@ -31,6 +31,7 @@ std::unique_ptr<OperationPass<ModuleOp>> createESItoHWPass();
|
|||
std::unique_ptr<OperationPass<ModuleOp>> createESIConnectServicesPass();
|
||||
std::unique_ptr<OperationPass<ModuleOp>> createESIAddCPPAPIPass();
|
||||
std::unique_ptr<OperationPass<ModuleOp>> createESICleanMetadataPass();
|
||||
std::unique_ptr<OperationPass<ModuleOp>> createESIBuildManifestPass();
|
||||
|
||||
/// Generate the code for registering passes.
|
||||
#define GEN_PASS_REGISTRATION
|
||||
|
|
|
@ -51,6 +51,16 @@ def ESIEmitCollateral: Pass<"esi-emit-collateral", "mlir::ModuleOp"> {
|
|||
];
|
||||
}
|
||||
|
||||
def ESIBuildManifest : Pass<"esi-build-manifest", "mlir::ModuleOp"> {
|
||||
let summary = "Build a manifest of an ESI system";
|
||||
let constructor = "circt::esi::createESIBuildManifestPass()";
|
||||
let dependentDialects = ["circt::hw::HWDialect", "circt::sv::SVDialect"];
|
||||
let options = [
|
||||
Option<"toFile", "to-file", "std::string",
|
||||
"", "Write the manifest JSON directly to this file">
|
||||
];
|
||||
}
|
||||
|
||||
def ESICleanMetadata : Pass<"esi-clean-metadata", "mlir::ModuleOp"> {
|
||||
let summary = "Clean up ESI service metadata";
|
||||
let constructor = "circt::esi::createESICleanMetadataPass()";
|
||||
|
|
|
@ -23,6 +23,7 @@ set(srcs
|
|||
Passes/ESILowerToHW.cpp
|
||||
Passes/ESILowerTypes.cpp
|
||||
Passes/ESICleanMetadata.cpp
|
||||
Passes/ESIBuildManifest.cpp
|
||||
|
||||
APIUtilities.cpp
|
||||
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
//===- ESIBuildManifest.cpp - Build ESI system manifest ---------*- C++ -*-===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "../PassDetails.h"
|
||||
|
||||
#include "circt/Dialect/ESI/APIUtilities.h"
|
||||
#include "circt/Dialect/ESI/ESIOps.h"
|
||||
#include "circt/Dialect/ESI/ESIPasses.h"
|
||||
|
||||
#include "circt/Dialect/HW/HWSymCache.h"
|
||||
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/ADT/TypeSwitch.h"
|
||||
#include "llvm/Support/Compression.h"
|
||||
#include "llvm/Support/JSON.h"
|
||||
|
||||
using namespace circt;
|
||||
using namespace esi;
|
||||
|
||||
namespace {
|
||||
struct ESIBuildManifestPass
|
||||
: public ESIBuildManifestBase<ESIBuildManifestPass> {
|
||||
void runOnOperation() override;
|
||||
|
||||
private:
|
||||
/// Get the types of an operations, but only if the operation is relevant.
|
||||
void scrapeTypes(Operation *);
|
||||
|
||||
/// Get a JSON representation of a type.
|
||||
llvm::json::Value json(Type);
|
||||
/// Get a JSON representation of the manifest.
|
||||
std::string json();
|
||||
|
||||
// Type table.
|
||||
void addType(Type type) {
|
||||
if (typeLookup.count(type))
|
||||
return;
|
||||
typeLookup[type] = types.size();
|
||||
types.push_back(type);
|
||||
}
|
||||
SmallVector<Type, 8> types;
|
||||
DenseMap<Type, size_t> typeLookup;
|
||||
|
||||
hw::HWSymbolCache symCache;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
void ESIBuildManifestPass::runOnOperation() {
|
||||
MLIRContext *ctxt = &getContext();
|
||||
Operation *mod = getOperation();
|
||||
symCache.addDefinitions(mod);
|
||||
symCache.freeze();
|
||||
|
||||
// Gather the relevant types.
|
||||
mod->walk([&](Operation *op) { scrapeTypes(op); });
|
||||
|
||||
// JSONify the manifest.
|
||||
std::string jsonManifest = json();
|
||||
|
||||
// Append a verbatim with the manifest to the end of the module.
|
||||
OpBuilder b = OpBuilder::atBlockEnd(&mod->getRegion(0).getBlocks().front());
|
||||
auto verbatim = b.create<sv::VerbatimOp>(b.getUnknownLoc(),
|
||||
StringAttr::get(ctxt, jsonManifest));
|
||||
auto outputFileAttr =
|
||||
hw::OutputFileAttr::getFromFilename(ctxt, "esi_system_manifest.json");
|
||||
verbatim->setAttr("output_file", outputFileAttr);
|
||||
|
||||
// If zlib is available, compress the manifest and append it to the module.
|
||||
SmallVector<uint8_t, 10 * 1024> compressedManifest;
|
||||
if (llvm::compression::zlib::isAvailable()) {
|
||||
// Compress the manifest.
|
||||
llvm::compression::zlib::compress(
|
||||
ArrayRef((uint8_t *)jsonManifest.data(), jsonManifest.length()),
|
||||
compressedManifest, llvm::compression::zlib::BestSizeCompression);
|
||||
|
||||
// Append a verbatim with the compressed manifest to the end of the module.
|
||||
auto compressedVerbatim = b.create<sv::VerbatimOp>(
|
||||
b.getUnknownLoc(),
|
||||
StringAttr::get(ctxt, StringRef((char *)compressedManifest.data(),
|
||||
compressedManifest.size())));
|
||||
auto compressedOutputFileAttr = hw::OutputFileAttr::getFromFilename(
|
||||
ctxt, "esi_system_manifest.json.zlib");
|
||||
compressedVerbatim->setAttr("output_file", compressedOutputFileAttr);
|
||||
} else {
|
||||
mod->emitWarning() << "zlib not available, skipping compressed manifest";
|
||||
}
|
||||
|
||||
// If directed, write the manifest to a file. Mostly for debugging.
|
||||
if (!toFile.empty()) {
|
||||
std::error_code ec;
|
||||
llvm::raw_fd_ostream os(toFile, ec);
|
||||
if (ec) {
|
||||
mod->emitError() << "Failed to open file for writing: " << ec.message();
|
||||
signalPassFailure();
|
||||
} else {
|
||||
os << jsonManifest;
|
||||
}
|
||||
|
||||
// If the compressed manifest is available, output it also.
|
||||
if (!compressedManifest.empty()) {
|
||||
llvm::raw_fd_ostream bos(toFile + ".zlib", ec);
|
||||
if (ec) {
|
||||
mod->emitError() << "Failed to open compressed file for writing: "
|
||||
<< ec.message();
|
||||
signalPassFailure();
|
||||
} else {
|
||||
bos.write((char *)compressedManifest.data(), compressedManifest.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ESIBuildManifestPass::json() {
|
||||
std::string jsonStrBuffer;
|
||||
llvm::raw_string_ostream os(jsonStrBuffer);
|
||||
llvm::json::OStream j(os, 2);
|
||||
|
||||
j.objectBegin();
|
||||
j.attribute("api_version", esiApiVersion);
|
||||
j.attributeArray("types", [&]() {
|
||||
for (auto type : types) {
|
||||
j.value(json(type));
|
||||
}
|
||||
});
|
||||
j.objectEnd();
|
||||
|
||||
return jsonStrBuffer;
|
||||
}
|
||||
|
||||
void ESIBuildManifestPass::scrapeTypes(Operation *op) {
|
||||
TypeSwitch<Operation *>(op).Case([&](CosimEndpointOp cosim) {
|
||||
addType(cosim.getSend().getType());
|
||||
addType(cosim.getRecv().getType());
|
||||
});
|
||||
}
|
||||
|
||||
/// Get a JSON representation of a type.
|
||||
// NOLINTNEXTLINE(misc-no-recursion)
|
||||
llvm::json::Value ESIBuildManifestPass::json(Type type) {
|
||||
using llvm::json::Array;
|
||||
using llvm::json::Object;
|
||||
using llvm::json::Value;
|
||||
|
||||
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(t.getInner())}});
|
||||
})
|
||||
.Case([&](ChannelBundleType t) {
|
||||
m = "bundle";
|
||||
Array fields;
|
||||
for (auto field : t.getChannels())
|
||||
fields.push_back(Object(
|
||||
{{"name", field.name.getValue()},
|
||||
{"direction", stringifyChannelDirection(field.direction)},
|
||||
{"type", json(field.type)}}));
|
||||
return Object({{"fields", Value(std::move(fields))}});
|
||||
})
|
||||
.Case([&](AnyType t) {
|
||||
m = "any";
|
||||
return Object();
|
||||
})
|
||||
.Case([&](ListType t) {
|
||||
m = "list";
|
||||
return Object({{"element", json(t.getElementType())}});
|
||||
})
|
||||
.Case([&](hw::ArrayType t) {
|
||||
m = "array";
|
||||
return Object({{"size", t.getNumElements()},
|
||||
{"element", json(t.getElementType())}});
|
||||
})
|
||||
.Case([&](hw::StructType t) {
|
||||
m = "struct";
|
||||
Array fields;
|
||||
for (auto field : t.getElements())
|
||||
fields.push_back(Object({{"name", field.name.getValue()},
|
||||
{"type", json(field.type)}}));
|
||||
return Object({{"fields", Value(std::move(fields))}});
|
||||
})
|
||||
.Case([&](hw::TypeAliasType t) {
|
||||
m = "alias";
|
||||
return Object({{"name", t.getTypeDecl(symCache).getPreferredName()},
|
||||
{"inner", json(t.getInnerType())}});
|
||||
})
|
||||
.Case([&](IntegerType t) {
|
||||
m = "int";
|
||||
StringRef signedness =
|
||||
t.isSigned() ? "signed"
|
||||
: (t.isUnsigned() ? "unsigned" : "signless");
|
||||
return Object({{"signedness", signedness}});
|
||||
})
|
||||
.Default([&](Type t) {
|
||||
getOperation()->emitWarning()
|
||||
<< "ESI system manifest: unknown type: " << t;
|
||||
return Object();
|
||||
});
|
||||
|
||||
// Common metadata.
|
||||
std::string circtName;
|
||||
llvm::raw_string_ostream(circtName) << type;
|
||||
o["circt_name"] = circtName;
|
||||
int64_t width = hw::getBitWidth(type);
|
||||
if (width >= 0)
|
||||
o["hw_bitwidth"] = width;
|
||||
o["dialect"] = type.getDialect().getNamespace();
|
||||
if (m.length())
|
||||
o["mnemonic"] = m;
|
||||
return o;
|
||||
}
|
||||
std::unique_ptr<OperationPass<ModuleOp>>
|
||||
circt::esi::createESIBuildManifestPass() {
|
||||
return std::make_unique<ESIBuildManifestPass>();
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
// RUN: circt-opt %s --esi-build-manifest=to-file=%t1.json
|
||||
// RUN: FileCheck --input-file=%t1.json %s
|
||||
|
||||
hw.type_scope @__hw_typedecls {
|
||||
hw.typedecl @foo, "Foo" : i1
|
||||
}
|
||||
!alias = !hw.typealias<@__hw_typedecls::@foo, i1>
|
||||
|
||||
hw.module @top(in %clk: !seq.clock, in %rst: i1) {
|
||||
%0 = esi.null : !esi.channel<si14>
|
||||
esi.cosim %clk, %rst, %0, "t2" : !esi.channel<si14> -> !esi.channel<i32>
|
||||
|
||||
%1 = esi.null : !esi.channel<!esi.list<ui14>>
|
||||
esi.cosim %clk, %rst, %1, "t1" : !esi.channel<!esi.list<ui14>> -> !esi.channel<!hw.array<3xi5>>
|
||||
|
||||
%2 = esi.null : !esi.channel<!esi.any>
|
||||
esi.cosim %clk, %rst, %2, "t2" : !esi.channel<!esi.any> -> !esi.channel<!hw.struct<"foo": i5>>
|
||||
|
||||
%3 = esi.null : !esi.channel<!alias>
|
||||
esi.cosim %clk, %rst, %3, "t3" : !esi.channel<!alias> -> !esi.channel<i32>
|
||||
}
|
||||
|
||||
// CHECK: {
|
||||
// CHECK-NEXT: "api_version": 1,
|
||||
// CHECK-LABEL: "types": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<si14>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "si14",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 14,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "signed"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<i32>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "i32",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 32,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "signless"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<!esi.list<ui14>>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "!esi.list<ui14>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "element": {
|
||||
// CHECK-NEXT: "circt_name": "ui14",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 14,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "unsigned"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "list"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<!hw.array<3xi5>>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "!hw.array<3xi5>",
|
||||
// CHECK-NEXT: "dialect": "hw",
|
||||
// CHECK-NEXT: "element": {
|
||||
// CHECK-NEXT: "circt_name": "i5",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 5,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "signless"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "hw_bitwidth": 15,
|
||||
// CHECK-NEXT: "mnemonic": "array",
|
||||
// CHECK-NEXT: "size": 3
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<!esi.any>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "!esi.any",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "mnemonic": "any"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<!hw.struct<foo: i5>>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "!hw.struct<foo: i5>",
|
||||
// CHECK-NEXT: "dialect": "hw",
|
||||
// CHECK-NEXT: "fields": [
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "name": "foo",
|
||||
// CHECK-NEXT: "type": {
|
||||
// CHECK-NEXT: "circt_name": "i5",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 5,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "signless"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ],
|
||||
// CHECK-NEXT: "hw_bitwidth": 5,
|
||||
// CHECK-NEXT: "mnemonic": "struct"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: {
|
||||
// CHECK-NEXT: "circt_name": "!esi.channel<!hw.typealias<@__hw_typedecls::@foo, i1>>",
|
||||
// CHECK-NEXT: "dialect": "esi",
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "!hw.typealias<@__hw_typedecls::@foo, i1>",
|
||||
// CHECK-NEXT: "dialect": "hw",
|
||||
// CHECK-NEXT: "hw_bitwidth": 1,
|
||||
// CHECK-NEXT: "inner": {
|
||||
// CHECK-NEXT: "circt_name": "i1",
|
||||
// CHECK-NEXT: "dialect": "builtin",
|
||||
// CHECK-NEXT: "hw_bitwidth": 1,
|
||||
// CHECK-NEXT: "mnemonic": "int",
|
||||
// CHECK-NEXT: "signedness": "signless"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "alias",
|
||||
// CHECK-NEXT: "name": "Foo"
|
||||
// CHECK-NEXT: },
|
||||
// CHECK-NEXT: "mnemonic": "channel"
|
||||
// CHECK-NEXT: }
|
||||
// CHECK-NEXT: ]
|
||||
// CHECK-NEXT: }
|
Loading…
Reference in New Issue