diff --git a/CMakeLists.txt b/CMakeLists.txt index dce4faa0e85a..c48e4347c2c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,8 @@ else() message(STATUS "Found Cap'nProto at ${CapnProto_DIR}.") set(CMAKE_INSTALL_RPATH ${capnp_LIBDIR}) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + + option(ESI_COSIM "Enable ESI Cosimulation" ON) endif() endif() diff --git a/include/circt-c/Dialect/ESI.h b/include/circt-c/Dialect/ESI.h index 00f0f692b0a8..ec257b4ef312 100644 --- a/include/circt-c/Dialect/ESI.h +++ b/include/circt-c/Dialect/ESI.h @@ -19,10 +19,6 @@ extern "C" { MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(ESI, esi); MLIR_CAPI_EXPORTED void registerESIPasses(void); -MLIR_CAPI_EXPORTED void registerESITranslations(void); - -MLIR_CAPI_EXPORTED MlirLogicalResult -circtESIExportCosimSchema(MlirModule, MlirStringCallback, void *userData); MLIR_CAPI_EXPORTED bool circtESITypeIsAChannelType(MlirType type); MLIR_CAPI_EXPORTED MlirType circtESIChannelTypeGet(MlirType inner, diff --git a/include/circt/Dialect/ESI/APIUtilities.h b/include/circt/Dialect/ESI/APIUtilities.h index 2418337ee701..2b60caa1728a 100644 --- a/include/circt/Dialect/ESI/APIUtilities.h +++ b/include/circt/Dialect/ESI/APIUtilities.h @@ -47,6 +47,8 @@ class ESIAPIType { // API-safe name for this type which should work with most languages. StringRef name() const; + uint64_t size() const; + // Capnproto-safe type id for this type. uint64_t typeID() const; diff --git a/include/circt/Dialect/ESI/ESIChannels.td b/include/circt/Dialect/ESI/ESIChannels.td index 710f71d5832b..0edeed770c09 100644 --- a/include/circt/Dialect/ESI/ESIChannels.td +++ b/include/circt/Dialect/ESI/ESIChannels.td @@ -497,30 +497,6 @@ def RtlBitArrayType : Type()" " && $_self.cast<::circt::hw::ArrayType>().getElementType() ==" " ::mlir::IntegerType::get($_self.getContext(), 1)">, "an HW bit array">; -def CapnpDecodeOp : ESI_Physical_Op<"decode.capnp", [Pure]> { - let summary = "Translate bits in Cap'nProto messages to HW typed data"; - - let arguments = (ins ClockType:$clk, I1:$valid, RtlBitArrayType:$capnpBits); - let results = (outs AnyType:$decodedData); - - let assemblyFormat = [{ - $clk $valid $capnpBits attr-dict `:` qualified(type($capnpBits)) `->` - qualified(type($decodedData)) - }]; -} - -def CapnpEncodeOp : ESI_Physical_Op<"encode.capnp", [Pure]> { - let summary = "Translate HW typed data to Cap'nProto"; - - let arguments = (ins ClockType:$clk, I1:$valid, AnyType:$dataToEncode); - let results = (outs RtlBitArrayType:$capnpBits); - - let assemblyFormat = [{ - $clk $valid $dataToEncode attr-dict `:` qualified(type($dataToEncode)) - `->` qualified(type($capnpBits)) - }]; -} - def NullSourceOp : ESI_Physical_Op<"null", [Pure]> { let summary = "An op which never produces messages."; diff --git a/include/circt/Dialect/ESI/ESIDialect.h b/include/circt/Dialect/ESI/ESIDialect.h index 0dcd46303646..9897b63edbae 100644 --- a/include/circt/Dialect/ESI/ESIDialect.h +++ b/include/circt/Dialect/ESI/ESIDialect.h @@ -28,8 +28,6 @@ namespace circt { namespace esi { void registerESIPasses(); -void registerESITranslations(); -LogicalResult exportCosimSchema(ModuleOp module, llvm::raw_ostream &os); /// Name of dialect attribute which governs whether or not to bundle (i.e. use /// SystemVerilog interfaces) channel signal wires on external modules. diff --git a/include/circt/Dialect/ESI/ESIPasses.h b/include/circt/Dialect/ESI/ESIPasses.h index 39e04398092d..077487ce1adf 100644 --- a/include/circt/Dialect/ESI/ESIPasses.h +++ b/include/circt/Dialect/ESI/ESIPasses.h @@ -22,14 +22,12 @@ namespace circt { namespace esi { -std::unique_ptr> createESIEmitCollateralPass(); std::unique_ptr> createESIPhysicalLoweringPass(); std::unique_ptr> createESIBundleLoweringPass(); std::unique_ptr> createESIPortLoweringPass(); std::unique_ptr> createESITypeLoweringPass(); std::unique_ptr> createESItoHWPass(); std::unique_ptr> createESIConnectServicesPass(); -std::unique_ptr> createESIAddCPPAPIPass(); std::unique_ptr> createESICleanMetadataPass(); std::unique_ptr> createESIBuildManifestPass(); std::unique_ptr> createESIAppIDHierPass(); diff --git a/include/circt/Dialect/ESI/ESIPasses.td b/include/circt/Dialect/ESI/ESIPasses.td index ad7119b2aa60..2f29f9a648b0 100644 --- a/include/circt/Dialect/ESI/ESIPasses.td +++ b/include/circt/Dialect/ESI/ESIPasses.td @@ -26,20 +26,6 @@ def ESIConnectServices : Pass<"esi-connect-services", "mlir::ModuleOp"> { "circt::comb::CombDialect"]; } - -def ESIEmitCollateral: Pass<"esi-emit-collateral", "mlir::ModuleOp"> { - let summary = "Emit all the neccessary collateral"; - let constructor = "circt::esi::createESIEmitCollateralPass()"; - let dependentDialects = ["circt::sv::SVDialect"]; - let options = [ - Option<"schemaFile", "schema-file", "std::string", - "", "File to output capnp schema into">, - ListOption<"tops", "tops", "std::string", - "List of top modules to export Tcl for", - "llvm::cl::ZeroOrMore,"> - ]; -} - def ESIAppIDHier : Pass<"esi-appid-hier", "mlir::ModuleOp"> { let summary = "Build an AppID based hierarchy rooted at top module 'top'"; let constructor = "circt::esi::createESIAppIDHierPass()"; diff --git a/include/circt/Dialect/ESI/cosim/CMakeLists.txt b/include/circt/Dialect/ESI/cosim/CMakeLists.txt index aaade0017b09..f5787e5fc4a2 100644 --- a/include/circt/Dialect/ESI/cosim/CMakeLists.txt +++ b/include/circt/Dialect/ESI/cosim/CMakeLists.txt @@ -4,13 +4,8 @@ ## ##===----------------------------------------------------------------------===// -if(CapnProto_FOUND) - option(ESI_COSIM "Enable ESI Cosimulation" ON) - message("-- Enabling ESI cosim") - - file(READ CosimDpi.capnp EsiCosimSchema) - set(COSIM_SCHEMA_HDR ${CIRCT_BINARY_DIR}/include/circt/Dialect/ESI/CosimSchema.h) - configure_file(CosimSchema.h.in ${COSIM_SCHEMA_HDR}) +if (ESI_COSIM) + message("-- ESI cosim enabled") if (MSVC) string(REPLACE "/EHs-c-" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) diff --git a/include/circt/Dialect/ESI/cosim/CosimSchema.h.in b/include/circt/Dialect/ESI/cosim/CosimSchema.h.in deleted file mode 100644 index 34ba8c40e06c..000000000000 --- a/include/circt/Dialect/ESI/cosim/CosimSchema.h.in +++ /dev/null @@ -1,28 +0,0 @@ -//===- CosimSchema.h.in - template for the ESI cosim schema ----*- 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 -// -//===----------------------------------------------------------------------===// -// -// CMake configures this file to fill the Cosim Capnp RPC schema. -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_ESI_COSIM_COSIMSCHEMA_H -#define CIRCT_DIALECT_ESI_COSIM_COSIMSCHEMA_H - -namespace circt { -namespace esi { -namespace cosim { - -constexpr char CosimSchema[] = R"""( -@EsiCosimSchema@ -)"""; - -} // namespace cosim -} // namespace esi -} // namespace circt - -#endif diff --git a/include/circt/InitAllTranslations.h b/include/circt/InitAllTranslations.h index 468851347656..b622ed6132e5 100644 --- a/include/circt/InitAllTranslations.h +++ b/include/circt/InitAllTranslations.h @@ -29,7 +29,6 @@ namespace circt { // automatically. inline void registerAllTranslations() { static bool initOnce = []() { - esi::registerESITranslations(); calyx::registerToCalyxTranslation(); firrtl::registerFromFIRFileTranslation(); firrtl::registerToFIRFileTranslation(); diff --git a/integration_test/Dialect/ESI/cosim/basic.mlir b/integration_test/Dialect/ESI/cosim/basic.mlir deleted file mode 100644 index 0a42f5723cca..000000000000 --- a/integration_test/Dialect/ESI/cosim/basic.mlir +++ /dev/null @@ -1,48 +0,0 @@ -// REQUIRES: esi-cosim -// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw --lower-seq-to-sv | circt-opt --export-verilog -o %t3.mlir > %t1.sv -// RUN: circt-translate %s -export-esi-capnp -verify-diagnostics > %t2.capnp -// RUN: esi-cosim-runner.py --schema %t2.capnp %s %t1.sv %S/../supplements/integers.sv -// PY: import basic -// PY: rpc = basic.BasicSystemTester(rpcschemapath, simhostport) -// PY: print(rpc.list()) -// PY: rpc.testIntAcc(25) -// PY: rpc.testVectorSum(25) -// PY: rpc.testCrypto(25) - -hw.module.extern @IntAccNoBP(in %clk: !seq.clock, in %rst: i1, in %ints: !esi.channel, out totalOut: !esi.channel) attributes {esi.bundle} -hw.module.extern @IntArrSum(in %clk: !seq.clock, in %rst: i1, in %arr: !esi.channel>, out totalOut: !esi.channel>) attributes {esi.bundle} - -hw.module @ints(in %clk: !seq.clock, in %rst: i1) { - %intsIn = esi.cosim %clk, %rst, %intsTotalBuffered, "TestEP" : !esi.channel -> !esi.channel - %intsInBuffered = esi.buffer %clk, %rst, %intsIn {stages=2, name="intChan"} : i32 - %intsTotal = hw.instance "acc" @IntAccNoBP(clk: %clk: !seq.clock, rst: %rst: i1, ints: %intsInBuffered: !esi.channel) -> (totalOut: !esi.channel) - %intsTotalBuffered = esi.buffer %clk, %rst, %intsTotal {stages=2, name="totalChan"} : i32 -} - -hw.module @array(in %clk: !seq.clock, in %rst: i1) { - %arrIn = esi.cosim %clk, %rst, %arrTotalBuffered, "TestEP" : !esi.channel> -> !esi.channel> - %arrInBuffered = esi.buffer %clk, %rst, %arrIn {stages=2, name="arrChan"} : !hw.array<4 x si13> - %arrTotal = hw.instance "acc" @IntArrSum(clk: %clk: !seq.clock, rst: %rst: i1, arr: %arrInBuffered: !esi.channel>) -> (totalOut: !esi.channel>) - %arrTotalBuffered = esi.buffer %clk, %rst, %arrTotal {stages=2, name="totalChan"} : !hw.array<2 x ui24> -} - -!DataPkt = !hw.struct> -!pktChan = !esi.channel -!Config = !hw.struct> -!cfgChan = !esi.channel - -hw.module.extern @Encryptor(in %clk: !seq.clock, in %rst: i1, in %in: !pktChan, in %cfg: !cfgChan, out x: !pktChan) attributes {esi.bundle} - -hw.module @structs(in %clk:!seq.clock, in %rst:i1) { - %compressedData = hw.instance "otpCryptor" @Encryptor(clk: %clk: !seq.clock, rst: %rst: i1, in: %inputData: !pktChan, cfg: %cfg: !cfgChan) -> (x: !pktChan) - %inputData = esi.cosim %clk, %rst, %compressedData, "CryptoData" : !pktChan -> !pktChan - %c0 = hw.constant 0 : i1 - %null, %nullReady = esi.wrap.vr %c0, %c0 : i1 - %cfg = esi.cosim %clk, %rst, %null, "CryptoConfig" : !esi.channel -> !cfgChan -} - -hw.module @top(in %clk: !seq.clock, in %rst: i1) { - hw.instance "ints" @ints (clk: %clk: !seq.clock, rst: %rst: i1) -> () - hw.instance "array" @array(clk: %clk: !seq.clock, rst: %rst: i1) -> () - hw.instance "structs" @structs(clk: %clk: !seq.clock, rst: %rst: i1) -> () -} diff --git a/integration_test/Dialect/ESI/cosim/basic.py b/integration_test/Dialect/ESI/cosim/basic.py deleted file mode 100755 index 226b5b59c074..000000000000 --- a/integration_test/Dialect/ESI/cosim/basic.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/python3 - -import random -import esi_cosim - - -class BasicSystemTester(esi_cosim.CosimBase): - """Provides methods to test the 'basic' simulation.""" - - def testIntAcc(self, num_msgs): - ep = self.openEP("top.ints.TestEP", - sendType=self.schema.I32, - recvType=self.schema.I32) - sum = 0 - for _ in range(num_msgs): - i = random.randint(0, 77) - sum += i - print(f"Sending {i}") - ep.send(self.schema.I32.new_message(i=i)) - result = self.readMsg(ep, self.schema.I32) - print(f"Got {result}") - assert (result.i == sum) - - def testVectorSum(self, num_msgs): - ep = self.openEP("top.array.TestEP", - sendType=self.schema.ArrayOf2xUi24, - recvType=self.schema.ArrayOf4xSi13) - for _ in range(num_msgs): - # Since the result is unsigned, we need to make sure the sum is - # never negative. - arr = [ - random.randint(-468, 777), - random.randint(500, 1250), - random.randint(-468, 777), - random.randint(500, 1250) - ] - print(f"Sending {arr}") - ep.send(self.schema.ArrayOf4xSi13.new_message(l=arr)) - result = self.readMsg(ep, self.schema.ArrayOf2xUi24) - print(f"Got {result}") - assert (result.l[0] == arr[0] + arr[1]) - assert (result.l[1] == arr[2] + arr[3]) - - def testCrypto(self, num_msgs): - ep = self.openEP("top.structs.CryptoData", - sendType=self.schema.Struct15822124641382404136, - recvType=self.schema.Struct15822124641382404136) - cfg = self.openEP("top.structs.CryptoConfig", - sendType=self.schema.I1, - recvType=self.schema.Struct14745270011869700302) - - cfgWritten = False - for _ in range(num_msgs): - blob = [random.randint(0, 255) for x in range(32)] - print(f"Sending data {blob}") - ep.send( - self.schema.Struct15822124641382404136.new_message(encrypted=False, - blob=blob)) - - if not cfgWritten: - # Check that messages queue up properly waiting for the config. - otp = [random.randint(0, 255) for x in range(32)] - cfg.send( - self.schema.Struct14745270011869700302.new_message(encrypt=True, - otp=otp)) - cfgWritten = True - - result = self.readMsg(ep, self.schema.Struct15822124641382404136) - expectedResults = [x ^ y for (x, y) in zip(otp, blob)] - print(f"Got {blob}") - print(f"Exp {expectedResults}") - assert (list(result.blob) == expectedResults) diff --git a/lib/CAPI/Dialect/ESI.cpp b/lib/CAPI/Dialect/ESI.cpp index 0224758e11da..2e59fa3ed421 100644 --- a/lib/CAPI/Dialect/ESI.cpp +++ b/lib/CAPI/Dialect/ESI.cpp @@ -22,13 +22,6 @@ MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(ESI, esi, circt::esi::ESIDialect) void registerESIPasses() { circt::esi::registerESIPasses(); } -MlirLogicalResult circtESIExportCosimSchema(MlirModule module, - MlirStringCallback callback, - void *userData) { - mlir::detail::CallbackOstream stream(callback, userData); - return wrap(circt::esi::exportCosimSchema(unwrap(module), stream)); -} - bool circtESITypeIsAChannelType(MlirType type) { return unwrap(type).isa(); } diff --git a/lib/Dialect/ESI/APIUtilities.cpp b/lib/Dialect/ESI/APIUtilities.cpp index 0b2e029b563e..0e73f58e6b8e 100644 --- a/lib/Dialect/ESI/APIUtilities.cpp +++ b/lib/Dialect/ESI/APIUtilities.cpp @@ -54,6 +54,12 @@ bool ESIAPIType::isSupported() const { return circt::esi::isSupported(type, true); } +uint64_t ESIAPIType::size() const { + if (auto chan = dyn_cast(type)) + return hw::getBitWidth(chan.getInner()); + return hw::getBitWidth(type); +} + ESIAPIType::ESIAPIType(Type typeArg) : type(innerType(typeArg)) { TypeSwitch(type) .Case([this](IntegerType t) { diff --git a/lib/Dialect/ESI/CMakeLists.txt b/lib/Dialect/ESI/CMakeLists.txt index 933dd8d08eda..be7a87a2978e 100644 --- a/lib/Dialect/ESI/CMakeLists.txt +++ b/lib/Dialect/ESI/CMakeLists.txt @@ -14,9 +14,7 @@ set(srcs ESIPasses.cpp ESIServices.cpp ESIStdServices.cpp - ESITranslations.cpp ESITypes.cpp - Passes/ESIEmitCollateral.cpp Passes/ESILowerPhysical.cpp Passes/ESILowerBundles.cpp Passes/ESILowerPorts.cpp @@ -50,12 +48,6 @@ set(ESI_Deps MLIRESIInterfacesIncGen ) -if(CapnProto_FOUND) - option(ESI_CAPNP "Enable ESI Capnp features" ON) - list(APPEND srcs capnp/Schema.cpp) - list(APPEND ESI_LinkLibs CapnProto::capnp CapnProto::capnpc) -endif() - add_circt_dialect_library(CIRCTESI ${srcs} @@ -110,7 +102,7 @@ foreach(SRC ${ESI_RUNTIME_SRCS}) copy_esi_runtime(${SRC}) endforeach() -if (ESI_CAPNP) +if (ESI_COSIM) add_dependencies(esi-collateral EsiCosimCapnp) get_target_property(EsiCosimCapnp_bindir EsiCosimCapnp SOURCE_DIR) set(ESI_COSIM_SCHEMA "${EsiCosimCapnp_bindir}/CosimDpi.capnp") @@ -119,9 +111,6 @@ if (ESI_CAPNP) COMMAND ${CMAKE_COMMAND} -E copy "${ESI_COSIM_SCHEMA}" "${CMAKE_CURRENT_BINARY_DIR}/runtime/CosimDpi.capnp") - - target_compile_definitions(obj.CIRCTESI PRIVATE CAPNP) - target_link_libraries(obj.CIRCTESI CapnProto::capnp CapnProto::capnpc) endif() add_subdirectory(cosim) diff --git a/lib/Dialect/ESI/ESITranslations.cpp b/lib/Dialect/ESI/ESITranslations.cpp deleted file mode 100644 index 6b208c312d71..000000000000 --- a/lib/Dialect/ESI/ESITranslations.cpp +++ /dev/null @@ -1,197 +0,0 @@ -//===- ESITranslations.cpp - ESI translations -------------------*- C++ -*-===// -// -// ESI translations: -// - Cap'nProto schema generation -// -//===----------------------------------------------------------------------===// - -#include "circt/Dialect/ESI/ESIDialect.h" -#include "circt/Dialect/ESI/ESIOps.h" -#include "circt/Dialect/HW/HWDialect.h" -#include "circt/Dialect/SV/SVDialect.h" -#include "circt/Support/LLVM.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/IR/BuiltinDialect.h" -#include "mlir/IR/BuiltinOps.h" -#include "mlir/Tools/mlir-translate/Translation.h" -#include "llvm/Support/Format.h" - -#include - -#ifdef CAPNP -#include "capnp/ESICapnp.h" -#include "circt/Dialect/ESI/CosimSchema.h" -#endif - -using namespace circt; -using namespace circt::esi; - -//===----------------------------------------------------------------------===// -// ESI Cosim Cap'nProto schema generation. -// -// Cosimulation in ESI is done over capnp. This translation walks the IR, finds -// all the `esi.cosim` ops, and creates a schema for all the types. It requires -// CAPNP to be enabled. -//===----------------------------------------------------------------------===// - -#ifdef CAPNP - -namespace { - -struct ErrorCountingHandler : public mlir::ScopedDiagnosticHandler { - ErrorCountingHandler(mlir::MLIRContext *context) - : mlir::ScopedDiagnosticHandler(context) { - setHandler([this](Diagnostic &diag) -> LogicalResult { - if (diag.getSeverity() == mlir::DiagnosticSeverity::Error) - ++errorCount; - return failure(); - }); - } - - size_t errorCount = 0; -}; - -struct ExportCosimSchema { - ExportCosimSchema(ModuleOp module, llvm::raw_ostream &os) - : module(module), os(os), handler(module.getContext()), - unknown(UnknownLoc::get(module.getContext())) {} - - /// Emit the whole schema. - LogicalResult emit(); - - /// Collect the types for which we need to emit a schema. Output some metadata - /// comments. - LogicalResult visitEndpoint(CosimEndpointOp); - -private: - ModuleOp module; - llvm::raw_ostream &os; - ErrorCountingHandler handler; - const Location unknown; - - // All the `esi.cosim` input and output types encountered during the IR walk. - // This is NOT in a deterministic order! - llvm::SmallVector> types; -}; -} // anonymous namespace - -LogicalResult ExportCosimSchema::visitEndpoint(CosimEndpointOp ep) { - auto sendTypeSchema = - std::make_shared(ep.getSend().getType()); - if (!sendTypeSchema->isSupported()) - return ep.emitOpError("Type ") - << ep.getSend().getType() << " not supported."; - types.push_back(sendTypeSchema); - - auto recvTypeSchema = - std::make_shared(ep.getRecv().getType()); - if (!recvTypeSchema->isSupported()) - return ep.emitOpError("Type '") - << ep.getRecv().getType() << "' not supported."; - types.push_back(recvTypeSchema); - - os << "# Endpoint "; - StringAttr epName = ep->getAttrOfType("name"); - if (epName) - os << epName << " endpoint at " << ep.getLoc() << ":\n"; - os << "# Send type: "; - sendTypeSchema->writeMetadata(os); - os << "\n"; - - os << "# Recv type: "; - recvTypeSchema->writeMetadata(os); - os << "\n"; - - return success(); -} - -static void emitCosimSchemaBody(llvm::raw_ostream &os) { - StringRef entireSchemaFile = circt::esi::cosim::CosimSchema; - size_t idLocation = entireSchemaFile.find("@0x"); - size_t newlineAfter = entireSchemaFile.find('\n', idLocation); - - os << "\n\n" - << "#########################################################\n" - << "## Standard RPC interfaces.\n" - << "#########################################################\n"; - os << entireSchemaFile.substr(newlineAfter) << "\n"; -} - -LogicalResult ExportCosimSchema::emit() { - os << "#########################################################\n" - << "## ESI generated schema.\n" - << "#########################################################\n"; - - // Walk and collect the type data. - auto walkResult = module.walk([this](CosimEndpointOp ep) { - if (failed(visitEndpoint(ep))) - return mlir::WalkResult::interrupt(); - return mlir::WalkResult::advance(); - }); - if (walkResult.wasInterrupted()) - return failure(); - os << "#########################################################\n"; - - // We need a sorted list to ensure determinism. - llvm::sort(types.begin(), types.end(), - [](auto &a, auto &b) { return a->typeID() > b->typeID(); }); - - // Compute and emit the capnp file id. - uint64_t fileHash = 2544816649379317016; // Some random number. - for (auto &schema : types) - fileHash = llvm::hashing::detail::hash_16_bytes(fileHash, schema->typeID()); - // Capnp IDs always have a '1' high bit. - fileHash |= 0x8000000000000000; - capnp::emitCapnpID(os, fileHash) << ";\n\n"; - - os << "#########################################################\n" - << "## Types for your design.\n" - << "#########################################################\n\n"; - // Iterate through the various types and emit their schemas. - auto end = std::unique( - types.begin(), types.end(), - [&](const auto &lhs, const auto &rhs) { return *lhs == *rhs; }); - for (auto typeIter = types.begin(); typeIter != end; ++typeIter) { - if (failed((*typeIter)->write(os))) - // If we fail during an emission, dump out early since the output may be - // corrupted. - return failure(); - } - - // Include the RPC schema in each generated file. - emitCosimSchemaBody(os); - - return success(handler.errorCount == 0); -} - -LogicalResult circt::esi::exportCosimSchema(ModuleOp module, - llvm::raw_ostream &os) { - ExportCosimSchema schema(module, os); - return schema.emit(); -} - -#else // Not CAPNP - -LogicalResult circt::esi::exportCosimSchema(ModuleOp module, - llvm::raw_ostream &os) { - return mlir::emitError(UnknownLoc::get(module.getContext()), - "Not compiled with CAPNP support"); -} - -#endif - -//===----------------------------------------------------------------------===// -// Register all ESI translations. -//===----------------------------------------------------------------------===// - -void circt::esi::registerESITranslations() { -#ifdef CAPNP - mlir::TranslateFromMLIRRegistration cosimToCapnp( - "export-esi-capnp", "ESI Cosim Cap'nProto schema generation", - exportCosimSchema, [](mlir::DialectRegistry ®istry) { - registry.insert(); - }); -#endif -} diff --git a/lib/Dialect/ESI/Passes/ESIEmitCollateral.cpp b/lib/Dialect/ESI/Passes/ESIEmitCollateral.cpp deleted file mode 100644 index 8c195c03002b..000000000000 --- a/lib/Dialect/ESI/Passes/ESIEmitCollateral.cpp +++ /dev/null @@ -1,160 +0,0 @@ -//===- ESIEmitCollateral.cpp - Emit ESI collateral pass ---------*- 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 -// -//===----------------------------------------------------------------------===// -// -// Emit ESI collateral pass. Collateral includes the capnp schema and a JSON -// descriptor of the service hierarchy. -// -//===----------------------------------------------------------------------===// - -#include "../PassDetails.h" - -#include "circt/Dialect/ESI/APIUtilities.h" -#include "circt/Dialect/ESI/ESIOps.h" -#include "circt/Dialect/ESI/ESIPasses.h" -#include "circt/Dialect/ESI/ESIServices.h" -#include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/SV/SVOps.h" -#include "circt/Support/LLVM.h" -#include "circt/Support/SymCache.h" - -#include "mlir/Transforms/DialectConversion.h" - -#include "llvm/ADT/StringExtras.h" -#include "llvm/ADT/TypeSwitch.h" -#include "llvm/Support/JSON.h" - -using namespace circt; -using namespace circt::esi; -using namespace circt::hw; - -static llvm::json::Value toJSON(Type type) { - // TODO: This is far from complete. Build out as necessary. - using llvm::json::Array; - using llvm::json::Object; - using llvm::json::Value; - - StringRef dialect = type.getDialect().getNamespace(); - std::string m; - Object o = TypeSwitch(type) - .Case([&](ChannelType t) { - m = "channel"; - return Object({{"inner", toJSON(t.getInner())}}); - }) - .Case([&](AnyType t) { - m = "any"; - return Object(); - }) - .Case([&](StructType t) { - m = "struct"; - Array fields; - for (auto field : t.getElements()) - fields.push_back(Object({{"name", field.name.getValue()}, - {"type", toJSON(field.type)}})); - return Object({{"fields", Value(std::move(fields))}}); - }) - .Default([&](Type t) { - llvm::raw_string_ostream(m) << t; - return Object(); - }); - o["dialect"] = dialect; - if (m.length()) - o["mnemonic"] = m; - return o; -} - -// Serialize an attribute to a JSON value. -static llvm::json::Value toJSON(Attribute attr) { - // TODO: This is far from complete. Build out as necessary. - using llvm::json::Value; - return TypeSwitch(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; - typeMD["type_desc"] = toJSON(t); - - std::string buf; - llvm::raw_string_ostream(buf) << t; - typeMD["mlir_name"] = buf; - - if (auto chanType = t.dyn_cast()) { - Type inner = chanType.getInner(); - typeMD["hw_bitwidth"] = hw::getBitWidth(inner); - ESIAPIType cosimSchema(inner); - typeMD["capnp_type_id"] = cosimSchema.typeID(); - typeMD["capnp_name"] = cosimSchema.name().str(); - } else { - typeMD["hw_bitwidth"] = hw::getBitWidth(t); - } - return typeMD; - }) - .Case([&](ArrayAttr a) { - return llvm::json::Array( - llvm::map_range(a, [](Attribute a) { return toJSON(a); })); - }) - .Case([&](DictionaryAttr a) { - llvm::json::Object dict; - for (const auto &entry : a.getValue()) - dict[entry.getName().getValue()] = toJSON(entry.getValue()); - return dict; - }) - .Case([&](InnerRefAttr ref) { - llvm::json::Object dict; - dict["outer_sym"] = ref.getModule().getValue(); - dict["inner"] = ref.getName().getValue(); - return dict; - }) - .Default([&](Attribute a) { - std::string buff; - llvm::raw_string_ostream(buff) << a; - return buff; - }); -} - -namespace { -/// Run all the physical lowerings. -struct ESIEmitCollateralPass - : public ESIEmitCollateralBase { - void runOnOperation() override; -}; -} // anonymous namespace - -void ESIEmitCollateralPass::runOnOperation() { - ModuleOp mod = getOperation(); - auto *ctxt = &getContext(); - - // Check for cosim endpoints in the design. If the design doesn't have any - // we don't need a schema. - WalkResult cosimWalk = - mod.walk([](CosimEndpointOp _) { return WalkResult::interrupt(); }); - if (!cosimWalk.wasInterrupted()) - return; - - // Generate the schema - std::string schemaStrBuffer; - llvm::raw_string_ostream os(schemaStrBuffer); - if (failed(exportCosimSchema(mod, os))) { - signalPassFailure(); - return; - } - - // And stuff if in a verbatim op with a filename, optionally. - OpBuilder b = OpBuilder::atBlockEnd(mod.getBody()); - auto verbatim = b.create(b.getUnknownLoc(), - StringAttr::get(ctxt, os.str())); - if (!schemaFile.empty()) { - auto outputFileAttr = OutputFileAttr::getFromFilename(ctxt, schemaFile); - verbatim->setAttr("output_file", outputFileAttr); - } -} - -std::unique_ptr> -circt::esi::createESIEmitCollateralPass() { - return std::make_unique(); -} diff --git a/lib/Dialect/ESI/Passes/ESILowerToHW.cpp b/lib/Dialect/ESI/Passes/ESILowerToHW.cpp index cd6d30ddc14a..4e511bbbcc47 100644 --- a/lib/Dialect/ESI/Passes/ESILowerToHW.cpp +++ b/lib/Dialect/ESI/Passes/ESILowerToHW.cpp @@ -13,6 +13,7 @@ #include "../PassDetails.h" #include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/ESI/APIUtilities.h" #include "circt/Dialect/ESI/ESIOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/SV/SVOps.h" @@ -26,10 +27,6 @@ #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/JSON.h" -#ifdef CAPNP -#include "../capnp/ESICapnp.h" -#endif - using namespace circt; using namespace circt::esi; using namespace circt::esi::detail; @@ -325,11 +322,6 @@ struct CosimLowering : public OpConversionPattern { LogicalResult CosimLowering::matchAndRewrite(CosimEndpointOp ep, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const { -#ifndef CAPNP - (void)builder; - return rewriter.notifyMatchFailure( - ep, "Cosim lowering requires the ESI capnp plugin, which was disabled."); -#else auto loc = ep.getLoc(); auto *ctxt = rewriter.getContext(); auto operands = adaptor.getOperands(); @@ -340,10 +332,10 @@ CosimLowering::matchAndRewrite(CosimEndpointOp ep, OpAdaptor adaptor, circt::BackedgeBuilder bb(rewriter, loc); Type ui64Type = IntegerType::get(ctxt, 64, IntegerType::SignednessSemantics::Unsigned); - capnp::CapnpTypeSchema sendTypeSchema(send.getType()); + ESIAPIType sendTypeSchema(send.getType()); if (!sendTypeSchema.isSupported()) return rewriter.notifyMatchFailure(ep, "Send type not supported yet"); - capnp::CapnpTypeSchema recvTypeSchema(ep.getRecv().getType()); + ESIAPIType recvTypeSchema(ep.getRecv().getType()); if (!recvTypeSchema.isSupported()) return rewriter.notifyMatchFailure(ep, "Recv type not supported yet"); @@ -371,9 +363,8 @@ CosimLowering::matchAndRewrite(CosimEndpointOp ep, OpAdaptor adaptor, auto sendReady = bb.get(rewriter.getI1Type()); UnwrapValidReadyOp unwrapSend = rewriter.create(loc, send, sendReady); - auto encodeData = rewriter.create(loc, egestBitArrayType, clk, - unwrapSend.getValid(), - unwrapSend.getRawOutput()); + auto castedSendData = rewriter.create( + loc, egestBitArrayType, unwrapSend.getRawOutput()); // Get information necessary for injest path. auto recvReady = bb.get(rewriter.getI1Type()); @@ -389,7 +380,7 @@ CosimLowering::matchAndRewrite(CosimEndpointOp ep, OpAdaptor adaptor, StringAttr nameAttr = ep->getAttr("name").dyn_cast_or_null(); StringRef name = nameAttr ? nameAttr.getValue() : "CosimEndpointOp"; Value epInstInputs[] = { - clk, rst, recvReady, unwrapSend.getValid(), encodeData.getCapnpBits(), + clk, rst, recvReady, unwrapSend.getValid(), castedSendData.getResult(), }; auto cosimEpModule = rewriter.create( @@ -399,76 +390,18 @@ CosimLowering::matchAndRewrite(CosimEndpointOp ep, OpAdaptor adaptor, // Set up the injest path. Value recvDataFromCosim = cosimEpModule.getResult(1); Value recvValidFromCosim = cosimEpModule.getResult(0); - auto decodeData = - rewriter.create(loc, recvTypeSchema.getType(), clk, - recvValidFromCosim, recvDataFromCosim); + auto castedRecvData = rewriter.create( + loc, recvTypeSchema.getType(), recvDataFromCosim); WrapValidReadyOp wrapRecv = rewriter.create( - loc, decodeData.getDecodedData(), recvValidFromCosim); + loc, castedRecvData.getResult(), recvValidFromCosim); recvReady.setValue(wrapRecv.getReady()); // Replace the CosimEndpointOp op. rewriter.replaceOp(ep, wrapRecv.getChanOutput()); return success(); -#endif // CAPNP } -namespace { -/// Lower the encode gasket to SV/HW. -struct EncoderLowering : public OpConversionPattern { -public: - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(CapnpEncodeOp enc, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const final { -#ifndef CAPNP - return rewriter.notifyMatchFailure(enc, - "encode.capnp lowering requires the ESI " - "capnp plugin, which was disabled."); -#else - capnp::CapnpTypeSchema encodeType(enc.getDataToEncode().getType()); - if (!encodeType.isSupported()) - return rewriter.notifyMatchFailure(enc, "Type not supported yet"); - auto operands = adaptor.getOperands(); - Value encoderOutput = encodeType.buildEncoder(rewriter, operands[0], - operands[1], operands[2]); - assert(encoderOutput && "Error in TypeSchema.buildEncoder()"); - rewriter.replaceOp(enc, encoderOutput); - return success(); -#endif - } -}; -} // anonymous namespace - -namespace { -/// Lower the decode gasket to SV/HW. -struct DecoderLowering : public OpConversionPattern { -public: - using OpConversionPattern::OpConversionPattern; - - LogicalResult - matchAndRewrite(CapnpDecodeOp dec, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const final { -#ifndef CAPNP - return rewriter.notifyMatchFailure(dec, - "decode.capnp lowering requires the ESI " - "capnp plugin, which was disabled."); -#else - capnp::CapnpTypeSchema decodeType(dec.getDecodedData().getType()); - if (!decodeType.isSupported()) - return rewriter.notifyMatchFailure(dec, "Type not supported yet"); - auto operands = adaptor.getOperands(); - Value decoderOutput = decodeType.buildDecoder(rewriter, operands[0], - operands[1], operands[2]); - assert(decoderOutput && "Error in TypeSchema.buildDecoder()"); - rewriter.replaceOp(dec, decoderOutput); - return success(); -#endif - } -}; -} // namespace - void ESItoHWPass::runOnOperation() { auto top = getOperation(); auto *ctxt = &getContext(); @@ -491,7 +424,6 @@ void ESItoHWPass::runOnOperation() { pass1Target.addLegalDialect(); pass1Target.addLegalDialect(); pass1Target.addLegalOp(); - pass1Target.addLegalOp(); pass1Target.addIllegalOp(); pass1Target.addIllegalOp(); @@ -520,8 +452,6 @@ void ESItoHWPass::runOnOperation() { pass2Patterns.insert>(ctxt); pass2Patterns.insert>(ctxt); pass2Patterns.insert(ctxt); - pass2Patterns.insert(ctxt); - pass2Patterns.insert(ctxt); if (failed( applyPartialConversion(top, pass2Target, std::move(pass2Patterns)))) signalPassFailure(); diff --git a/lib/Dialect/ESI/capnp/ESICapnp.h b/lib/Dialect/ESI/capnp/ESICapnp.h deleted file mode 100644 index 83a67bc695be..000000000000 --- a/lib/Dialect/ESI/capnp/ESICapnp.h +++ /dev/null @@ -1,87 +0,0 @@ -//===- ESICapnp.h - ESI Cap'nProto library utilies --------------*- 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 -// -//===----------------------------------------------------------------------===// -// -// ESI utility code which requires libcapnp and libcapnpc. -// -//===----------------------------------------------------------------------===// - -// NOLINTNEXTLINE(llvm-header-guard) -#ifndef CIRCT_DIALECT_ESI_CAPNP_ESICAPNP_H -#define CIRCT_DIALECT_ESI_CAPNP_ESICAPNP_H - -#include "circt/Dialect/ESI/APIUtilities.h" -#include "circt/Dialect/ESI/ESIOps.h" -#include "circt/Dialect/HW/HWOps.h" -#include "mlir/Support/IndentedOstream.h" -#include "llvm/ADT/MapVector.h" -#include "llvm/Support/Format.h" - -#include - -namespace mlir { -class Type; -struct LogicalResult; -class Value; -class OpBuilder; -} // namespace mlir -namespace llvm { -class raw_ostream; -class StringRef; -} // namespace llvm - -namespace circt { -namespace esi { -namespace capnp { - -/// Emit an ID in capnp format. -inline llvm::raw_ostream &emitCapnpID(llvm::raw_ostream &os, int64_t id) { - return os << "@" << llvm::format_hex(id, /*width=*/16 + 2); -} - -namespace detail { -struct CapnpTypeSchemaImpl; -} // namespace detail - -/// Generate and reason about a Cap'nProto schema for a particular MLIR type. -class CapnpTypeSchema : public ESIAPIType { -public: - CapnpTypeSchema(mlir::Type); - - using ESIAPIType::operator==; - - /// Size in bits of the capnp message. - size_t size() const; - - /// Write out the schema in its entirety. - mlir::LogicalResult write(llvm::raw_ostream &os) const; - - /// Build an HW/SV dialect capnp encoder for this type. - mlir::Value buildEncoder(mlir::OpBuilder &, mlir::Value clk, - mlir::Value valid, mlir::Value rawData) const; - /// Build an HW/SV dialect capnp decoder for this type. - mlir::Value buildDecoder(mlir::OpBuilder &, mlir::Value clk, - mlir::Value valid, mlir::Value capnpData) const; - - /// Write out the name and ID in capnp schema format. - void writeMetadata(llvm::raw_ostream &os) const; - -private: - /// The implementation of this. Separate to hide the details and avoid having - /// to include the capnp headers in this header. - std::shared_ptr s; - - /// Cache of the decode/encode modules; - static llvm::SmallDenseMap decImplMods; - static llvm::SmallDenseMap encImplMods; -}; - -} // namespace capnp -} // namespace esi -} // namespace circt - -#endif // CIRCT_DIALECT_ESI_CAPNP_ESICAPNP_H diff --git a/lib/Dialect/ESI/capnp/Schema.cpp b/lib/Dialect/ESI/capnp/Schema.cpp deleted file mode 100644 index 37fffb628750..000000000000 --- a/lib/Dialect/ESI/capnp/Schema.cpp +++ /dev/null @@ -1,1163 +0,0 @@ -//===- Schema.cpp - ESI Cap'nProto schema utilities -------------*- 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 "ESICapnp.h" -#include "circt/Dialect/Comb/CombOps.h" -#include "circt/Dialect/ESI/ESITypes.h" -#include "circt/Dialect/HW/HWDialect.h" -#include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/HW/HWTypes.h" -#include "circt/Dialect/SV/SVOps.h" -#include "circt/Dialect/Seq/SeqOps.h" -#include "mlir/Support/IndentedOstream.h" - -// NOLINTNEXTLINE(clang-diagnostic-error) -#include "capnp/schema-parser.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/IR/Builders.h" -#include "mlir/IR/BuiltinAttributes.h" -#include "mlir/IR/BuiltinTypes.h" -#include "llvm/ADT/IntervalMap.h" -#include "llvm/ADT/TypeSwitch.h" -#include "llvm/Support/Format.h" - -#include -#include - -using namespace circt::esi::capnp::detail; -using namespace circt; -using circt::comb::ICmpPredicate; - -namespace { -struct GasketComponent; -struct Slice; -} // anonymous namespace - -//===----------------------------------------------------------------------===// -// CapnpTypeSchema class implementation. -//===----------------------------------------------------------------------===// - -namespace circt { -namespace esi { -namespace capnp { - -namespace detail { -/// Actual implementation of `CapnpTypeSchema` to keep all the details out of -/// the header. -struct CapnpTypeSchemaImpl { -public: - CapnpTypeSchemaImpl(CapnpTypeSchema &base); - CapnpTypeSchemaImpl(const CapnpTypeSchemaImpl &) = delete; - LogicalResult write(llvm::raw_ostream &os) const; - - bool operator==(const CapnpTypeSchemaImpl &) const; - - // Compute the expected size of the capnp message in bits. - size_t size() const; - - /// Build an HW/SV dialect capnp encoder for this type. - hw::HWModuleOp buildEncoder(Value clk, Value valid, Value); - /// Build an HW/SV dialect capnp decoder for this type. - hw::HWModuleOp buildDecoder(Value clk, Value valid, Value); - -private: - ::capnp::ParsedSchema getSchema() const; - ::capnp::StructSchema getCapnpTypeSchema() const; - - CapnpTypeSchema &base; - - ::capnp::SchemaParser parser; - mutable ::capnp::ParsedSchema rootSchema; - mutable ::capnp::StructSchema typeSchema; -}; -} // namespace detail -} // namespace capnp -} // namespace esi -} // namespace circt - -/// Return the encoding value for the size of this type (from the encoding -/// spec): 0 = 0 bits, 1 = 1 bit, 2 = 1 byte, 3 = 2 bytes, 4 = 4 bytes, 5 = 8 -/// bytes (non-pointer), 6 = 8 bytes (pointer). -static size_t bitsEncoding(::capnp::schema::Type::Reader type) { - using ty = ::capnp::schema::Type; - switch (type.which()) { - case ty::VOID: - return 0; - case ty::BOOL: - return 1; - case ty::UINT8: - case ty::INT8: - return 2; - case ty::UINT16: - case ty::INT16: - return 3; - case ty::UINT32: - case ty::INT32: - return 4; - case ty::UINT64: - case ty::INT64: - return 5; - case ty::ANY_POINTER: - case ty::DATA: - case ty::INTERFACE: - case ty::LIST: - case ty::STRUCT: - case ty::TEXT: - return 6; - default: - llvm_unreachable("Type not yet supported"); - } -} - -/// Return the number of bits used by a Capnp type. -static size_t bits(::capnp::schema::Type::Reader type) { - size_t enc = bitsEncoding(type); - if (enc <= 1) - return enc; - if (enc == 6) - return 64; - return 1 << (enc + 1); -} - -/// Return true if 'type' is capnp pointer. -static bool isPointerType(::capnp::schema::Type::Reader type) { - using ty = ::capnp::schema::Type; - switch (type.which()) { - case ty::ANY_POINTER: - case ty::DATA: - case ty::INTERFACE: - case ty::LIST: - case ty::STRUCT: - case ty::TEXT: - return true; - default: - return false; - } -} - -CapnpTypeSchemaImpl::CapnpTypeSchemaImpl(CapnpTypeSchema &base) : base(base) {} - -/// Write a valid capnp schema to memory, then parse it out of memory using the -/// capnp library. Writing and parsing text within a single process is ugly, but -/// this is by far the easiest way to do this. This isn't the use case for which -/// Cap'nProto was designed. -::capnp::ParsedSchema CapnpTypeSchemaImpl::getSchema() const { - if (rootSchema != ::capnp::ParsedSchema()) - return rootSchema; - - // Write the schema to `schemaText`. - std::string schemaText; - llvm::raw_string_ostream os(schemaText); - emitCapnpID(os, 0xFFFFFFFFFFFFFFFF) << ";\n"; - auto rc = write(os); - assert(succeeded(rc) && "Failed schema text output."); - (void)rc; - os.str(); - - // Write `schemaText` to an in-memory filesystem then parse it. Yes, this is - // the only way to do this. - kj::Own fs = kj::newDiskFilesystem(); - kj::Own dir = kj::newInMemoryDirectory(kj::nullClock()); - kj::Path fakePath = kj::Path::parse("schema.capnp"); - { // Ensure that 'fakeFile' has flushed. - auto fakeFile = dir->openFile(fakePath, kj::WriteMode::CREATE); - fakeFile->writeAll(schemaText); - } - rootSchema = parser.parseFromDirectory(*dir, std::move(fakePath), nullptr); - return rootSchema; -} - -/// Find the schema corresponding to `type` and return it. -::capnp::StructSchema CapnpTypeSchemaImpl::getCapnpTypeSchema() const { - if (typeSchema != ::capnp::StructSchema()) - return typeSchema; - uint64_t id = base.typeID(); - for (auto schemaNode : getSchema().getAllNested()) { - if (schemaNode.getProto().getId() == id) { - typeSchema = schemaNode.asStruct(); - return typeSchema; - } - } - llvm_unreachable("A node with a matching ID should always be found."); -} - -/// Returns the expected size of an array (capnp list) in 64-bit words. -static int64_t size(hw::ArrayType mType, capnp::schema::Field::Reader cField) { - assert(cField.isSlot()); - auto cType = cField.getSlot().getType(); - assert(cType.isList()); - size_t elementBits = bits(cType.getList().getElementType()); - int64_t listBits = mType.getNumElements() * elementBits; - return llvm::divideCeil(listBits, 64); -} - -/// Compute the size of a capnp struct, in 64-bit words. -static int64_t size(capnp::schema::Node::Struct::Reader cStruct, - ArrayRef mFields) { - using namespace capnp::schema; - int64_t size = (1 + // Header - cStruct.getDataWordCount() + cStruct.getPointerCount()); - auto cFields = cStruct.getFields(); - for (Field::Reader cField : cFields) { - assert(!cField.isGroup() && "Capnp groups are not supported"); - // Capnp code order is the index in the MLIR fields array. - assert(cField.getCodeOrder() < mFields.size()); - - // The size of the thing to which the pointer is pointing, not the size of - // the pointer itself. - int64_t pointedToSize = - TypeSwitch(mFields[cField.getCodeOrder()].type) - .Case([](IntegerType) { return 0; }) - .Case([cField](hw::ArrayType mType) { - return ::size(mType, cField); - }); - size += pointedToSize; - } - return size; // Convert from 64-bit words to bits. -} - -// Compute the expected size of the capnp message in bits. -size_t CapnpTypeSchemaImpl::size() const { - auto schema = getCapnpTypeSchema(); - auto structProto = schema.getProto().getStruct(); - return ::size(structProto, base.getFields()) * 64; -} - -/// Write a valid Capnp type. -static void emitCapnpType(Type type, llvm::raw_ostream &os) { - llvm::TypeSwitch(type) - .Case([&os](IntegerType intTy) { - auto w = intTy.getWidth(); - if (w == 0) { - os << "Void"; - } else if (w == 1) { - os << "Bool"; - } else { - if (intTy.isSigned()) - os << "Int"; - else - os << "UInt"; - - // Round up. - if (w <= 8) - os << "8"; - else if (w <= 16) - os << "16"; - else if (w <= 32) - os << "32"; - else if (w <= 64) - os << "64"; - else - assert(false && "Type not supported. Integer too wide. Please " - "check support first with isSupported()"); - } - }) - .Case([&os](hw::ArrayType arrTy) { - os << "List("; - emitCapnpType(arrTy.getElementType(), os); - os << ')'; - }) - .Case([](hw::StructType structTy) { - assert(false && "Struct containing structs not supported"); - }) - .Default([](Type) { - assert(false && "Type not supported. Please check support first with " - "isSupported()"); - }); -} - -/// This function is essentially a placeholder which only supports ints. It'll -/// need to be re-worked when we start supporting structs, arrays, unions, -/// enums, etc. -LogicalResult CapnpTypeSchemaImpl::write(llvm::raw_ostream &rawOS) const { - mlir::raw_indented_ostream os(rawOS); - - // Since capnp requires messages to be structs, emit a wrapper struct. - os << "struct "; - base.writeMetadata(rawOS); - os << " {\n"; - os.indent(); - - size_t counter = 0; - size_t maxNameLength = 0; - for (auto field : base.getFields()) - maxNameLength = std::max(maxNameLength, field.name.size()); - - for (auto field : base.getFields()) { - // Specify the actual type, followed by the capnp field. - os << field.name.getValue(); - std::string padding = std::string(maxNameLength - field.name.size(), ' '); - os << padding << " @" << counter++ << " :"; - emitCapnpType(field.type, os.getOStream()); - os << "; # Actual type is " << field.type << ".\n"; - } - - os.unindent(); - os << "}\n\n"; - return success(); -} - -//===----------------------------------------------------------------------===// -// Helper classes for common operations in the encode / decoders -//===----------------------------------------------------------------------===// - -namespace { -/// Provides easy methods to build common operations. -struct GasketBuilder { -public: - GasketBuilder() {} // To satisfy containers. - GasketBuilder(OpBuilder &b, Location loc) : builder(&b), location(loc) {} - - /// Get a zero constant of 'width' bit width. - GasketComponent zero(uint64_t width) const; - /// Get a constant 'value' of a certain bit width. - GasketComponent constant(uint64_t width, uint64_t value) const; - - /// Get 'p' bits of i1 padding. - Slice padding(uint64_t p) const; - - Location loc() const { return *location; } - void setLoc(Location loc) { location = loc; } - OpBuilder &b() const { return *builder; } - MLIRContext *ctxt() const { return builder->getContext(); } - -protected: - OpBuilder *builder; - std::optional location; -}; -} // anonymous namespace - -namespace { -/// Contains helper methods to assist with naming and casting. -struct GasketComponent : GasketBuilder { -public: - GasketComponent() {} // To satisfy containers. - GasketComponent(OpBuilder &b, Value init) - : GasketBuilder(b, init.getLoc()), s(init) {} - GasketComponent(std::initializer_list values) { - *this = GasketComponent::concat(values); - } - - /// Set the "name" attribute of a value's op. - template - T &name(const Twine &name) { - std::string nameStr = name.str(); - if (nameStr.empty()) - return *(T *)this; - auto nameAttr = StringAttr::get(ctxt(), nameStr); - s.getDefiningOp()->setAttr("name", nameAttr); - return *(T *)this; - } - template - T &name(capnp::Text::Reader fieldName, const Twine &nameSuffix) { - return name(StringRef(fieldName.cStr()) + nameSuffix); - } - - StringRef getName() const { - auto nameAttr = s.getDefiningOp()->getAttrOfType("name"); - if (nameAttr) - return nameAttr.getValue(); - return StringRef(); - } - - /// Construct a bitcast. - GasketComponent cast(Type t) const { - auto dst = builder->create(loc(), t, s); - auto gc = GasketComponent(*builder, dst); - StringRef name = getName(); - if (name.empty()) - return gc; - return gc.name(name + "_casted"); - ; - } - - /// Construct a bitcast. - Slice castBitArray() const; - - /// Downcast an int, accounting for signedness. - GasketComponent downcast(IntegerType t) const { - // Since the HW dialect operators only operate on signless integers, we - // have to cast to signless first, then cast the sign back. - assert(s.getType().isa()); - Value signlessVal = s; - if (!signlessVal.getType().isSignlessInteger()) - signlessVal = builder->create( - loc(), builder->getIntegerType(s.getType().getIntOrFloatBitWidth()), - s); - - if (!t.isSigned()) { - auto extracted = - builder->create(loc(), t, signlessVal, 0); - return GasketComponent(*builder, extracted).cast(t); - } - auto magnitude = builder->create( - loc(), builder->getIntegerType(t.getWidth() - 1), signlessVal, 0); - auto sign = builder->create( - loc(), builder->getIntegerType(1), signlessVal, t.getWidth() - 1); - auto result = builder->create(loc(), sign, magnitude); - - // We still have to cast to handle signedness. - return GasketComponent(*builder, result).cast(t); - } - - /// Pad this value with zeros up to `finalBits`. - GasketComponent padTo(uint64_t finalBits) const; - - /// Returns the bit width of this value. - uint64_t size() const { return hw::getBitWidth(s.getType()); } - - /// Build a component by concatenating some values. - static GasketComponent concat(ArrayRef concatValues); - - bool operator==(const GasketComponent &that) { return this->s == that.s; } - bool operator!=(const GasketComponent &that) { return this->s != that.s; } - Operation *operator->() const { return s.getDefiningOp(); } - Value getValue() const { return s; } - Type getType() const { return s.getType(); } - operator Value() { return s; } - -protected: - Value s; -}; -} // anonymous namespace - -namespace { -/// Holds a 'slice' of an array and is able to construct more slice ops, then -/// cast to a type. A sub-slice holds a pointer to the slice which created it, -/// so it forms a hierarchy. This is so we can easily track offsets from the -/// root message for pointer resolution. -/// -/// Requirement: any slice which has sub-slices must not be free'd before its -/// children slices. -struct Slice : public GasketComponent { -private: - Slice(const Slice *parent, std::optional offset, Value val) - : GasketComponent(*parent->builder, val), parent(parent), - offsetIntoParent(offset) { - type = val.getType().dyn_cast(); - assert(type && "Value must be array type"); - } - -public: - Slice(OpBuilder &b, Value val) - : GasketComponent(b, val), parent(nullptr), offsetIntoParent(0) { - type = val.getType().dyn_cast(); - assert(type && "Value must be array type"); - } - Slice(GasketComponent gc) - : GasketComponent(gc), parent(nullptr), offsetIntoParent(0) { - type = gc.getValue().getType().dyn_cast(); - assert(type && "Value must be array type"); - } - - /// Create an op to slice the array from lsb to lsb + size. Return a new slice - /// with that op. - Slice slice(int64_t lsb, int64_t size) const { - hw::ArrayType dstTy = hw::ArrayType::get(type.getElementType(), size); - IntegerType idxTy = - builder->getIntegerType(llvm::Log2_64_Ceil(type.getNumElements())); - Value lsbConst = builder->create(loc(), idxTy, lsb); - Value newSlice = - builder->create(loc(), dstTy, s, lsbConst); - return Slice(this, lsb, newSlice); - } - - /// Create an op to slice the array from lsb to lsb + size. Return a new slice - /// with that op. If lsb is greater width thn necessary, lop off the high - /// bits. - Slice slice(Value lsb, int64_t size) const { - assert(lsb.getType().isa()); - - unsigned expIdxWidth = llvm::Log2_64_Ceil(type.getNumElements()); - int64_t lsbWidth = lsb.getType().getIntOrFloatBitWidth(); - if (lsbWidth > expIdxWidth) - lsb = builder->create( - loc(), builder->getIntegerType(expIdxWidth), lsb, 0); - else if (lsbWidth < expIdxWidth) - assert(false && "LSB Value must not be smaller than expected."); - auto dstTy = hw::ArrayType::get(type.getElementType(), size); - Value newSlice = builder->create(loc(), dstTy, s, lsb); - return Slice(this, std::nullopt, newSlice); - } - Slice &name(const Twine &name) { return GasketComponent::name(name); } - Slice &name(capnp::Text::Reader fieldName, const Twine &nameSuffix) { - return GasketComponent::name(fieldName.cStr(), nameSuffix); - } - Slice castToSlice(Type elemTy, size_t size, StringRef name = StringRef(), - Twine nameSuffix = Twine()) const { - auto arrTy = hw::ArrayType::get(elemTy, size); - GasketComponent rawCast = - GasketComponent::cast(arrTy).name(name + nameSuffix); - return Slice(*builder, rawCast); - } - - GasketComponent operator[](Value idx) const { - return GasketComponent(*builder, - builder->create(loc(), s, idx)); - } - - GasketComponent operator[](size_t idx) const { - IntegerType idxTy = - builder->getIntegerType(llvm::Log2_32_Ceil(type.getNumElements())); - auto idxVal = builder->create(loc(), idxTy, idx); - return GasketComponent(*builder, - builder->create(loc(), s, idxVal)); - } - - /// Return the root of this slice hierarchy. - const Slice &getRootSlice() const { - if (parent == nullptr) - return *this; - return parent->getRootSlice(); - } - - std::optional getOffsetFromRoot() const { - if (parent == nullptr) - return 0; - auto parentOffset = parent->getOffsetFromRoot(); - if (!offsetIntoParent || !parentOffset) - return std::nullopt; - return *offsetIntoParent + *parentOffset; - } - - uint64_t size() const { return type.getNumElements(); } - -private: - hw::ArrayType type; - const Slice *parent; - std::optional offsetIntoParent; -}; -} // anonymous namespace - -// The following methods have to be defined out-of-line because they use types -// which aren't yet defined when they are declared. - -GasketComponent GasketBuilder::zero(uint64_t width) const { - return GasketComponent(*builder, - builder->create( - loc(), builder->getIntegerType(width), 0)); -} -GasketComponent GasketBuilder::constant(uint64_t width, uint64_t value) const { - return GasketComponent(*builder, - builder->create( - loc(), builder->getIntegerType(width), value)); -} - -Slice GasketBuilder::padding(uint64_t p) const { - auto zero = GasketBuilder::zero(p); - return zero.castBitArray(); -} - -Slice GasketComponent::castBitArray() const { - auto dstTy = - hw::ArrayType::get(builder->getI1Type(), hw::getBitWidth(s.getType())); - if (s.getType() == dstTy) - return Slice(*builder, s); - auto dst = builder->create(loc(), dstTy, s); - return Slice(*builder, dst); -} - -GasketComponent -GasketComponent::concat(ArrayRef concatValues) { - assert(concatValues.size() > 0); - auto builder = concatValues[0].builder; - auto loc = concatValues[0].loc(); - SmallVector values; - for (auto gc : concatValues) { - values.push_back(gc.castBitArray()); - } - // Since the "endianness" of `values` is the reverse of ArrayConcat, we must - // reverse ourselves. - std::reverse(values.begin(), values.end()); - return GasketComponent(*builder, - builder->create(loc, values)); -} -namespace { -/// Utility class for building sv::AssertOps. Since SV assertions need to be in -/// an `always` block (so the simulator knows when to check the assertion), we -/// build them all in a region intended for assertions. -class AssertBuilder : public OpBuilder { -public: - AssertBuilder(Location loc, Region &r) : OpBuilder(r), loc(loc) {} - - void assertPred(GasketComponent veg, ICmpPredicate pred, int64_t expected) { - if (veg.getValue().getType().isa()) { - assertPred(veg.getValue(), pred, expected); - return; - } - - auto valTy = veg.getValue().getType().dyn_cast(); - assert(valTy && valTy.getElementType() == veg.b().getIntegerType(1) && - "Can only compare ints and bit arrays"); - assertPred( - veg.cast(veg.b().getIntegerType(valTy.getNumElements())).getValue(), - pred, expected); - } - - void assertEqual(GasketComponent s, int64_t expected) { - assertPred(s, ICmpPredicate::eq, expected); - } - -private: - void assertPred(Value val, ICmpPredicate pred, int64_t expected) { - auto expectedVal = create(loc, val.getType(), expected); - create( - loc, - create(loc, getI1Type(), pred, val, expectedVal, false), - sv::DeferAssertAttr::get(loc.getContext(), sv::DeferAssert::Immediate)); - } - Location loc; -}; -} // anonymous namespace - -//===----------------------------------------------------------------------===// -// Capnp encode "gasket" HW builders. -// -// These have the potential to get large and complex as we add more types. The -// encoding spec is here: https://capnproto.org/encoding.html -//===----------------------------------------------------------------------===// - -namespace { -/// Helps build capnp message DAGs, which are stored in 'segments'. To better -/// reason about something which is more memory-like than wire-like, this class -/// contains a data structure to efficiently model memory and map it to Values -/// (wires). -class CapnpSegmentBuilder : public GasketBuilder { -public: - CapnpSegmentBuilder(OpBuilder &b, Location loc, uint64_t expectedSize) - : GasketBuilder(b, loc), segmentValues(allocator), messageSize(0), - expectedSize(expectedSize) {} - CapnpSegmentBuilder(const CapnpSegmentBuilder &) = delete; - ~CapnpSegmentBuilder() {} - - GasketComponent build(::capnp::schema::Node::Struct::Reader cStruct, - ArrayRef mFieldValues); - -private: - /// Allocate and build a struct. Return the address of the data section as an - /// offset into the 'memory' map. - GasketComponent encodeStructAt(uint64_t ptrLoc, - ::capnp::schema::Node::Struct::Reader cStruct, - ArrayRef mFieldValues); - /// Build a value from the 'memory' map. Concatenates all the values in the - /// 'memory' map, filling in the blank addresses with padding. - GasketComponent compile() const; - - /// Encode 'val' and place the value at the specified 'memory' offset. - void encodeFieldAt(uint64_t offset, GasketComponent val, - ::capnp::schema::Type::Reader type); - /// Allocate and build a list, returning the address which was allocated. - uint64_t buildList(Slice val, ::capnp::schema::Type::Reader type); - - /// Insert 'val' into the 'memory' map. - void insert(uint64_t offset, GasketComponent val) { - uint64_t valSize = val.size(); - assert(!segmentValues.overlaps(offset, offset + valSize - 1)); - assert(offset + valSize - 1 < expectedSize && - "Tried to insert above the max expected size of the message."); - segmentValues.insert(offset, offset + valSize - 1, val); - } - - /// This is where the magic lives. An IntervalMap allows us to efficiently - /// model segment 'memory' and to place Values at any address. We can then - /// manage 'memory allocations' (figuring out where to place pointed to - /// objects) separately from the data contained in those values, some of which - /// are pointers themselves. - llvm::IntervalMap::Allocator allocator; - llvm::IntervalMap segmentValues; - - /// Track the allocated message size. Increase to 'alloc' more. - uint64_t messageSize; - uint64_t alloc(size_t bits) { - uint64_t ptr = messageSize; - messageSize += bits; - return ptr; - } - - /// The expected maximum size of the message. - uint64_t expectedSize; -}; -} // anonymous namespace - -void CapnpSegmentBuilder::encodeFieldAt(uint64_t offset, GasketComponent val, - ::capnp::schema::Type::Reader type) { - TypeSwitch(val.getValue().getType()) - .Case([&](IntegerType it) { insert(offset, val); }) - .Case([&](hw::ArrayType arrTy) { - uint64_t listOffset = buildList(Slice(val), type); - int32_t relativeOffset = (listOffset - offset - 64) / 64; - insert(offset, - GasketComponent::concat( - {constant(2, 1), constant(30, relativeOffset), - constant(3, bitsEncoding(type.getList().getElementType())), - constant(29, arrTy.getNumElements())})); - }); -} - -uint64_t CapnpSegmentBuilder::buildList(Slice val, - ::capnp::schema::Type::Reader type) { - hw::ArrayType arrTy = val.getValue().getType().cast(); - auto elemType = type.getList().getElementType(); - size_t elemWidth = bits(elemType); - uint64_t listSize = elemWidth * arrTy.getNumElements(); - uint64_t m; - if ((m = listSize % 64) != 0) - listSize += (64 - m); - uint64_t listOffset = alloc(listSize); - - for (size_t i = 0, e = arrTy.getNumElements(); i < e; ++i) { - size_t elemNum = e - i - 1; - encodeFieldAt(listOffset + (elemNum * elemWidth), val[i], elemType); - } - return listOffset; -} - -GasketComponent CapnpSegmentBuilder::encodeStructAt( - uint64_t ptrLoc, ::capnp::schema::Node::Struct::Reader cStruct, - ArrayRef mFieldValues) { - - assert(ptrLoc % 64 == 0); - size_t structSize = - (cStruct.getDataWordCount() + cStruct.getPointerCount()) * 64; - uint64_t structDataSectionOffset = alloc(structSize); - uint64_t structPointerSectionOffset = - structDataSectionOffset + (cStruct.getDataWordCount() * 64); - assert(structDataSectionOffset % 64 == 0); - int64_t relativeStructDataOffsetWords = - ((structDataSectionOffset - ptrLoc) / 64) - - /*offset from end of pointer.*/ 1; - GasketComponent structPtr = {constant(2, 0), - constant(30, relativeStructDataOffsetWords), - constant(16, cStruct.getDataWordCount()), - constant(16, cStruct.getPointerCount())}; - - // Loop through data fields. - for (auto field : cStruct.getFields()) { - uint16_t idx = field.getCodeOrder(); - assert(idx < mFieldValues.size() && - "Capnp struct longer than fieldValues."); - auto cFieldType = field.getSlot().getType(); - uint64_t fieldOffset = - (isPointerType(cFieldType) ? structPointerSectionOffset - : structDataSectionOffset) + - field.getSlot().getOffset() * bits(cFieldType); - encodeFieldAt(fieldOffset, mFieldValues[idx], cFieldType); - } - - return structPtr; -} - -GasketComponent CapnpSegmentBuilder::compile() const { - // Fill in missing bits. - SmallVector segmentValuesPlusPadding; - uint64_t lastStop = 0; - for (auto it = segmentValues.begin(), e = segmentValues.end(); it != e; - ++it) { - auto value = it.value(); - int64_t padBits = it.start() - lastStop; - assert(padBits >= 0 && "Overlap not allowed"); - if (padBits) - segmentValuesPlusPadding.push_back(padding(padBits)); - segmentValuesPlusPadding.push_back(value.castBitArray()); - // IntervalMap has inclusive ranges, but we want to reason about [,) regions - // to make the math work. - lastStop = it.stop() + 1; - } - assert(expectedSize >= lastStop); - if (lastStop != expectedSize) - segmentValuesPlusPadding.push_back(padding(expectedSize - lastStop)); - - return GasketComponent::concat(segmentValuesPlusPadding); -} - -GasketComponent -CapnpSegmentBuilder::build(::capnp::schema::Node::Struct::Reader cStruct, - ArrayRef mFieldValues) { - uint64_t rootPtrLoc = alloc(64); - assert(rootPtrLoc == 0); - auto rootPtr = encodeStructAt(rootPtrLoc, cStruct, mFieldValues); - insert(rootPtrLoc, rootPtr); - return compile(); -} - -/// Build an HW/SV dialect capnp encoder module for this type. Inputs need to -/// be packed and unpadded. -hw::HWModuleOp CapnpTypeSchemaImpl::buildEncoder(Value clk, Value valid, - Value operandVal) { - Location loc = operandVal.getDefiningOp()->getLoc(); - ModuleOp topMod = operandVal.getDefiningOp()->getParentOfType(); - OpBuilder b = OpBuilder::atBlockEnd(topMod.getBody()); - - SmallString<64> modName; - modName.append("encode"); - modName.append(base.name()); - SmallVector ports; - ports.push_back(hw::PortInfo{ - {b.getStringAttr("clk"), clk.getType(), hw::ModulePort::Direction::Input}, - 0}); - ports.push_back(hw::PortInfo{{b.getStringAttr("valid"), valid.getType(), - hw::ModulePort::Direction::Input}, - 1}); - ports.push_back( - hw::PortInfo{{b.getStringAttr("unencodedInput"), operandVal.getType(), - hw::ModulePort::Direction::Input}, - 2}); - hw::ArrayType modOutputType = hw::ArrayType::get(b.getI1Type(), size()); - ports.push_back(hw::PortInfo{{b.getStringAttr("encoded"), modOutputType, - hw::ModulePort::Direction::Output}, - 0}); - hw::HWModuleOp retMod = b.create( - operandVal.getLoc(), b.getStringAttr(modName), ports); - - Block *innerBlock = retMod.getBodyBlock(); - b.setInsertionPointToStart(innerBlock); - clk = innerBlock->getArgument(0); - valid = innerBlock->getArgument(1); - GasketComponent operand(b, innerBlock->getArgument(2)); - operand.setLoc(loc); - - ::capnp::schema::Node::Reader rootProto = getCapnpTypeSchema().getProto(); - auto st = rootProto.getStruct(); - CapnpSegmentBuilder seg(b, loc, size()); - - // The values in the struct we are encoding. - SmallVector fieldValues; - assert(operand.getValue().getType() == base.getType()); - if (auto structTy = base.getType().dyn_cast()) { - for (auto field : structTy.getElements()) { - fieldValues.push_back(GasketComponent( - b, b.create(loc, operand, field))); - } - } else { - fieldValues.push_back(GasketComponent(b, operand)); - } - GasketComponent ret = seg.build(st, fieldValues); - - innerBlock->getTerminator()->erase(); - b.setInsertionPointToEnd(innerBlock); - b.create(loc, ValueRange{ret}); - return retMod; -} - -//===----------------------------------------------------------------------===// -// Capnp decode "gasket" HW builders. -// -// These have the potential to get large and complex as we add more types. The -// encoding spec is here: https://capnproto.org/encoding.html -//===----------------------------------------------------------------------===// - -/// Construct the proper operations to decode a capnp list. This only works for -/// arrays of ints or bools. Will need to be updated for structs and lists of -/// lists. -static GasketComponent decodeList(hw::ArrayType type, - capnp::schema::Field::Reader field, - Slice ptrSection, AssertBuilder &asserts) { - capnp::schema::Type::Reader capnpType = field.getSlot().getType(); - assert(capnpType.isList()); - assert(capnpType.getList().hasElementType()); - - auto loc = ptrSection.loc(); - OpBuilder &b = ptrSection.b(); - GasketBuilder gb(b, loc); - - // Get the list pointer and break out its parts. - auto ptr = ptrSection.slice(field.getSlot().getOffset() * 64, 64) - .name(field.getName(), "_ptr"); - auto ptrType = ptr.slice(0, 2).name(field.getName(), "_ptrType"); - auto offset = ptr.slice(2, 30) - .cast(b.getIntegerType(30)) - .name(field.getName(), "_offset"); - auto elemSize = ptr.slice(32, 3).name(field.getName(), "_elemSize"); - auto length = ptr.slice(35, 29).name(field.getName(), "_listLength"); - - // Assert that ptr type == list type; - asserts.assertEqual(ptrType, 1); - - // Assert that the element size in the message matches our expectation. - auto expectedElemSizeBits = bits(capnpType.getList().getElementType()); - unsigned expectedElemSizeField; - switch (expectedElemSizeBits) { - case 0: - expectedElemSizeField = 0; - break; - case 1: - expectedElemSizeField = 1; - break; - case 8: - expectedElemSizeField = 2; - break; - case 16: - expectedElemSizeField = 3; - break; - case 32: - expectedElemSizeField = 4; - break; - case 64: - expectedElemSizeField = 5; - break; - default: - llvm_unreachable("bits() returned unexpected value"); - } - asserts.assertEqual(elemSize, expectedElemSizeField); - - // Assert that the length of the list (array) is at most the length of the - // array. - asserts.assertPred(length, ICmpPredicate::ule, type.getNumElements()); - - // Get the entire message slice, compute the offset into the list, then get - // the list data in an ArrayType. - auto msg = ptr.getRootSlice(); - auto ptrOffset = ptr.getOffsetFromRoot(); - assert(ptrOffset); - GasketComponent offsetInBits( - b, b.create(loc, offset, gb.zero(6))); - GasketComponent listOffset( - b, b.create(loc, offsetInBits, - gb.constant(36, *ptrOffset + 64), false)); - listOffset.name(field.getName(), "_listOffset"); - auto listSlice = - msg.slice(listOffset, type.getNumElements() * expectedElemSizeBits) - .name("list"); - - // Cast to an array of capnp int elements. - assert(type.getElementType().isa() && - "DecodeList() only works on arrays of ints currently"); - Type capnpElemTy = - b.getIntegerType(expectedElemSizeBits, IntegerType::Signless); - auto arrayOfElements = - listSlice.castToSlice(capnpElemTy, type.getNumElements()); - if (arrayOfElements.getValue().getType() == type) - return arrayOfElements; - - // Collect the reduced elements. - SmallVector arrayValues; - for (size_t i = 0, e = type.getNumElements(); i < e; ++i) { - auto capnpElem = arrayOfElements[i].name(field.getName(), "_capnp_elem"); - auto esiElem = capnpElem.downcast(type.getElementType().cast()) - .name(field.getName(), "_elem"); - arrayValues.push_back(esiElem); - } - auto array = b.create(loc, arrayValues); - return GasketComponent(b, array); -} - -/// Construct the proper operations to convert a capnp field to 'type'. -static GasketComponent decodeField(Type type, - capnp::schema::Field::Reader field, - Slice dataSection, Slice ptrSection, - AssertBuilder &asserts) { - GasketComponent esiValue = - TypeSwitch(type) - .Case([&](IntegerType it) { - auto slice = dataSection.slice(field.getSlot().getOffset() * - bits(field.getSlot().getType()), - it.getWidth()); - return slice.name(field.getName(), "_bits").cast(type); - }) - .Case([&](hw::ArrayType at) { - return decodeList(at, field, ptrSection, asserts); - }); - esiValue.name(field.getName().cStr(), "Value"); - return esiValue; -} - -/// Build an HW/SV dialect capnp decoder module for this type. Outputs packed -/// and unpadded data. -hw::HWModuleOp CapnpTypeSchemaImpl::buildDecoder(Value clk, Value valid, - Value operandVal) { - auto loc = operandVal.getDefiningOp()->getLoc(); - auto topMod = operandVal.getDefiningOp()->getParentOfType(); - OpBuilder b = OpBuilder::atBlockEnd(topMod.getBody()); - - SmallString<64> modName; - modName.append("decode"); - modName.append(base.name()); - SmallVector ports; - ports.push_back(hw::PortInfo{ - {b.getStringAttr("clk"), clk.getType(), hw::ModulePort::Direction::Input}, - 0}); - ports.push_back(hw::PortInfo{{b.getStringAttr("valid"), valid.getType(), - hw::ModulePort::Direction::Input}, - 1}); - ports.push_back( - hw::PortInfo{{b.getStringAttr("encodedInput"), operandVal.getType(), - hw::ModulePort::Direction::Input}, - 2}); - ports.push_back(hw::PortInfo{{b.getStringAttr("decoded"), base.getType(), - hw::ModulePort::Direction::Output}, - 0}); - hw::HWModuleOp retMod = b.create( - operandVal.getLoc(), b.getStringAttr(modName), ports); - - Block *innerBlock = retMod.getBodyBlock(); - b.setInsertionPointToStart(innerBlock); - clk = innerBlock->getArgument(0); - valid = innerBlock->getArgument(1); - operandVal = innerBlock->getArgument(2); - - // Various useful integer types. - auto i16 = b.getIntegerType(16); - - size_t size = this->size(); - hw::ArrayType operandType = operandVal.getType().dyn_cast(); - assert(operandType && operandType.getNumElements() == size && - "Operand type and length must match the type's capnp size."); - (void)size; - (void)operandType; - - Slice operand(b, operandVal); - operand.setLoc(loc); - - auto hwClk = b.create(clk.getLoc(), clk).getResult(); - auto alwaysAt = - b.create(loc, sv::EventControl::AtPosEdge, hwClk); - auto ifValid = - OpBuilder(alwaysAt.getBodyRegion()).create(loc, valid); - AssertBuilder asserts(loc, ifValid.getBodyRegion()); - - // The next 64-bits of a capnp message is the root struct pointer. - ::capnp::schema::Node::Reader rootProto = getCapnpTypeSchema().getProto(); - auto ptr = operand.slice(0, 64).name("rootPointer"); - - // Since this is the root, we _expect_ the offset to be zero but that's only - // guaranteed to be the case with canonically-encoded messages. - // TODO: support cases where the pointer offset is non-zero. - Slice assertPtr(ptr); - auto typeAndOffset = assertPtr.slice(0, 32).name("typeAndOffset"); - if (base.getType().isInteger(0)) { - asserts.assertEqual(typeAndOffset.slice(0, 2), 0); - asserts.assertEqual(typeAndOffset.slice(2, 30), 0x3FFFFFFF); - } else { - asserts.assertEqual(typeAndOffset, 0); - } - - // We expect the data section to be equal to the computed data section size. - auto dataSectionSize = - assertPtr.slice(32, 16).cast(i16).name("dataSectionSize"); - asserts.assertEqual(dataSectionSize, - rootProto.getStruct().getDataWordCount()); - - // We expect the pointer section to be equal to the computed pointer section - // size. - auto ptrSectionSize = - assertPtr.slice(48, 16).cast(i16).name("ptrSectionSize"); - asserts.assertEqual(ptrSectionSize, rootProto.getStruct().getPointerCount()); - - // Get pointers to the data and pointer sections. - auto st = rootProto.getStruct(); - auto dataSection = - operand.slice(64, st.getDataWordCount() * 64).name("dataSection"); - auto ptrSection = operand - .slice(64 + (st.getDataWordCount() * 64), - rootProto.getStruct().getPointerCount() * 64) - .name("ptrSection"); - - // Loop through fields. - SmallVector fieldValues; - for (auto field : st.getFields()) { - uint16_t idx = field.getCodeOrder(); - assert(idx < base.getFields().size() && - "Capnp struct longer than fieldTypes."); - fieldValues.push_back(decodeField(base.getFields()[idx].type, field, - dataSection, ptrSection, asserts)); - } - - // What to return depends on the type. (e.g. structs have to be constructed - // from the field values.) - GasketComponent ret = - TypeSwitch(base.getType()) - .Case([&fieldValues](IntegerType) { return fieldValues[0]; }) - .Case([&fieldValues](hw::ArrayType) { return fieldValues[0]; }) - .Case([&](hw::StructType) { - SmallVector rawValues(llvm::map_range( - fieldValues, [](GasketComponent c) { return c.getValue(); })); - return GasketComponent(b, b.create( - loc, base.getType(), rawValues)); - }); - ret.name(base.name()); - - innerBlock->getTerminator()->erase(); - b.setInsertionPointToEnd(innerBlock); - auto outputOp = b.create(loc, ValueRange{ret.getValue()}); - alwaysAt->moveBefore(outputOp); - return retMod; -} - -//===----------------------------------------------------------------------===// -// CapnpTypeSchema wrapper. -//===----------------------------------------------------------------------===// - -llvm::SmallDenseMap - circt::esi::capnp::CapnpTypeSchema::decImplMods; -llvm::SmallDenseMap - circt::esi::capnp::CapnpTypeSchema::encImplMods; - -size_t circt::esi::capnp::CapnpTypeSchema::size() const { return s->size(); } - -circt::esi::capnp::CapnpTypeSchema::CapnpTypeSchema(Type outerType) - : circt::esi::ESIAPIType(outerType) { - s = std::make_shared(*this); -} - -LogicalResult -circt::esi::capnp::CapnpTypeSchema::write(llvm::raw_ostream &os) const { - return s->write(os); -} - -void circt::esi::capnp::CapnpTypeSchema::writeMetadata( - llvm::raw_ostream &os) const { - os << name() << " "; - emitCapnpID(os, typeID()); -} - -Value circt::esi::capnp::CapnpTypeSchema::buildEncoder(OpBuilder &builder, - Value clk, Value valid, - Value rawData) const { - hw::HWModuleOp encImplMod; - auto encImplIT = encImplMods.find(getType()); - if (encImplIT == encImplMods.end()) { - encImplMod = s->buildEncoder(clk, valid, rawData); - encImplMods[getType()] = encImplMod; - } else { - encImplMod = encImplIT->second; - } - - SmallString<64> instName; - instName.append("encode"); - instName.append(name()); - instName.append("Inst"); - auto encodeInst = - builder.create(rawData.getLoc(), encImplMod, instName, - ArrayRef{clk, valid, rawData}); - return encodeInst.getResult(0); -} - -Value circt::esi::capnp::CapnpTypeSchema::buildDecoder(OpBuilder &builder, - Value clk, Value valid, - Value capnpData) const { - hw::HWModuleOp decImplMod; - auto decImplIT = decImplMods.find(getType()); - if (decImplIT == decImplMods.end()) { - decImplMod = s->buildDecoder(clk, valid, capnpData); - decImplMods[getType()] = decImplMod; - } else { - decImplMod = decImplIT->second; - } - - SmallString<64> instName; - instName.append("decode"); - instName.append(name()); - instName.append("Inst"); - auto decodeInst = - builder.create(capnpData.getLoc(), decImplMod, instName, - ArrayRef{clk, valid, capnpData}); - return decodeInst.getResult(0); -} diff --git a/test/Dialect/ESI/cosim.mlir b/test/Dialect/ESI/cosim.mlir index ca080bf4254d..a3c4d70674b8 100644 --- a/test/Dialect/ESI/cosim.mlir +++ b/test/Dialect/ESI/cosim.mlir @@ -1,6 +1,5 @@ // REQUIRES: capnp // RUN: circt-opt %s -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck %s -// RUN: circt-opt %s --esi-emit-collateral=schema-file=%t1.capnp --lower-esi-ports --lower-esi-to-hw --lower-seq-to-sv --export-verilog -verify-diagnostics | FileCheck --check-prefix=COSIM %s hw.module.extern @Sender(out x: !esi.channel) hw.module.extern @Reciever(in %a: !esi.channel) @@ -27,18 +26,4 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK: esi.cosim %clk, %rst, %send2.x, "ArrTestEP" : !esi.channel -> !esi.channel> hw.instance "arrRecv" @ArrReciever (x: %cosimArrRecv: !esi.channel>) -> () - - // Ensure that the file hash is deterministic. - // COSIM: @0xccf233b58d85e822; - // COSIM-LABEL: struct Si14 @0x9bd5e507cce05cc1 - // COSIM: i @0 :Int16; - // COSIM-LABEL: struct I32 @0x92cd59dfefaacbdb - // COSIM: i @0 :UInt32; - // Ensure the standard RPC interface is tacked on. - // COSIM: interface CosimDpiServer - // COSIM: list @0 () -> (ifaces :List(EsiDpiInterfaceDesc)); - // COSIM: open @1 [S, T] (iface :EsiDpiInterfaceDesc) -> (iface :EsiDpiEndpoint(S, T)); - - // COSIM-LABEL: hw.module @top(in %clk : i1, in %rst : i1) - // COSIM: %TestEP.DataOutValid, %TestEP.DataOut, %TestEP.DataInReady = hw.instance "TestEP" @Cosim_Endpoint(clk: %clk: i1, rst: %rst: i1, DataOutReady: %{{.+}}: i1, DataInValid: %{{.+}}: i1, DataIn: %{{.+}}: !hw.array<128xi1>) -> (DataOutValid: i1, DataOut: !hw.array<128xi1>, DataInReady: i1) } diff --git a/test/Dialect/ESI/cosim_structs.mlir b/test/Dialect/ESI/cosim_structs.mlir deleted file mode 100644 index 179a1dd7d03f..000000000000 --- a/test/Dialect/ESI/cosim_structs.mlir +++ /dev/null @@ -1,22 +0,0 @@ -// REQUIRES: capnp -// RUN: circt-translate %s -export-esi-capnp -verify-diagnostics | FileCheck --check-prefix=CAPNP %s -// RUN: circt-opt %s --lower-esi-ports --lower-esi-to-hw -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck --check-prefix=COSIM %s - -!DataPkt = !hw.struct> -!pktChan = !esi.channel - -hw.module.extern @Compressor(in %in : !esi.channel, out x : !pktChan) - -hw.module @top(in %clk : !seq.clock, in %rst : i1) { - %compressedData = hw.instance "compressor" @Compressor(in: %inputData: !esi.channel) -> (x: !pktChan) - %inputData = esi.cosim %clk, %rst, %compressedData, "Compressor" : !pktChan -> !esi.channel -} - -// CAPNP: struct Struct{{.+}} -// CAPNP-NEXT: encrypted @0 :Bool; -// CAPNP-NEXT: compressionLevel @1 :UInt8; -// CAPNP-NEXT: blob @2 :List(UInt8); - -// COSIM-DAG: hw.instance "encodeStruct{{.+}}Inst" @encodeStruct{{.+}}(clk: %clk: !seq.clock, valid: %{{.+}}: i1, unencodedInput: %{{.+}}: !hw.struct>) -> (encoded: !hw.array<448xi1>) -// COSIM-DAG: hw.instance "Compressor" @Cosim_Endpoint(clk: %clk: !seq.clock, rst: %rst: i1, {{.+}}, {{.+}}, {{.+}}) -> ({{.+}}) -// COSIM-DAG: hw.module @encode{{.+}}(in %clk : !seq.clock, in %valid : i1, in %unencodedInput : !hw.struct>, out encoded : !hw.array<448xi1>)