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

[docs] Add basic pass tutorial #7012

Merged
merged 15 commits into from
May 10, 2024
Merged
127 changes: 125 additions & 2 deletions docs/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ $ mkdir llvm/build
$ cd llvm/build
$ cmake -G Ninja ../llvm \
-DLLVM_ENABLE_PROJECTS="mlir" \
-DLLVM_TARGETS_TO_BUILD="X86;RISCV" \
-DLLVM_TARGETS_TO_BUILD="host" \
-DLLVM_ENABLE_ASSERTIONS=ON \
-DCMAKE_BUILD_TYPE=DEBUG \
dobios marked this conversation as resolved.
Show resolved Hide resolved
-DLLVM_USE_SPLIT_DWARF=ON \
Expand Down Expand Up @@ -282,4 +282,127 @@ Patches are submitted to LLVM/MLIR via GitHub pull-requests, the basic flow is a
6) Publish the branch on your fork of the repository and create a GitHub pull-request.

When your review converges and your patch is approved, it can be merged directly on GitHub.
If you have commit access, you can do this yourself, otherwise a reviewer can do it for you.
If you have commit access, you can do this yourself, otherwise a reviewer can do it for you.

## Writing a basic CIRCT Pass

Passes can be added at several levels in CIRCT. Here we illustrate this with a simple pass, targetting
the `hw` dialect, that replaces all of the wire names with `foo. This example is very basic and is
meant for people who want to get a quick and dirty start at writing passes for CIRCT. For more detailed
tutorials, we recommend looking at the [MLIR docs](https://mlir.llvm.org/docs/PassManagement/), and their
[Toy tutorials](https://mlir.llvm.org/docs/Tutorials/Toy/).
To add a simple dialect pass, that doesn't perform any dialect conversion, you can do the following:
1) Update your dialect’s Passes.td file to define what your pass will do in a high-level, well documented way,
e.g. in `include/circt/Dialect/HW/Passes.td`:
```
def FooWires : Pass<"hw-foo-wires", "hw::HwModuleOp"> {
let summary = "Change all wires' name to foo_<n>.";
let description = [{
Very basic pass that numbers all of the wires in a given module.
The wires' names are then all converte to foo_<that number>.
}];
let constructor = "circt::hw::createFooWiresPass()";
}
```
Once this is added, compile CIRCT. This will generate a base class for your pass following the naming
defined in the [tablegen description](https://mlir.llvm.org/docs/PassManagement/#tablegen-specification).
This base class will contain some methods that need to be implemented.
Your goal is to now implement those.

2) Create a new file that contains your pass in the dialect's pass folder, e.g. in `lib/Dialect/HW/Transforms`.
Don't forget to also add it in the folder's CMakeLists.txt.
This file should implement:
- A struct with the name of your pass, which extends the pass base class generated by the tablegen description.
- This struct should define an override of the `void runOnOperation()` method, e.g.
```cpp
namespace {
// A test pass that simply replaces all wire names with foo_<n>
struct FooWiresPass : FooWiresBase<FooWiresPass> {
void runOnOperation() override;
};
}
```
- The `runOnOperation` method will contain the actual logic of your pass, which you can now implement, e.g.
```cpp
void FooWiresPass::runOnOperation() {
size_t nWires = 0; // Counts the number of wires modified
getOperation().walk([&](hw::WireOp wire) { // Walk over every wire in the module
wire.setName("foo_" + std::to_string(nWires++)); // Rename said wire
});
}
```
> *Note*: Here `getOperation().walk([&](WireOp wire) { ... });` is used to traverse every wire in the design, you can also
> use it to generically walk over all operations and then visit them individually
> using a [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) by overloading type visitors
> defined by each dialect. More details can be found in the
> [MLIR docs](https://mlir.llvm.org/docs/Tutorials/UnderstandingTheIRStructure/), e.g.
```cpp
#include "circt/Dialect/HW/HWVisitors.h" // defines dispatchTypeOpVisitor which calls visit(op)

