Utopia is an open-source high-level synthesis tool for streaming dataflow computers.
With the use of its domain-specific language, DFCxx, a streaming computation dataflow can be specified. This specification is used to compile a generator for corresponding SystemVerilog modules.
DFCxx provides a library of C++ classes (like Stream
, Scalar
, Kernel
), allowing relations between objects to represent the computational logic of a streaming computer.
By inheriting from abstract class dfcxx::Kernel
and implementing at least a single constructor, this new class represents the main unit of computation, kernel.
The following code describes a kernel for the pipelined computation of a second-order polynomial x2 + 2 * x for unsigned 32-bit integer values.
#include "dfcxx/DFCXX.h"
class Polynomial2 : public dfcxx::Kernel {
public:
std::string_view getName() override {
return "Polynomial2"; // Explicitly set kernel's name.
}
~Polynomial2() override = default;
Polynomial2() : dfcxx::Kernel() {
using dfcxx::DFType;
using dfcxx::DFVariable;
const DFType &type = dfUInt(32); // Unsigned 32-bit integer type declaration.
DFVariable x = io.input("x", type); // Input stream named "x" is declared.
DFVariable squared = x * x; // "x * x" is computed.
DFVariable squaredPlusX = squared + x; // "x" is added to "x * x".
DFVariable result = squaredPlusX + x; // "x" is added to "x * x + x".
DFVariable out = io.output("out", type); // Output stream named "out" is declared.
out.connect(result); // "out" stream is connected to the result of computation.
}
};
User-defined source files and include directories for a kernel are used in the compilation of Utopia HLS, so that the resulting binary executable becomes a generator for application-specific SystemVerilog modules.
The computational logic of a streaming dataflow computer has to be statically scheduled. Currently there are two implementations of scheduling algorithms: linear programming and a greedy as-soon-as-possible algorithm.
When the Utopia HLS binary executable is compiled, its integrated command line interface allows a filesystem path to a JSON latency configuration file to be specified, as well as the chosen scheduling strategy.
Utopia HLS is distributed under the Apache License, Version 2.0.
See CODE_STYLE.md
for more details.
It is recommended to use Utopia HLS on Debian-based operating systems (e.g. Ubuntu 20.04). The required dependencies' package names are specific to apt
package manager, present in Debian-based operating systems, and are specified below:
build-essential
clang
+lld
org++
+gcc
as a C/C++ compilercmake
ver. 3.20.0 or higher (*)liblpsolve55-dev
ninja-build
(preferred) ormake
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:
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 and use sudo apt install cmake
again.
Utopia HLS utilizes SystemVerilog-generation capabilities from CIRCT project, which itself is based on LLVM Multi-Level Intermediate Representation (MLIR) project.
Currently supported CIRCT release: 1.72.0, Apr 5 2024 (link)
For both CIRCT and MLIR projects it is possible to download precompiled libraries (this approach is more convenient and saves time), but both projects can also be compiled from sources.
Precompiled releases of CIRCT include the precompiled LLVM MLIR releases as well: visit the corresponding page to see all available releases.
Note that it's the libraries that are required, not the binary executables.
Look for the archives which have the following names:
circt-full-static-<ARCH>
or circt-full-shared-<ARCH>
for static and dynamic libraries respectively.
Current releases have an inconsistency in their configuration files, requiring manual editing of configuration files - this process is described below:
- Downloading the chosen release archive and extract the files.
- Find the file
lib/cmake/mlir/MLIRTargets.mlir
and open it with a text editor of your choice. - Look for the
_NOT_FOUND_MESSAGE_targets
near the end of the file:
for 1.72.0, it's line 3012 forcirct-full-static-linux-x64
-archive and line 3008 forcirct-full-shared-linux-x64.tar
-archive. - Remove all the
"CIRCT*"
-entries from the correspondingforeach
-statement and save the file.
The foreach
-statement before editing the file:
# Make sure the targets which have been exported in some other
# export set exist.
unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)
foreach(_target "LLVMSupport" "LLVMCore" "LLVMMC" "LLVMTarget" "LLVMAsmParser" "LLVMBinaryFormat" "LLVMBitReader" "LLVMBitWriter" "LLVMFrontendOpenMP" "LLVMTransformUtils" "LLVMTargetParser" "LLVMIRReader" "LLVMipo" "LLVMLinker" "LLVMPasses" "LLVMMCParser" "LLVMLineEditor" "LLVMTableGen" "LLVMAnalysis" "LLVMCoroutines" "LLVMAggressiveInstCombine" "LLVMInstCombine" "LLVMScalarOpts" "LLVMVectorize" "LLVMExecutionEngine" "LLVMObject" "LLVMOrcJIT" "LLVMJITLink" "LLVMX86CodeGen" "LLVMX86Desc" "LLVMX86Info" "LLVMX86AsmParser" "LLVMX86Disassembler" "CIRCTAffineToLoopSchedule" "CIRCTArcToLLVM" "CIRCTCalyxToFSM" "CIRCTCalyxToHW" "CIRCTCalyxNative" "CIRCTCombToArith" "CIRCTCombToLLVM" "CIRCTCombToSMT" "CIRCTConvertToArcs" "CIRCTDCToHW" "CIRCTExportChiselInterface" "CIRCTExportVerilog" "CIRCTFIRRTLToHW" "CIRCTFSMToSV" "CIRCTHandshakeToDC" "CIRCTHandshakeToHW" "CIRCTHWArithToHW" "CIRCTHWToLLHD" "CIRCTHWToLLVM" "CIRCTHWToBTOR2" "CIRCTHWToSMT" "CIRCTHWToSV" "CIRCTHWToSystemC" "CIRCTLLHDToLLVM" "CIRCTLoopScheduleToCalyx" "CIRCTMooreToCore" "CIRCTPipelineToHW" "CIRCTSCFToCalyx" "CIRCTSeqToSV" "CIRCTSimToSV" "CIRCTCFToHandshake" "CIRCTVerifToSMT" "CIRCTVerifToSV" "CIRCTExportFIRRTL" "CIRCTComb" "CIRCTCombTransforms" "CIRCTDebug" "CIRCTESI" "CIRCTFIRRTL" "CIRCTImportFIRFile" "CIRCTMSFT" "CIRCTMSFTTransforms" "CIRCTHW" "CIRCTLLHD" "CIRCTMoore" "CIRCTOM" "CIRCTOMEvaluator" "CIRCTSeq" "CIRCTSeqTransforms" "CIRCTSV" "CIRCTSVTransforms" "CIRCTFSM" "CIRCTFSMTransforms" "CIRCTHandshake" "CIRCTHandshakeTransforms" "CIRCTHWArith" "CIRCTVerif" "CIRCTLTL" "CIRCTEmit" "CIRCTFirtool" )
if(NOT TARGET "${_target}" )
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets} ${_target}")
endif()
endforeach()
The foreach
-statement after editing the file:
# Make sure the targets which have been exported in some other
# export set exist.
unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)
foreach(_target "LLVMSupport" "LLVMCore" "LLVMMC" "LLVMTarget" "LLVMAsmParser" "LLVMBinaryFormat" "LLVMBitReader" "LLVMBitWriter" "LLVMFrontendOpenMP" "LLVMTransformUtils" "LLVMTargetParser" "LLVMIRReader" "LLVMipo" "LLVMLinker" "LLVMPasses" "LLVMMCParser" "LLVMLineEditor" "LLVMTableGen" "LLVMAnalysis" "LLVMCoroutines" "LLVMAggressiveInstCombine" "LLVMInstCombine" "LLVMScalarOpts" "LLVMVectorize" "LLVMExecutionEngine" "LLVMObject" "LLVMOrcJIT" "LLVMJITLink" "LLVMX86CodeGen" "LLVMX86Desc" "LLVMX86Info" "LLVMX86AsmParser" "LLVMX86Disassembler" )
if(NOT TARGET "${_target}" )
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets} ${_target}")
endif()
endforeach()
That's it: both CIRCT and LLVM can be used now.
MLIR is included in CIRCT in the form of a Git submodule, so the compilation process starts with cloning the CIRCT repository into some directory CIRCT_DIR
.
cd <WORKDIR>
git clone --depth 1 --branch firtool-1.72.0 https://github.com/llvm/circt/ <CIRCT_DIR>
cd <CIRCT_DIR>
git submodule init
git submodule update
Now that llvm
-subdirectory has been initialized, we may build MLIR in some directory <LLVM_BUILD_DIR>
.
The following commands are used to build the simplest MLIR revision, which allows debugging - RelWithDebInfo
may be changed for Release
or Debug
if the user deems it so.
cd llvm
cmake -S llvm -B <LLVM_BUILD_DIR> -DLLVM_ENABLE_PROJECTS="mlir" -DLLVM_TARGETS_TO_BUILD="host" -DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build <LLVM_BUILD_DIR>
This process is going to take a while.
After building MLIR, it can be used to build CIRCT in some directory <CIRCT_BUILD_DIR>
.
cd <CIRCT_DIR>
cmake -S . -B <CIRCT_BUILD_DIR> -DMLIR_DIR=<LLVM_BUILD_DIR>/lib/cmake/mlir -DLLVM_DIR=<LLVM_BUILD_DIR>/lib/cmake/llvm -DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build <CIRCT_BUILD_DIR>
Utopia HLS is a CMake-based project, so the compilation can be set via as much as two commands.
CMake buildsystem-generation script accepts a number of required and optional arguments (prefixed with -D
), which are presented below:
SRC_FILES
: (quoted) required semicolon-separated list of filesystem paths; is used to specify source files for user-defined kernels to be added to the Utopia HLS compilation.CMAKE_PREFIX_PATH
: standard CMake variable; optional (quoted) semicolon-separated list of full filesystem paths; is used to locate installed CIRCT and MLIR packages. In case a precompiled CIRCT release is used, specifying a path to its unarchived top-level directory is enough (e.g.-DCMAKE_PREFIX_PATH=~/firtool-1.72.0
). In case CIRCT and MLIR are compiled manually, paths to each independent build (not source) directory have to be specified (e.g.-DCMAKE_PREFIX_PATH-"~/circt/build;~/circt/llvm/build"
). This variable may be omitted if CIRCT and MLIR are installed in default system paths.INCLUDE_DIRS
: (quoted) optional semicolon-separated list of filesystem paths; is used to specify include directories for user-defined kernels to be added to the Utopia HLS compilation.OUT
: optional string; used to specify a custom name for the final Utopia HLS executable. Unless it is explicitly provided, the final executable will be namedumain
.BUILD_TESTS
: optional binary (ON
/OFF
) variable; switched toOFF
by default; is used to enable tests compilation (targettest/utest
). Values other thanON
are interpreted asOFF
.
Other standard CMake variables/options may also be specified to affect the final compilation process.
Here is an example of a fully-formed CMake buildsystem-generation script (directory build
may be changed for any other directory):
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_PREFIX_PATH=~/firtool-1.72.0 -DINCLUDE_DIRS=~ -DSRC_FILES="~/utopia-user/simple.cpp;~/utopia-user/buf.cpp" -DOUT=executable -DBUILD_TESTS=ON
To start the compilation process, a simple cmake --build
-command is used.
cmake --build build
User-defined kernels are comprised of valid C++ headers and source files. One of the provided source files has to define a function with the following signature:
std::unique_ptr<dfcxx::Kernel> start();
This function will be called from internal Utopia HLS to get the pointer to the top-level DFCxx kernel.
Kernel-object be a valid DFCxx kernel, instantiating a class derived from dfcxx::Kernel
class.
Here is an example of a valid start
-function definition (some kernel named Polynomial2
is used):
std::unique_ptr<dfcxx::Kernel> start() {
Polynomial2 *kernel = new Polynomial2();
return std::unique_ptr<dfcxx::Kernel>(kernel);
}
The compiled Utopia HLS executable has a CLI to accept a number of parameters affecting the final output. Utopia HLS CLI is based on a hierarchical structure of subcommands, namely modes.
For the executable there are three general arguments: -h,--help
, -H,--help-all
and -v,--version
for printing simple and extended help-messages, and printing current executable's version respectively.
Unless neither of the three arguments is used, first argument is the mode which the executable has to function in. Currently there are only two available modes: hls
and sim
.
hls
mode is used to perform the high-level synthesis of digital hardware description from the input DFCxx kernel. 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 indocs/latency_config.md
.--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.--out-dot <PATH>
: 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.
Here is an example of an Utopia HLS CLI call:
umain hls --config ~/utopia-user/config.json --out-sv ~/outFile.sv --out-dfcir ~/outFile2.mlir -a
sim
mode is used to simulate the input DFCxx kernel. The list of arguments for sim
-mode is presented below:
-h,--help
: optional flag; used to print the help-message about other arguments.--in <PATH>
: optional filesystem-path option; used to specify the input file for simulation data (default:sim.txt
). Its format is presented indocs/simulation.md
.--out <PATH>
: optional filesystem-path option; used to specify the output VCD file (default:sim_out.vcd
).
Root subdirectory examples
contains different examples of DFCxx kernels, start
-function definitions and JSON configuration files.
For example, given subdirectory polynomial2
, the compilation and execution commands might look the following (setting INCLUDE_DIRS
is not necessary, as the required headers reside right next to their executable):
cmake -S . -B build -G Ninja -DCMAKE_PREFIX_PATH=~/firtool-1.72.0 -DSRC_FILES="~/utopia-hls/examples/polynomial2/polynomial2.cpp"
cmake --build build
build/src/umain hls --config examples/polynomial2/add_int_2_mul_int3.json -a --out-sv output
The execution command is going to pass a JSON configuration file (with latencies 2 and 3 for integer addition
and multiplication respectively) to Utopia HLS, resulting in the creation of the file output
, containing a SystemVerilog
module for Polynomial2
kernel with a greedy ASAP-scheduling.
The same kernel can be simulated with:
build/src/umain sim --in examples/polynomial2/sim.txt --out output.vcd
This command uses the simulation data from sim.txt
file and stores the simulation trace in the output file output.vcd
.
WORK IN PROGRESS