Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

SystemVerilog stubs generation #45

Merged
merged 8 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Download APT dependencies
run: |
sudo apt update
sudo apt install build-essential clang cmake g++ gcc liblpsolve55-dev lld make ninja-build
sudo apt install build-essential clang cmake g++ gcc liblpsolve55-dev lld make ninja-build libctemplate-dev

- name: Download and configure CIRCT & LLVM
env:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ It is recommended to use Utopia HLS on Debian-based operating systems (e.g. Ubun
* `cmake` ver. 3.20.0 or higher (*)
* `liblpsolve55-dev`
* `ninja-build` (preferred) or `make`
* `libctemplate-dev` as a template generator for SystemVerilog stubs

The following command can be used to install all of these dependencies regardless of what exactly will be used to compile Utopia HLS:

```bash
sudo apt install build-essential clang cmake g++ gcc liblpsolve55-dev lld make ninja-build
sudo apt install build-essential clang cmake g++ gcc liblpsolve55-dev lld make ninja-build libctemplate-dev
```

(*)**Note**: in case `cmake` which was installed from `apt install` has a version lower than 3.20.0, follow this [guide](https://apt.kitware.com/) and use `sudo apt install cmake` again.
Expand Down Expand Up @@ -217,6 +218,7 @@ The list of arguments for `hls`-mode is presented below:
* `-h,--help`: *optional* flag; used to print the help-message about other arguments.
* `--config <PATH>`: *required* filesystem-path option; used to specify the file for a JSON latency configuration file. Its format is presented in *JSON Configuration* section.
* `--out-sv <PATH>`: *optional* filesystem-path option; used to specify the output SystemVerilog file.
* `--out-sv-lib <PATH>`: *optional* filesystem-path option; used to specify the output SystemVerilog file for generated operations library.
* `--out-dfcir <PATH>`: *optional* filesystem-path option; used to specify the output DFCIR file.
* `--out-firrtl <PATH>`: *optional* filesystem-path option; used to specify the output FIRRTL 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**.
Expand Down
42 changes: 42 additions & 0 deletions cmake/FindCTemplate.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
find_path(CTemplate_INCLUDE_DIR "ctemplate/template.h"
PATH_SUFFIXES include)

if(NOT CTemplate_LIBRARY)
find_library(CTemplate_LIBRARY ctemplate PATH_SUFFIXES lib)
endif()

if(NOT CTemplate_nothreads_LIBRARY)
find_library(CTemplate_nothreads_LIBRARY ctemplate_nothreads
PATH_SUFFIXES lib)
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CTemplate
REQUIRED_VARS CTemplate_LIBRARY CTemplate_INCLUDE_DIR)

if(CTemplate_FOUND)
set(CTemplate_INCLUDE_DIRS ${CTemplate_INCLUDE_DIR})

if(NOT CTemplate_LIBRARIES)
set(CTemplate_LIBRARIES ${CTemplate_LIBRARY})
endif()

if(NOT TARGET CTemplate::CTemplate)
add_library(CTemplate::CTemplate UNKNOWN IMPORTED)
set_target_properties(CTemplate::CTemplate PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${CTemplate_INCLUDE_DIRS}"
INTERFACE_LINK_LIBRARIES "$<LINK_ONLY:Threads::Threads>"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${CTemplate_LIBRARY}"
)
endif()

if((NOT TARGET CTemplate::nothreads) AND (CTemplate_nothreads_LIBRARY))
add_library(CTemplate::nothreads UNKNOWN IMPORTED)
set_target_properties(CTemplate::nothreads PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${CTemplate_INCLUDE_DIRS}"
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${CTemplate_nothreads_LIBRARY}"
)
endif()
endif()
1 change: 1 addition & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"asap_scheduler" : false,
"lp_scheduler" : false,
"out_sv" : "",
"out_sv_lib" : "",
"out_dfcir" : "",
"out_firrtl" : ""
}
Expand Down
1 change: 1 addition & 0 deletions src/model/dfcir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ set(CMAKE_CXX_STANDARD 17)
# TODO: Figure out how to pass Tablegen includes the other way.
# Issue #15 (https://github.com/ispras/utopia-hls/issues/15).
include_directories(${MLIR_INCLUDE_DIRS})
find_package(CTemplate REQUIRED COMPONENTS nothreads)
add_subdirectory(include)
add_subdirectory(lib)
8 changes: 5 additions & 3 deletions src/model/dfcir/include/dfcir/conversions/DFCIRPasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "dfcir/DFCIROperations.h"
#include "mlir/Pass/Pass.h"

