Skip to content

Commit

Permalink
Merge pull request #45 from ispras/gen_stubs
Browse files Browse the repository at this point in the history
SystemVerilog stubs generation
  • Loading branch information
ssmolov authored Aug 8, 2024
2 parents 1b884c2 + a7237a9 commit 768cca0
Show file tree
Hide file tree
Showing 16 changed files with 326 additions and 7 deletions.
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 @@ -10,7 +10,9 @@
#define DFCIR_PASSES_H

#include "dfcir/DFCIROperations.h"

#include "mlir/Pass/Pass.h"
#include "llvm/Support/raw_ostream.h"

#include "memory"

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
8 changes: 8 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,13 @@ 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 +36,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

0 comments on commit 768cca0

Please sign in to comment.