mirror of https://github.com/llvm/circt.git
[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:
parent
597b1b969d
commit
846dd02bde
|
@ -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">,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 = [{
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
add_circt_dialect_library(CIRCTArcTransforms
|
||||
AddTaps.cpp
|
||||
Dedup.cpp
|
||||
InferMemories.cpp
|
||||
InlineArcs.cpp
|
||||
|
|
|
@ -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: }
|
||||
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue