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

  1. Create a new directory in benchmark/userbench, with a C source file of the same name in inside.
  2. Also create a Makefile in that new directory, with the contents include ../rules/DFG_from_source_code.mk. There are other options that can be passed by setting variables above the include line.
  3. Modify <source-code-requirement-label>{.interpreted-text role=”ref”} your source code and include the dfg-loop-tag-label in the loop of your interest.
  4. 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 of loop means that unless multiple loops are being extracted, there is no reason to set it
  • DFG_FLAGS --Passed to opt when creating the DFG. Currently there are no user-spcefiable flags
  • CFLAGS --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.

  1. 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.
  2. 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.
  3. 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

  1. Setup -- Create the file & generation skeleton function.
  2. Adding primitives --Here you add the different PEs which will make up the architecture.
  3. 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.

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.

  1. Create a class. This class must inherit from Module.
  2. 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");
    }
    
  3. 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));