diff --git a/README.md b/README.md index 4d5de9c..6913873 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,7 @@ The list of arguments for `hls`-mode is presented below: * `--out-sv-lib `: *optional* filesystem-path option; used to specify the output SystemVerilog file for generated operations library. * `--out-dfcir `: *optional* filesystem-path option; used to specify the output DFCIR file. * `--out-firrtl `: *optional* filesystem-path option; used to specify the output FIRRTL file. +* `--out-dot `: *optional* filesystem-path option; used to specify the output DOT file. * `-a` or `-l`: *required* flag; used to specify the chosen scheduling strategy - either as-soon-as-possible or linear programming. **Exactly one of these flags has to be specified**. **At least one of the `out-*` options has to be specified.** diff --git a/config.json b/config.json index 4142592..4504a0d 100644 --- a/config.json +++ b/config.json @@ -6,6 +6,7 @@ "out_sv" : "", "out_sv_lib" : "", "out_dfcir" : "", - "out_firrtl" : "" + "out_firrtl" : "", + "out_dot" : "" } } diff --git a/src/model/dfcxx/CMakeLists.txt b/src/model/dfcxx/CMakeLists.txt index a3aac0a..fbe1eca 100644 --- a/src/model/dfcxx/CMakeLists.txt +++ b/src/model/dfcxx/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) project(DFCXX LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) - +find_package(CTemplate REQUIRED COMPONENTS nothreads) add_subdirectory(include) add_subdirectory(includeDev) add_subdirectory(lib) diff --git a/src/model/dfcxx/include/dfcxx/kernel.h b/src/model/dfcxx/include/dfcxx/kernel.h index 1282ba6..d9e2c09 100644 --- a/src/model/dfcxx/include/dfcxx/kernel.h +++ b/src/model/dfcxx/include/dfcxx/kernel.h @@ -24,6 +24,12 @@ #include #include +// This forward declaration is needed to avoid +// users having to include LLVM headers. +namespace llvm { + class raw_fd_ostream; +} + namespace dfcxx { class DFCIRBuilder; @@ -36,6 +42,8 @@ class Kernel { TypeBuilder typeBuilder; VarBuilder varBuilder; Graph graph; + + bool compileDot(llvm::raw_fd_ostream *stream); protected: IO io; diff --git a/src/model/dfcxx/include/dfcxx/typedefs.h b/src/model/dfcxx/include/dfcxx/typedefs.h index 250add7..2ac86a1 100644 --- a/src/model/dfcxx/include/dfcxx/typedefs.h +++ b/src/model/dfcxx/include/dfcxx/typedefs.h @@ -65,6 +65,7 @@ enum class OutputFormatID : uint8_t { SVLibrary, DFCIR, FIRRTL, + DOT, // Utility value. Constains the number of elements in the enum. COUNT }; diff --git a/src/model/dfcxx/includeDev/dfcxx/converter.h b/src/model/dfcxx/includeDev/dfcxx/converter.h index c16bcc6..7162796 100644 --- a/src/model/dfcxx/includeDev/dfcxx/converter.h +++ b/src/model/dfcxx/includeDev/dfcxx/converter.h @@ -12,6 +12,7 @@ #include "dfcxx/typedefs.h" #include "dfcir/conversions/DFCIRPasses.h" +#include "llvm/Support/raw_ostream.h" #include "mlir/IR/BuiltinOps.h" #include diff --git a/src/model/dfcxx/lib/dfcxx/CMakeLists.txt b/src/model/dfcxx/lib/dfcxx/CMakeLists.txt index a82e35a..d8e1be7 100644 --- a/src/model/dfcxx/lib/dfcxx/CMakeLists.txt +++ b/src/model/dfcxx/lib/dfcxx/CMakeLists.txt @@ -40,6 +40,13 @@ target_include_directories(DFCXX $ ) +set(TEMPLATES_PATH "${PROJECT_SOURCE_DIR}/templates") + +add_compile_definitions( + TEMPLATES_PATH="${TEMPLATES_PATH}" + DOT_TEMPLATE_PATH="${TEMPLATES_PATH}/dot.tpl" +) + ## MLIRDFCIR is ensured to be compiled beforehand. target_link_libraries(DFCXX PRIVATE $ @@ -49,6 +56,8 @@ target_link_libraries(DFCXX PRIVATE $ PRIVATE $ PRIVATE $ + PRIVATE $ + PRIVATE $ ) add_library(Utopia::DFCXX ALIAS DFCXX) diff --git a/src/model/dfcxx/lib/dfcxx/kernel.cpp b/src/model/dfcxx/lib/dfcxx/kernel.cpp index b508fc0..fe81172 100644 --- a/src/model/dfcxx/lib/dfcxx/kernel.cpp +++ b/src/model/dfcxx/lib/dfcxx/kernel.cpp @@ -9,8 +9,15 @@ #include "dfcxx/converter.h" #include "dfcxx/IRbuilders/builder.h" #include "dfcxx/kernel.h" +#include "dfcxx/vars/constant.h" +#include "ctemplate/template.h" +#include "llvm/Support/raw_ostream.h" + +#include #include +#include +#include namespace dfcxx { @@ -40,6 +47,94 @@ DFType Kernel::dfBool() { return DFType(storage.addType(type)); } +bool Kernel::compileDot(llvm::raw_fd_ostream *stream) { + using ctemplate::TemplateDictionary; + + uint64_t counter = 0; + std::unordered_map idMapping; + auto getName = [&idMapping, &counter] (const Node &node) -> std::string { + // If the node is named - just return the name. + auto name = DFVariable(node.var).getName(); + if (!name.empty()) { + return name.data(); + } + // If the mapping contains the node name - return it. + auto it = idMapping.find(node); + if (it != idMapping.end()) { + return it->second; + } + // Otherwise create and return the new node name mapping. + return (idMapping[node] = "node" + std::to_string(counter++)); + }; + + std::string result; + TemplateDictionary *dict = new TemplateDictionary("dot"); + dict->SetValue("KERNEL_NAME", this->getName().data()); + auto time = std::time(nullptr); + auto *localTime = std::localtime(&time); + dict->SetFormattedValue("GEN_TIME", + "%d-%d-%d %d:%d:%d", + localTime->tm_mday, + localTime->tm_mon + 1, + localTime->tm_year + 1900, + localTime->tm_hour, + localTime->tm_min, + localTime->tm_sec); + + for (Node node : graph.nodes) { + TemplateDictionary *elem = dict->AddSectionDictionary("ELEMENTS"); + auto name = getName(node); + elem->SetValue("NAME", name); + std::string shape = "box"; + std::string label = name; + auto data = node.data; + switch (node.type) { + case OFFSET: + shape = "diamond"; + label = std::to_string(data.offset); + break; + case IN: shape = "invtriangle"; break; + case OUT: shape = "triangle"; break; + case MUX: shape = "invtrapezium"; break; + case ADD: label = "+"; break; + case SUB: label = "-"; break; + case MUL: label = "*"; break; + case DIV: label = "/"; break; + case AND: label = "&"; break; + case OR: label = "|"; break; + case XOR: label = "^"; break; + case NOT: label = "!"; break; + case NEG: label = "-"; break; + case LESS: label = "<"; break; + case LESSEQ: label = "<="; break; + case GREATER: label = ">"; break; + case GREATEREQ: label = ">="; break; + case EQ: label = "=="; break; + case NEQ: label = "!="; break; + case SHL: label = "<< " + std::to_string(data.bitShift); break; + case SHR: label = ">> " + std::to_string(data.bitShift); break; + default: break; // Silences -Wswitch warning for "CONST". + } + + elem->SetValue("SHAPE", shape); + elem->SetValue("LABEL", label); + + unsigned i = 0; + for (Channel chan : graph.inputs[node]) { + TemplateDictionary *conn = elem->AddSectionDictionary("CONNECTIONS"); + conn->SetValue("SRC_NAME", getName(chan.source)); + conn->SetValue("TRG_NAME", name); + conn->SetValue("CON_LABEL", std::to_string(i++)); + } + } + + ctemplate::ExpandTemplate(DOT_TEMPLATE_PATH, ctemplate::DO_NOT_STRIP, + dict, &result); + delete dict; + + *stream << result; + return true; +} bool Kernel::compile(const DFLatencyConfig &config, const std::vector &outputPaths, @@ -55,9 +150,16 @@ bool Kernel::compile(const DFLatencyConfig &config, ? new llvm::raw_fd_ostream(outputPaths[i], ec) : nullptr; } - bool result = DFCIRConverter(config).convertAndPrint(compiled, - outputStreams, - sched); + bool result = true; + // Compile the kernel to DOT if such stream is specified. + if (auto *stream = outputStreams[OUT_FORMAT_ID_INT(DOT)]) { + result &= compileDot(stream); + } + if (result) { + result &= DFCIRConverter(config).convertAndPrint(compiled, + outputStreams, + sched); + } // Every created output stream has to be closed explicitly. for (llvm::raw_fd_ostream *stream : outputStreams) { if (stream) { diff --git a/src/model/dfcxx/templates/dot.tpl b/src/model/dfcxx/templates/dot.tpl new file mode 100644 index 0000000..8d4268a --- /dev/null +++ b/src/model/dfcxx/templates/dot.tpl @@ -0,0 +1,15 @@ +{{#LICENSE}} +{{!===---------------------------------------------------------------------===}} +{{! }} +{{! Part of the Utopia HLS Project, under the Apache License v2.0 }} +{{! SPDX-License-Identifier: Apache-2.0 }} +{{! Copyright 2024 ISP RAS (http://www.ispras.ru) }} +{{! }} +{{!===---------------------------------------------------------------------===}} +{{/LICENSE}}// This file has been automatically generated by Utopia HLS at {{GEN_TIME}}. +digraph {{KERNEL_NAME}} { + {{#ELEMENTS}}{{NAME}} [shape={{SHAPE}}, label="{{LABEL}}"] + {{#CONNECTIONS}}{{SRC_NAME}} -> {{TRG_NAME}} [label="{{CON_LABEL}}"] + {{/CONNECTIONS}} + {{/ELEMENTS}} +} diff --git a/src/options.h b/src/options.h index 1ad7e61..e762926 100644 --- a/src/options.h +++ b/src/options.h @@ -39,6 +39,7 @@ #define OUT_SV_LIB_JSON "out_sv_lib" #define OUT_DFCIR_JSON "out_dfcir" #define OUT_FIRRTL_JSON "out_firrtl" +#define OUT_DOT_JSON "out_dot" //===----------------------------------------------------------------------===// // CLI args/flags definitions @@ -53,6 +54,7 @@ #define OUT_SV_LIB_ARG CLI_ARG("out-sv-lib") #define OUT_DFCIR_ARG CLI_ARG("out-dfcir") #define OUT_FIRRTL_ARG CLI_ARG("out-firrtl") +#define OUT_DOT_ARG CLI_ARG("out-dot") //===----------------------------------------------------------------------===// @@ -195,6 +197,9 @@ struct HlsOptions final : public AppOptions { outputGroup->add_option(OUT_FIRRTL_ARG, outNames[OUT_FORMAT_ID_INT(FIRRTL)], "Path to output scheduled FIRRTL"); + outputGroup->add_option(OUT_DOT_ARG, + outNames[OUT_FORMAT_ID_INT(DOT)], + "Path to output a DFCxx kernel in DOT format."); outputGroup->require_option(); } @@ -206,6 +211,7 @@ struct HlsOptions final : public AppOptions { get(json, OUT_SV_LIB_JSON, outNames[OUT_FORMAT_ID_INT(SVLibrary)]); get(json, OUT_DFCIR_JSON, outNames[OUT_FORMAT_ID_INT(DFCIR)]); get(json, OUT_FIRRTL_JSON, outNames[OUT_FORMAT_ID_INT(FIRRTL)]); + get(json, OUT_DOT_JSON, outNames[OUT_FORMAT_ID_INT(DOT)]); } std::string latConfigFile; diff --git a/test/model/dfcxx/output_formats.cpp b/test/model/dfcxx/output_formats.cpp index 63c342c..337af74 100644 --- a/test/model/dfcxx/output_formats.cpp +++ b/test/model/dfcxx/output_formats.cpp @@ -47,13 +47,22 @@ TEST(DFCxxOutputFormats, FIRRTL) { EXPECT_EQ(kernel.compile(config, paths, dfcxx::ASAP), true); } +TEST(DFCxxOutputFormats, DOT) { + Polynomial2 kernel; + DFOutputPaths paths = { + {dfcxx::OutputFormatID::DOT, NULLDEVICE} + }; + EXPECT_EQ(kernel.compile(config, paths, dfcxx::ASAP), true); +} + TEST(DFCxxOutputFormats, All) { Polynomial2 kernel; DFOutputPaths paths = { {dfcxx::OutputFormatID::SystemVerilog, NULLDEVICE}, {dfcxx::OutputFormatID::SVLibrary, NULLDEVICE}, {dfcxx::OutputFormatID::DFCIR, NULLDEVICE}, - {dfcxx::OutputFormatID::FIRRTL, NULLDEVICE} + {dfcxx::OutputFormatID::FIRRTL, NULLDEVICE}, + {dfcxx::OutputFormatID::DOT, NULLDEVICE} }; EXPECT_EQ(kernel.compile(config, paths, dfcxx::ASAP), true); }