[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:
John Demme 2023-10-17 13:27:45 -07:00 committed by GitHub
parent 285980c15d
commit 77ab38628c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 372 additions and 0 deletions

View File

@ -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

View File

@ -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()";

View File

@ -23,6 +23,7 @@ set(srcs
Passes/ESILowerToHW.cpp
Passes/ESILowerTypes.cpp
Passes/ESICleanMetadata.cpp
Passes/ESIBuildManifest.cpp
APIUtilities.cpp

View File

@ -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>();
}

View File

@ -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: }