#include "llvm/Support/raw_ostream.h"

#include "memory"

namespace mlir::dfcir {
Expand Down Expand Up @@ -65,15 +67,15 @@ namespace mlir::dfcir {
using std::unique_ptr;
using mlir::Pass;

unique_ptr<Pass> createDFCIRToFIRRTLPass(LatencyConfig *config = nullptr);
unique_ptr<Pass> createDFCIRToFIRRTLPass(LatencyConfig *config);

unique_ptr<Pass> createDFCIRASAPSchedulerPass();

unique_ptr<Pass> createDFCIRLinearSchedulerPass();

} // namespace mlir::dfcir
unique_ptr<Pass> createFIRRTLStubGeneratorPass(llvm::raw_ostream *stream);

#define GEN_PASS_REGISTRATION
} // namespace mlir::dfcir

#include "dfcir/conversions/DFCIRPasses.h.inc"

Expand Down
14 changes: 14 additions & 0 deletions src/model/dfcir/include/dfcir/conversions/DFCIRPasses.td
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,18 @@ def DFCIRLinearSchedulerPass: Pass<"dfcir-linear-scheduler-pass", "mlir::ModuleO
let constructor = "mlir::dfcir::createDFCIRLinearSchedulerPass()";
}

def FIRRTLStubGeneratorPass: Pass<"firrtl-stub-generator-pass", "mlir::ModuleOp"> {
let summary = "Generate stub modules for pipelined computational operations.";

let options = [
Option<"stream",
"stream",
"llvm::raw_ostream *",
"nullptr",
"Stream to dump stubs to.">
];

let constructor = "mlir::dfcir::createFIRRTLStubGeneratorPass()";
}

#endif // DFCIR_Passes
5 changes: 5 additions & 0 deletions src/model/dfcir/lib/dfcir/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ target_include_directories(MLIRDFCIR
## Issue #16 (https://github.com/ispras/utopia-hls/issues/16).
)

set(TEMPLATES_PATH "${PROJECT_SOURCE_DIR}/templates")

add_compile_definitions(TEMPLATES_PATH="${TEMPLATES_PATH}" STUBS_TEMPLATE_PATH="${TEMPLATES_PATH}/stubs.tpl")

