CIRCT 22.0.0git
Loading...
Searching...
No Matches
LowerTypes.cpp
Go to the documentation of this file.
1//===- LowerTypes.cpp - Lower Aggregate Types -------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the LowerTypes pass. This pass replaces aggregate types
10// with expanded values.
11//
12// This pass walks the operations in reverse order. This lets it visit users
13// before defs. Users can usually be expanded out to multiple operations (think
14// mux of a bundle to muxes of each field) with a temporary subWhatever op
15// inserted. When processing an aggregate producer, we blow out the op as
16// appropriate, then walk the users, often those are subWhatever ops which can
17// be bypassed and deleted. Function arguments are logically last on the
18// operation visit order and walked left to right, being peeled one layer at a
19// time with replacements inserted to the right of the original argument.
20//
21// Each processing of an op peels one layer of aggregate type off. Because new
22// ops are inserted immediately above the current up, the walk will visit them
23// next, effectively recusing on the aggregate types, without recusing. These
24// potentially temporary ops(if the aggregate is complex) effectively serve as
25// the worklist. Often aggregates are shallow, so the new ops are the final
26// ones.
27//
28//===----------------------------------------------------------------------===//
29
38#include "circt/Support/Debug.h"
39#include "mlir/IR/ImplicitLocOpBuilder.h"
40#include "mlir/IR/Threading.h"
41#include "mlir/Pass/Pass.h"
42#include "llvm/ADT/APSInt.h"
43#include "llvm/ADT/BitVector.h"
44#include "llvm/Support/Debug.h"
45
46#define DEBUG_TYPE "firrtl-lower-types"
47
48namespace circt {
49namespace firrtl {
50#define GEN_PASS_DEF_LOWERFIRRTLTYPES
51#include "circt/Dialect/FIRRTL/Passes.h.inc"
52} // namespace firrtl
53} // namespace circt
54
55using namespace circt;
56using namespace firrtl;
57
58// TODO: check all argument types
59namespace {
60/// This represents a flattened bundle field element.
61struct FlatBundleFieldEntry {
62 /// This is the underlying ground type of the field.
63 FIRRTLBaseType type;
64 /// The index in the parent type
65 size_t index;
66 /// The fieldID
67 unsigned fieldID;
68 /// This is a suffix to add to the field name to make it unique.
69 SmallString<16> suffix;
70 /// This indicates whether the field was flipped to be an output.
71 bool isOutput;
72
73 FlatBundleFieldEntry(const FIRRTLBaseType &type, size_t index,
74 unsigned fieldID, StringRef suffix, bool isOutput)
75 : type(type), index(index), fieldID(fieldID), suffix(suffix),
76 isOutput(isOutput) {}
77
78 void dump() const {
79 llvm::errs() << "FBFE{" << type << " index<" << index << "> fieldID<"
80 << fieldID << "> suffix<" << suffix << "> isOutput<"
81 << isOutput << ">}\n";
82 }
83};
84} // end anonymous namespace
85
86/// Return fieldType or fieldType as same ref as type.
88 return mapBaseType(type, [&](auto) { return fieldType; });
89}
90
91/// Return fieldType or fieldType as same ref as type.
92static Type mapLoweredType(Type type, FIRRTLBaseType fieldType) {
93 auto ftype = type_dyn_cast<FIRRTLType>(type);
94 if (!ftype)
95 return type;
96 return mapLoweredType(ftype, fieldType);
97}
98
99/// Return true if the type is a 1d vector type or ground type.
102 .Case<BundleType>([&](auto bundle) { return false; })
103 .Case<FVectorType>([&](FVectorType vector) {
104 // When the size is 1, lower the vector into a scalar.
105 return vector.getElementType().isGround() &&
106 vector.getNumElements() > 1;
107 })
108 .Default([](auto groundType) { return true; });
109}
110
111// NOLINTBEGIN(misc-no-recursion)
112/// Return true if the type has a bundle type as subtype.
115 .Case<BundleType>([&](auto bundle) { return true; })
116 .Case<FVectorType>([&](FVectorType vector) {
117 return containsBundleType(vector.getElementType());
118 })
119 .Default([](auto groundType) { return false; });
120}
121// NOLINTEND(misc-no-recursion)
122
123/// Return true if we can preserve the type.
124static bool isPreservableAggregateType(Type type,
126 if (auto refType = type_dyn_cast<RefType>(type)) {
127 // Always preserve rwprobe's.
128 if (refType.getForceable())
129 return true;
130 // FIXME: Don't preserve read-only RefType for now. This is workaround for
131 // MemTap which causes type mismatches (issue 4479).
132 return false;
133 }
134
135 // Return false if no aggregate value is preserved.
136 if (mode == PreserveAggregate::None)
137 return false;
138
139 auto firrtlType = type_dyn_cast<FIRRTLBaseType>(type);
140 if (!firrtlType)
141 return false;
142
143 // We can a preserve the type iff (i) the type is not passive, (ii) the type
144 // doesn't contain analog and (iii) type don't contain zero bitwidth.
145 if (!firrtlType.isPassive() || firrtlType.containsAnalog() ||
146 hasZeroBitWidth(firrtlType))
147 return false;
148
149 switch (mode) {
151 return true;
153 return isOneDimVectorType(firrtlType);
155 return !containsBundleType(firrtlType);
156 default:
157 llvm_unreachable("unexpected mode");
158 }
159}
160
161/// Peel one layer of an aggregate type into its components. Type may be
162/// complex, but empty, in which case fields is empty, but the return is true.
163static bool peelType(Type type, SmallVectorImpl<FlatBundleFieldEntry> &fields,
165 // If the aggregate preservation is enabled and the type is preservable,
166 // then just return.
167 if (isPreservableAggregateType(type, mode))
168 return false;
169
170 if (auto refType = type_dyn_cast<RefType>(type))
171 type = refType.getType();
173 .Case<BundleType>([&](auto bundle) {
174 SmallString<16> tmpSuffix;
175 // Otherwise, we have a bundle type. Break it down.
176 for (size_t i = 0, e = bundle.getNumElements(); i < e; ++i) {
177 auto elt = bundle.getElement(i);
178 // Construct the suffix to pass down.
179 tmpSuffix.resize(0);
180 tmpSuffix.push_back('_');
181 tmpSuffix.append(elt.name.getValue());
182 fields.emplace_back(elt.type, i, bundle.getFieldID(i), tmpSuffix,
183 elt.isFlip);
184 }
185 return true;
186 })
187 .Case<FVectorType>([&](auto vector) {
188 // Increment the field ID to point to the first element.
189 for (size_t i = 0, e = vector.getNumElements(); i != e; ++i) {
190 fields.emplace_back(vector.getElementType(), i, vector.getFieldID(i),
191 "_" + std::to_string(i), false);
192 }
193 return true;
194 })
195 .Default([](auto op) { return false; });
196}
197
198/// Return if something is not a normal subaccess. Non-normal includes
199/// zero-length vectors and constant indexes (which are really subindexes).
200static bool isNotSubAccess(Operation *op) {
201 SubaccessOp sao = llvm::dyn_cast<SubaccessOp>(op);
202 if (!sao)
203 return true;
204 ConstantOp arg =
205 llvm::dyn_cast_or_null<ConstantOp>(sao.getIndex().getDefiningOp());
206 return arg && sao.getInput().getType().base().getNumElements() != 0;
207}
208
209/// Look through and collect subfields leading to a subaccess.
210static SmallVector<Operation *> getSAWritePath(Operation *op) {
211 SmallVector<Operation *> retval;
212 auto defOp = op->getOperand(0).getDefiningOp();
213 while (isa_and_nonnull<SubfieldOp, SubindexOp, SubaccessOp>(defOp)) {
214 retval.push_back(defOp);
215 defOp = defOp->getOperand(0).getDefiningOp();
216 }
217 // Trim to the subaccess
218 while (!retval.empty() && isNotSubAccess(retval.back()))
219 retval.pop_back();
220 return retval;
221}
222
223/// Clone memory for the specified field. Returns null op on error.
224static MemOp cloneMemWithNewType(ImplicitLocOpBuilder *b, MemOp op,
225 FlatBundleFieldEntry field) {
226 SmallVector<Type, 8> ports;
227 SmallVector<Attribute, 8> portNames;
228 SmallVector<Attribute, 8> portLocations;
229
230 auto oldPorts = op.getPorts();
231 for (size_t portIdx = 0, e = oldPorts.size(); portIdx < e; ++portIdx) {
232 auto port = oldPorts[portIdx];
233 ports.push_back(
234 MemOp::getTypeForPort(op.getDepth(), field.type, port.second));
235 portNames.push_back(port.first);
236 }
237
238 // It's easier to duplicate the old annotations, then fix and filter them.
239 auto newMem =
240 MemOp::create(*b, ports, op.getReadLatency(), op.getWriteLatency(),
241 op.getDepth(), op.getRuw(), b->getArrayAttr(portNames),
242 (op.getName() + field.suffix).str(), op.getNameKind(),
243 op.getAnnotations(), op.getPortAnnotations(),
244 op.getInnerSymAttr(), op.getInitAttr(), op.getPrefixAttr());
245
246 if (op.getInnerSym()) {
247 op.emitError("cannot split memory with symbol present");
248 return {};
249 }
250
251 SmallVector<Attribute> newAnnotations;
252 for (size_t portIdx = 0, e = newMem.getNumResults(); portIdx < e; ++portIdx) {
253 auto portType = type_cast<BundleType>(newMem.getResult(portIdx).getType());
254 auto oldPortType = type_cast<BundleType>(op.getResult(portIdx).getType());
255 SmallVector<Attribute> portAnno;
256 for (auto attr : newMem.getPortAnnotation(portIdx)) {
257 Annotation anno(attr);
258 if (auto annoFieldID = anno.getFieldID()) {
259 auto targetIndex = oldPortType.getIndexForFieldID(annoFieldID);
260
261 // Apply annotations to all elements if the target is the whole
262 // sub-field.
263 if (annoFieldID == oldPortType.getFieldID(targetIndex)) {
264 anno.setMember(
265 "circt.fieldID",
266 b->getI32IntegerAttr(portType.getFieldID(targetIndex)));
267 portAnno.push_back(anno.getDict());
268 continue;
269 }
270
271 // Handle aggregate sub-fields, including `(r/w)data` and `(w)mask`.
272 if (type_isa<BundleType>(oldPortType.getElement(targetIndex).type)) {
273 // Check whether the annotation falls into the range of the current
274 // field. Note that the `field` here is peeled from the `data`
275 // sub-field of the memory port, thus we need to add the fieldID of
276 // `data` or `mask` sub-field to get the "real" fieldID.
277 auto fieldID = field.fieldID + oldPortType.getFieldID(targetIndex);
278 if (annoFieldID >= fieldID &&
279 annoFieldID <=
280 fieldID + hw::FieldIdImpl::getMaxFieldID(field.type)) {
281 // Set the field ID of the new annotation.
282 auto newFieldID =
283 annoFieldID - fieldID + portType.getFieldID(targetIndex);
284 anno.setMember("circt.fieldID", b->getI32IntegerAttr(newFieldID));
285 portAnno.push_back(anno.getDict());
286 }
287 }
288 } else
289 portAnno.push_back(attr);
290 }
291 newAnnotations.push_back(b->getArrayAttr(portAnno));
292 }
293 newMem.setAllPortAnnotations(newAnnotations);
294 return newMem;
295}
296
297//===----------------------------------------------------------------------===//
298// Module Type Lowering
299//===----------------------------------------------------------------------===//
300namespace {
301
302struct AttrCache {
303 AttrCache(MLIRContext *context) {
304 i64ty = IntegerType::get(context, 64);
305 nameAttr = StringAttr::get(context, "name");
306 nameKindAttr = StringAttr::get(context, "nameKind");
307 sPortDirections = StringAttr::get(context, "portDirections");
308 sPortNames = StringAttr::get(context, "portNames");
309 sPortTypes = StringAttr::get(context, "portTypes");
310 sPortSymbols = StringAttr::get(context, "portSymbols");
311 sPortLocations = StringAttr::get(context, "portLocations");
312 sPortAnnotations = StringAttr::get(context, "portAnnotations");
313 sPortDomains = StringAttr::get(context, "domainInfo");
314 sEmpty = StringAttr::get(context, "");
315 aEmpty = ArrayAttr::get(context, {});
316 }
317 AttrCache(const AttrCache &) = default;
318
319 Type i64ty;
320 StringAttr nameAttr, nameKindAttr, sPortDirections, sPortNames, sPortTypes,
321 sPortSymbols, sPortLocations, sPortAnnotations, sPortDomains, sEmpty;
322 ArrayAttr aEmpty;
323};
324
325// The visitors all return true if the operation should be deleted, false if
326// not.
327struct TypeLoweringVisitor : public FIRRTLVisitor<TypeLoweringVisitor, bool> {
328
329 TypeLoweringVisitor(
330 MLIRContext *context, PreserveAggregate::PreserveMode preserveAggregate,
331 Convention bodyConvention,
332 PreserveAggregate::PreserveMode memoryPreservationMode,
333 SymbolTable &symTbl, const AttrCache &cache,
334 const llvm::DenseMap<FModuleLike, Convention> &conventionTable)
335 : context(context), defaultAggregatePreservationMode(preserveAggregate),
336 memoryPreservationMode(memoryPreservationMode), symTbl(symTbl),
337 cache(cache), conventionTable(conventionTable) {
338 bodyAggregatePreservationMode = bodyConvention == Convention::Scalarized
340 : defaultAggregatePreservationMode;
341 }
342 using FIRRTLVisitor<TypeLoweringVisitor, bool>::visitDecl;
343 using FIRRTLVisitor<TypeLoweringVisitor, bool>::visitExpr;
344 using FIRRTLVisitor<TypeLoweringVisitor, bool>::visitStmt;
345
346 /// If the referenced operation is a FModuleOp or an FExtModuleOp, perform
347 /// type lowering on all operations.
348 void lowerModule(FModuleLike op);
349
350 bool lowerArg(FModuleLike module, size_t argIndex, size_t argsRemoved,
351 SmallVectorImpl<PortInfo> &newArgs,
352 SmallVectorImpl<Value> &lowering);
353 std::pair<Value, PortInfo> addArg(Operation *module, unsigned insertPt,
354 unsigned insertPtOffset, FIRRTLType srcType,
355 const FlatBundleFieldEntry &field,
356 PortInfo &oldArg, hw::InnerSymAttr newSym);
357
358 // Helpers to manage state.
359 bool visitDecl(FExtModuleOp op);
360 bool visitDecl(FModuleOp op);
361 bool visitDecl(InstanceOp op);
362 bool visitDecl(MemOp op);
363 bool visitDecl(NodeOp op);
364 bool visitDecl(RegOp op);
365 bool visitDecl(WireOp op);
366 bool visitDecl(RegResetOp op);
367 bool visitExpr(InvalidValueOp op);
368 bool visitExpr(SubaccessOp op);
369 bool visitExpr(VectorCreateOp op);
370 bool visitExpr(BundleCreateOp op);
371 bool visitExpr(ElementwiseAndPrimOp op);
372 bool visitExpr(ElementwiseOrPrimOp op);
373 bool visitExpr(ElementwiseXorPrimOp op);
374 bool visitExpr(MultibitMuxOp op);
375 bool visitExpr(MuxPrimOp op);
376 bool visitExpr(Mux2CellIntrinsicOp op);
377 bool visitExpr(Mux4CellIntrinsicOp op);
378 bool visitExpr(BitCastOp op);
379 bool visitExpr(RefSendOp op);
380 bool visitExpr(RefResolveOp op);
381 bool visitExpr(RefCastOp op);
382 bool visitStmt(ConnectOp op);
383 bool visitStmt(MatchingConnectOp op);
384 bool visitStmt(RefDefineOp op);
385 bool visitStmt(WhenOp op);
386 bool visitStmt(LayerBlockOp op);
387 bool visitUnrealizedConversionCast(mlir::UnrealizedConversionCastOp op);
388
389 bool isFailed() const { return encounteredError; }
390
391 bool visitInvalidOp(Operation *op) {
392 if (auto castOp = dyn_cast<mlir::UnrealizedConversionCastOp>(op))
393 return visitUnrealizedConversionCast(castOp);
394 return false;
395 }
396
397private:
398 void processUsers(Value val, ArrayRef<Value> mapping);
399 bool processSAPath(Operation *);
400 void lowerBlock(Block *);
401 void lowerSAWritePath(Operation *, ArrayRef<Operation *> writePath);
402
403 /// Lower a "producer" operation one layer based on policy.
404 /// Use the provided \p clone function to generate individual ops for
405 /// the expanded subelements/fields. The type used to determine if lowering
406 /// is needed is either \p srcType if provided or from the assumed-to-exist
407 /// first result of the operation. When lowering, the clone callback will be
408 /// invoked with each subelement/field of this type.
409 bool lowerProducer(
410 Operation *op,
411 llvm::function_ref<Value(const FlatBundleFieldEntry &, ArrayAttr)> clone,
412 Type srcType = {});
413
414 /// Filter out and return \p annotations that target includes \field,
415 /// modifying as needed to adjust fieldID's relative to to \field.
416 ArrayAttr filterAnnotations(MLIRContext *ctxt, ArrayAttr annotations,
417 FIRRTLType srcType, FlatBundleFieldEntry field);
418
419 /// Partition inner symbols on given type. Fails if any symbols
420 /// cannot be assigned to a field, such as inner symbol on root.
421 LogicalResult partitionSymbols(hw::InnerSymAttr sym, FIRRTLType parentType,
422 SmallVectorImpl<hw::InnerSymAttr> &newSyms,
423 Location errorLoc);
424
426 getPreservationModeForPorts(FModuleLike moduleLike);
427 Value getSubWhatever(Value val, size_t index);
428
429 size_t uniqueIdx = 0;
430 std::string uniqueName() {
431 auto myID = uniqueIdx++;
432 return (Twine("__GEN_") + Twine(myID)).str();
433 }
434
435 MLIRContext *context;
436
437 /// Aggregate preservation mode.
438 PreserveAggregate::PreserveMode defaultAggregatePreservationMode;
439 PreserveAggregate::PreserveMode bodyAggregatePreservationMode;
440 PreserveAggregate::PreserveMode memoryPreservationMode;
441
442 /// The builder is set and maintained in the main loop.
443 ImplicitLocOpBuilder *builder;
444
445 // Keep a symbol table around for resolving symbols
446 SymbolTable &symTbl;
447
448 // Cache some attributes
449 const AttrCache &cache;
450
451 const llvm::DenseMap<FModuleLike, Convention> &conventionTable;
452
453 // Set true if the lowering failed.
454 bool encounteredError = false;
455};
456} // namespace
457
458/// Return aggregate preservation mode for the module ports. If the module has a
459/// scalarized linkage, then we may not preserve it's aggregate ports.
461TypeLoweringVisitor::getPreservationModeForPorts(FModuleLike module) {
462 auto lookup = conventionTable.find(module);
463 if (lookup == conventionTable.end())
464 return defaultAggregatePreservationMode;
465 switch (lookup->second) {
466 case Convention::Scalarized:
468 case Convention::Internal:
469 return defaultAggregatePreservationMode;
470 }
471 llvm_unreachable("Unknown convention");
472 return defaultAggregatePreservationMode;
473}
474
475Value TypeLoweringVisitor::getSubWhatever(Value val, size_t index) {
476 if (type_isa<BundleType>(val.getType()))
477 return SubfieldOp::create(*builder, val, index);
478 if (type_isa<FVectorType>(val.getType()))
479 return SubindexOp::create(*builder, val, index);
480 if (type_isa<RefType>(val.getType()))
481 return RefSubOp::create(*builder, val, index);
482 llvm_unreachable("Unknown aggregate type");
483 return nullptr;
484}
485
486/// Conditionally expand a subaccessop write path
487bool TypeLoweringVisitor::processSAPath(Operation *op) {
488 // Does this LHS have a subaccessop?
489 SmallVector<Operation *> writePath = getSAWritePath(op);
490 if (writePath.empty())
491 return false;
492
493 lowerSAWritePath(op, writePath);
494 // Unhook the writePath from the connect. This isn't the right type, but we
495 // are deleting the op anyway.
496 op->eraseOperands(0, 2);
497 // See how far up the tree we can delete things.
498 for (size_t i = 0; i < writePath.size(); ++i) {
499 if (writePath[i]->use_empty()) {
500 writePath[i]->erase();
501 } else {
502 break;
503 }
504 }
505 return true;
506}
507
508void TypeLoweringVisitor::lowerBlock(Block *block) {
509 // Lower the operations bottom up.
510 for (auto it = block->rbegin(), e = block->rend(); it != e;) {
511 auto &iop = *it;
512 builder->setInsertionPoint(&iop);
513 builder->setLoc(iop.getLoc());
514 bool removeOp = dispatchVisitor(&iop);
515 ++it;
516 // Erase old ops eagerly so we don't have dangling uses we've already
517 // lowered.
518 if (removeOp)
519 iop.erase();
520 }
521}
522
523ArrayAttr TypeLoweringVisitor::filterAnnotations(MLIRContext *ctxt,
524 ArrayAttr annotations,
525 FIRRTLType srcType,
526 FlatBundleFieldEntry field) {
527 SmallVector<Attribute> retval;
528 if (!annotations || annotations.empty())
529 return ArrayAttr::get(ctxt, retval);
530 for (auto opAttr : annotations) {
531 Annotation anno(opAttr);
532 auto fieldID = anno.getFieldID();
533 anno.removeMember("circt.fieldID");
534
535 // If no fieldID set, or points to root, forward the annotation without the
536 // fieldID field (which was removed above).
537 if (fieldID == 0) {
538 retval.push_back(anno.getAttr());
539 continue;
540 }
541 // Check whether the annotation falls into the range of the current field.
542
543 if (fieldID < field.fieldID ||
544 fieldID > field.fieldID + hw::FieldIdImpl::getMaxFieldID(field.type))
545 continue;
546
547 // Add fieldID back if non-zero relative to this field.
548 if (auto newFieldID = fieldID - field.fieldID) {
549 // If the target is a subfield/subindex of the current field, create a
550 // new annotation with the correct circt.fieldID.
551 anno.setMember("circt.fieldID", builder->getI32IntegerAttr(newFieldID));
552 }
553
554 retval.push_back(anno.getAttr());
555 }
556 return ArrayAttr::get(ctxt, retval);
557}
558
559LogicalResult TypeLoweringVisitor::partitionSymbols(
560 hw::InnerSymAttr sym, FIRRTLType parentType,
561 SmallVectorImpl<hw::InnerSymAttr> &newSyms, Location errorLoc) {
562
563 // No symbol, nothing to partition.
564 if (!sym || sym.empty())
565 return success();
566
567 auto *context = sym.getContext();
568
569 auto baseType = getBaseType(parentType);
570 if (!baseType)
571 return mlir::emitError(errorLoc,
572 "unable to partition symbol on unsupported type ")
573 << parentType;
574
575 return TypeSwitch<FIRRTLType, LogicalResult>(baseType)
576 .Case<BundleType, FVectorType>([&](auto aggType) -> LogicalResult {
577 struct BinningInfo {
578 uint64_t index;
579 uint64_t relFieldID;
580 hw::InnerSymPropertiesAttr prop;
581 };
582
583 // Walk each inner symbol, compute binning information/assignment.
584 SmallVector<BinningInfo> binning;
585 for (auto prop : sym) {
586 auto fieldID = prop.getFieldID();
587 // Special-case fieldID == 0, helper methods require non-zero fieldID.
588 if (fieldID == 0)
589 return mlir::emitError(errorLoc, "unable to lower due to symbol ")
590 << prop.getName()
591 << " with target not preserved by lowering";
592 auto [index, relFieldID] = aggType.getIndexAndSubfieldID(fieldID);
593 binning.push_back({index, relFieldID, prop});
594 }
595
596 // Sort by index, fieldID.
597 llvm::stable_sort(binning, [&](auto &lhs, auto &rhs) {
598 return std::tuple(lhs.index, lhs.relFieldID) <
599 std::tuple(rhs.index, rhs.relFieldID);
600 });
601 assert(!binning.empty());
602
603 // Populate newSyms, group all symbols on same index.
604 newSyms.resize(aggType.getNumElements());
605 for (auto binIt = binning.begin(), binEnd = binning.end();
606 binIt != binEnd;) {
607 auto curIndex = binIt->index;
608 SmallVector<hw::InnerSymPropertiesAttr> propsForIndex;
609 // Gather all adjacent symbols for this index.
610 while (binIt != binEnd && binIt->index == curIndex) {
611 propsForIndex.push_back(hw::InnerSymPropertiesAttr::get(
612 context, binIt->prop.getName(), binIt->relFieldID,
613 binIt->prop.getSymVisibility()));
614 ++binIt;
615 }
616
617 assert(!newSyms[curIndex]);
618 newSyms[curIndex] = hw::InnerSymAttr::get(context, propsForIndex);
619 }
620 return success();
621 })
622 .Default([&](auto ty) {
623 return mlir::emitError(
624 errorLoc, "unable to partition symbol on unsupported type ")
625 << ty;
626 });
627}
628
629bool TypeLoweringVisitor::lowerProducer(
630 Operation *op,
631 llvm::function_ref<Value(const FlatBundleFieldEntry &, ArrayAttr)> clone,
632 Type srcType) {
633
634 if (!srcType)
635 srcType = op->getResult(0).getType();
636 auto srcFType = type_dyn_cast<FIRRTLType>(srcType);
637 if (!srcFType)
638 return false;
639 SmallVector<FlatBundleFieldEntry, 8> fieldTypes;
640
641 if (!peelType(srcFType, fieldTypes, bodyAggregatePreservationMode))
642 return false;
643
644 SmallVector<Value> lowered;
645 // Loop over the leaf aggregates.
646 SmallString<16> loweredName;
647 auto nameKindAttr = op->getAttrOfType<NameKindEnumAttr>(cache.nameKindAttr);
648
649 if (auto nameAttr = op->getAttrOfType<StringAttr>(cache.nameAttr))
650 loweredName = nameAttr.getValue();
651 auto baseNameLen = loweredName.size();
652 auto oldAnno = dyn_cast_or_null<ArrayAttr>(op->getAttr("annotations"));
653
654 SmallVector<hw::InnerSymAttr> fieldSyms(fieldTypes.size());
655 if (auto symOp = dyn_cast<hw::InnerSymbolOpInterface>(op)) {
656 if (failed(partitionSymbols(symOp.getInnerSymAttr(), srcFType, fieldSyms,
657 symOp.getLoc()))) {
658 encounteredError = true;
659 return false;
660 }
661 }
662
663 for (const auto &[field, sym] : llvm::zip_equal(fieldTypes, fieldSyms)) {
664 if (!loweredName.empty()) {
665 loweredName.resize(baseNameLen);
666 loweredName += field.suffix;
667 }
668
669 // For all annotations on the parent op, filter them based on the target
670 // attribute.
671 ArrayAttr loweredAttrs =
672 filterAnnotations(context, oldAnno, srcFType, field);
673 auto newVal = clone(field, loweredAttrs);
674
675 // If inner symbols on this field, add to new op.
676 if (sym) {
677 // Splitting up something with symbols on it should lower to ops
678 // that also can have symbols on them.
679 auto newSymOp = newVal.getDefiningOp<hw::InnerSymbolOpInterface>();
680 assert(
681 newSymOp &&
682 "op with inner symbol lowered to op that cannot take inner symbol");
683 newSymOp.setInnerSymbolAttr(sym);
684 }
685
686 // Carry over the name, if present.
687 if (auto *newOp = newVal.getDefiningOp()) {
688 if (!loweredName.empty())
689 newOp->setAttr(cache.nameAttr, StringAttr::get(context, loweredName));
690 if (nameKindAttr)
691 newOp->setAttr(cache.nameKindAttr, nameKindAttr);
692
693 // Clone discardable attributes as well.
694 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
695 }
696 lowered.push_back(newVal);
697 }
698
699 processUsers(op->getResult(0), lowered);
700 return true;
701}
702
703void TypeLoweringVisitor::processUsers(Value val, ArrayRef<Value> mapping) {
704 for (auto *user : llvm::make_early_inc_range(val.getUsers())) {
705 TypeSwitch<Operation *, void>(user)
706 .Case<SubindexOp>([mapping](SubindexOp sio) {
707 Value repl = mapping[sio.getIndex()];
708 sio.replaceAllUsesWith(repl);
709 sio.erase();
710 })
711 .Case<SubfieldOp>([mapping](SubfieldOp sfo) {
712 // Get the input bundle type.
713 Value repl = mapping[sfo.getFieldIndex()];
714 sfo.replaceAllUsesWith(repl);
715 sfo.erase();
716 })
717 .Case<RefSubOp>([mapping](RefSubOp refSub) {
718 Value repl = mapping[refSub.getIndex()];
719 refSub.replaceAllUsesWith(repl);
720 refSub.erase();
721 })
722 .Default([&](auto op) {
723 // This means we have already processed the user, and it didn't lower
724 // its inputs. This is an opaque user, which will continue to have
725 // aggregate type as input, even after LowerTypes. So, construct the
726 // vector/bundle back from the lowered elements to ensure a valid
727 // input into the opaque op. This only supports Bundles and Vectors.
728
729 // This builder ensures that the aggregate construction happens at the
730 // user location, and the LowerTypes algorithm will not touch them any
731 // more, because LowerTypes was reverse iterating on the block and the
732 // user has already been processed.
733 ImplicitLocOpBuilder b(user->getLoc(), user);
734
735 // This shouldn't happen (non-FIRRTLBaseType's in lowered types, or
736 // refs), check explicitly here for clarity/early detection.
737 assert(llvm::none_of(mapping, [](auto v) {
738 auto fbasetype = type_dyn_cast<FIRRTLBaseType>(v.getType());
739 return !fbasetype || fbasetype.containsReference();
740 }));
741
742 Value input =
743 TypeSwitch<Type, Value>(val.getType())
744 .template Case<FVectorType>([&](auto vecType) {
745 return b.createOrFold<VectorCreateOp>(vecType, mapping);
746 })
747 .template Case<BundleType>([&](auto bundleType) {
748 return b.createOrFold<BundleCreateOp>(bundleType, mapping);
749 })
750 .Default([&](auto _) -> Value { return {}; });
751 if (!input) {
752 user->emitError("unable to reconstruct source of type ")
753 << val.getType();
754 encounteredError = true;
755 return;
756 }
757 user->replaceUsesOfWith(val, input);
758 });
759 }
760}
761
762void TypeLoweringVisitor::lowerModule(FModuleLike op) {
763 if (auto module = llvm::dyn_cast<FModuleOp>(*op))
764 visitDecl(module);
765 else if (auto extModule = llvm::dyn_cast<FExtModuleOp>(*op))
766 visitDecl(extModule);
767}
768
769// Creates and returns a new block argument of the specified type to the
770// module. This also maintains the name attribute for the new argument,
771// possibly with a new suffix appended.
772std::pair<Value, PortInfo>
773TypeLoweringVisitor::addArg(Operation *module, unsigned insertPt,
774 unsigned insertPtOffset, FIRRTLType srcType,
775 const FlatBundleFieldEntry &field, PortInfo &oldArg,
776 hw::InnerSymAttr newSym) {
777 Value newValue;
778 FIRRTLType fieldType = mapLoweredType(srcType, field.type);
779 if (auto mod = llvm::dyn_cast<FModuleOp>(module)) {
780 Block *body = mod.getBodyBlock();
781 // Append the new argument.
782 newValue = body->insertArgument(insertPt, fieldType, oldArg.loc);
783 }
784
785 // Save the name attribute for the new argument.
786 auto name = builder->getStringAttr(oldArg.name.getValue() + field.suffix);
787
788 // Populate the new arg attributes.
789 auto newAnnotations = filterAnnotations(
790 context, oldArg.annotations.getArrayAttr(), srcType, field);
791 // Flip the direction if the field is an output.
792 auto direction = (Direction)((unsigned)oldArg.direction ^ field.isOutput);
793
794 return std::make_pair(
795 newValue, PortInfo{name, fieldType, direction, newSym, oldArg.loc,
796 AnnotationSet(newAnnotations), oldArg.domains});
797}
798
799// Lower arguments with bundle type by flattening them.
800bool TypeLoweringVisitor::lowerArg(FModuleLike module, size_t argIndex,
801 size_t argsRemoved,
802 SmallVectorImpl<PortInfo> &newArgs,
803 SmallVectorImpl<Value> &lowering) {
804
805 // Flatten any bundle types.
806 SmallVector<FlatBundleFieldEntry> fieldTypes;
807 auto srcType = type_cast<FIRRTLType>(newArgs[argIndex].type);
808 if (!peelType(srcType, fieldTypes, getPreservationModeForPorts(module)))
809 return false;
810
811 SmallVector<hw::InnerSymAttr> fieldSyms(fieldTypes.size());
812 if (failed(partitionSymbols(newArgs[argIndex].sym, srcType, fieldSyms,
813 newArgs[argIndex].loc))) {
814 encounteredError = true;
815 return false;
816 }
817
818 for (const auto &[idx, field, fieldSym] :
819 llvm::enumerate(fieldTypes, fieldSyms)) {
820 auto newValue = addArg(module, 1 + argIndex + idx, argsRemoved, srcType,
821 field, newArgs[argIndex], fieldSym);
822 newArgs.insert(newArgs.begin() + 1 + argIndex + idx, newValue.second);
823 // Lower any other arguments by copying them to keep the relative order.
824 lowering.push_back(newValue.first);
825 }
826 return true;
827}
828
829static Value cloneAccess(ImplicitLocOpBuilder *builder, Operation *op,
830 Value rhs) {
831 if (auto rop = llvm::dyn_cast<SubfieldOp>(op))
832 return SubfieldOp::create(*builder, rhs, rop.getFieldIndex());
833 if (auto rop = llvm::dyn_cast<SubindexOp>(op))
834 return SubindexOp::create(*builder, rhs, rop.getIndex());
835 if (auto rop = llvm::dyn_cast<SubaccessOp>(op))
836 return SubaccessOp::create(*builder, rhs, rop.getIndex());
837 op->emitError("Unknown accessor");
838 return nullptr;
839}
840
841void TypeLoweringVisitor::lowerSAWritePath(Operation *op,
842 ArrayRef<Operation *> writePath) {
843 SubaccessOp sao = cast<SubaccessOp>(writePath.back());
844 FVectorType saoType = sao.getInput().getType();
845 auto selectWidth = llvm::Log2_64_Ceil(saoType.getNumElements());
846
847 for (size_t index = 0, e = saoType.getNumElements(); index < e; ++index) {
848 auto cond = EQPrimOp::create(
849 *builder, sao.getIndex(),
850 builder->createOrFold<ConstantOp>(UIntType::get(context, selectWidth),
851 APInt(selectWidth, index)));
852 WhenOp::create(*builder, cond, false, [&]() {
853 // Recreate the write Path
854 Value leaf = SubindexOp::create(*builder, sao.getInput(), index);
855 for (int i = writePath.size() - 2; i >= 0; --i) {
856 if (auto access = cloneAccess(builder, writePath[i], leaf))
857 leaf = access;
858 else {
859 encounteredError = true;
860 return;
861 }
862 }
863
864 emitConnect(*builder, leaf, op->getOperand(1));
865 });
866 }
867}
868
869// Expand connects of aggregates
870bool TypeLoweringVisitor::visitStmt(ConnectOp op) {
871 if (processSAPath(op))
872 return true;
873
874 // Attempt to get the bundle types.
875 SmallVector<FlatBundleFieldEntry> fields;
876
877 // We have to expand connections even if the aggregate preservation is true.
878 if (!peelType(op.getDest().getType(), fields, PreserveAggregate::None))
879 return false;
880
881 // Loop over the leaf aggregates.
882 for (const auto &field : llvm::enumerate(fields)) {
883 Value src = getSubWhatever(op.getSrc(), field.index());
884 Value dest = getSubWhatever(op.getDest(), field.index());
885 if (field.value().isOutput)
886 std::swap(src, dest);
887 emitConnect(*builder, dest, src);
888 }
889 return true;
890}
891
892// Expand connects of aggregates
893bool TypeLoweringVisitor::visitStmt(MatchingConnectOp op) {
894 if (processSAPath(op))
895 return true;
896
897 // Attempt to get the bundle types.
898 SmallVector<FlatBundleFieldEntry> fields;
899
900 // We have to expand connections even if the aggregate preservation is true.
901 if (!peelType(op.getDest().getType(), fields, PreserveAggregate::None))
902 return false;
903
904 // Loop over the leaf aggregates.
905 for (const auto &field : llvm::enumerate(fields)) {
906 Value src = getSubWhatever(op.getSrc(), field.index());
907 Value dest = getSubWhatever(op.getDest(), field.index());
908 if (field.value().isOutput)
909 std::swap(src, dest);
910 MatchingConnectOp::create(*builder, dest, src);
911 }
912 return true;
913}
914
915// Expand connects of references-of-aggregates
916bool TypeLoweringVisitor::visitStmt(RefDefineOp op) {
917 // Attempt to get the bundle types.
918 SmallVector<FlatBundleFieldEntry> fields;
919
920 if (!peelType(op.getDest().getType(), fields, bodyAggregatePreservationMode))
921 return false;
922
923 // Loop over the leaf aggregates.
924 for (const auto &field : llvm::enumerate(fields)) {
925 Value src = getSubWhatever(op.getSrc(), field.index());
926 Value dest = getSubWhatever(op.getDest(), field.index());
927 assert(!field.value().isOutput && "unexpected flip in reftype destination");
928 RefDefineOp::create(*builder, dest, src);
929 }
930 return true;
931}
932
933bool TypeLoweringVisitor::visitStmt(WhenOp op) {
934 // The WhenOp itself does not require any lowering, the only value it uses
935 // is a one-bit predicate. Recursively visit all regions so internal
936 // operations are lowered.
937
938 // Visit operations in the then block.
939 lowerBlock(&op.getThenBlock());
940
941 // Visit operations in the else block.
942 if (op.hasElseRegion())
943 lowerBlock(&op.getElseBlock());
944 return false; // don't delete the when!
945}
946
947/// Lower any types declared in layer blocks.
948bool TypeLoweringVisitor::visitStmt(LayerBlockOp op) {
949 lowerBlock(op.getBody());
950 return false;
951}
952
953/// Lower memory operations. A new memory is created for every leaf
954/// element in a memory's data type.
955bool TypeLoweringVisitor::visitDecl(MemOp op) {
956 // Attempt to get the bundle types.
957 SmallVector<FlatBundleFieldEntry> fields;
958
959 // MemOp should have ground types so we can't preserve aggregates.
960 if (!peelType(op.getDataType(), fields, memoryPreservationMode))
961 return false;
962
963 if (op.getInnerSym()) {
964 op->emitError() << "has a symbol, but no symbols may exist on aggregates "
965 "passed through LowerTypes";
966 encounteredError = true;
967 return false;
968 }
969
970 SmallVector<MemOp> newMemories;
971 SmallVector<WireOp> oldPorts;
972
973 // Wires for old ports
974 for (unsigned int index = 0, end = op.getNumResults(); index < end; ++index) {
975 auto result = op.getResult(index);
976 if (op.getPortKind(index) == MemOp::PortKind::Debug) {
977 op.emitOpError("cannot lower memory with debug port");
978 encounteredError = true;
979 return false;
980 }
981 auto wire =
982 WireOp::create(*builder, result.getType(),
983 (op.getName() + "_" + op.getPortName(index)).str());
984 oldPorts.push_back(wire);
985 result.replaceAllUsesWith(wire.getResult());
986 }
987 // If annotations targeting fields of an aggregate are present, we cannot
988 // flatten the memory. It must be split into one memory per aggregate field.
989 // Do not overwrite the pass flag!
990
991 // Memory for each field
992 for (const auto &field : fields) {
993 auto newMemForField = cloneMemWithNewType(builder, op, field);
994 if (!newMemForField) {
995 op.emitError("failed cloning memory for field");
996 encounteredError = true;
997 return false;
998 }
999 newMemories.push_back(newMemForField);
1000 }
1001 // Hook up the new memories to the wires the old memory was replaced with.
1002 for (size_t index = 0, rend = op.getNumResults(); index < rend; ++index) {
1003 auto result = oldPorts[index].getResult();
1004 auto rType = type_cast<BundleType>(result.getType());
1005 for (size_t fieldIndex = 0, fend = rType.getNumElements();
1006 fieldIndex != fend; ++fieldIndex) {
1007 auto name = rType.getElement(fieldIndex).name.getValue();
1008 auto oldField = SubfieldOp::create(*builder, result, fieldIndex);
1009 // data and mask depend on the memory type which was split. They can also
1010 // go both directions, depending on the port direction.
1011 if (name == "data" || name == "mask" || name == "wdata" ||
1012 name == "wmask" || name == "rdata") {
1013 for (const auto &field : fields) {
1014 auto realOldField = getSubWhatever(oldField, field.index);
1015 auto newField = getSubWhatever(
1016 newMemories[field.index].getResult(index), fieldIndex);
1017 if (rType.getElement(fieldIndex).isFlip)
1018 std::swap(realOldField, newField);
1019 emitConnect(*builder, newField, realOldField);
1020 }
1021 } else {
1022 for (auto mem : newMemories) {
1023 auto newField =
1024 SubfieldOp::create(*builder, mem.getResult(index), fieldIndex);
1025 emitConnect(*builder, newField, oldField);
1026 }
1027 }
1028 }
1029 }
1030 return true;
1031}
1032
1033bool TypeLoweringVisitor::visitDecl(FExtModuleOp extModule) {
1034 ImplicitLocOpBuilder theBuilder(extModule.getLoc(), context);
1035 builder = &theBuilder;
1036
1037 // Top level builder
1038 OpBuilder builder(context);
1039
1040 // Lower the module block arguments.
1041 SmallVector<unsigned> argsToRemove;
1042 auto newArgs = extModule.getPorts();
1043
1044 for (size_t argIndex = 0, argsRemoved = 0; argIndex < newArgs.size();
1045 ++argIndex) {
1046 SmallVector<Value> lowering;
1047 if (lowerArg(extModule, argIndex, argsRemoved, newArgs, lowering)) {
1048 argsToRemove.push_back(argIndex);
1049 ++argsRemoved;
1050 }
1051 // lowerArg might have invalidated any reference to newArgs, be careful
1052 }
1053
1054 // Remove block args that have been lowered
1055 for (auto toRemove : llvm::reverse(argsToRemove))
1056 newArgs.erase(newArgs.begin() + toRemove);
1057
1058 SmallVector<NamedAttribute, 8> newModuleAttrs;
1059
1060 // Copy over any attributes that weren't original argument attributes.
1061 for (auto attr : extModule->getAttrDictionary())
1062 // Drop old "portNames", directions, and argument attributes. These are
1063 // handled differently below.
1064 if (attr.getName() != "portDirections" && attr.getName() != "portNames" &&
1065 attr.getName() != "portTypes" && attr.getName() != "portAnnotations" &&
1066 attr.getName() != "portSymbols" && attr.getName() != "portLocations")
1067 newModuleAttrs.push_back(attr);
1068
1069 SmallVector<Direction> newArgDirections;
1070 SmallVector<Attribute> newArgNames;
1071 SmallVector<Attribute, 8> newArgTypes;
1072 SmallVector<Attribute, 8> newArgSyms;
1073 SmallVector<Attribute, 8> newArgLocations;
1074 SmallVector<Attribute, 8> newArgAnnotations;
1075 SmallVector<Attribute, 8> newArgDomains;
1076
1077 for (auto &port : newArgs) {
1078 newArgDirections.push_back(port.direction);
1079 newArgNames.push_back(port.name);
1080 newArgTypes.push_back(TypeAttr::get(port.type));
1081 newArgSyms.push_back(port.sym);
1082 newArgLocations.push_back(port.loc);
1083 newArgAnnotations.push_back(port.annotations.getArrayAttr());
1084 if (auto domains = port.domains)
1085 newArgDomains.push_back(domains);
1086 else
1087 newArgDomains.push_back(cache.aEmpty);
1088 }
1089
1090 newModuleAttrs.push_back(
1091 NamedAttribute(cache.sPortDirections,
1092 direction::packAttribute(context, newArgDirections)));
1093
1094 newModuleAttrs.push_back(
1095 NamedAttribute(cache.sPortNames, builder.getArrayAttr(newArgNames)));
1096
1097 newModuleAttrs.push_back(
1098 NamedAttribute(cache.sPortTypes, builder.getArrayAttr(newArgTypes)));
1099
1100 newModuleAttrs.push_back(NamedAttribute(
1101 cache.sPortLocations, builder.getArrayAttr(newArgLocations)));
1102
1103 newModuleAttrs.push_back(NamedAttribute(
1104 cache.sPortAnnotations, builder.getArrayAttr(newArgAnnotations)));
1105
1106 newModuleAttrs.push_back(
1107 NamedAttribute(cache.sPortDomains, builder.getArrayAttr(newArgDomains)));
1108
1109 // Update the module's attributes.
1110 extModule->setAttrs(newModuleAttrs);
1111 FModuleLike::fixupPortSymsArray(newArgSyms, context);
1112 extModule.setPortSymbols(newArgSyms);
1113
1114 return false;
1115}
1116
1117bool TypeLoweringVisitor::visitDecl(FModuleOp module) {
1118 auto *body = module.getBodyBlock();
1119
1120 ImplicitLocOpBuilder theBuilder(module.getLoc(), context);
1121 builder = &theBuilder;
1122
1123 // Lower the operations.
1124 lowerBlock(body);
1125
1126 // Lower the module block arguments.
1127 llvm::BitVector argsToRemove;
1128 auto newArgs = module.getPorts();
1129
1130 size_t argsRemoved = 0;
1131 for (size_t argIndex = 0; argIndex < newArgs.size(); ++argIndex) {
1132 SmallVector<Value> lowerings;
1133 if (lowerArg(module, argIndex, argsRemoved, newArgs, lowerings)) {
1134 auto arg = module.getArgument(argIndex);
1135 processUsers(arg, lowerings);
1136 argsToRemove.push_back(true);
1137 ++argsRemoved;
1138 } else
1139 argsToRemove.push_back(false);
1140 // lowerArg might have invalidated any reference to newArgs, be careful
1141 }
1142
1143 // Remove block args that have been lowered.
1144 if (argsRemoved != 0) {
1145 body->eraseArguments(argsToRemove);
1146 size_t size = newArgs.size();
1147 for (size_t src = 0, dst = 0; src < size; ++src) {
1148 if (argsToRemove[src])
1149 continue;
1150 newArgs[dst] = newArgs[src];
1151 ++dst;
1152 }
1153 newArgs.erase(newArgs.end() - argsRemoved, newArgs.end());
1154 }
1155
1156 SmallVector<NamedAttribute, 8> newModuleAttrs;
1157
1158 // Copy over any attributes that weren't original argument attributes.
1159 for (auto attr : module->getAttrDictionary())
1160 // Drop old "portNames", directions, and argument attributes. These are
1161 // handled differently below.
1162 if (attr.getName() != "portNames" && attr.getName() != "portDirections" &&
1163 attr.getName() != "portTypes" && attr.getName() != "portAnnotations" &&
1164 attr.getName() != "portSymbols" && attr.getName() != "portLocations")
1165 newModuleAttrs.push_back(attr);
1166
1167 SmallVector<Direction> newArgDirections;
1168 SmallVector<Attribute> newArgNames;
1169 SmallVector<Attribute> newArgTypes;
1170 SmallVector<Attribute> newArgSyms;
1171 SmallVector<Attribute> newArgLocations;
1172 SmallVector<Attribute, 8> newArgAnnotations;
1173 SmallVector<Attribute> newPortDomains;
1174 for (auto &port : newArgs) {
1175 newArgDirections.push_back(port.direction);
1176 newArgNames.push_back(port.name);
1177 newArgTypes.push_back(TypeAttr::get(port.type));
1178 newArgSyms.push_back(port.sym);
1179 newArgLocations.push_back(port.loc);
1180 newArgAnnotations.push_back(port.annotations.getArrayAttr());
1181 if (auto domains = port.domains)
1182 newPortDomains.push_back(domains);
1183 else
1184 newPortDomains.push_back(cache.aEmpty);
1185 }
1186
1187 newModuleAttrs.push_back(
1188 NamedAttribute(cache.sPortDirections,
1189 direction::packAttribute(context, newArgDirections)));
1190
1191 newModuleAttrs.push_back(
1192 NamedAttribute(cache.sPortNames, builder->getArrayAttr(newArgNames)));
1193
1194 newModuleAttrs.push_back(
1195 NamedAttribute(cache.sPortTypes, builder->getArrayAttr(newArgTypes)));
1196
1197 newModuleAttrs.push_back(NamedAttribute(
1198 cache.sPortLocations, builder->getArrayAttr(newArgLocations)));
1199
1200 newModuleAttrs.push_back(NamedAttribute(
1201 cache.sPortAnnotations, builder->getArrayAttr(newArgAnnotations)));
1202
1203 newModuleAttrs.push_back(NamedAttribute(
1204 cache.sPortDomains, builder->getArrayAttr(newPortDomains)));
1205
1206 // Update the module's attributes.
1207 module->setAttrs(newModuleAttrs);
1208 FModuleLike::fixupPortSymsArray(newArgSyms, context);
1209 module.setPortSymbols(newArgSyms);
1210 return false;
1211}
1212
1213/// Lower a wire op with a bundle to multiple non-bundled wires.
1214bool TypeLoweringVisitor::visitDecl(WireOp op) {
1215 if (op.isForceable())
1216 return false;
1217
1218 auto clone = [&](const FlatBundleFieldEntry &field,
1219 ArrayAttr attrs) -> Value {
1220 return WireOp::create(*builder,
1221 mapLoweredType(op.getDataRaw().getType(), field.type),
1222 "", NameKindEnum::DroppableName, attrs, StringAttr{})
1223 .getResult();
1224 };
1225 return lowerProducer(op, clone);
1226}
1227
1228/// Lower a reg op with a bundle to multiple non-bundled regs.
1229bool TypeLoweringVisitor::visitDecl(RegOp op) {
1230 if (op.isForceable())
1231 return false;
1232
1233 auto clone = [&](const FlatBundleFieldEntry &field,
1234 ArrayAttr attrs) -> Value {
1235 return RegOp::create(*builder, field.type, op.getClockVal(), "",
1236 NameKindEnum::DroppableName, attrs, StringAttr{})
1237 .getResult();
1238 };
1239 return lowerProducer(op, clone);
1240}
1241
1242/// Lower a reg op with a bundle to multiple non-bundled regs.
1243bool TypeLoweringVisitor::visitDecl(RegResetOp op) {
1244 if (op.isForceable())
1245 return false;
1246
1247 auto clone = [&](const FlatBundleFieldEntry &field,
1248 ArrayAttr attrs) -> Value {
1249 auto resetVal = getSubWhatever(op.getResetValue(), field.index);
1250 return RegResetOp::create(*builder, field.type, op.getClockVal(),
1251 op.getResetSignal(), resetVal, "",
1252 NameKindEnum::DroppableName, attrs, StringAttr{})
1253 .getResult();
1254 };
1255 return lowerProducer(op, clone);
1256}
1257
1258/// Lower a wire op with a bundle to multiple non-bundled wires.
1259bool TypeLoweringVisitor::visitDecl(NodeOp op) {
1260 if (op.isForceable())
1261 return false;
1262
1263 auto clone = [&](const FlatBundleFieldEntry &field,
1264 ArrayAttr attrs) -> Value {
1265 auto input = getSubWhatever(op.getInput(), field.index);
1266 return NodeOp::create(*builder, input, "", NameKindEnum::DroppableName,
1267 attrs)
1268 .getResult();
1269 };
1270 return lowerProducer(op, clone);
1271}
1272
1273/// Lower an InvalidValue op with a bundle to multiple non-bundled InvalidOps.
1274bool TypeLoweringVisitor::visitExpr(InvalidValueOp op) {
1275 auto clone = [&](const FlatBundleFieldEntry &field,
1276 ArrayAttr attrs) -> Value {
1277 return InvalidValueOp::create(*builder, field.type);
1278 };
1279 return lowerProducer(op, clone);
1280}
1281
1282// Expand muxes of aggregates
1283bool TypeLoweringVisitor::visitExpr(MuxPrimOp op) {
1284 auto clone = [&](const FlatBundleFieldEntry &field,
1285 ArrayAttr attrs) -> Value {
1286 auto high = getSubWhatever(op.getHigh(), field.index);
1287 auto low = getSubWhatever(op.getLow(), field.index);
1288 return MuxPrimOp::create(*builder, op.getSel(), high, low);
1289 };
1290 return lowerProducer(op, clone);
1291}
1292
1293// Expand muxes of aggregates
1294bool TypeLoweringVisitor::visitExpr(Mux2CellIntrinsicOp op) {
1295 auto clone = [&](const FlatBundleFieldEntry &field,
1296 ArrayAttr attrs) -> Value {
1297 auto high = getSubWhatever(op.getHigh(), field.index);
1298 auto low = getSubWhatever(op.getLow(), field.index);
1299 return Mux2CellIntrinsicOp::create(*builder, op.getSel(), high, low);
1300 };
1301 return lowerProducer(op, clone);
1302}
1303
1304// Expand muxes of aggregates
1305bool TypeLoweringVisitor::visitExpr(Mux4CellIntrinsicOp op) {
1306 auto clone = [&](const FlatBundleFieldEntry &field,
1307 ArrayAttr attrs) -> Value {
1308 auto v3 = getSubWhatever(op.getV3(), field.index);
1309 auto v2 = getSubWhatever(op.getV2(), field.index);
1310 auto v1 = getSubWhatever(op.getV1(), field.index);
1311 auto v0 = getSubWhatever(op.getV0(), field.index);
1312 return Mux4CellIntrinsicOp::create(*builder, op.getSel(), v3, v2, v1, v0);
1313 };
1314 return lowerProducer(op, clone);
1315}
1316
1317// Expand UnrealizedConversionCastOp of aggregates
1318bool TypeLoweringVisitor::visitUnrealizedConversionCast(
1319 mlir::UnrealizedConversionCastOp op) {
1320 auto clone = [&](const FlatBundleFieldEntry &field,
1321 ArrayAttr attrs) -> Value {
1322 auto input = getSubWhatever(op.getOperand(0), field.index);
1323 return mlir::UnrealizedConversionCastOp::create(*builder, field.type, input)
1324 .getResult(0);
1325 };
1326 // If the input to the cast is not a FIRRTL type, getSubWhatever cannot handle
1327 // it, donot lower the op.
1328 if (!type_isa<FIRRTLType>(op->getOperand(0).getType()))
1329 return false;
1330 return lowerProducer(op, clone);
1331}
1332
1333// Expand BitCastOp of aggregates
1334bool TypeLoweringVisitor::visitExpr(BitCastOp op) {
1335 Value srcLoweredVal = op.getInput();
1336 // If the input is of aggregate type, then cat all the leaf fields to form a
1337 // UInt type result. That is, first bitcast the aggregate type to a UInt.
1338 // Attempt to get the bundle types.
1339 SmallVector<FlatBundleFieldEntry> fields;
1340 if (peelType(op.getInput().getType(), fields, PreserveAggregate::None)) {
1341 size_t uptoBits = 0;
1342 // Loop over the leaf aggregates and concat each of them to get a UInt.
1343 // Bitcast the fields to handle nested aggregate types.
1344 for (const auto &field : llvm::enumerate(fields)) {
1345 auto fieldBitwidth = *getBitWidth(field.value().type);
1346 // Ignore zero width fields, like empty bundles.
1347 if (fieldBitwidth == 0)
1348 continue;
1349 Value src = getSubWhatever(op.getInput(), field.index());
1350 // The src could be an aggregate type, bitcast it to a UInt type.
1351 src = builder->createOrFold<BitCastOp>(
1352 UIntType::get(context, fieldBitwidth), src);
1353 // Take the first field, or else Cat the previous fields with this field.
1354 if (uptoBits == 0)
1355 srcLoweredVal = src;
1356 else {
1357 if (type_isa<BundleType>(op.getInput().getType())) {
1358 srcLoweredVal =
1359 CatPrimOp::create(*builder, ValueRange{srcLoweredVal, src});
1360 } else {
1361 srcLoweredVal =
1362 CatPrimOp::create(*builder, ValueRange{src, srcLoweredVal});
1363 }
1364 }
1365 // Record the total bits already accumulated.
1366 uptoBits += fieldBitwidth;
1367 }
1368 } else {
1369 srcLoweredVal = builder->createOrFold<AsUIntPrimOp>(srcLoweredVal);
1370 }
1371 // Now the input has been cast to srcLoweredVal, which is of UInt type.
1372 // If the result is an aggregate type, then use lowerProducer.
1373 if (type_isa<BundleType, FVectorType>(op.getResult().getType())) {
1374 // uptoBits is used to keep track of the bits that have been extracted.
1375 size_t uptoBits = 0;
1376 auto aggregateBits = *getBitWidth(op.getResult().getType());
1377 auto clone = [&](const FlatBundleFieldEntry &field,
1378 ArrayAttr attrs) -> Value {
1379 // All the fields must have valid bitwidth, a requirement for BitCastOp.
1380 auto fieldBits = *getBitWidth(field.type);
1381 // If empty field, then it doesnot have any use, so replace it with an
1382 // invalid op, which should be trivially removed.
1383 if (fieldBits == 0)
1384 return InvalidValueOp::create(*builder, field.type);
1385
1386 // Assign the field to the corresponding bits from the input.
1387 // Bitcast the field, incase its an aggregate type.
1388 BitsPrimOp extractBits;
1389 if (type_isa<BundleType>(op.getResult().getType())) {
1390 extractBits = BitsPrimOp::create(*builder, srcLoweredVal,
1391 aggregateBits - uptoBits - 1,
1392 aggregateBits - uptoBits - fieldBits);
1393 } else {
1394 extractBits = BitsPrimOp::create(*builder, srcLoweredVal,
1395 uptoBits + fieldBits - 1, uptoBits);
1396 }
1397 uptoBits += fieldBits;
1398 return BitCastOp::create(*builder, field.type, extractBits);
1399 };
1400 return lowerProducer(op, clone);
1401 }
1402
1403 // If ground type, then replace the result.
1404 if (type_isa<SIntType>(op.getType()))
1405 srcLoweredVal = AsSIntPrimOp::create(*builder, srcLoweredVal);
1406 op.getResult().replaceAllUsesWith(srcLoweredVal);
1407 return true;
1408}
1409
1410bool TypeLoweringVisitor::visitExpr(RefSendOp op) {
1411 auto clone = [&](const FlatBundleFieldEntry &field,
1412 ArrayAttr attrs) -> Value {
1413 return RefSendOp::create(*builder,
1414 getSubWhatever(op.getBase(), field.index));
1415 };
1416 // Be careful re:what gets lowered, consider ref.send of non-passive
1417 // and whether we're using the ref or the base type to choose
1418 // whether this should be lowered.
1419 return lowerProducer(op, clone);
1420}
1421
1422bool TypeLoweringVisitor::visitExpr(RefResolveOp op) {
1423 auto clone = [&](const FlatBundleFieldEntry &field,
1424 ArrayAttr attrs) -> Value {
1425 Value src = getSubWhatever(op.getRef(), field.index);
1426 return RefResolveOp::create(*builder, src);
1427 };
1428 // Lower according to lowering of the reference.
1429 // Particularly, preserve if rwprobe.
1430 return lowerProducer(op, clone, op.getRef().getType());
1431}
1432
1433bool TypeLoweringVisitor::visitExpr(RefCastOp op) {
1434 auto clone = [&](const FlatBundleFieldEntry &field,
1435 ArrayAttr attrs) -> Value {
1436 auto input = getSubWhatever(op.getInput(), field.index);
1437 return RefCastOp::create(*builder,
1438 RefType::get(field.type,
1439 op.getType().getForceable(),
1440 op.getType().getLayer()),
1441 input);
1442 };
1443 return lowerProducer(op, clone);
1444}
1445
1446bool TypeLoweringVisitor::visitDecl(InstanceOp op) {
1447 bool skip = true;
1448 SmallVector<Type, 8> resultTypes;
1449 SmallVector<int64_t, 8> endFields; // Compressed sparse row encoding
1450 auto oldPortAnno = op.getPortAnnotations();
1451 SmallVector<Direction> newDirs;
1452 SmallVector<Attribute> newNames;
1453 // TODO: Properly lower `newDomains`.
1454 SmallVector<Attribute> newDomains;
1455 SmallVector<Attribute> newPortAnno;
1456 PreserveAggregate::PreserveMode mode = getPreservationModeForPorts(
1457 cast<FModuleLike>(op.getReferencedOperation(symTbl)));
1458
1459 endFields.push_back(0);
1460 for (size_t i = 0, e = op.getNumResults(); i != e; ++i) {
1461 auto srcType = type_cast<FIRRTLType>(op.getType(i));
1462
1463 // Flatten any nested bundle types the usual way.
1464 SmallVector<FlatBundleFieldEntry, 8> fieldTypes;
1465 if (!peelType(srcType, fieldTypes, mode)) {
1466 newDirs.push_back(op.getPortDirection(i));
1467 newNames.push_back(op.getPortNameAttr(i));
1468 newDomains.push_back(builder->getArrayAttr({}));
1469 resultTypes.push_back(srcType);
1470 newPortAnno.push_back(oldPortAnno[i]);
1471 } else {
1472 skip = false;
1473 auto oldName = op.getPortName(i);
1474 auto oldDir = op.getPortDirection(i);
1475 // Store the flat type for the new bundle type.
1476 for (const auto &field : fieldTypes) {
1477 newDirs.push_back(direction::get((unsigned)oldDir ^ field.isOutput));
1478 newNames.push_back(builder->getStringAttr(oldName + field.suffix));
1479 newDomains.push_back(builder->getArrayAttr({}));
1480 resultTypes.push_back(mapLoweredType(srcType, field.type));
1481 auto annos = filterAnnotations(
1482 context, dyn_cast_or_null<ArrayAttr>(oldPortAnno[i]), srcType,
1483 field);
1484 newPortAnno.push_back(annos);
1485 }
1486 }
1487 endFields.push_back(resultTypes.size());
1488 }
1489
1490 auto sym = getInnerSymName(op);
1491
1492 if (skip) {
1493 return false;
1494 }
1495
1496 // FIXME: annotation update
1497 auto newInstance = InstanceOp::create(
1498 *builder, resultTypes, op.getModuleNameAttr(), op.getNameAttr(),
1499 op.getNameKindAttr(), direction::packAttribute(context, newDirs),
1500 builder->getArrayAttr(newNames), builder->getArrayAttr(newDomains),
1501 op.getAnnotations(), builder->getArrayAttr(newPortAnno),
1502 op.getLayersAttr(), op.getLowerToBindAttr(), op.getDoNotPrintAttr(),
1503 sym ? hw::InnerSymAttr::get(sym) : hw::InnerSymAttr());
1504
1505 newInstance->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1506
1507 SmallVector<Value> lowered;
1508 for (size_t aggIndex = 0, eAgg = op.getNumResults(); aggIndex != eAgg;
1509 ++aggIndex) {
1510 lowered.clear();
1511 for (size_t fieldIndex = endFields[aggIndex],
1512 eField = endFields[aggIndex + 1];
1513 fieldIndex < eField; ++fieldIndex)
1514 lowered.push_back(newInstance.getResult(fieldIndex));
1515 if (lowered.size() != 1 ||
1516 op.getType(aggIndex) != resultTypes[endFields[aggIndex]])
1517 processUsers(op.getResult(aggIndex), lowered);
1518 else
1519 op.getResult(aggIndex).replaceAllUsesWith(lowered[0]);
1520 }
1521 return true;
1522}
1523
1524bool TypeLoweringVisitor::visitExpr(SubaccessOp op) {
1525 auto input = op.getInput();
1526 FVectorType vType = input.getType();
1527
1528 // Check for empty vectors
1529 if (vType.getNumElements() == 0) {
1530 Value inv = InvalidValueOp::create(*builder, vType.getElementType());
1531 op.replaceAllUsesWith(inv);
1532 return true;
1533 }
1534
1535 // Check for constant instances
1536 if (ConstantOp arg =
1537 llvm::dyn_cast_or_null<ConstantOp>(op.getIndex().getDefiningOp())) {
1538 auto sio = SubindexOp::create(*builder, op.getInput(),
1539 arg.getValue().getExtValue());
1540 op.replaceAllUsesWith(sio.getResult());
1541 return true;
1542 }
1543
1544 // Construct a multibit mux
1545 SmallVector<Value> inputs;
1546 inputs.reserve(vType.getNumElements());
1547 for (int index = vType.getNumElements() - 1; index >= 0; index--)
1548 inputs.push_back(SubindexOp::create(*builder, input, index));
1549
1550 Value multibitMux = MultibitMuxOp::create(*builder, op.getIndex(), inputs);
1551 op.replaceAllUsesWith(multibitMux);
1552 return true;
1553}
1554
1555bool TypeLoweringVisitor::visitExpr(VectorCreateOp op) {
1556 auto clone = [&](const FlatBundleFieldEntry &field,
1557 ArrayAttr attrs) -> Value {
1558 return op.getOperand(field.index);
1559 };
1560 return lowerProducer(op, clone);
1561}
1562
1563bool TypeLoweringVisitor::visitExpr(BundleCreateOp op) {
1564 auto clone = [&](const FlatBundleFieldEntry &field,
1565 ArrayAttr attrs) -> Value {
1566 return op.getOperand(field.index);
1567 };
1568 return lowerProducer(op, clone);
1569}
1570
1571bool TypeLoweringVisitor::visitExpr(ElementwiseOrPrimOp op) {
1572 auto clone = [&](const FlatBundleFieldEntry &field,
1573 ArrayAttr attrs) -> Value {
1574 Value operands[] = {getSubWhatever(op.getLhs(), field.index),
1575 getSubWhatever(op.getRhs(), field.index)};
1576 return type_isa<BundleType, FVectorType>(field.type)
1577 ? (Value)ElementwiseOrPrimOp::create(*builder, field.type,
1578 operands)
1579 : (Value)OrPrimOp::create(*builder, operands);
1580 };
1581
1582 return lowerProducer(op, clone);
1583}
1584
1585bool TypeLoweringVisitor::visitExpr(ElementwiseAndPrimOp op) {
1586 auto clone = [&](const FlatBundleFieldEntry &field,
1587 ArrayAttr attrs) -> Value {
1588 Value operands[] = {getSubWhatever(op.getLhs(), field.index),
1589 getSubWhatever(op.getRhs(), field.index)};
1590 return type_isa<BundleType, FVectorType>(field.type)
1591 ? (Value)ElementwiseAndPrimOp::create(*builder, field.type,
1592 operands)
1593 : (Value)AndPrimOp::create(*builder, operands);
1594 };
1595
1596 return lowerProducer(op, clone);
1597}
1598
1599bool TypeLoweringVisitor::visitExpr(ElementwiseXorPrimOp op) {
1600 auto clone = [&](const FlatBundleFieldEntry &field,
1601 ArrayAttr attrs) -> Value {
1602 Value operands[] = {getSubWhatever(op.getLhs(), field.index),
1603 getSubWhatever(op.getRhs(), field.index)};
1604 return type_isa<BundleType, FVectorType>(field.type)
1605 ? (Value)ElementwiseXorPrimOp::create(*builder, field.type,
1606 operands)
1607 : (Value)XorPrimOp::create(*builder, operands);
1608 };
1609
1610 return lowerProducer(op, clone);
1611}
1612
1613bool TypeLoweringVisitor::visitExpr(MultibitMuxOp op) {
1614 auto clone = [&](const FlatBundleFieldEntry &field,
1615 ArrayAttr attrs) -> Value {
1616 SmallVector<Value> newInputs;
1617 newInputs.reserve(op.getInputs().size());
1618 for (auto input : op.getInputs()) {
1619 auto inputSub = getSubWhatever(input, field.index);
1620 newInputs.push_back(inputSub);
1621 }
1622 return MultibitMuxOp::create(*builder, op.getIndex(), newInputs);
1623 };
1624 return lowerProducer(op, clone);
1625}
1626
1627//===----------------------------------------------------------------------===//
1628// Pass Infrastructure
1629//===----------------------------------------------------------------------===//
1630
1631namespace {
1632struct LowerTypesPass
1633 : public circt::firrtl::impl::LowerFIRRTLTypesBase<LowerTypesPass> {
1634 using Base::Base;
1635
1636 void runOnOperation() override;
1637};
1638} // end anonymous namespace
1639
1640// This is the main entrypoint for the lowering pass.
1641void LowerTypesPass::runOnOperation() {
1643
1644 std::vector<FModuleLike> ops;
1645 // Symbol Table
1646 auto &symTbl = getAnalysis<SymbolTable>();
1647 // Cached attr
1648 AttrCache cache(&getContext());
1649
1650 DenseMap<FModuleLike, Convention> conventionTable;
1651 auto circuit = getOperation();
1652 for (auto module : circuit.getOps<FModuleLike>()) {
1653 conventionTable.insert({module, module.getConvention()});
1654 ops.push_back(module);
1655 }
1656
1657 // This lambda, executes in parallel for each Op within the circt.
1658 auto lowerModules = [&](FModuleLike op) -> LogicalResult {
1659 // Use body type lowering attribute if it exists, otherwise use internal.
1660 Convention convention = Convention::Internal;
1661 if (auto conventionAttr = dyn_cast_or_null<ConventionAttr>(
1662 op->getDiscardableAttr("body_type_lowering")))
1663 convention = conventionAttr.getValue();
1664
1665 auto tl =
1666 TypeLoweringVisitor(&getContext(), preserveAggregate, convention,
1667 preserveMemories, symTbl, cache, conventionTable);
1668 tl.lowerModule(op);
1669
1670 return LogicalResult::failure(tl.isFailed());
1671 };
1672
1673 auto result = failableParallelForEach(&getContext(), ops, lowerModules);
1674
1675 if (failed(result))
1676 signalPassFailure();
1677}
assert(baseType &&"element must be base type")
static SmallVector< Value > extractBits(OpBuilder &builder, Value val)
static void dump(DIModule &module, raw_indented_ostream &os)
static bool isPreservableAggregateType(Type type, PreserveAggregate::PreserveMode mode)
Return true if we can preserve the type.
static FIRRTLType mapLoweredType(FIRRTLType type, FIRRTLBaseType fieldType)
Return fieldType or fieldType as same ref as type.
static MemOp cloneMemWithNewType(ImplicitLocOpBuilder *b, MemOp op, FlatBundleFieldEntry field)
Clone memory for the specified field. Returns null op on error.
static bool containsBundleType(FIRRTLType type)
Return true if the type has a bundle type as subtype.
static Value cloneAccess(ImplicitLocOpBuilder *builder, Operation *op, Value rhs)
static bool peelType(Type type, SmallVectorImpl< FlatBundleFieldEntry > &fields, PreserveAggregate::PreserveMode mode)
Peel one layer of an aggregate type into its components.
static bool isNotSubAccess(Operation *op)
Return if something is not a normal subaccess.
static SmallVector< Operation * > getSAWritePath(Operation *op)
Look through and collect subfields leading to a subaccess.
static bool isOneDimVectorType(FIRRTLType type)
Return true if the type is a 1d vector type or ground type.
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
Definition Debug.h:70
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
ArrayAttr getArrayAttr() const
Return this annotation set as an ArrayAttr.
This class provides a read-only projection of an annotation.
DictionaryAttr getDict() const
Get the data dictionary of this attribute.
unsigned getFieldID() const
Get the field id this attribute targets.
void setMember(StringAttr name, Attribute value)
Add or set a member of the annotation to a value.
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
ResultType visitInvalidOp(Operation *op, ExtraArgs... args)
visitInvalidOp is an override point for non-FIRRTL dialect operations.
@ All
Preserve all aggregate values.
Definition Passes.h:40
@ OneDimVec
Preserve only 1d vectors of ground type (e.g. UInt<2>[3]).
Definition Passes.h:34
@ Vec
Preserve only vectors (e.g. UInt<2>[3][3]).
Definition Passes.h:37
@ None
Don't preserve aggregate at all.
Definition Passes.h:31
mlir::DenseBoolArrayAttr packAttribute(MLIRContext *context, ArrayRef< Direction > directions)
Return a DenseBoolArrayAttr containing the packed representation of an array of directions.
static Direction get(bool isOutput)
Return an output direction if isOutput is true, otherwise return an input direction.
Definition FIRRTLEnums.h:36
Direction
This represents the direction of a single port.
Definition FIRRTLEnums.h:27
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
FIRRTLType mapBaseType(FIRRTLType type, function_ref< FIRRTLBaseType(FIRRTLBaseType)> fn)
Return a FIRRTLType with its base type component mutated by the given function.
bool hasZeroBitWidth(FIRRTLType type)
Return true if the type has zero bit width.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition FIRRTLOps.h:108
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
This holds the name and type that describes the module's ports.