Skip to content

Commit

Permalink
[FIRRTL][OMIR] Update OMIR SRAM Paths (llvm#2024)
Browse files Browse the repository at this point in the history
Extend the `EmitOMIR` pass to handle the conversion of relative targets
in the `instancePath` field of `OMSRAM` nodes to absolute targets.

Co-authored-by: Prithayan Barua <prithayan@gmail.com>
Co-authored-by: Fabian Schuiki <fabian@schuiki.ch>
  • Loading branch information
prithayan and fabianschuiki authored Oct 22, 2021
1 parent f239be4 commit b77f14b
Show file tree
Hide file tree
Showing 7 changed files with 460 additions and 67 deletions.
13 changes: 9 additions & 4 deletions include/circt/Dialect/FIRRTL/InstanceGraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,16 @@ class InstanceGraph {
using InstancePath = ArrayRef<InstanceOp>;

template <typename T>
static T &operator<<(T &os, const InstancePath &path) {
os << "$root";
inline static T &formatInstancePath(T &into, const InstancePath &path) {
into << "$root";
for (auto inst : path)
os << "/" << inst.name() << ":" << inst.moduleName();
return os;
into << "/" << inst.name() << ":" << inst.moduleName();
return into;
}

template <typename T>
static T &operator<<(T &os, const InstancePath &path) {
return formatInstancePath(os, path);
}

/// A data structure that caches and provides absolute paths to module instances
Expand Down
5 changes: 3 additions & 2 deletions lib/Dialect/FIRRTL/Import/FIRAnnotations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -888,11 +888,12 @@ scatterOMIR(Attribute original, unsigned &annotationID,
// Convert a string-encoded type to a dictionary that includes the type
// information and an identifier derived from the current annotationID. Then
// increment the annotationID. Return the constructed dictionary.
auto addID = [&](StringRef tpe) -> DictionaryAttr {
auto addID = [&](StringRef tpe, StringRef path) -> DictionaryAttr {
NamedAttrList fields;
fields.append("id",
IntegerAttr::get(IntegerType::get(ctx, 64), annotationID++));
fields.append("omir.tracker", UnitAttr::get(ctx));
fields.append("path", StringAttr::get(ctx, path));
fields.append("type", StringAttr::get(ctx, tpe));
return DictionaryAttr::getWithSorted(ctx, fields);
};
Expand Down Expand Up @@ -932,7 +933,7 @@ scatterOMIR(Attribute original, unsigned &annotationID,
newAnnotations[leafTarget].push_back(
DictionaryAttr::get(ctx, tracker));

return addID(tpe);
return addID(tpe, value);
}

// The following are types that may exist, but we do not unbox them. At
Expand Down
160 changes: 150 additions & 10 deletions lib/Dialect/FIRRTL/Transforms/EmitOMIR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "AnnotationDetails.h"
#include "PassDetails.h"
#include "circt/Dialect/FIRRTL/CircuitNamespace.h"
#include "circt/Dialect/FIRRTL/InstanceGraph.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/SV/SVDialect.h"
Expand Down Expand Up @@ -49,6 +50,7 @@ class EmitOMIRPass : public EmitOMIRBase<EmitOMIRPass> {

private:
void runOnOperation() override;
void makeTrackerAbsolute(Tracker &tracker);

void emitSourceInfo(Location input, SmallString<64> &into);
void emitOMNode(Attribute node, llvm::json::OStream &jsonStream);
Expand Down Expand Up @@ -80,18 +82,49 @@ class EmitOMIRPass : public EmitOMIRBase<EmitOMIRPass> {

/// Whether any errors have occurred in the current `runOnOperation`.
bool anyFailures;
/// A symbol table for the current operation.
/// Analyses for the current operation; only valid within `runOnOperation`.
SymbolTable *symtbl;
CircuitNamespace *circuitNamespace;
InstancePathCache *instancePaths;
/// OMIR target trackers gathered in the current operation, by tracker ID.
DenseMap<Attribute, Tracker> trackers;
/// The list of symbols to be interpolated in the verbatim JSON. This gets
/// populated as the JSON is constructed and module and instance names are
/// collected.
SmallVector<Attribute> symbols;
SmallDenseMap<Attribute, unsigned> symbolIndices;
/// Temporary `firrtl.nla` operations to be deleted at the end of the pass.
SmallVector<NonLocalAnchor> removeTempNLAs;
};
} // namespace

/// Check if an `OMNode` is an `OMSRAM` and requires special treatment of its
/// instance path field. This returns the ID of the tracker stored in the
/// `instancePath` field if the node has an array field `omType` that contains a
/// `OMString:OMSRAM` entry.
static IntegerAttr isOMSRAM(Attribute &node) {
auto dict = node.dyn_cast<DictionaryAttr>();
if (!dict)
return {};
auto idAttr = dict.getAs<StringAttr>("id");
if (!idAttr)
return {};
IntegerAttr id;
if (auto infoAttr = dict.getAs<DictionaryAttr>("fields")) {
if (auto iP = infoAttr.getAs<DictionaryAttr>("instancePath"))
if (auto v = iP.getAs<DictionaryAttr>("value"))
if (v.getAs<UnitAttr>("omir.tracker"))
id = v.getAs<IntegerAttr>("id");
if (auto omTy = infoAttr.getAs<DictionaryAttr>("omType"))
if (auto valueArr = omTy.getAs<ArrayAttr>("value"))
for (auto attr : valueArr)
if (auto str = attr.dyn_cast<StringAttr>())
if (str.getValue().equals("OMString:OMSRAM"))
return id;
}
return {};
}

//===----------------------------------------------------------------------===//
// Pass Implementation
//===----------------------------------------------------------------------===//
Expand All @@ -100,16 +133,22 @@ void EmitOMIRPass::runOnOperation() {
MLIRContext *context = &getContext();
anyFailures = false;
symtbl = nullptr;
circuitNamespace = nullptr;
instancePaths = nullptr;
trackers.clear();
symbols.clear();
symbolIndices.clear();
removeTempNLAs.clear();
CircuitOp circuitOp = getOperation();

// Gather the relevant annotations from the circuit. On the one hand these are
// all the actual `OMIRAnnotation`s that need processing and emission, as well
// as an optional `OMIRFileAnnotation` that overrides the default OMIR output
// file.
// file. Also while we're at it, keep track of all OMIR nodes that qualify as
// an SRAM and that require their trackers to be turned into NLAs starting at
// the root of the hierarchy.
SmallVector<ArrayRef<Attribute>> annoNodes;
DenseSet<Attribute> sramIDs;
Optional<StringRef> outputFilename = {};

AnnotationSet::removeAnnotations(circuitOp, [&](Annotation anno) {
Expand All @@ -135,17 +174,29 @@ void EmitOMIRPass::runOnOperation() {
}
LLVM_DEBUG(llvm::dbgs() << "- OMIR: " << nodesAttr << "\n");
annoNodes.push_back(nodesAttr.getValue());
for (auto node : nodesAttr) {
if (auto id = isOMSRAM(node)) {
LLVM_DEBUG(llvm::dbgs() << " - is SRAM with tracker " << id << "\n");
sramIDs.insert(id);
}
}
return true;
}
return false;
});
if (anyFailures)
return signalPassFailure();

// Traverse the IR and collect all tracker annotations that were previously
// scattered into the circuit.
// Establish some of the analyses we need throughout the pass.
SymbolTable currentSymtbl(circuitOp);
CircuitNamespace currentCircuitNamespace(circuitOp);
InstancePathCache currentInstancePaths(getAnalysis<InstanceGraph>());
symtbl = &currentSymtbl;
circuitNamespace = &currentCircuitNamespace;
instancePaths = &currentInstancePaths;

// Traverse the IR and collect all tracker annotations that were previously
// scattered into the circuit.
circuitOp.walk([&](Operation *op) {
AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
if (!anno.isClass(omirTrackerAnnoClass))
Expand All @@ -162,6 +213,8 @@ void EmitOMIRPass::runOnOperation() {
if (auto nlaSym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal"))
tracker.nla =
dyn_cast_or_null<NonLocalAnchor>(symtbl->lookup(nlaSym.getAttr()));
if (sramIDs.erase(tracker.id))
makeTrackerAbsolute(tracker);
trackers.insert({tracker.id, tracker});
return true;
});
Expand Down Expand Up @@ -195,6 +248,31 @@ void EmitOMIRPass::runOnOperation() {
if (anyFailures)
return signalPassFailure();

// Delete the temporary NLAs. This requires us to visit all the nodes along
// the NLA's path and remove `circt.nonlocal` annotations referring to the
// NLA.
for (auto nla : removeTempNLAs) {
LLVM_DEBUG(llvm::dbgs() << "Removing " << nla << "\n");
for (auto modName : nla.modpath().getAsRange<FlatSymbolRefAttr>()) {
Operation *mod = symtbl->lookup(modName.getValue());
mod->walk([&](InstanceOp instOp) {
AnnotationSet::removeAnnotations(instOp, [&](Annotation anno) {
auto match =
anno.isClass("circt.nonlocal") &&
anno.getMember<FlatSymbolRefAttr>("circt.nonlocal").getAttr() ==
nla.sym_nameAttr();
if (match)
LLVM_DEBUG(llvm::dbgs()
<< "- Removing " << anno.getDict() << " from " << modName
<< "." << instOp.name() << "\n");
return match;
});
});
}
nla->erase();
}
removeTempNLAs.clear();

// Emit the OMIR JSON as a verbatim op.
auto builder = OpBuilder(circuitOp);
builder.setInsertionPointAfter(circuitOp);
Expand All @@ -206,6 +284,66 @@ void EmitOMIRPass::runOnOperation() {
verbatimOp.symbolsAttr(ArrayAttr::get(context, symbols));
}

/// Make a tracker absolute by adding an NLA to it which starts at the root
/// module of the circuit. Generates an error if any module along the path is
/// instantiated multiple times.
void EmitOMIRPass::makeTrackerAbsolute(Tracker &tracker) {
auto *context = &getContext();
auto builder = OpBuilder::atBlockBegin(getOperation().getBody());

// Pick a name for the NLA that doesn't collide with anything.
auto opName = tracker.op->getAttrOfType<StringAttr>("name");
auto nlaName = circuitNamespace->newName("omir_nla_" + opName.getValue());

// Assemble the NLA annotation to be put on all the operations participating
// in the path.
auto nlaAttr = builder.getDictionaryAttr({
builder.getNamedAttr("circt.nonlocal",
FlatSymbolRefAttr::get(context, nlaName)),
builder.getNamedAttr("class", StringAttr::get(context, "circt.nonlocal")),
});

// Get all the paths instantiating this module.
auto mod = tracker.op->getParentOfType<FModuleOp>();
auto paths = instancePaths->getAbsolutePaths(mod);
if (paths.empty()) {
tracker.op->emitError("OMIR node targets uninstantiated component `")
<< opName.getValue() << "`";
anyFailures = true;
return;
}
if (paths.size() > 1) {
auto diag = tracker.op->emitError("OMIR node targets ambiguous component `")
<< opName.getValue() << "`";
diag.attachNote(tracker.op->getLoc())
<< "may refer to the following paths:";
for (auto path : paths)
formatInstancePath(diag.attachNote(tracker.op->getLoc()) << "- ", path);
anyFailures = true;
return;
}

// Assemble the module and name path for the NLA. Also attach an NLA reference
// annotation to each instance participating in the path.
SmallVector<Attribute> modpath, namepath;
auto addToPath = [&](Operation *op, StringAttr name) {
AnnotationSet annos(op);
annos.addAnnotations(nlaAttr);
annos.applyToOperation(op);
modpath.push_back(FlatSymbolRefAttr::get(op->getParentOfType<FModuleOp>()));
namepath.push_back(name);
};
for (InstanceOp inst : paths[0])
addToPath(inst, inst.nameAttr());
addToPath(tracker.op, opName);

// Add the NLA to the tracker and mark it to be deleted later.
tracker.nla = builder.create<NonLocalAnchor>(
builder.getUnknownLoc(), builder.getStringAttr(nlaName),
builder.getArrayAttr(modpath), builder.getArrayAttr(namepath));
removeTempNLAs.push_back(tracker.nla);
}

/// Emit a source locator into a string, for inclusion in the `info` field of
/// `OMNode` and `OMField`.
void EmitOMIRPass::emitSourceInfo(Location input, SmallString<64> &into) {
Expand Down Expand Up @@ -424,8 +562,10 @@ void EmitOMIRPass::emitTrackedTarget(DictionaryAttr node,
auto diag = getOperation().emitError("tracked OMIR target of type `")
<< type << "` was deleted";
diag.attachNote(getOperation().getLoc())
<< type << " should never be deleted";
diag.attachNote(getOperation().getLoc()) << node;
<< "`" << type << "` should never be deleted";
if (auto path = node.getAs<StringAttr>("path"))
diag.attachNote(getOperation().getLoc())
<< "original path: `" << path.getValue() << "`";
anyFailures = true;
return jsonStream.value("<error>");
}
Expand Down Expand Up @@ -480,14 +620,14 @@ void EmitOMIRPass::emitTrackedTarget(DictionaryAttr node,
// Serialize any potential component *inside* the module that this target may
// specifically refer to.
StringRef componentName;
if (isa<WireOp, RegOp, RegResetOp, InstanceOp, NodeOp>(tracker.op)) {
AnnotationSet::addDontTouch(tracker.op);
LLVM_DEBUG(llvm::dbgs()
<< "Marking OMIR-targeted " << tracker.op << " as dont-touch\n");
if (isa<WireOp, RegOp, RegResetOp, InstanceOp, NodeOp, MemOp>(tracker.op)) {
// TODO: This should *really* drop a symbol placeholder into the JSON. But
// we currently don't have any symbols for these FIRRTL ops. May be solved
// through NLAs.
componentName = tracker.op->getAttrOfType<StringAttr>("name").getValue();
AnnotationSet::addDontTouch(tracker.op);
LLVM_DEBUG(llvm::dbgs() << "Marking OMIR-targeted `" << componentName
<< "` as dont-touch\n");
} else if (!isa<FModuleOp>(tracker.op)) {
tracker.op->emitError("invalid target for `") << type << "` OMIR";
anyFailures = true;
Expand Down
Loading

0 comments on commit b77f14b

Please sign in to comment.