mirror of https://github.com/llvm/circt.git
Advanced LayerSink (#7548)
This commit is contained in:
parent
c0e31955ce
commit
ec8ffaf4fe
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ®ion : 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>();
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
add_circt_dialect_library(CIRCTFIRRTLTransforms
|
||||
AssignOutputDirs.cpp
|
||||
AddSeqMemPorts.cpp
|
||||
AdvancedLayerSink.cpp
|
||||
AssignOutputDirs.cpp
|
||||
BlackBoxReader.cpp
|
||||
CheckCombLoops.cpp
|
||||
CheckLayers.cpp
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>>
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue