Advanced LayerSink (#7548)

This commit is contained in:
Robert Young 2024-10-22 10:18:07 -04:00 committed by GitHub
parent c0e31955ce
commit ec8ffaf4fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 1221 additions and 17 deletions

View File

@ -195,6 +195,8 @@ std::unique_ptr<mlir::Pass> createLayerMergePass();
std::unique_ptr<mlir::Pass> createLayerSinkPass();
std::unique_ptr<mlir::Pass> createAdvancedLayerSinkPass();
std::unique_ptr<mlir::Pass> createMaterializeDebugInfoPass();
std::unique_ptr<mlir::Pass> createLintingPass();

View File

@ -857,6 +857,15 @@ def LayerSink : Pass<"firrtl-layer-sink", "firrtl::FModuleOp"> {
];
}
def AdvancedLayerSink : Pass<"firrtl-advanced-layer-sink", "firrtl::CircuitOp"> {
let summary = "Sink operations into layer blocks";
let description = [{
This pass sinks ops into layers, whenever possible, to minimize unused
hardware in the design.
}];
let constructor = "::circt::firrtl::createAdvancedLayerSinkPass()";
}
def MaterializeDebugInfo :
Pass<"firrtl-materialize-debug-info", "firrtl::FModuleOp"> {
let summary = "Generate debug ops to track FIRRTL values";

View File

@ -98,6 +98,7 @@ public:
bool shouldConvertProbesToSignals() const { return probesToSignals; }
bool shouldReplaceSequentialMemories() const { return replSeqMem; }
bool shouldDisableOptimization() const { return disableOptimization; }
bool shouldAdvancedLayerSink() const { return advancedLayerSink; }
bool shouldLowerMemories() const { return lowerMemories; }
bool shouldDedup() const { return !noDedup; }
bool shouldEnableDebugInfo() const { return enableDebugInfo; }
@ -231,6 +232,11 @@ public:
return *this;
}
FirtoolOptions &setAdvancedLayerSink(bool value) {
advancedLayerSink = value;
return *this;
}
FirtoolOptions &setLowerMemories(bool value) {
lowerMemories = value;
return *this;
@ -382,6 +388,7 @@ private:
bool disableAggressiveMergeConnections;
bool emitOMIR;
std::string omirOutFile;
bool advancedLayerSink;
bool lowerMemories;
std::string blackBoxRootPath;
bool replSeqMem;

View File

@ -0,0 +1,458 @@
//===- AdvancedLayerSink.cpp - Sink ops into layer blocks -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This pass sinks operations into layer blocks.
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "circt/Support/Debug.h"
#include "mlir/IR/Dominance.h"
#include "mlir/IR/Iterators.h"
#include "mlir/IR/Threading.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Transforms/ControlFlowSinkUtils.h"
#define DEBUG_TYPE "firrtl-layer-sink"
namespace circt {
namespace firrtl {
#define GEN_PASS_DEF_ADVANCEDLAYERSINK
#include "circt/Dialect/FIRRTL/Passes.h.inc"
} // namespace firrtl
} // namespace circt
using namespace circt;
using namespace firrtl;
using namespace mlir;
//===----------------------------------------------------------------------===//
// Helpers
//===----------------------------------------------------------------------===//
// NOLINTBEGIN(misc-no-recursion)
namespace {
/// Walk the ops in `block` bottom-up, back-to-front order. The block walk API
/// in upstream MLIR, although it takes an Iterator parameter, will always walk
/// the top block front-to-back. This walk function will actually walk the ops
/// under `block` back-to-front.
template <typename T>
void walkBwd(Block *block, T &&f) {
for (auto &op :
llvm::make_early_inc_range(llvm::reverse(block->getOperations()))) {
for (auto &region : op.getRegions())
for (auto &block : region.getBlocks())
walkBwd(&block, f);
f(&op);
}
}
} // namespace
// NOLINTEND(misc-no-recursion)
static bool isAncestor(Block *block, Block *other) {
if (block == other)
return true;
return block->getParent()->isProperAncestor(other->getParent());
}
//===----------------------------------------------------------------------===//
// Effectfulness Analysis.
//===----------------------------------------------------------------------===//
/// A table that can determine whether an operation is effectful.
namespace {
class EffectInfo {
public:
EffectInfo(CircuitOp circuit, InstanceGraph &instanceGraph) {
DenseSet<InstanceGraphNode *> visited;
for (auto *root : instanceGraph) {
for (auto *node : llvm::post_order_ext(root, visited)) {
auto *op = node->getModule().getOperation();
update(op);
}
}
}
/// True if the given operation is NOT moveable due to some effect.
bool effectful(Operation *op) const {
if (!AnnotationSet(op).canBeDeleted() || hasDontTouch(op))
return true;
if (auto name = dyn_cast<FNamableOp>(op))
if (!name.hasDroppableName())
return true;
if (op->getNumRegions() != 0)
return true;
if (auto instance = dyn_cast<InstanceOp>(op))
return effectfulModules.contains(instance.getModuleNameAttr().getAttr());
if (isa<FConnectLike, WireOp, RegResetOp, RegOp, MemOp, NodeOp>(op))
return false;
return !(mlir::isMemoryEffectFree(op) ||
mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op) ||
mlir::hasSingleEffect<mlir::MemoryEffects::Read>(op));
}
private:
/// Record whether the module contains any effectful ops.
void update(FModuleOp module) {
module.getBodyBlock()->walk([&](Operation *op) {
if (effectful(op)) {
markEffectful(module);
return WalkResult::interrupt();
}
return WalkResult::advance();
});
}
void update(FModuleLike module) {
if (!AnnotationSet(module).canBeDeleted())
return markEffectful(module);
auto *op = module.getOperation();
// Regular modules may be pure.
if (auto m = dyn_cast<FModuleOp>(op))
return update(m);
// Memory modules are pure.
if (auto m = dyn_cast<FMemModuleOp>(op))
return;
// All other kinds of modules are effectful.
// intmodules, extmodules, classes.
return markEffectful(module);
}
void update(Operation *op) {
if (auto module = dyn_cast<FModuleLike>(op))
update(module);
}
/// Record that the given module contains an effectful operation.
void markEffectful(FModuleLike module) {
effectfulModules.insert(module.getModuleNameAttr());
}
DenseSet<StringAttr> effectfulModules;
};
} // namespace
//===----------------------------------------------------------------------===//
// Demands Analysis.
//===----------------------------------------------------------------------===//
namespace {
/// The LCA of the blocks in which a value is used/demanded. Lattice value.
struct Demand {
constexpr Demand() : Demand(nullptr) {}
constexpr Demand(Block *block) : block(block) {}
constexpr Demand merge(Demand other) const {
if (block == other.block)
return Demand(block);
if (other.block == nullptr)
return Demand(block);
if (block == nullptr)
return Demand(other.block);
auto *b = block;
while (b && !isAncestor(b, other.block))
b = b->getParentOp()->getBlock();
return Demand(b);
}
bool mergeIn(Demand other) {
auto prev = *this;
auto next = merge(other);
*this = next;
return prev != next;
}
constexpr bool operator==(Demand rhs) const { return block == rhs.block; }
constexpr bool operator!=(Demand rhs) const { return block != rhs.block; }
constexpr operator bool() const { return block; }
Block *block;
};
} // namespace
/// True if this operation is a good site to sink operations.
static bool isValidDest(Operation *op) {
return op && (isa<LayerBlockOp>(op) || isa<FModuleOp>(op));
}
/// True if we are prevented from sinking operations into the regions of the op.
static bool isBarrier(Operation *op) { return !isValidDest(op); }
/// Adjust the demand based on the location of the op being demanded. Ideally,
/// we can sink an op directly to its site of use. However, there are two
/// issues.
///
/// 1) Typically, an op will dominate every demand, but for hardware
/// declarations such as wires, the declaration will demand any connections
/// driving it. In this case, the relationship is reversed: the demander
/// dominates the demandee. This can cause us to pull connect-like ops up and
/// and out of their enclosing block (ref-defines can be buried under
/// layerblocks). To avoid this, we set an upper bound on the demand: the
/// enclosing block of the demandee.
///
/// 2) not all regions are valid sink targets. If there is a sink-barrier
/// between the operation and its demand, we adjust the demand upwards so that
/// there is no sink barrier between the demandee and the demand site.
static Demand clamp(Operation *op, Demand demand) {
if (!demand)
return nullptr;
auto *upper = op->getBlock();
assert(upper && "this should not be called on a top-level operation.");
if (!isAncestor(upper, demand.block))
demand = upper;
for (auto *i = demand.block; i != upper; i = i->getParentOp()->getBlock())
if (isBarrier(i->getParentOp()))
demand = i->getParentOp()->getBlock();
return demand;
}
namespace {
struct DemandInfo {
using WorkStack = std::vector<Operation *>;
DemandInfo(const EffectInfo &, FModuleOp);
Demand getDemandFor(Operation *op) const { return table.lookup(op); }
/// Starting at effectful ops and output ports, propagate the demand of values
/// through the design, running until fixpoint. At the end, we have an
/// accurate picture of where every operation can be sunk, while preserving
/// effects in the program.
void run(const EffectInfo &, FModuleOp, WorkStack &);
/// Update the demand for the given op. If the demand changes, place the op
/// onto the worklist.
void update(WorkStack &work, Operation *op, Demand demand) {
if (table[op].mergeIn(clamp(op, demand)))
work.push_back(op);
}
void update(WorkStack &work, Value value, Demand demand) {
if (auto result = dyn_cast<OpResult>(value))
update(work, cast<OpResult>(value).getOwner(), demand);
}
void updateConnects(WorkStack &, Value, Demand);
void updateConnects(WorkStack &, Operation *, Demand);
llvm::DenseMap<Operation *, Demand> table;
};
} // namespace
DemandInfo::DemandInfo(const EffectInfo &effectInfo, FModuleOp module) {
WorkStack work;
Block *body = module.getBodyBlock();
ArrayRef<bool> dirs = module.getPortDirections();
for (unsigned i = 0, e = module.getNumPorts(); i < e; ++i)
if (direction::get(dirs[i]) == Direction::Out)
updateConnects(work, body->getArgument(i), module.getBodyBlock());
module.getBodyBlock()->walk([&](Operation *op) {
if (effectInfo.effectful(op))
update(work, op, op->getBlock());
});
run(effectInfo, module, work);
}
void DemandInfo::run(const EffectInfo &effectInfo, FModuleOp, WorkStack &work) {
while (!work.empty()) {
auto *op = work.back();
work.pop_back();
auto demand = getDemandFor(op);
for (auto operand : op->getOperands())
update(work, operand, demand);
updateConnects(work, op, demand);
}
}
// The value represents a hardware declaration, such as a wire or port. Search
// backwards through uses, looking through aliasing operations such as
// subfields, to find connects that drive the given value. All driving
// connects of a value are demanded by the same region as the value. If the
// demand of the connect op is updated, then the demand will propagate
// forwards to its operands through the normal forward-propagation of demand.
void DemandInfo::updateConnects(WorkStack &work, Value value, Demand demand) {
struct StackElement {
Value value;
Value::user_iterator it;
};
SmallVector<StackElement> stack;
stack.push_back({value, value.user_begin()});
while (!stack.empty()) {
auto &top = stack.back();
auto end = top.value.user_end();
while (true) {
if (top.it == end) {
stack.pop_back();
break;
}
auto *user = *(top.it++);
if (auto connect = dyn_cast<FConnectLike>(user)) {
if (connect.getDest() == top.value) {
update(work, connect, demand);
}
continue;
}
if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(user)) {
for (auto result : user->getResults())
stack.push_back({result, result.user_begin()});
break;
}
}
}
}
void DemandInfo::updateConnects(WorkStack &work, Operation *op, Demand demand) {
if (isa<WireOp, RegResetOp, RegOp, MemOp, ObjectOp>(op)) {
for (auto result : op->getResults())
updateConnects(work, result, demand);
} else if (auto inst = dyn_cast<InstanceOp>(op)) {
auto dirs = inst.getPortDirections();
for (unsigned i = 0, e = inst->getNumResults(); i < e; ++i) {
if (direction::get(dirs[i]) == Direction::In)
updateConnects(work, inst.getResult(i), demand);
}
}
}
//===----------------------------------------------------------------------===//
// ModuleLayerSink
//===----------------------------------------------------------------------===//
namespace {
class ModuleLayerSink {
public:
static bool run(FModuleOp module, const EffectInfo &effectInfo) {
return ModuleLayerSink(module, effectInfo)();
}
private:
ModuleLayerSink(FModuleOp module, const EffectInfo &effectInfo)
: module(module), effectInfo(effectInfo) {}
bool operator()();
void moveLayersToBack(Operation *op);
void moveLayersToBack(Block *block);
FModuleOp module;
const EffectInfo &effectInfo;
bool changed;
};
} // namespace
// NOLINTNEXTLINE(misc-no-recursion)
void ModuleLayerSink::moveLayersToBack(Operation *op) {
for (auto &r : op->getRegions())
for (auto &b : r.getBlocks())
moveLayersToBack(&b);
}
// NOLINTNEXTLINE(misc-no-recursion)
void ModuleLayerSink::moveLayersToBack(Block *block) {
auto i = block->rbegin();
auto e = block->rend();
// Leave layerblocks that are already "at the back" where they are.
while (i != e) {
auto *op = &*i;
moveLayersToBack(op);
if (!isa<LayerBlockOp>(op))
break;
++i;
}
if (i == e)
return;
// We have found a non-layerblock op at position `i`.
// Any block in front of `i`, will be moved to after `i`.
auto *ip = &*i;
while (i != e) {
auto *op = &*i;
moveLayersToBack(op);
if (isa<LayerBlockOp>(op)) {
++i;
op->moveAfter(ip);
changed = true;
} else {
++i;
}
}
}
bool ModuleLayerSink::operator()() {
moveLayersToBack(module.getBodyBlock());
DemandInfo demandInfo(effectInfo, module);
walkBwd(module.getBodyBlock(), [&](Operation *op) {
auto demand = demandInfo.getDemandFor(op);
if (!demand) {
op->erase();
changed = true;
return;
}
if (demand == op->getBlock())
return;
op->moveBefore(demand.block, demand.block->begin());
changed = true;
});
return changed;
}
//===----------------------------------------------------------------------===//
// LayerSinkPass
//===----------------------------------------------------------------------===//
namespace {
/// A control-flow sink pass.
struct AdvancedLayerSinkPass final
: public circt::firrtl::impl::AdvancedLayerSinkBase<AdvancedLayerSinkPass> {
void runOnOperation() override;
};
} // namespace
void AdvancedLayerSinkPass::runOnOperation() {
auto circuit = getOperation();
LLVM_DEBUG(debugPassHeader(this)
<< "\n"
<< "Circuit: '" << circuit.getName() << "'\n";);
auto &instanceGraph = getAnalysis<InstanceGraph>();
EffectInfo effectInfo(circuit, instanceGraph);
std::atomic<bool> changed(false);
parallelForEach(&getContext(), circuit.getOps<FModuleOp>(),
[&](FModuleOp module) {
if (ModuleLayerSink::run(module, effectInfo))
changed = true;
});
if (!changed)
markAllAnalysesPreserved();
else
markAnalysesPreserved<InstanceGraph>();
}
//===----------------------------------------------------------------------===//
// Pass Constructor
//===----------------------------------------------------------------------===//
std::unique_ptr<mlir::Pass> circt::firrtl::createAdvancedLayerSinkPass() {
return std::make_unique<AdvancedLayerSinkPass>();
}