namespace {
// A test pass that simply replaces all wire names with foo_<n>
struct VisitorExamplePass
: public VisitorExampleBase<VisitorExamplePass>,
public hw::TypeOpVisitor<VisitorExamplePass> // Allows for the visitor overloads to be added
{
public:
void runOnOperation() override;

// Vistor overloads
void visitTypeOp(hw::ConstantOp op) { /*...*/ }
void visitTypeOp(/*other hw operations*/) { /*...*/ }
};
}

void VisitorExamplePass::runOnOperation() {
module.walk([&](Operation* op) { dispatchTypeOpVisitor(op); });
}
```
- Finally implement the constructor for the pass, as defined in the tablegen description, e.g.
```cpp
std::unique_ptr<mlir::Pass> circt::hw::createFooWiresPass() {
return std::make_unique<FooWiresPass>();
}
```

3) Define the pass constructor in the dialect's `Passes.h` file, e.g. in `include/circt/Dialect/hw/HWPasses.h` add:
```cpp
std::unique_ptr<mlir::Pass> createFooWiresPass();
```
4) Make sure to add a test to check your pass, e.g. in `test/Dialect/HW`:
```mlir
// RUN: circt-opt --hw-foo-wires %s | FileCheck %s

hw.module @foo(in %a: i32, in %b: i32, out out: i32) {
// CHECK: %c1_i32 = hw.constant 1 : i32
// CHECK: %foo_0 = hw.wire %c1_i32 : i32
// CHECK: %foo_1 = hw.wire %a : i32
// CHECK: %foo_2 = hw.wire %b : i32
// CHECK: %0 = comb.add bin %foo_1, %foo_0 : i32
// CHECK: %foo_3 = hw.wire %0 : i32
// CHECK: %1 = comb.add bin %foo_3, %foo_2 : i32
// CHECK: %foo_4 = hw.wire %1 : i32
// CHECK: hw.output %foo_4 : i32
%c1 = hw.constant 1 : i32
%wire_1 = hw.wire %c1 : i32
%wire_a = hw.wire %a : i32
%wire_b = hw.wire %b : i32
%ap1 = comb.add bin %wire_a, %wire_1 : i32
%wire_ap1 = hw.wire %ap1 : i32
%ap1pb = comb.add bin %wire_ap1, %wire_b : i32
%wire_ap1pb = hw.wire %ap1pb : i32
hw.output %wire_ap1pb : i32
}
```
Now re-run cmake and compile CIRCT and you should be able to run your new pass!
By default, every pass is accessible using `circt-opt` through the name that was defined in the tablegen description,
e.g. `circt-opt --hw-foo-wires <file_name>.mlir`.
These passes can also be included in certain compilation pipelines that are well packaged in tools like firtool.
We recommend looking at recently merged Pull-Requests or other [MLIR tutorials](https://mlir.llvm.org/docs/Tutorials/)
to learn how to go beyond what we've shown in this quick start guide.
The full source code of this pass is available at the following links:
- Pass Source: [FooWires.cpp](https://github.com/llvm/circt/blob/main/lib/Dialect/HW/Transforms/FooWires.cpp)
- Pass Test: [foo.mlir](https://github.com/llvm/circt/blob/main/test/Dialect/HW/foo.mlir)
- HW Dialect Pass Header: [HWPasses.h](https://github.com/llvm/circt/blob/main/include/circt/Dialect/HW/HWPasses.h#L32)
- Tablegen Description: [Passes.td](https://github.com/llvm/circt/blob/main/include/circt/Dialect/HW/Passes.td#L81-L88)
- [CMakeLists.txt](https://github.com/llvm/circt/blob/main/lib/Dialect/HW/Transforms/CMakeLists.txt#L8)
1 change: 1 addition & 0 deletions include/circt/Dialect/HW/HWPasses.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ std::unique_ptr<mlir::Pass> createFlattenIOPass(bool recursiveFlag = true,
char joinChar = '.');
std::unique_ptr<mlir::Pass> createVerifyInnerRefNamespacePass();
std::unique_ptr<mlir::Pass> createFlattenModulesPass();
std::unique_ptr<mlir::Pass> createFooWiresPass();

/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
Expand Down
12 changes: 12 additions & 0 deletions include/circt/Dialect/HW/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,16 @@ def VerifyInnerRefNamespace : Pass<"hw-verify-irn"> {
let constructor = "circt::hw::createVerifyInnerRefNamespacePass()";
}

/**
* Tutorial Pass, doesn't do anything interesting
*/
def FooWires : Pass<"hw-foo-wires", "hw::HWModuleOp"> {
let summary = "Change all wires' name to foo_<n>.";
let description = [{
Very basic pass that numbers all of the wires in a given module.
The wires' names are then all converte to foo_<that number>.
}];
let constructor = "circt::hw::createFooWiresPass()";
}

#endif // CIRCT_DIALECT_HW_PASSES_TD
1 change: 1 addition & 0 deletions lib/Dialect/HW/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_circt_dialect_library(CIRCTHWTransforms
FlattenIO.cpp
VerifyInnerRefNamespace.cpp
FlattenModules.cpp
FooWires.cpp

DEPENDS
CIRCTHWTransformsIncGen
Expand Down
38 changes: 38 additions & 0 deletions lib/Dialect/HW/Transforms/FooWires.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===- FooWires.cpp - Replace all wire names with foo ------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//===----------------------------------------------------------------------===//
//
// Replace all wire names with foo.
//
//===----------------------------------------------------------------------===//

#include "PassDetails.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/HWPasses.h"
#include "circt/Dialect/HW/HWTypes.h"
#include "mlir/Pass/Pass.h"

using namespace circt;
using namespace hw;

namespace {
// A test pass that simply replaces all wire names with foo_<n>
struct FooWiresPass : FooWiresBase<FooWiresPass> {
void runOnOperation() override;
};
} // namespace

void FooWiresPass::runOnOperation() {
size_t nWires = 0; // Counts the number of wires modified
getOperation().walk(
[&](hw::WireOp wire) { // Walk over every wire in the module
wire.setName("foo_" + std::to_string(nWires++)); // Rename said wire
});
}

std::unique_ptr<mlir::Pass> circt::hw::createFooWiresPass() {
return std::make_unique<FooWiresPass>();
}
22 changes: 22 additions & 0 deletions test/Dialect/HW/foo.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: circt-opt --hw-foo-wires %s | FileCheck %s

hw.module @foo(in %a: i32, in %b: i32, out out: i32) {
// CHECK: %c1_i32 = hw.constant 1 : i32
// CHECK: %foo_0 = hw.wire %c1_i32 : i32
// CHECK: %foo_1 = hw.wire %a : i32
// CHECK: %foo_2 = hw.wire %b : i32
// CHECK: %0 = comb.add bin %foo_1, %foo_0 : i32
// CHECK: %foo_3 = hw.wire %0 : i32
// CHECK: %1 = comb.add bin %foo_3, %foo_2 : i32
// CHECK: %foo_4 = hw.wire %1 : i32
// CHECK: hw.output %foo_4 : i32
%c1 = hw.constant 1 : i32
%wire_1 = hw.wire %c1 name "wire_1" : i32
%wire_a = hw.wire %a name "wire_a" : i32
%wire_b = hw.wire %b name "wire_b" : i32
%ap1 = comb.add bin %wire_a, %wire_1 : i32
%wire_ap1 = hw.wire %ap1 name "wire_ap1" : i32
%ap1pb = comb.add bin %wire_ap1, %wire_b : i32
%wire_ap1pb = hw.wire %ap1pb : i32
hw.output %wire_ap1pb : i32
}
Loading