mirror of https://github.com/llvm/circt.git
858 lines
29 KiB
C++
858 lines
29 KiB
C++
//===- LowerOpenAggs.cpp - Lower Open Aggregate Types -----------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the LowerOpenAggs pass. This pass replaces the open
|
|
// aggregate types with hardware aggregates, with non-hardware fields
|
|
// expanded out as with LowerTypes.
|
|
//
|
|
// This pass supports reference and property types.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PassDetails.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
|
|
#include "circt/Dialect/FIRRTL/FieldRefCache.h"
|
|
#include "circt/Dialect/FIRRTL/Passes.h"
|
|
#include "circt/Support/Debug.h"
|
|
#include "mlir/IR/BuiltinAttributes.h"
|
|
#include "mlir/IR/ImplicitLocOpBuilder.h"
|
|
#include "mlir/IR/Threading.h"
|
|
#include "mlir/IR/Visitors.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/FormatAdapters.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
#include <vector>
|
|
|
|
#define DEBUG_TYPE "firrtl-lower-open-aggs"
|
|
|
|
using namespace circt;
|
|
using namespace firrtl;
|
|
|
|
namespace {
|
|
|
|
/// Information on non-hw (ref/prop) elements.
|
|
struct NonHWField {
|
|
/// Type of the field, not a hardware type.
|
|
FIRRTLType type;
|
|
/// FieldID relative to base of converted type.
|
|
uint64_t fieldID;
|
|
/// Relative orientation. False means aligned.
|
|
bool isFlip;
|
|
/// String suffix naming this field.
|
|
SmallString<16> suffix;
|
|
|
|
/// Print this structure to the specified stream.
|
|
void print(raw_ostream &os, unsigned indent = 0) const;
|
|
|
|
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
|
/// Print this structure to llvm::errs().
|
|
LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
|
|
#endif
|
|
};
|
|
|
|
/// Structure that describes how a given operation with a type (which may or may
|
|
/// not contain non-hw typess) should be lowered to one or more operations with
|
|
/// other types.
|
|
struct MappingInfo {
|
|
/// Preserve this type. Map any uses of old directly to new.
|
|
bool identity;
|
|
|
|
// When not identity, the type will be split:
|
|
|
|
/// Type of the hardware-only portion. May be null, indicating all non-hw.
|
|
Type hwType;
|
|
|
|
/// List of the individual non-hw fields to be split out.
|
|
SmallVector<NonHWField, 0> fields;
|
|
|
|
/// List of fieldID's of interior nodes that map to nothing. HW-only
|
|
/// projection is empty, and not leaf.
|
|
SmallVector<uint64_t, 0> mapToNullInteriors;
|
|
|
|
hw::InnerSymAttr newSym = {};
|
|
|
|
/// Determine number of types this argument maps to.
|
|
size_t count(bool includeErased = false) const {
|
|
if (identity)
|
|
return 1;
|
|
return fields.size() + (hwType ? 1 : 0) + (includeErased ? 1 : 0);
|
|
}
|
|
|
|
/// Print this structure to the specified stream.
|
|
void print(raw_ostream &os, unsigned indent = 0) const;
|
|
|
|
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
|
|
/// Print this structure to llvm::errs().
|
|
LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
|
|
#endif
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void NonHWField::print(llvm::raw_ostream &os, unsigned indent) const {
|
|
os << llvm::formatv("{0}- type: {2}\n"
|
|
"{1}fieldID: {3}\n"
|
|
"{1}isFlip: {4}\n"
|
|
"{1}suffix: \"{5}\"\n",
|
|
llvm::fmt_pad("", indent, 0),
|
|
llvm::fmt_pad("", indent + 2, 0), type, fieldID, isFlip,
|
|
suffix);
|
|
}
|
|
void MappingInfo::print(llvm::raw_ostream &os, unsigned indent) const {
|
|
if (identity) {
|
|
os << "<identity>";
|
|
return;
|
|
}
|
|
|
|
os.indent(indent) << "hardware: ";
|
|
if (hwType)
|
|
os << hwType;
|
|
else
|
|
os << "<none>";
|
|
os << "\n";
|
|
|
|
os.indent(indent) << "non-hardware:\n";
|
|
for (auto &field : fields)
|
|
field.print(os, indent + 2);
|
|
|
|
os.indent(indent) << "mappedToNull:\n";
|
|
for (auto &null : mapToNullInteriors)
|
|
os.indent(indent + 2) << "- " << null << "\n";
|
|
|
|
os.indent(indent) << "newSym: ";
|
|
if (newSym)
|
|
os << newSym;
|
|
else
|
|
os << "<empty>";
|
|
}
|
|
|
|
template <typename Range>
|
|
LogicalResult walkMappings(
|
|
Range &&range, bool includeErased,
|
|
llvm::function_ref<LogicalResult(size_t, MappingInfo &, size_t)> callback) {
|
|
size_t count = 0;
|
|
for (const auto &[index, pmi] : llvm::enumerate(range)) {
|
|
if (failed(callback(index, pmi, count)))
|
|
return failure();
|
|
count += pmi.count(includeErased);
|
|
}
|
|
return success();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Visitor
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
class Visitor : public FIRRTLVisitor<Visitor, LogicalResult> {
|
|
public:
|
|
explicit Visitor(MLIRContext *context) : context(context){};
|
|
|
|
/// Entrypoint.
|
|
LogicalResult visit(FModuleLike mod);
|
|
|
|
using FIRRTLVisitor<Visitor, LogicalResult>::visitDecl;
|
|
using FIRRTLVisitor<Visitor, LogicalResult>::visitExpr;
|
|
using FIRRTLVisitor<Visitor, LogicalResult>::visitStmt;
|
|
|
|
LogicalResult visitDecl(InstanceOp op);
|
|
LogicalResult visitDecl(WireOp op);
|
|
|
|
LogicalResult visitExpr(OpenSubfieldOp op);
|
|
LogicalResult visitExpr(OpenSubindexOp op);
|
|
|
|
LogicalResult visitUnhandledOp(Operation *op) {
|
|
auto notOpenAggType = [](auto type) {
|
|
return !isa<OpenBundleType, OpenVectorType>(type);
|
|
};
|
|
if (!llvm::all_of(op->getOperandTypes(), notOpenAggType) ||
|
|
!llvm::all_of(op->getResultTypes(), notOpenAggType))
|
|
return op->emitOpError(
|
|
"unhandled use or producer of types containing non-hw types");
|
|
return success();
|
|
}
|
|
|
|
LogicalResult visitInvalidOp(Operation *op) { return visitUnhandledOp(op); }
|
|
|
|
/// Whether any changes were made.
|
|
bool madeChanges() const { return changesMade; }
|
|
|
|
private:
|
|
/// Convert a type to its HW-only projection, adjusting symbols. Gather
|
|
/// non-hw elements encountered and their names / positions. Returns a
|
|
/// MappingInfo with its findings.
|
|
FailureOr<MappingInfo> mapType(Type type, Location errorLoc,
|
|
hw::InnerSymAttr sym = {});
|
|
|
|
/// Helper to record changes that may have been made.
|
|
void recordChanges(bool changed) {
|
|
if (changed)
|
|
changesMade = true;
|
|
}
|
|
|
|
MLIRContext *context;
|
|
|
|
/// Map non-HW fields to their new Value.
|
|
/// Null value indicates no equivalent (dead).
|
|
/// These values are available wherever the root is used.
|
|
DenseMap<FieldRef, Value> nonHWValues;
|
|
|
|
/// Map from original to its hw-only aggregate equivalent.
|
|
DenseMap<Value, Value> hwOnlyAggMap;
|
|
|
|
/// List of operations to erase at the end.
|
|
SmallVector<Operation *> opsToErase;
|
|
|
|
/// FieldRef cache. Be careful to only use this for operations
|
|
/// in the original IR / not mutated.
|
|
FieldRefCache refs;
|
|
|
|
/// Whether IR was changed.
|
|
bool changesMade = false;
|
|
};
|
|
} // namespace
|
|
|
|
LogicalResult Visitor::visit(FModuleLike mod) {
|
|
auto ports = mod.getPorts();
|
|
|
|
SmallVector<MappingInfo, 16> portMappings;
|
|
for (auto &port : ports) {
|
|
auto pmi = mapType(port.type, port.loc, port.sym);
|
|
if (failed(pmi))
|
|
return failure();
|
|
portMappings.push_back(*pmi);
|
|
}
|
|
|
|
/// Total number of types mapped to.
|
|
/// Include erased ports.
|
|
size_t countWithErased = 0;
|
|
for (auto &pmi : portMappings)
|
|
countWithErased += pmi.count(/*includeErased=*/true);
|
|
|
|
/// Ports to add.
|
|
SmallVector<std::pair<unsigned, PortInfo>> newPorts;
|
|
|
|
/// Ports to remove.
|
|
BitVector portsToErase(countWithErased);
|
|
|
|
/// Go through each port mapping, gathering information about all new ports.
|
|
LLVM_DEBUG({
|
|
llvm::dbgs().indent(2) << "- name: "
|
|
<< cast<mlir::SymbolOpInterface>(*mod).getNameAttr()
|
|
<< "\n";
|
|
llvm::dbgs().indent(4) << "ports:\n";
|
|
});
|
|
auto result = walkMappings(
|
|
portMappings, /*includeErased=*/true,
|
|
[&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
|
|
LLVM_DEBUG({
|
|
llvm::dbgs().indent(6) << "- name: " << ports[index].name << "\n";
|
|
llvm::dbgs().indent(8) << "type: " << ports[index].type << "\n";
|
|
llvm::dbgs().indent(8) << "mapping:\n";
|
|
pmi.print(llvm::dbgs(), /*indent=*/10);
|
|
llvm::dbgs() << "\n";
|
|
});
|
|
// Index for inserting new points next to this point.
|
|
// (Immediately after current port's index).
|
|
auto idxOfInsertPoint = index + 1;
|
|
|
|
if (pmi.identity)
|
|
return success();
|
|
|
|
auto &port = ports[index];
|
|
|
|
// If not identity, mark this port for eventual removal.
|
|
portsToErase.set(newIndex);
|
|
|
|
// Create new hw-only port, this will generally replace this port.
|
|
if (pmi.hwType) {
|
|
auto newPort = port;
|
|
newPort.type = pmi.hwType;
|
|
newPort.sym = pmi.newSym;
|
|
newPorts.emplace_back(idxOfInsertPoint, newPort);
|
|
|
|
assert(!port.sym ||
|
|
(pmi.newSym && port.sym.size() == pmi.newSym.size()));
|
|
|
|
// If want to run this pass later, need to fixup annotations.
|
|
if (!port.annotations.empty())
|
|
return mlir::emitError(port.loc)
|
|
<< "annotations on open aggregates not handled yet";
|
|
} else {
|
|
assert(!port.sym && !pmi.newSym);
|
|
if (!port.annotations.empty())
|
|
return mlir::emitError(port.loc)
|
|
<< "annotations found on aggregate with no HW";
|
|
}
|
|
|
|
// Create ports for each non-hw field.
|
|
for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
|
|
auto name = StringAttr::get(context,
|
|
Twine(port.name.strref()) + field.suffix);
|
|
auto orientation =
|
|
(Direction)((unsigned)port.direction ^ field.isFlip);
|
|
PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
|
|
port.loc, std::nullopt);
|
|
newPorts.emplace_back(idxOfInsertPoint, pi);
|
|
}
|
|
return success();
|
|
});
|
|
if (failed(result))
|
|
return failure();
|
|
|
|
// Insert the new ports!
|
|
mod.insertPorts(newPorts);
|
|
recordChanges(!newPorts.empty());
|
|
|
|
assert(mod->getNumRegions() == 1);
|
|
|
|
// (helper to determine/get the body block if present)
|
|
auto getBodyBlock = [](auto mod) {
|
|
auto &blocks = mod->getRegion(0).getBlocks();
|
|
return !blocks.empty() ? &blocks.front() : nullptr;
|
|
};
|
|
|
|
// Process body block.
|
|
// Create mapping for ports, then visit all operations within.
|
|
if (auto *block = getBodyBlock(mod)) {
|
|
// Create mappings for split ports.
|
|
auto result =
|
|
walkMappings(portMappings, /*includeErased=*/true,
|
|
[&](auto index, MappingInfo &pmi, auto newIndex) {
|
|
// Nothing to do for identity.
|
|
if (pmi.identity)
|
|
return success();
|
|
|
|
// newIndex is index of this port after insertion.
|
|
// This will be removed.
|
|
assert(portsToErase.test(newIndex));
|
|
auto oldPort = block->getArgument(newIndex);
|
|
auto newPortIndex = newIndex;
|
|
|
|
// Create mappings for split ports.
|
|
if (pmi.hwType)
|
|
hwOnlyAggMap[oldPort] =
|
|
block->getArgument(++newPortIndex);
|
|
|
|
for (auto &field : pmi.fields) {
|
|
auto ref = FieldRef(oldPort, field.fieldID);
|
|
auto newVal = block->getArgument(++newPortIndex);
|
|
nonHWValues[ref] = newVal;
|
|
}
|
|
for (auto fieldID : pmi.mapToNullInteriors) {
|
|
auto ref = FieldRef(oldPort, fieldID);
|
|
assert(!nonHWValues.count(ref));
|
|
nonHWValues[ref] = {};
|
|
}
|
|
|
|
return success();
|
|
});
|
|
if (failed(result))
|
|
return failure();
|
|
|
|
// Walk the module.
|
|
LLVM_DEBUG(llvm::dbgs().indent(4) << "body:\n");
|
|
if (block
|
|
->walk<mlir::WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
|
|
return dispatchVisitor(op);
|
|
})
|
|
.wasInterrupted())
|
|
return failure();
|
|
|
|
assert(opsToErase.empty() || madeChanges());
|
|
|
|
// Cleanup dead operations.
|
|
for (auto &op : llvm::reverse(opsToErase))
|
|
op->erase();
|
|
}
|
|
|
|
// Drop dead ports.
|
|
mod.erasePorts(portsToErase);
|
|
recordChanges(portsToErase.any());
|
|
|
|
LLVM_DEBUG(refs.printStats(llvm::dbgs()));
|
|
|
|
return success();
|
|
}
|
|
|
|
LogicalResult Visitor::visitExpr(OpenSubfieldOp op) {
|
|
// Changes will be made.
|
|
recordChanges(true);
|
|
|
|
// We're indexing into an OpenBundle, which contains some non-hw elements and
|
|
// may contain hw elements.
|
|
|
|
// By the time this is reached, the "root" storage for the input
|
|
// has already been handled and mapped to its new location(s),
|
|
// such that the hardware-only contents are split from non-hw.
|
|
|
|
// If there is a hardware portion selected by this operation,
|
|
// create a "closed" subfieldop using the hardware-only new storage,
|
|
// and add an entry mapping our old (soon, dead) result to
|
|
// this new hw-only result (of the subfieldop).
|
|
|
|
// Downstream indexing operations will expect that they can
|
|
// still chase up through this operation, and that they will find
|
|
// the hw-only portion in the map.
|
|
|
|
// If this operation selects a non-hw element (not mixed),
|
|
// look up where that ref now lives and update all users to use that instead.
|
|
// (This case falls under "this selects only non-hw", which means
|
|
// that this operation is now dead).
|
|
|
|
// In all cases, this operation will be dead and should be removed.
|
|
opsToErase.push_back(op);
|
|
|
|
// Chase this to its original root.
|
|
// If the FieldRef for this selection has a new home,
|
|
// RAUW to that value and this op is dead.
|
|
auto resultRef = refs.getFieldRefFromValue(op.getResult());
|
|
auto nonHWForResult = nonHWValues.find(resultRef);
|
|
if (nonHWForResult != nonHWValues.end()) {
|
|
// If has nonHW portion, RAUW to it.
|
|
if (auto newResult = nonHWForResult->second) {
|
|
assert(op.getResult().getType() == newResult.getType());
|
|
assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
|
|
op.getResult().replaceAllUsesWith(newResult);
|
|
}
|
|
return success();
|
|
}
|
|
|
|
assert(hwOnlyAggMap.count(op.getInput()));
|
|
|
|
auto newInput = hwOnlyAggMap[op.getInput()];
|
|
assert(newInput);
|
|
|
|
auto bundleType = type_cast<BundleType>(newInput.getType());
|
|
|
|
// Recompute the "actual" index for this field, it may have changed.
|
|
auto fieldName = op.getFieldName();
|
|
auto newFieldIndex = bundleType.getElementIndex(fieldName);
|
|
assert(newFieldIndex.has_value());
|
|
|
|
ImplicitLocOpBuilder builder(op.getLoc(), op);
|
|
auto newOp = builder.create<SubfieldOp>(newInput, *newFieldIndex);
|
|
if (auto name = op->getAttrOfType<StringAttr>("name"))
|
|
newOp->setAttr("name", name);
|
|
|
|
hwOnlyAggMap[op.getResult()] = newOp;
|
|
|
|
if (type_isa<FIRRTLBaseType>(op.getType()))
|
|
op.getResult().replaceAllUsesWith(newOp.getResult());
|
|
|
|
return success();
|
|
}
|
|
|
|
LogicalResult Visitor::visitExpr(OpenSubindexOp op) {
|
|
// Changes will be made.
|
|
recordChanges(true);
|
|
|
|
// In all cases, this operation will be dead and should be removed.
|
|
opsToErase.push_back(op);
|
|
|
|
// Chase this to its original root.
|
|
// If the FieldRef for this selection has a new home,
|
|
// RAUW to that value and this op is dead.
|
|
auto resultRef = refs.getFieldRefFromValue(op.getResult());
|
|
auto nonHWForResult = nonHWValues.find(resultRef);
|
|
if (nonHWForResult != nonHWValues.end()) {
|
|
// If has nonHW portion, RAUW to it.
|
|
if (auto newResult = nonHWForResult->second) {
|
|
assert(op.getResult().getType() == newResult.getType());
|
|
assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
|
|
op.getResult().replaceAllUsesWith(newResult);
|
|
}
|
|
return success();
|
|
}
|
|
|
|
assert(hwOnlyAggMap.count(op.getInput()));
|
|
|
|
auto newInput = hwOnlyAggMap[op.getInput()];
|
|
assert(newInput);
|
|
|
|
ImplicitLocOpBuilder builder(op.getLoc(), op);
|
|
auto newOp = builder.create<SubindexOp>(newInput, op.getIndex());
|
|
if (auto name = op->getAttrOfType<StringAttr>("name"))
|
|
newOp->setAttr("name", name);
|
|
|
|
hwOnlyAggMap[op.getResult()] = newOp;
|
|
|
|
if (type_isa<FIRRTLBaseType>(op.getType()))
|
|
op.getResult().replaceAllUsesWith(newOp.getResult());
|
|
return success();
|
|
}
|
|
|
|
LogicalResult Visitor::visitDecl(InstanceOp op) {
|
|
// Rewrite ports same strategy as for modules.
|
|
|
|
SmallVector<MappingInfo, 16> portMappings;
|
|
|
|
for (auto type : op.getResultTypes()) {
|
|
auto pmi = mapType(type, op.getLoc());
|
|
if (failed(pmi))
|
|
return failure();
|
|
portMappings.push_back(*pmi);
|
|
}
|
|
|
|
/// Total number of types mapped to.
|
|
size_t countWithErased = 0;
|
|
for (auto &pmi : portMappings)
|
|
countWithErased += pmi.count(/*includeErased=*/true);
|
|
|
|
/// Ports to add.
|
|
SmallVector<std::pair<unsigned, PortInfo>> newPorts;
|
|
|
|
/// Ports to remove.
|
|
BitVector portsToErase(countWithErased);
|
|
|
|
/// Go through each port mapping, gathering information about all new ports.
|
|
LLVM_DEBUG({
|
|
llvm::dbgs().indent(6) << "- instance:\n";
|
|
llvm::dbgs().indent(10) << "name: " << op.getInstanceNameAttr() << "\n";
|
|
llvm::dbgs().indent(10) << "module: " << op.getModuleNameAttr() << "\n";
|
|
llvm::dbgs().indent(10) << "ports:\n";
|
|
});
|
|
auto result = walkMappings(
|
|
portMappings, /*includeErased=*/true,
|
|
[&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
|
|
LLVM_DEBUG({
|
|
llvm::dbgs().indent(12)
|
|
<< "- name: " << op.getPortName(index) << "\n";
|
|
llvm::dbgs().indent(14) << "type: " << op.getType(index) << "\n";
|
|
llvm::dbgs().indent(14) << "mapping:\n";
|
|
pmi.print(llvm::dbgs(), /*indent=*/16);
|
|
llvm::dbgs() << "\n";
|
|
});
|
|
// Index for inserting new points next to this point.
|
|
// (Immediately after current port's index).
|
|
auto idxOfInsertPoint = index + 1;
|
|
|
|
if (pmi.identity)
|
|
return success();
|
|
|
|
// If not identity, mark this port for eventual removal.
|
|
portsToErase.set(newIndex);
|
|
|
|
auto portName = op.getPortName(index);
|
|
auto portDirection = op.getPortDirection(index);
|
|
auto loc = op.getLoc();
|
|
|
|
// Create new hw-only port, this will generally replace this port.
|
|
if (pmi.hwType) {
|
|
PortInfo hwPort(portName, pmi.hwType, portDirection,
|
|
/*symName=*/StringAttr{}, loc,
|
|
AnnotationSet(op.getPortAnnotation(index)));
|
|
newPorts.emplace_back(idxOfInsertPoint, hwPort);
|
|
|
|
// If want to run this pass later, need to fixup annotations.
|
|
if (!op.getPortAnnotation(index).empty())
|
|
return mlir::emitError(op.getLoc())
|
|
<< "annotations on open aggregates not handled yet";
|
|
} else {
|
|
if (!op.getPortAnnotation(index).empty())
|
|
return mlir::emitError(op.getLoc())
|
|
<< "annotations found on aggregate with no HW";
|
|
}
|
|
|
|
// Create ports for each non-hw field.
|
|
for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
|
|
auto name =
|
|
StringAttr::get(context, Twine(portName.strref()) + field.suffix);
|
|
auto orientation =
|
|
(Direction)((unsigned)portDirection ^ field.isFlip);
|
|
PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
|
|
loc, std::nullopt);
|
|
newPorts.emplace_back(idxOfInsertPoint, pi);
|
|
}
|
|
return success();
|
|
});
|
|
if (failed(result))
|
|
return failure();
|
|
|
|
// If no new ports, we're done.
|
|
if (newPorts.empty())
|
|
return success();
|
|
|
|
// Changes will be made.
|
|
recordChanges(true);
|
|
|
|
// Create new instance op with desired ports.
|
|
|
|
// TODO: add and erase ports without intermediate + various array attributes.
|
|
auto tempOp = op.cloneAndInsertPorts(newPorts);
|
|
opsToErase.push_back(tempOp);
|
|
ImplicitLocOpBuilder builder(op.getLoc(), op);
|
|
auto newInst = tempOp.erasePorts(builder, portsToErase);
|
|
|
|
auto mappingResult = walkMappings(
|
|
portMappings, /*includeErased=*/false,
|
|
[&](auto index, MappingInfo &pmi, auto newIndex) {
|
|
// Identity means index -> newIndex.
|
|
auto oldResult = op.getResult(index);
|
|
if (pmi.identity) {
|
|
// (Just do the RAUW here instead of tracking the mapping for this
|
|
// too.)
|
|
assert(oldResult.getType() == newInst.getType(newIndex));
|
|
oldResult.replaceAllUsesWith(newInst.getResult(newIndex));
|
|
return success();
|
|
}
|
|
|
|
// Create mappings for updating open aggregate users.
|
|
auto newPortIndex = newIndex;
|
|
if (pmi.hwType)
|
|
hwOnlyAggMap[oldResult] = newInst.getResult(newPortIndex++);
|
|
|
|
for (auto &field : pmi.fields) {
|
|
auto ref = FieldRef(oldResult, field.fieldID);
|
|
auto newVal = newInst.getResult(newPortIndex++);
|
|
assert(newVal.getType() == field.type);
|
|
nonHWValues[ref] = newVal;
|
|
}
|
|
for (auto fieldID : pmi.mapToNullInteriors) {
|
|
auto ref = FieldRef(oldResult, fieldID);
|
|
assert(!nonHWValues.count(ref));
|
|
nonHWValues[ref] = {};
|
|
}
|
|
return success();
|
|
});
|
|
if (failed(mappingResult))
|
|
return failure();
|
|
|
|
opsToErase.push_back(op);
|
|
|
|
return success();
|
|
}
|
|
|
|
LogicalResult Visitor::visitDecl(WireOp op) {
|
|
auto pmi = mapType(op.getResultTypes()[0], op.getLoc(), op.getInnerSymAttr());
|
|
if (failed(pmi))
|
|
return failure();
|
|
MappingInfo mappings = *pmi;
|
|
|
|
LLVM_DEBUG({
|
|
llvm::dbgs().indent(6) << "- wire:\n";
|
|
llvm::dbgs().indent(10) << "name: " << op.getNameAttr() << "\n";
|
|
llvm::dbgs().indent(10) << "type: " << op.getType(0) << "\n";
|
|
llvm::dbgs().indent(12) << "mapping:\n";
|
|
mappings.print(llvm::dbgs(), 14);
|
|
llvm::dbgs() << "\n";
|
|
});
|
|
|
|
if (mappings.identity)
|
|
return success();
|
|
|
|
// Changes will be made.
|
|
recordChanges(true);
|
|
|
|
ImplicitLocOpBuilder builder(op.getLoc(), op);
|
|
|
|
if (!op.getAnnotations().empty())
|
|
return mlir::emitError(op.getLoc())
|
|
<< "annotations on open aggregates not handled yet";
|
|
|
|
// Create the new HW wire.
|
|
if (mappings.hwType)
|
|
hwOnlyAggMap[op.getResult()] =
|
|
builder
|
|
.create<WireOp>(mappings.hwType, op.getName(), op.getNameKind(),
|
|
op.getAnnotations(), mappings.newSym,
|
|
op.getForceable())
|
|
.getResult();
|
|
|
|
// Create the non-HW wires. Non-HW wire names are always droppable.
|
|
for (auto &[type, fieldID, _, suffix] : mappings.fields)
|
|
nonHWValues[FieldRef(op.getResult(), fieldID)] =
|
|
builder
|
|
.create<WireOp>(type,
|
|
builder.getStringAttr(Twine(op.getName()) + suffix),
|
|
NameKindEnum::DroppableName)
|
|
.getResult();
|
|
|
|
for (auto fieldID : mappings.mapToNullInteriors)
|
|
nonHWValues[FieldRef(op.getResult(), fieldID)] = {};
|
|
|
|
opsToErase.push_back(op);
|
|
|
|
return success();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Type Conversion
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
FailureOr<MappingInfo> Visitor::mapType(Type type, Location errorLoc,
|
|
hw::InnerSymAttr sym) {
|
|
MappingInfo pi{false, {}, {}, {}};
|
|
auto ftype = type_dyn_cast<FIRRTLType>(type);
|
|
// Anything that isn't an open aggregates is left alone.
|
|
if (!ftype || !isa<OpenBundleType, OpenVectorType>(ftype)) {
|
|
pi.identity = true;
|
|
return pi;
|
|
}
|
|
|
|
SmallVector<hw::InnerSymPropertiesAttr> newProps;
|
|
|
|
// NOLINTBEGIN(misc-no-recursion)
|
|
auto recurse = [&](auto &&f, FIRRTLType type, const Twine &suffix = "",
|
|
bool flip = false, uint64_t fieldID = 0,
|
|
uint64_t newFieldID = 0) -> FailureOr<FIRRTLBaseType> {
|
|
auto newType =
|
|
TypeSwitch<FIRRTLType, FailureOr<FIRRTLBaseType>>(type)
|
|
.Case<FIRRTLBaseType>([](auto base) { return base; })
|
|
.template Case<OpenBundleType>([&](OpenBundleType obTy)
|
|
-> FailureOr<FIRRTLBaseType> {
|
|
SmallVector<BundleType::BundleElement> hwElements;
|
|
uint64_t id = 0;
|
|
for (const auto &[index, element] :
|
|
llvm::enumerate(obTy.getElements())) {
|
|
auto base =
|
|
f(f, element.type, suffix + "_" + element.name.strref(),
|
|
flip ^ element.isFlip, fieldID + obTy.getFieldID(index),
|
|
newFieldID + id + 1);
|
|
if (failed(base))
|
|
return failure();
|
|
if (*base) {
|
|
hwElements.emplace_back(element.name, element.isFlip, *base);
|
|
id += hw::FieldIdImpl::getMaxFieldID(*base) + 1;
|
|
}
|
|
}
|
|
|
|
if (hwElements.empty()) {
|
|
pi.mapToNullInteriors.push_back(fieldID);
|
|
return FIRRTLBaseType{};
|
|
}
|
|
|
|
return BundleType::get(context, hwElements, obTy.isConst());
|
|
})
|
|
.template Case<OpenVectorType>([&](OpenVectorType ovTy)
|
|
-> FailureOr<FIRRTLBaseType> {
|
|
uint64_t id = 0;
|
|
FIRRTLBaseType convert;
|
|
// Walk for each index to extract each leaf separately, but expect
|
|
// same hw-only type for all.
|
|
for (auto idx : llvm::seq<size_t>(0U, ovTy.getNumElements())) {
|
|
auto hwElementType =
|
|
f(f, ovTy.getElementType(), suffix + "_" + Twine(idx), flip,
|
|
fieldID + ovTy.getFieldID(idx), newFieldID + id + 1);
|
|
if (failed(hwElementType))
|
|
return failure();
|
|
assert((!convert || convert == *hwElementType) &&
|
|
"expected same hw type for all elements");
|
|
convert = *hwElementType;
|
|
if (convert)
|
|
id += hw::FieldIdImpl::getMaxFieldID(convert) + 1;
|
|
}
|
|
|
|
if (!convert) {
|
|
pi.mapToNullInteriors.push_back(fieldID);
|
|
return FIRRTLBaseType{};
|
|
}
|
|
|
|
return FVectorType::get(convert, ovTy.getNumElements(),
|
|
ovTy.isConst());
|
|
})
|
|
.template Case<RefType>([&](RefType ref) {
|
|
auto f = NonHWField{ref, fieldID, flip, {}};
|
|
suffix.toVector(f.suffix);
|
|
pi.fields.emplace_back(std::move(f));
|
|
return FIRRTLBaseType{};
|
|
})
|
|
// This is identical to the RefType case above, but copied out
|
|
// to try to fix a bug when combining + auto w/MSVC.
|
|
.template Case<PropertyType>([&](PropertyType prop) {
|
|
auto f = NonHWField{prop, fieldID, flip, {}};
|
|
suffix.toVector(f.suffix);
|
|
pi.fields.emplace_back(std::move(f));
|
|
return FIRRTLBaseType{};
|
|
})
|
|
.Default([&](auto _) {
|
|
pi.mapToNullInteriors.push_back(fieldID);
|
|
return FIRRTLBaseType{};
|
|
});
|
|
if (failed(newType))
|
|
return failure();
|
|
|
|
// If there's a symbol on this, add it with adjusted fieldID.
|
|
if (sym)
|
|
if (auto symOnThis = sym.getSymIfExists(fieldID)) {
|
|
if (!*newType)
|
|
return mlir::emitError(errorLoc, "inner symbol ")
|
|
<< symOnThis << " mapped to non-HW type";
|
|
newProps.push_back(hw::InnerSymPropertiesAttr::get(
|
|
context, symOnThis, newFieldID,
|
|
StringAttr::get(context, "public")));
|
|
}
|
|
return newType;
|
|
};
|
|
|
|
auto hwType = recurse(recurse, ftype);
|
|
if (failed(hwType))
|
|
return failure();
|
|
pi.hwType = *hwType;
|
|
|
|
assert(pi.hwType != type);
|
|
// NOLINTEND(misc-no-recursion)
|
|
|
|
if (sym) {
|
|
assert(sym.size() == newProps.size());
|
|
|
|
if (!pi.hwType && !newProps.empty())
|
|
return mlir::emitError(errorLoc, "inner symbol on non-HW type");
|
|
|
|
llvm::sort(newProps, [](auto &p, auto &q) {
|
|
return p.getFieldID() < q.getFieldID();
|
|
});
|
|
pi.newSym = hw::InnerSymAttr::get(context, newProps);
|
|
}
|
|
|
|
return pi;
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Pass Infrastructure
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
struct LowerOpenAggsPass : public LowerOpenAggsBase<LowerOpenAggsPass> {
|
|
LowerOpenAggsPass() = default;
|
|
void runOnOperation() override;
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
// This is the main entrypoint for the lowering pass.
|
|
void LowerOpenAggsPass::runOnOperation() {
|
|
LLVM_DEBUG(debugPassHeader(this) << "\n");
|
|
SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Visiting modules:\n");
|
|
std::atomic<bool> madeChanges = false;
|
|
auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
|
|
Visitor visitor(&getContext());
|
|
auto result = visitor.visit(cast<FModuleLike>(op));
|
|
if (visitor.madeChanges())
|
|
madeChanges = true;
|
|
return result;
|
|
});
|
|
|
|
if (result.failed())
|
|
signalPassFailure();
|
|
if (!madeChanges)
|
|
markAllAnalysesPreserved();
|
|
}
|
|
|
|
/// This is the pass constructor.
|
|
std::unique_ptr<mlir::Pass> circt::firrtl::createLowerOpenAggsPass() {
|
|
return std::make_unique<LowerOpenAggsPass>();
|
|
}
|