View File

@ -1,6 +1,7 @@
add_circt_dialect_library(CIRCTFIRRTLTransforms
AssignOutputDirs.cpp
AddSeqMemPorts.cpp
AdvancedLayerSink.cpp
AssignOutputDirs.cpp
BlackBoxReader.cpp
CheckCombLoops.cpp
CheckLayers.cpp

View File

@ -135,11 +135,14 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm,
if (opt.shouldConvertProbesToSignals())
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createProbesToSignalsPass());
{
auto &modulePM = pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>();
modulePM.addPass(firrtl::createLayerMergePass());
modulePM.addPass(firrtl::createLayerSinkPass());
}
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
firrtl::createLayerMergePass());
if (opt.shouldAdvancedLayerSink())
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createAdvancedLayerSinkPass());
else
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
firrtl::createLayerSinkPass());
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createInlinerPass());
@ -564,6 +567,11 @@ struct FirtoolCmdOptions {
"output-omir", llvm::cl::desc("File name for the output omir"),
llvm::cl::init("")};
llvm::cl::opt<bool> advancedLayerSink{
"advanced-layer-sink",
llvm::cl::desc("Sink logic into layer blocks (advanced)"),
llvm::cl::init(false)};
llvm::cl::opt<bool> lowerMemories{
"lower-memories",
llvm::cl::desc("Lower memories to have memories with masks as an "
@ -747,10 +755,11 @@ circt::firtool::FirtoolOptions::FirtoolOptions()
exportChiselInterface(false), chiselInterfaceOutDirectory(""),
vbToBV(false), noDedup(false), companionMode(firrtl::CompanionMode::Bind),
disableAggressiveMergeConnections(false), emitOMIR(true), omirOutFile(""),
lowerMemories(false), blackBoxRootPath(""), replSeqMem(false),
replSeqMemFile(""), extractTestCode(false), ignoreReadEnableMem(false),
disableRandom(RandomKind::None), outputAnnotationFilename(""),
enableAnnotationWarning(false), addMuxPragmas(false),
advancedLayerSink(false), lowerMemories(false), blackBoxRootPath(""),
replSeqMem(false), replSeqMemFile(""), extractTestCode(false),
ignoreReadEnableMem(false), disableRandom(RandomKind::None),
outputAnnotationFilename(""), enableAnnotationWarning(false),
addMuxPragmas(false),
verificationFlavor(firrtl::VerificationFlavor::None),
emitSeparateAlwaysBlocks(false), etcDisableInstanceExtraction(false),
etcDisableRegisterExtraction(false), etcDisableModuleInlining(false),
@ -782,6 +791,7 @@ circt::firtool::FirtoolOptions::FirtoolOptions()
clOptions->disableAggressiveMergeConnections;
emitOMIR = clOptions->emitOMIR;
omirOutFile = clOptions->omirOutFile;
advancedLayerSink = clOptions->advancedLayerSink;
lowerMemories = clOptions->lowerMemories;
blackBoxRootPath = clOptions->blackBoxRootPath;
replSeqMem = clOptions->replSeqMem;

View File

@ -0,0 +1,717 @@
// RUN: circt-opt -pass-pipeline="builtin.module(firrtl.circuit(firrtl-advanced-layer-sink))" -allow-unregistered-dialect %s | FileCheck %s
//===----------------------------------------------------------------------===//
// Movement of layers to the back of their containing block.
//===----------------------------------------------------------------------===//
// using interesting_name to prevent layer-sink from deleting unused wires.
firrtl.circuit "Top" {
// CHECK: firrtl.module @Top() {
// CHECK: }
firrtl.module @Top() {}
}
// Empty layerblock.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @A {
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {}
}
}
// Empty layerblock in layerblock.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @B bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @A {
// CHECK: firrtl.layerblock @A::@B {
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {
firrtl.layerblock @A::@B {
}
}
}
}
// Layerblock at end already.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @B bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: %w = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%w = firrtl.wire interesting_name : !firrtl.uint<1>
firrtl.layerblock @A {
}
}
}
// Layerblock NOT at end already.
firrtl.circuit "Top" {
firrtl.layer @A bind {
}
// CHECK: firrtl.module @Top() {
// CHECK: %w = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {
}
%w = firrtl.wire interesting_name : !firrtl.uint<1>
}
}
// Parent layerblock at end already, nested layerblock NOT at end.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @B bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: %w1 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: %w2 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A::@B {
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%w1 = firrtl.wire interesting_name : !firrtl.uint<1>
firrtl.layerblock @A {
firrtl.layerblock @A::@B {
}
%w2 = firrtl.wire interesting_name : !firrtl.uint<1>
}
}
}
// Parent layerblock NOT at end already, nested layerblock NOT at end.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @B bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: %w1 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: %w2 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A::@B {
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {
firrtl.layerblock @A::@B {
}
%w2 = firrtl.wire interesting_name : !firrtl.uint<1>
}
%w1 = firrtl.wire interesting_name : !firrtl.uint<1>
}
}
// Moving a layer past an op preserves the ordering of layerblocks.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @A1 bind {}
firrtl.layer @A2 bind {}
firrtl.layer @A3 bind {}
}
firrtl.layer @B bind {}
firrtl.layer @C bind {}
// CHECK: firrtl.module @Top() {
// CHECK: %w1 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: %w2 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A::@A1 {
// CHECK: }
// CHECK: firrtl.layerblock @A::@A2 {
// CHECK: }
// CHECK: firrtl.layerblock @A::@A3 {
// CHECK: }
// CHECK: }
// CHECK: firrtl.layerblock @B {
// CHECK: }
// CHECK: firrtl.layerblock @C {
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {
firrtl.layerblock @A::@A1 {
}
firrtl.layerblock @A::@A2 {
}
%w2 = firrtl.wire interesting_name : !firrtl.uint<1>
firrtl.layerblock @A::@A3 {
}
}
firrtl.layerblock @B {
}
%w1 = firrtl.wire interesting_name : !firrtl.uint<1>
firrtl.layerblock @C {
}
}
}
// Parent layerblock NOT at end already, nested layerblock NOT at end.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @B bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: %w1 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: %w2 = firrtl.wire interesting_name : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A::@B {
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top() {
firrtl.layerblock @A {
firrtl.layerblock @A::@B {
}
%w2 = firrtl.wire interesting_name : !firrtl.uint<1>
}
%w1 = firrtl.wire interesting_name : !firrtl.uint<1>
}
}
//===----------------------------------------------------------------------===//
// Basic Sinking.
//===----------------------------------------------------------------------===//
// Sink a chain of expressions.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @A {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %0 = firrtl.not %c0_ui1 : (!firrtl.uint<1>) -> !firrtl.uint<1>
// CHECK: %node = firrtl.node %0 : !firrtl.uint<1>
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%0 = firrtl.not %c0_ui1 : (!firrtl.uint<1>) -> !firrtl.uint<1>
%node = firrtl.node %0 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
// Sink a chain of expressions rooted at an input port.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(in %port: !firrtl.uint<1>) {
// CHECK: firrtl.layerblock @A {
// CHECK: %0 = firrtl.not %port : (!firrtl.uint<1>) -> !firrtl.uint<1>
// CHECK: %node = firrtl.node %0 : !firrtl.uint<1>
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top(in %port : !firrtl.uint<1>) {
%0 = firrtl.not %port : (!firrtl.uint<1>) -> !firrtl.uint<1>
%node = firrtl.node %0 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
// Sink a node to the LCA of its uses.
firrtl.circuit "Top" {
firrtl.layer @A bind {
firrtl.layer @A1 bind {}
firrtl.layer @A2 bind {}
}
// CHECK: firrtl.module @Top(in %port: !firrtl.uint<1>) {
// CHECK: firrtl.layerblock @A {
// CHECK: %node = firrtl.node %port : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A::@A1 {
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: firrtl.layerblock @A::@A2 {
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top(in %port: !firrtl.uint<1>) {
%node = firrtl.node %port : !firrtl.uint<1>
firrtl.layerblock @A {
firrtl.layerblock @A::@A1 {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
firrtl.layerblock @A::@A2 {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
}
// Output ports are demands: The unknown op should drag %node into layerblock
// @A, but the connection to %port, which uses %node as a source, will prevent
// the sinking of node into the layerblock.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @Top(out %port: !firrtl.uint<1>) {
%c = firrtl.constant 0 : !firrtl.uint<1>
%0 = firrtl.not %port : (!firrtl.uint<1>) -> !firrtl.uint<1>
%node = firrtl.node %c : !firrtl.uint<1>
firrtl.connect %port, %node : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
//===----------------------------------------------------------------------===//
// Sinking Loops.
//===----------------------------------------------------------------------===//
// Sink a loop between two wires.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @A {
// CHECK: %w1 = firrtl.wire : !firrtl.uint<1>
// CHECK: %w2 = firrtl.wire : !firrtl.uint<1>
// CHECK: firrtl.matchingconnect %w1, %w2 : !firrtl.uint<1>
// CHECK: firrtl.matchingconnect %w2, %w1 : !firrtl.uint<1>
// CHECK: "unknown"(%w2) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%w1 = firrtl.wire : !firrtl.uint<1>
%w2 = firrtl.wire : !firrtl.uint<1>
firrtl.matchingconnect %w1, %w2 : !firrtl.uint<1>
firrtl.matchingconnect %w2, %w1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%w2) : (!firrtl.uint<1>) -> ()
}
}
}
// Sink a self-connected register.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) {
// CHECK: firrtl.layerblock @A {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %r = firrtl.regreset %clock, %reset, %c0_ui1 : !firrtl.clock, !firrtl.asyncreset, !firrtl.uint<1>, !firrtl.uint<1>
// CHECK: firrtl.matchingconnect %r, %r : !firrtl.uint<1>
// CHECK: "unknown"(%r) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%r = firrtl.regreset %clock, %reset, %c0_ui1 : !firrtl.clock, !firrtl.asyncreset, !firrtl.uint<1>, !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%r) : (!firrtl.uint<1>) -> ()
}
firrtl.matchingconnect %r, %r : !firrtl.uint<1>
}
}
//===----------------------------------------------------------------------===//
// Sinking Instances.
//===----------------------------------------------------------------------===//
// Pure instances can be moved.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @Pure(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
firrtl.matchingconnect %o, %i : !firrtl.uint<1>
}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @A {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %pure_i, %pure_o = firrtl.instance pure @Pure(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
// CHECK: firrtl.matchingconnect %pure_i, %c0_ui1 : !firrtl.uint<1>
// CHECK: "unknown"(%pure_o) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%pure_i, %pure_o = firrtl.instance pure @Pure(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %pure_i, %c0_ui1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%pure_o) : (!firrtl.uint<1>) -> ()
}
}
}
// Effectful instances cannot be moved.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @Effectful(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
firrtl.matchingconnect %o, %i : !firrtl.uint<1>
"unknown"() : () -> ()
}
// CHECK: firrtl.module @Top() {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %effectful_i, %effectful_o = firrtl.instance effectful @Effectful(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
// CHECK: firrtl.matchingconnect %effectful_i, %c0_ui1 : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: "unknown"(%effectful_o) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%effectful_i, %effectful_o = firrtl.instance effectful @Effectful(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %effectful_i, %c0_ui1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%effectful_o) : (!firrtl.uint<1>) -> ()
}
}
}
// Parents of effectful instances cannot be moved.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @Effectful(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
firrtl.matchingconnect %o, %i : !firrtl.uint<1>
"unknown"() : () -> ()
}
firrtl.module @EffectfulParent(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
%effectful_i, %effectful_o = firrtl.instance effectful @Effectful(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %effectful_i, %i : !firrtl.uint<1>
firrtl.matchingconnect %o, %effectful_o : !firrtl.uint<1>
}
// CHECK: firrtl.module @Top() {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %parent_i, %parent_o = firrtl.instance parent @EffectfulParent(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
// CHECK: firrtl.matchingconnect %parent_i, %c0_ui1 : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: "unknown"(%parent_o) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%parent_i, %parent_o = firrtl.instance parent @EffectfulParent(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %parent_i, %c0_ui1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%parent_o) : (!firrtl.uint<1>) -> ()
}
}
}
// Modules with a layerblock cannot be moved, regardless of effectfulness. This
// would result in a bind-under-bind.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @HasLayerblock(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
firrtl.matchingconnect %o, %i : !firrtl.uint<1>
firrtl.layerblock @A {}
}
// CHECK: firrtl.module @Top() {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %hasLayerblock_i, %hasLayerblock_o = firrtl.instance hasLayerblock @HasLayerblock(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
// CHECK: firrtl.matchingconnect %hasLayerblock_i, %c0_ui1 : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: "unknown"(%hasLayerblock_o) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%hasLayerblock_i, %hasLayerblock_o = firrtl.instance hasLayerblock @HasLayerblock(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %hasLayerblock_i, %c0_ui1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%hasLayerblock_o) : (!firrtl.uint<1>) -> ()
}
}
}
// Parents of modules with a layerblock cannot be moved.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
firrtl.module @HasLayerblock(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
firrtl.matchingconnect %o, %i : !firrtl.uint<1>
firrtl.layerblock @A {}
}
firrtl.module @HasLayerblockParent(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) {
%hasLayerblock_i, %hasLayerblock_o = firrtl.instance hasLayerblock @HasLayerblock(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %hasLayerblock_i, %i : !firrtl.uint<1>
firrtl.matchingconnect %o, %hasLayerblock_o : !firrtl.uint<1>
}
// CHECK: firrtl.module @Top() {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %hasLayerblockParent_i, %hasLayerblockParent_o = firrtl.instance hasLayerblockParent @HasLayerblockParent(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
// CHECK: firrtl.matchingconnect %hasLayerblockParent_i, %c0_ui1 : !firrtl.uint<1>
// CHECK: firrtl.layerblock @A {
// CHECK: "unknown"(%hasLayerblockParent_o) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%hasLayerblockParent_i, %hasLayerblockParent_o = firrtl.instance hasLayerblockParent @HasLayerblockParent(in i: !firrtl.uint<1>, out o: !firrtl.uint<1>)
firrtl.matchingconnect %hasLayerblockParent_i, %c0_ui1 : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%hasLayerblockParent_o) : (!firrtl.uint<1>) -> ()
}
}
}
//===----------------------------------------------------------------------===//
// Connects at end / dominance preservation.
//===----------------------------------------------------------------------===//
// Connect gets dragged forwards into layerblock.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top() {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: "unknown"(%c0_ui1) : (!firrtl.uint<1>) -> ()
// CHECK: firrtl.layerblock @A {
// CHECK: %w = firrtl.wire : !firrtl.bundle<a: uint<1>, b: uint<1>>
// CHECK: %0 = firrtl.subfield %w[a] : !firrtl.bundle<a: uint<1>, b: uint<1>>
// CHECK: %1 = firrtl.subfield %w[b] : !firrtl.bundle<a: uint<1>, b: uint<1>>
// CHECK: firrtl.matchingconnect %0, %c0_ui1 : !firrtl.uint<1>
// CHECK: firrtl.matchingconnect %1, %c0_ui1 : !firrtl.uint<1>
// CHECK: "unknown"(%1) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%w = firrtl.wire : !firrtl.bundle<a: uint<1>, b: uint<1>>
%0 = firrtl.subfield %w[a] : !firrtl.bundle<a: uint<1>, b: uint<1>>
%1 = firrtl.subfield %w[b] : !firrtl.bundle<a: uint<1>, b: uint<1>>
firrtl.layerblock @A {
"unknown"(%1) : (!firrtl.uint<1>) -> ()
}
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
"unknown"(%c0_ui1) : (!firrtl.uint<1>) -> ()
firrtl.matchingconnect %0, %c0_ui1 : !firrtl.uint<1>
firrtl.matchingconnect %1, %c0_ui1 : !firrtl.uint<1>
}
}
// Check that layer-sink doesn't accidentally un-nest layerblocks while
// rearranging the order of ops.
firrtl.circuit "Top" {
firrtl.layer @L1 bind {
firrtl.layer @L2 bind {}
}
// CHECK: firrtl.module @Top() {
// CHECK: firrtl.layerblock @L1 {
// CHECK: firrtl.layerblock @L1::@L2 {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %w = firrtl.wire : !firrtl.uint<1>
// CHECK: firrtl.matchingconnect %w, %c0_ui1 : !firrtl.uint<1>
// CHECK: "unknown"(%w) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%w = firrtl.wire : !firrtl.uint<1>
firrtl.layerblock @L1 {
firrtl.layerblock @L1::@L2 {
"unknown"(%w) : (!firrtl.uint<1>) -> ()
}
}
firrtl.matchingconnect %w, %c0_ui1 : !firrtl.uint<1>
}
}
//===----------------------------------------------------------------------===//
// Connects that are buried in layerblocks, will stay in layerblocks.
//===----------------------------------------------------------------------===//
// Ref-define buried inside layerblock drives a coloured probe port.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(out %port: !firrtl.probe<uint<1>, @A>) {
// CHECK: firrtl.layerblock @A {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1>
// CHECK: %1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
// CHECK: firrtl.ref.define %port, %1 : !firrtl.probe<uint<1>, @A>
// CHECK: }
// CHECK: }
firrtl.module @Top(out %port: !firrtl.probe<uint<1>, @A>) {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1>
%1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
firrtl.layerblock @A {
firrtl.ref.define %port, %1 : !firrtl.probe<uint<1>, @A>
}
}
}
// Ref-define buried inside layerblock drives a coloured wire.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top() {
// CHECK: %w = firrtl.wire : !firrtl.probe<uint<1>, @A>
// CHECK: "unknown"(%w) : (!firrtl.probe<uint<1>, @A>) -> ()
// CHECK: firrtl.layerblock @A {
// CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1>
// CHECK: %1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
// CHECK: firrtl.ref.define %w, %1 : !firrtl.probe<uint<1>, @A>
// CHECK: }
// CHECK: }
firrtl.module @Top() {
%c0_ui1 = firrtl.constant 0 : !firrtl.uint<1>
%0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1>
%1 = firrtl.ref.cast %0 : (!firrtl.probe<uint<1>>) -> !firrtl.probe<uint<1>, @A>
%w = firrtl.wire : !firrtl.probe<uint<1>, @A>
"unknown"(%w) : (!firrtl.probe<uint<1>, @A>) -> ()
firrtl.layerblock @A {
firrtl.ref.define %w, %1 : !firrtl.probe<uint<1>, @A>
}
}
}
//===----------------------------------------------------------------------===//
// Sinking through Whens.
//===----------------------------------------------------------------------===//
// Sink an op that is used under a when, without sinking into the when.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(in %port: !firrtl.uint<1>) {
// CHECK: firrtl.layerblock @A {
// CHECK: %node = firrtl.node %port : !firrtl.uint<1>
// CHECK: firrtl.when %port : !firrtl.uint<1> {
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top(in %port: !firrtl.uint<1>) {
%node = firrtl.node %port : !firrtl.uint<1>
firrtl.layerblock @A {
firrtl.when %port : !firrtl.uint<1> {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
}
// Sink an op that is used under both arms of a when, without sinking into the when.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(in %port: !firrtl.uint<1>) {
// CHECK: firrtl.layerblock @A {
// CHECK: %node = firrtl.node %port : !firrtl.uint<1>
// CHECK: firrtl.when %port : !firrtl.uint<1> {
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: } else {
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top(in %port: !firrtl.uint<1>) {
%node = firrtl.node %port : !firrtl.uint<1>
firrtl.layerblock @A {
firrtl.when %port : !firrtl.uint<1> {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
} else {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
}
// Check that sinking occurs within a when.
firrtl.circuit "Top" {
firrtl.layer @A bind {}
// CHECK: firrtl.module @Top(in %port: !firrtl.uint<1>) {
// CHECK: firrtl.when %port : !firrtl.uint<1> {
// CHECK: firrtl.layerblock @A {
// CHECK: %node = firrtl.node %port : !firrtl.uint<1>
// CHECK: "unknown"(%node) : (!firrtl.uint<1>) -> ()
// CHECK: }
// CHECK: }
// CHECK: }
firrtl.module @Top(in %port: !firrtl.uint<1>) {
firrtl.when %port : !firrtl.uint<1> {
%node = firrtl.node %port : !firrtl.uint<1>
firrtl.layerblock @A {
"unknown"(%node) : (!firrtl.uint<1>) -> ()
}
}
}
}
//===----------------------------------------------------------------------===//
// Misc
//===----------------------------------------------------------------------===//
// Tests of things which do not currently sink, but should.
//
// CHECK-LABEL: firrtl.circuit "LayerSinkExpectedFailures"
firrtl.circuit "LayerSinkExpectedFailures" {
firrtl.layer @Subaccess bind {}
firrtl.layer @Subfield bind {}
firrtl.layer @Subindex bind {}
// CHECK: firrtl.module @LayerSinkExpectedFailures
firrtl.module @LayerSinkExpectedFailures(in %a: !firrtl.uint<1>) {
// CHECK-NEXT: firrtl.layerblock @Subaccess {
// CHECK-NEXT: %wire_subaccess = firrtl.wire
// CHECK-NEXT: %0 = firrtl.subaccess %wire_subaccess[%a] : !firrtl.vector<uint<1>, 2>, !firrtl.uint<1>
// CHECK-NEXT: firrtl.matchingconnect %0, %0 : !firrtl.uint<1>
// CHECK-NEXT: "unknown"(%wire_subaccess) : (!firrtl.vector<uint<1>, 2>) -> ()
// CHECK-NEXT: }
%wire_subaccess = firrtl.wire : !firrtl.vector<uint<1>, 2>
%0 = firrtl.subaccess %wire_subaccess[%a] : !firrtl.vector<uint<1>, 2>, !firrtl.uint<1>
firrtl.matchingconnect %0, %0 : !firrtl.uint<1>
firrtl.layerblock @Subaccess {
"unknown"(%wire_subaccess) : (!firrtl.vector<uint<1>, 2>) -> ()
}
// CHECK-NEXT: firrtl.layerblock @Subfield {
// CHECK-NEXT: %wire_subfield = firrtl.wire
// CHECK-NEXT: %0 = firrtl.subfield %wire_subfield[a]
// CHECK-NEXT: firrtl.matchingconnect %0, %0
// CHECK-NEXT: "unknown"(%wire_subfield) : (!firrtl.bundle<a: uint<1>>) -> ()
// CHECK-NEXT: }
%wire_subfield = firrtl.wire : !firrtl.bundle<a: uint<1>>
%1 = firrtl.subfield %wire_subfield[a] : !firrtl.bundle<a: uint<1>>
firrtl.matchingconnect %1, %1 : !firrtl.uint<1>
firrtl.layerblock @Subfield {
"unknown"(%wire_subfield) : (!firrtl.bundle<a: uint<1>>) -> ()
}
// CHECK-NEXT: firrtl.layerblock @Subindex {
// CHECK-NEXT: %wire_subindex = firrtl.wire
// CHECK-NEXT: %0 = firrtl.subindex %wire_subindex[0]
// CHECK-NEXT: firrtl.matchingconnect %0, %0
// CHECK-NEXT: "unknown"(%wire_subindex) : (!firrtl.vector<uint<1>, 1>) -> ()
// CHECK-NEXT: }
%wire_subindex = firrtl.wire : !firrtl.vector<uint<1>, 1>
%2 = firrtl.subindex %wire_subindex[0] : !firrtl.vector<uint<1>, 1>
firrtl.matchingconnect %2, %2 : !firrtl.uint<1>
firrtl.layerblock @Subindex {
"unknown"(%wire_subindex) : (!firrtl.vector<uint<1>, 1>) -> ()
}
}
}
firrtl.circuit "Sub" {
firrtl.layer @Subfield bind {}
firrtl.module @Sub() {
%w = firrtl.wire {annotations = [{class = "firrtl.transforms.DontTouchAnnotation"}]} : !firrtl.bundle<a: uint<1>>
%w_a = firrtl.subfield %w[a] : !firrtl.bundle<a: uint<1>>
%z = firrtl.constant 0 : !firrtl.uint<1>
// {{op connects to a destination which is defined outside its enclosing layer block}}
firrtl.matchingconnect %w_a, %z: !firrtl.uint<1>
firrtl.layerblock @Subfield {
firrtl.node %w_a : !firrtl.uint<1>
}
}
}

View File

@ -1,5 +1,5 @@
// RUN: circt-opt %s -split-input-file | circt-opt -split-input-file
// RUN: firtool %s -split-input-file
// RUN: firtool %s -preserve-values=named -split-input-file
// These tests are just for demonstrating RefOps, and expected to not error.
// Simple 1 level read from wire.
@ -27,7 +27,7 @@ firrtl.circuit "SimpleRead" {
firrtl.ref.define %_a, %1 : !firrtl.probe<uint<1>>
}
firrtl.module @SimpleRead() {
%bar_a = firrtl.instance bar @Bar(out _a: !firrtl.probe<uint<1>>)
%bar_a = firrtl.instance bar interesting_name @Bar(out _a: !firrtl.probe<uint<1>>)
%a = firrtl.wire : !firrtl.uint<1>
%0 = firrtl.ref.resolve %bar_a : !firrtl.probe<uint<1>>
firrtl.matchingconnect %a, %0 : !firrtl.uint<1>
@ -48,7 +48,7 @@ firrtl.circuit "ForwardToInstance" {
firrtl.ref.define %_a, %bar_2 : !firrtl.probe<uint<1>>
}
firrtl.module @ForwardToInstance() {
%bar_a = firrtl.instance bar @Bar(out _a: !firrtl.probe<uint<1>>)
%bar_a = firrtl.instance bar interesting_name @Bar(out _a: !firrtl.probe<uint<1>>)
%a = firrtl.wire : !firrtl.uint<1>
%0 = firrtl.ref.resolve %bar_a : !firrtl.probe<uint<1>>
firrtl.matchingconnect %a, %0 : !firrtl.uint<1>
@ -74,7 +74,7 @@ firrtl.circuit "ForwardToInstance" {
}
firrtl.module @ForwardToInstance() {
%bar_a = firrtl.instance bar @Bar(out _a: !firrtl.probe<uint<1>>)
%a = firrtl.wire : !firrtl.uint<1>
%a = firrtl.wire interesting_name : !firrtl.uint<1>
// Reader 2
%0 = firrtl.ref.resolve %bar_a : !firrtl.probe<uint<1>>
firrtl.matchingconnect %a, %0 : !firrtl.uint<1>
@ -98,8 +98,8 @@ firrtl.circuit "DUT" {
firrtl.ref.define %ref_out2, %2 : !firrtl.probe<uint<4>>
}
firrtl.module @DUT() {
%view_out1, %view_out2 = firrtl.instance sub @Submodule(out ref_out1: !firrtl.probe<uint<1>>, out ref_out2: !firrtl.probe<uint<4>>)
%view_in1, %view_in2 = firrtl.instance MyView_companion @MyView_companion(in ref_in1: !firrtl.uint<1>, in ref_in2: !firrtl.uint<4>)
%view_out1, %view_out2 = firrtl.instance sub interesting_name @Submodule(out ref_out1: !firrtl.probe<uint<1>>, out ref_out2: !firrtl.probe<uint<4>>)
%view_in1, %view_in2 = firrtl.instance MyView_companion interesting_name @MyView_companion(in ref_in1: !firrtl.uint<1>, in ref_in2: !firrtl.uint<4>)
%1 = firrtl.ref.resolve %view_out1 : !firrtl.probe<uint<1>>
%2 = firrtl.ref.resolve %view_out2 : !firrtl.probe<uint<4>>
@ -155,7 +155,7 @@ firrtl.circuit "Issue3715" {
}
}
firrtl.module @Issue3715(in %p: !firrtl.uint<1>) {
%test_in, %test_x = firrtl.instance test @Test(in p: !firrtl.uint<1>, out x: !firrtl.probe<uint<2>>)
%test_in, %test_x = firrtl.instance test interesting_name @Test(in p: !firrtl.uint<1>, out x: !firrtl.probe<uint<2>>)
firrtl.matchingconnect %test_in, %p : !firrtl.uint<1>
%x = firrtl.ref.resolve %test_x : !firrtl.probe<uint<2>>
}