[Arc] Add tap op to observe ports/wires and AddTaps pass (#4684)

Add an `arc.tap` operation that allows us to assign a name to an
arbitrary SSA value such that it remains observable after any subsequent
transformations. Also add an `AddTaps` pass which adds an `arc.tap` op
to every module port and every wire, making them observable even after
transformations have completely flattened the original hierarchy.

Co-authored-by: Zachary Yedidia <zyedidia@gmail.com>
This commit is contained in:
Fabian Schuiki 2023-03-16 15:47:42 -07:00 committed by GitHub
parent 597b1b969d
commit 846dd02bde
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 0 deletions

View File

@ -245,6 +245,16 @@ def MemoryWriteOp : ArcOp<"memory_write", [
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// Miscellaneous
//===----------------------------------------------------------------------===//
def TapOp : ArcOp<"tap"> {
let summary = "A tracker op to observe a value under a given name";
let arguments = (ins AnySignlessInteger:$value, StrAttr:$name);
let assemblyFormat = [{ $value attr-dict `:` type($value) }];
}
def LutOp : ArcOp<"lut", [
IsolatedFromAbove,
SingleBlockImplicitTerminator<"arc::OutputOp">,

View File

@ -19,6 +19,9 @@ class Pass;
namespace circt {
namespace arc {
std::unique_ptr<mlir::Pass>
createAddTapsPass(llvm::Optional<bool> tapPorts = {},
llvm::Optional<bool> tapWires = {});
std::unique_ptr<mlir::Pass> createDedupPass();
std::unique_ptr<mlir::Pass> createInferMemoriesPass();
std::unique_ptr<mlir::Pass> createInlineArcsPass();

View File

@ -11,6 +11,16 @@
include "mlir/Pass/PassBase.td"
def AddTaps : Pass<"arc-add-taps", "mlir::ModuleOp"> {
let summary = "Add taps to ports and wires such that they remain observable";
let constructor = "circt::arc::createAddTapsPass()";
let dependentDialects = ["arc::ArcDialect"];
let options = [
Option<"tapPorts", "ports", "bool", "true", "Make module ports observable">,
Option<"tapWires", "wires", "bool", "true", "Make wires observable">
];
}
def Dedup : Pass<"arc-dedup", "mlir::ModuleOp"> {
let summary = "Deduplicate identical arc definitions";
let description = [{

View File

@ -0,0 +1,88 @@
//===- AddTaps.cpp --------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/SV/SVOps.h"
using namespace circt;
using namespace arc;
using namespace hw;
using llvm::Optional;
namespace {
struct AddTapsPass : public AddTapsBase<AddTapsPass> {
void runOnOperation() override {
getOperation().walk([&](Operation *op) {
TypeSwitch<Operation *>(op).Case<HWModuleOp, sv::WireOp, hw::WireOp>(
[&](auto op) { tap(op); });
});
}
// Add taps for all module ports.
void tap(HWModuleOp moduleOp) {
if (!tapPorts)
return;
auto *outputOp = moduleOp.getBodyBlock()->getTerminator();
ModulePortInfo ports = moduleOp.getPorts();
// Add taps to inputs.
auto builder = OpBuilder::atBlockBegin(moduleOp.getBodyBlock());
for (auto [port, arg] : llvm::zip(ports.inputs, moduleOp.getArguments()))
builder.create<arc::TapOp>(arg.getLoc(), arg, port.getName());
// Add taps to outputs.
builder.setInsertionPoint(outputOp);
for (auto [port, result] :
llvm::zip(ports.outputs, outputOp->getOperands()))
builder.create<arc::TapOp>(result.getLoc(), result, port.getName());
}
// Add taps for SV wires.
void tap(sv::WireOp wireOp) {
if (!tapWires)
return;
sv::ReadInOutOp readOp;
for (auto *user : wireOp->getUsers())
if (auto op = dyn_cast<sv::ReadInOutOp>(user))
readOp = op;
OpBuilder builder(wireOp);
if (!readOp) {
builder.setInsertionPointAfter(wireOp);
readOp = builder.create<sv::ReadInOutOp>(wireOp.getLoc(), wireOp);
}
builder.setInsertionPointAfter(readOp);
builder.create<arc::TapOp>(readOp.getLoc(), readOp, wireOp.getName());
}
// Add taps for HW wires.
void tap(hw::WireOp wireOp) {
if (!tapWires)
return;
if (auto name = wireOp.getName()) {
OpBuilder builder(wireOp);
builder.setInsertionPointAfter(wireOp);
builder.create<arc::TapOp>(wireOp.getLoc(), wireOp, *name);
}
}
using AddTapsBase::tapPorts;
using AddTapsBase::tapWires;
};
} // namespace
std::unique_ptr<Pass> arc::createAddTapsPass(Optional<bool> tapPorts,
Optional<bool> tapWires) {
auto pass = std::make_unique<AddTapsPass>();
if (tapPorts)
pass->tapPorts = *tapPorts;
if (tapWires)
pass->tapWires = *tapWires;
return pass;
}

View File

@ -1,4 +1,5 @@
add_circt_dialect_library(CIRCTArcTransforms
AddTaps.cpp
Dedup.cpp
InferMemories.cpp
InlineArcs.cpp

View File

@ -0,0 +1,41 @@
// RUN: circt-opt %s --arc-add-taps | FileCheck %s
// CHECK-LABEL: hw.module @ObservePorts
hw.module @ObservePorts(%x: i4, %y: i4) -> (u: i4, v: i4) {
// CHECK-NEXT: arc.tap %x {name = "x"} : i4
// CHECK-NEXT: arc.tap %y {name = "y"} : i4
// CHECK-NEXT: %0 = comb.add
// CHECK-NEXT: %1 = comb.sub
%0 = comb.add %x, %y : i4
%1 = comb.sub %x, %y : i4
// CHECK-NEXT: arc.tap %0 {name = "u"} : i4
// CHECK-NEXT: arc.tap %1 {name = "v"} : i4
// CHECK-NEXT: hw.output
hw.output %0, %1 : i4, i4
}
// CHECK-NEXT: }
// CHECK-LABEL: hw.module @ObserveWires
hw.module @ObserveWires() {
// CHECK-NEXT: %x = sv.wire
// CHECK-NEXT: [[RD:%.+]] = sv.read_inout %x
// CHECK-NEXT: arc.tap [[RD]] {name = "x"} : i4
%x = sv.wire : !hw.inout<i4>
%0 = sv.read_inout %x : !hw.inout<i4>
// CHECK-NEXT: %y = sv.wire
// CHECK-NEXT: [[RD:%.+]] = sv.read_inout %y
// CHECK-NEXT: arc.tap [[RD]] {name = "y"} : i4
%y = sv.wire : !hw.inout<i4>
// CHECK-NEXT: hw.constant
// CHECK-NEXT: %z = hw.wire
// CHECK-NEXT: arc.tap %z {name = "z"} : i4
%c0_i4 = hw.constant 0 : i4
%z = hw.wire %c0_i4 : i4
// CHECK-NEXT: hw.output
}
// CHECK-NEXT: }

View File

@ -59,6 +59,14 @@ static cl::opt<std::string> outputFilename("o", cl::desc("Output filename"),
cl::init("-"),
cl::cat(mainCategory));
static cl::opt<bool> observePorts("observe-ports",
cl::desc("Make all ports observable"),
cl::init(false), cl::cat(mainCategory));
static cl::opt<bool> observeWires("observe-wires",
cl::desc("Make all wires observable"),
cl::init(false), cl::cat(mainCategory));
static cl::opt<bool>
verifyPasses("verify-each",
cl::desc("Run the verifier after each transformation pass"),
@ -116,6 +124,7 @@ static void populatePipeline(PassManager &pm) {
// represented as intrinsic ops.
if (untilReached(UntilPreprocessing))
return;
pm.addPass(arc::createAddTapsPass(observePorts, observeWires));
pm.addPass(arc::createStripSVPass());
pm.addPass(arc::createInferMemoriesPass());
pm.addPass(createCSEPass());