target_link_libraries(MLIRDFCIR
PUBLIC
MLIRIR
Expand All @@ -29,6 +33,7 @@ target_link_libraries(MLIRDFCIR

PRIVATE
LpSolve::LpSolve
CTemplate::nothreads
)

add_library(Utopia::MLIRDFCIR ALIAS MLIRDFCIR)
1 change: 1 addition & 0 deletions src/model/dfcir/lib/dfcir/conversions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set(PASSES
conversions/DFCIRASAPSchedulerPass.cpp
conversions/DFCIRLinearSchedulerPass.cpp
conversions/DFCIRLPUtils.cpp
conversions/FIRRTLStubsGeneratorPass.cpp
)

set(CONV_LIBS
Expand Down
7 changes: 6 additions & 1 deletion src/model/dfcir/lib/dfcir/conversions/DFCIRPassesUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ inline FExtModuleOp createBufferModule(OpBuilder &builder,
auto typeWidth =
circt::firrtl::getBitWidth(llvm::dyn_cast<FIRRTLBaseType>(type));
assert(typeWidth.has_value());
return builder.create<FExtModuleOp>(
IntegerType attrType = mlir::IntegerType::get(builder.getContext(), 32,
mlir::IntegerType::Unsigned);
auto module = builder.create<FExtModuleOp>(
loc,
mlir::StringAttr::get(builder.getContext(), name),
circt::firrtl::ConventionAttr::get(builder.getContext(),
Convention::Internal),
ports,
StringRef(name),
mlir::ArrayAttr());
module->setAttr(INSTANCE_LATENCY_ATTR,
mlir::IntegerAttr::get(attrType, stages));
return module;
}

inline FExtModuleOp createBufferModuleWithTypeName(OpBuilder &builder,
Expand Down
180 changes: 180 additions & 0 deletions src/model/dfcir/lib/dfcir/conversions/FIRRTLStubsGeneratorPass.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//===----------------------------------------------------------------------===//
//
// 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)
//
//===----------------------------------------------------------------------===//

#include "dfcir/conversions/DFCIRPasses.h"
#include "dfcir/conversions/DFCIRPassesUtils.h"

#include "circt/Dialect/FIRRTL/FIRRTLDialect.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "ctemplate/template.h"
#include "mlir/IR/BuiltinOps.h"

#include <algorithm>
#include <ctime>
#include <optional>
#include <string>

namespace mlir::dfcir {

#define GEN_PASS_DECL_FIRRTLSTUBGENERATORPASS
#define GEN_PASS_DEF_FIRRTLSTUBGENERATORPASS

#include "dfcir/conversions/DFCIRPasses.h.inc"

class FIRRTLStubGeneratorPass
: public impl::FIRRTLStubGeneratorPassBase<FIRRTLStubGeneratorPass> {
using TemplateDictionary = ctemplate::TemplateDictionary;
using FExtModuleOp = circt::firrtl::FExtModuleOp;
using CircuitOp = circt::firrtl::CircuitOp;
using FIRRTLBaseType = circt::firrtl::FIRRTLBaseType;
using IntType = circt::firrtl::IntType;

private:
TemplateDictionary *processFIFOModule(TemplateDictionary *dict,
FExtModuleOp module) {
TemplateDictionary *result = dict->AddSectionDictionary("FIFO_MODULES");
auto ports = module.getPorts();
auto res1Type = llvm::cast<FIRRTLBaseType>(module.getPortType(0));
int32_t width = res1Type.getBitWidthOrSentinel();
result->SetFormattedValue("WIDTH", "%d", width - 1);
result->SetValue("RES1", ports[0].getName().data());
result->SetValue("ARG1", ports[1].getName().data());
result->SetValue("CLK", ports[2].getName().data());
return result;
}

TemplateDictionary *processBinModule(TemplateDictionary *dict,
FExtModuleOp module,
unsigned latency) {
TemplateDictionary *result = dict->AddSectionDictionary("BINARY_MODULES");
auto moduleName = module.getModuleName();
if (moduleName.contains(ADD_MODULE)) {
result->SetValue("OP", "+");
} else if (moduleName.contains(SUB_MODULE)) {
result->SetValue("OP", "-");
} else if (moduleName.contains(MUL_MODULE)) {
result->SetValue("OP", "*");
} else {
module.emitError("Unsupported binary operation.");
return nullptr;
}
auto ports = module.getPorts();
auto res1Type = llvm::cast<FIRRTLBaseType>(module.getPortType(0));
bool isSigned = llvm::cast<IntType>(res1Type).isSigned();
int32_t width3 = res1Type.getBitWidthOrSentinel();
result->SetFormattedValue("WIDTH3", "%d", width3 - 1);
auto arg1Type = llvm::cast<FIRRTLBaseType>(module.getPortType(1));
int32_t width1 = arg1Type.getBitWidthOrSentinel();
result->SetFormattedValue("WIDTH1", "%d", width1 - 1);
auto arg2Type = llvm::cast<FIRRTLBaseType>(module.getPortType(2));
int32_t width2 = arg2Type.getBitWidthOrSentinel();
result->SetFormattedValue("WIDTH2", "%d", width2 - 1);
int32_t rWidth = std::max(width1, width2);
result->SetFormattedValue("RWIDTH", "%d", rWidth - 1);
result->SetValue("RES1", ports[0].getName().data());
auto arg1 = ports[1].getName().data();
result->SetValue("ARG1", arg1);
auto arg2 = ports[2].getName().data();
result->SetValue("ARG2", arg2);
result->SetValue("CLK", ports[3].getName().data());
int32_t repeat1 = std::max(rWidth - width1, 0);
result->SetFormattedValue("REPEAT1", "%d", repeat1);
int32_t repeat2 = std::max(rWidth - width2, 0);
result->SetFormattedValue("REPEAT2", "%d", repeat2);
int32_t repeat3 = std::max(width3 - rWidth, 0);
result->SetFormattedValue("REPEAT3", "%d", repeat3);
int32_t diff = std::min(width3, rWidth);
result->SetFormattedValue("DIFF", "%d", diff - 1);
int32_t cat = std::max(width3, rWidth);
result->SetFormattedValue("CAT", "%d", cat - 1);
if (isSigned) {
result->SetFormattedValue("REPEAT_VAL1", "%s[%d]", arg1, width1 - 1);
result->SetFormattedValue("REPEAT_VAL2", "%s[%d]", arg2, width2 - 1);
result->SetFormattedValue("REPEAT_VAL3", "r[%d][%d]",
latency - 1, rWidth - 1);
} else {
result->SetValue("REPEAT_VAL1", "1'h0");
result->SetValue("REPEAT_VAL2", "1'h0");
result->SetValue("REPEAT_VAL3", "1'h0");
}
return result;
}

LogicalResult fillDictionary(TemplateDictionary *dict, CircuitOp circuit) {
Block *block = circuit.getBodyBlock();
auto begin = block->op_begin<FExtModuleOp>();
auto end = block->op_end<FExtModuleOp>();
for (auto op = begin; op != end; ++op) {
auto moduleName = (*op).getModuleName();
unsigned latency =
(*op)->getAttr(INSTANCE_LATENCY_ATTR)
.cast<IntegerAttr>().getUInt();
TemplateDictionary *moduleDict =
moduleName.contains(BUF_MODULE) ?
processFIFOModule(dict, *op) :
processBinModule(dict, *op, latency);
if (!moduleDict) {
return failure();
}
moduleDict->SetFormattedValue("LATENCY", "%u", latency - 1);
moduleDict->SetValue("MODULE_NAME", moduleName.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);
return success();
}

std::optional<std::string> generateOutput() {
std::string result;
TemplateDictionary *topLevelDict = new TemplateDictionary("stubs");

mlir::Operation *op = getOperation();
CircuitOp circuit = mlir::utils::findFirstOccurence<CircuitOp>(op);

if (failed(fillDictionary(topLevelDict, circuit))) {
delete topLevelDict;
return {};
}
ctemplate::ExpandTemplate(STUBS_TEMPLATE_PATH, ctemplate::DO_NOT_STRIP,
topLevelDict, &result);
delete topLevelDict;
return result;
}

public:
explicit FIRRTLStubGeneratorPass(const FIRRTLStubGeneratorPassOptions &opt)
: impl::FIRRTLStubGeneratorPassBase<FIRRTLStubGeneratorPass>(opt) {}

void runOnOperation() override {
auto outputOrError = generateOutput();

if (!outputOrError) {
return signalPassFailure();
}

*stream << *outputOrError;
}
};

std::unique_ptr<mlir::Pass>
createFIRRTLStubGeneratorPass(llvm::raw_ostream *stream) {
FIRRTLStubGeneratorPassOptions options;
options.stream = stream;
return std::make_unique<FIRRTLStubGeneratorPass>(options);
}

} // namespace mlir::dfcir
Loading