DFG Generation Guide
Loop DFG Generation
The user can create their own benchmarks that they want to map to different CGRA architectures. Here is the way of doing that.
Custom DFG/Benchmark
- Create a new directory in
benchmark/userbench
, with a C source file of the same name in inside. - Also create a
Makefile
in that new directory, with the contentsinclude ../rules/DFG_from_source_code.mk
. There are other options that can be passed by setting variables above theinclude
line. Modify <source-code-requirement-label>
{.interpreted-text role=”ref”} your source code and include thedfg-loop-tag-label
in the loop of your interest.- Run
$ make
in the current directory and the DFG with be generated, or$ make run
to run the mapper as well.
There are limitations to what kind of loop can generate DFG and be mapped. Limitations are explained in mapping limitations.
DFG Generation Options
DFG_from_source_code.mk
is sensitive to the following variables when creating the DFG. All have reasonable defaults, and can usually not be set.
LOOP_TAGS
--Space separated list of loop names. The default ofloop
means that unless multiple loops are being extracted, there is no reason to set itDFG_FLAGS
--Passed toopt
when creating the DFG. Currently there are no user-spcefiable flagsCFLAGS
--Flags directly passed when compiling the C source code. Defaults to-O3
, but can be replaced with other flags if the benchmark is not optimized as expcted. Reference to Clang documentation for possible flags.
It is also sensitive to CGRA_MAPPER_ARGS
and CGRA_ARCH_ARGS
when mapping to the DFG.
Source Code Requirement
Some modifications might be required for the DFG generation process to take place correctly. Before modifying any code, you have to have a clear understanding of which functions should be inlined and which functions should not be inlined.
- This DFG generation software can only support single C source file with header files. Make sure every thing that you want to map to a single source file.
- All the functions that contains within your loop of interest should be inlined. You must add
__attribute__((always_inline))
to the functions and put the definitions in the corresponding header file. - All the functions that contains your loop of interest should not be inlined. You must add
__attribute__((noinline))
to the functions and put the definitions in the source file.
DFG Loop Tag
For all the loops that you want to generate DFGs for, you have to add special tags to them.
In the first line of the loop, add //DFGLOOP: loop
which names the loop "loop". If extracting multiple DFGs, replace loop with one of the names in the LOOP_TAGS
variable. See benchmarks/microbench/two_loops
for an example of multiple loops.
C++ API User Guide
The C++ API provides a flexible platform for CGRA architects to create designs in c++ and determine whether or not certain benchmarks can be mapped to them.
Creating a New Architecture
- Setup -- Create the file & generation skeleton function.
- Adding primitives --Here you add the different PEs which will make up the architecture.
- Adding connections --Here, you connect the PEs together. Connections are made between ports and each PE has its own ports with a naming convention, which will be described later.
Setup
Create a file in src/archs
called MyArchitecture.cpp
(for example). You may wish to copy one of the existing architectures, such as [src/archs/ClusteredArch.cpp]{.title-ref} or [src/archs/HyCUBE.cpp]{.title-ref}.
At a minimum, the file must contain the following includes:
#include <CGRA/user-inc/UserArchs.h>
#include <CGRA/user-inc/UserModules.h>
And a custom member function of the UserArchs class:
std::unique_ptr<CGRA> UserArchs::createMyArch(const ConfigStore& args) {
std::unique_ptr<CGRA> cgra_storage(new CGRA());
auto& cgra = cgra_storage->getTopLevelModule();
// architecture generation code goes here -- add modules, connections, etc.
return cgra_storage;
}
Something like the following must also be added to UserArchs
's constructor to register the mapper and it's expected/default arguments.
registerGenerator("myarch", ArchitectureGenerator { "My CGRA Architecture", createMyArch, {
{"arg1", "default"},
{"arg2", 2},
{"arg3", "default3"},
}});
Adding Processing Elements
Here is a curated list of some modules that are available to add to your architecture. A complete list can be found by looking at the inheritance diagram for the Module class, which all modules derive from.
- FuncUnit Arithmetic operations.
- MemoryUnit Load and store operations.
- Multiplexer Zero-latency multiplexer.
- Register A data flip-flop.
- RegisterFile Multiplexed registers.
- IO Input/Output port. Shows up at the top level of generated Verilog.
- ConfigCell Configuration storage. Usually only needed for custom modules. Must be added with addConfig.
Modules can be added to cgra
with code like this example for FuncUnit
. All other modules can be added this way. Be sure that no two modules have the same name.
cgra.addSubModule(new FuncUnit("fu_1", {OpCode::ADD, OpCode::MUL}, 32 /* bit-width */));
Adding Connections
When you connect PEs together in the architecture, you must make connections by port names. Here in an example of connecting the output of a FuncUnit
, fu_1
, to the left input of another FuncUnit
, fu_2
.
cgra.addConnection("fu_1.out", "fu_2.in_a");
Creating Your Own Processing Element
In the C++ API, we allow the user to create a "black box" with user defined functionality and naming conventions. The user is able to create their own processing element that can consist of a combination of the provided processing elements. For a complete example of the C++ API being used, look at AdresPE.
- Create a class. This class must inherit from Module.
The constructor for your class is where the creation of the custom module happens.
- Compose your module of other modules by adding submodules
- Add ports for other modules to connect to.
- Add any ConfigCells. It will be automatically added to the scan-chain.
- Connect ports and submodules to other ports and submodules to create functionality, just like when making a CGRA. If you want to refer a the port you created inside the constructor, you must use
this.portName
.
For example,
MyModule::MyModule(std::string name, int size, int your, int argument) : Module(name, size) { addSubModule(new Register("reg")); addConfig(new ConfigCell("reg_en_cc"), {"reg.enable"}); addPort("reg_in", PORT_INPUT, size); addConnection("this.reg_in", "reg.in"); }
- Once you have created your module, be sure to include its header file into your main architecture file, then add it and connect it to other modules just like anything else.
cgra.addSubModule(new MyModule("name", size, your, arguments));