CIRCT 20.0.0git
Loading...
Searching...
No Matches
LowerOpenAggs.cpp
Go to the documentation of this file.
1//===- LowerOpenAggs.cpp - Lower Open 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 LowerOpenAggs pass. This pass replaces the open
10// aggregate types with hardware aggregates, with non-hardware fields
11// expanded out as with LowerTypes.
12//
13// This pass supports reference and property types.
14//
15//===----------------------------------------------------------------------===//
16
23#include "circt/Support/Debug.h"
24#include "mlir/IR/BuiltinAttributes.h"
25#include "mlir/IR/Threading.h"
26#include "mlir/IR/Visitors.h"
27#include "mlir/Pass/Pass.h"
28#include "llvm/Support/Debug.h"
29#include "llvm/Support/ErrorHandling.h"
30#include "llvm/Support/FormatAdapters.h"
31#include "llvm/Support/FormatVariadic.h"
32
33#define DEBUG_TYPE "firrtl-lower-open-aggs"
34
35namespace circt {
36namespace firrtl {
37#define GEN_PASS_DEF_LOWEROPENAGGS
38#include "circt/Dialect/FIRRTL/Passes.h.inc"
39} // namespace firrtl
40} // namespace circt
41
42using namespace circt;
43using namespace firrtl;
44
45namespace {
46
47/// Information on non-hw (ref/prop) elements.
48struct NonHWField {
49 /// Type of the field, not a hardware type.
50 FIRRTLType type;
51 /// FieldID relative to base of converted type.
52 uint64_t fieldID;
53 /// Relative orientation. False means aligned.
54 bool isFlip;
55 /// String suffix naming this field.
56 SmallString<16> suffix;
57
58 /// Print this structure to the specified stream.
59 void print(raw_ostream &os, unsigned indent = 0) const;
60
61#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
62 /// Print this structure to llvm::errs().
63 LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
64#endif
65};
66
67/// Structure that describes how a given operation with a type (which may or may
68/// not contain non-hw typess) should be lowered to one or more operations with
69/// other types.
70struct MappingInfo {
71 /// Preserve this type. Map any uses of old directly to new.
72 bool identity;
73
74 // When not identity, the type will be split:
75
76 /// Type of the hardware-only portion. May be null, indicating all non-hw.
77 Type hwType;
78
79 /// List of the individual non-hw fields to be split out.
80 SmallVector<NonHWField, 0> fields;
81
82 /// List of fieldID's of interior nodes that map to nothing. HW-only
83 /// projection is empty, and not leaf.
84 SmallVector<uint64_t, 0> mapToNullInteriors;
85
86 hw::InnerSymAttr newSym = {};
87
88 /// Determine number of types this argument maps to.
89 size_t count(bool includeErased = false) const {
90 if (identity)
91 return 1;
92 return fields.size() + (hwType ? 1 : 0) + (includeErased ? 1 : 0);
93 }
94
95 /// Print this structure to the specified stream.
96 void print(raw_ostream &os, unsigned indent = 0) const;
97
98#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
99 /// Print this structure to llvm::errs().
100 LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
101#endif
102};
103
104} // namespace
105
106void NonHWField::print(llvm::raw_ostream &os, unsigned indent) const {
107 os << llvm::formatv("{0}- type: {2}\n"
108 "{1}fieldID: {3}\n"
109 "{1}isFlip: {4}\n"
110 "{1}suffix: \"{5}\"\n",
111 llvm::fmt_pad("", indent, 0),
112 llvm::fmt_pad("", indent + 2, 0), type, fieldID, isFlip,
113 suffix);
114}
115void MappingInfo::print(llvm::raw_ostream &os, unsigned indent) const {
116 if (identity) {
117 os << "<identity>";
118 return;
119 }
120
121 os.indent(indent) << "hardware: ";
122 if (hwType)
123 os << hwType;
124 else
125 os << "<none>";
126 os << "\n";
127
128 os.indent(indent) << "non-hardware:\n";
129 for (auto &field : fields)
130 field.print(os, indent + 2);
131
132 os.indent(indent) << "mappedToNull:\n";
133 for (auto &null : mapToNullInteriors)
134 os.indent(indent + 2) << "- " << null << "\n";
135
136 os.indent(indent) << "newSym: ";
137 if (newSym)
138 os << newSym;
139 else
140 os << "<empty>";
141}
142
143template <typename Range>
144LogicalResult walkMappings(
145 Range &&range, bool includeErased,
146 llvm::function_ref<LogicalResult(size_t, MappingInfo &, size_t)> callback) {
147 size_t count = 0;
148 for (const auto &[index, pmi] : llvm::enumerate(range)) {
149 if (failed(callback(index, pmi, count)))
150 return failure();
151 count += pmi.count(includeErased);
152 }
153 return success();
154}
155
156//===----------------------------------------------------------------------===//
157// Visitor
158//===----------------------------------------------------------------------===//
159
160namespace {
161class Visitor : public FIRRTLVisitor<Visitor, LogicalResult> {
162public:
163 explicit Visitor(MLIRContext *context) : context(context){};
164
165 /// Entrypoint.
166 LogicalResult visit(FModuleLike mod);
167
168 using FIRRTLVisitor<Visitor, LogicalResult>::visitDecl;
169 using FIRRTLVisitor<Visitor, LogicalResult>::visitExpr;
170 using FIRRTLVisitor<Visitor, LogicalResult>::visitStmt;
171
172 LogicalResult visitDecl(InstanceOp op);
173 LogicalResult visitDecl(WireOp op);
174
175 LogicalResult visitExpr(OpenSubfieldOp op);
176 LogicalResult visitExpr(OpenSubindexOp op);
177
178 LogicalResult visitUnhandledOp(Operation *op) {
179 auto notOpenAggType = [](auto type) {
180 return !isa<OpenBundleType, OpenVectorType>(type);
181 };
182 if (!llvm::all_of(op->getOperandTypes(), notOpenAggType) ||
183 !llvm::all_of(op->getResultTypes(), notOpenAggType))
184 return op->emitOpError(
185 "unhandled use or producer of types containing non-hw types");
186 return success();
187 }
188
189 LogicalResult visitInvalidOp(Operation *op) { return visitUnhandledOp(op); }
190
191 /// Whether any changes were made.
192 bool madeChanges() const { return changesMade; }
193
194private:
195 /// Convert a type to its HW-only projection, adjusting symbols. Gather
196 /// non-hw elements encountered and their names / positions. Returns a
197 /// MappingInfo with its findings.
198 FailureOr<MappingInfo> mapType(Type type, Location errorLoc,
199 hw::InnerSymAttr sym = {});
200
201 /// Helper to record changes that may have been made.
202 void recordChanges(bool changed) {
203 if (changed)
204 changesMade = true;
205 }
206
207 MLIRContext *context;
208
209 /// Map non-HW fields to their new Value.
210 /// Null value indicates no equivalent (dead).
211 /// These values are available wherever the root is used.
212 DenseMap<FieldRef, Value> nonHWValues;
213
214 /// Map from original to its hw-only aggregate equivalent.
215 DenseMap<Value, Value> hwOnlyAggMap;
216
217 /// List of operations to erase at the end.
218 SmallVector<Operation *> opsToErase;
219
220 /// FieldRef cache. Be careful to only use this for operations
221 /// in the original IR / not mutated.
222 FieldRefCache refs;
223
224 /// Whether IR was changed.
225 bool changesMade = false;
226};
227} // namespace
228
229LogicalResult Visitor::visit(FModuleLike mod) {
230 auto ports = mod.getPorts();
231
232 SmallVector<MappingInfo, 16> portMappings;
233 for (auto &port : ports) {
234 auto pmi = mapType(port.type, port.loc, port.sym);
235 if (failed(pmi))
236 return failure();
237 portMappings.push_back(*pmi);
238 }
239
240 /// Total number of types mapped to.
241 /// Include erased ports.
242 size_t countWithErased = 0;
243 for (auto &pmi : portMappings)
244 countWithErased += pmi.count(/*includeErased=*/true);
245
246 /// Ports to add.
247 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
248
249 /// Ports to remove.
250 BitVector portsToErase(countWithErased);
251
252 /// Go through each port mapping, gathering information about all new ports.
253 LLVM_DEBUG({
254 llvm::dbgs().indent(2) << "- name: "
255 << cast<mlir::SymbolOpInterface>(*mod).getNameAttr()
256 << "\n";
257 llvm::dbgs().indent(4) << "ports:\n";
258 });
259 auto result = walkMappings(
260 portMappings, /*includeErased=*/true,
261 [&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
262 LLVM_DEBUG({
263 llvm::dbgs().indent(6) << "- name: " << ports[index].name << "\n";
264 llvm::dbgs().indent(8) << "type: " << ports[index].type << "\n";
265 llvm::dbgs().indent(8) << "mapping:\n";
266 pmi.print(llvm::dbgs(), /*indent=*/10);
267 llvm::dbgs() << "\n";
268 });
269 // Index for inserting new points next to this point.
270 // (Immediately after current port's index).
271 auto idxOfInsertPoint = index + 1;
272
273 if (pmi.identity)
274 return success();
275
276 auto &port = ports[index];
277
278 // If not identity, mark this port for eventual removal.
279 portsToErase.set(newIndex);
280
281 // Create new hw-only port, this will generally replace this port.
282 if (pmi.hwType) {
283 auto newPort = port;
284 newPort.type = pmi.hwType;
285 newPort.sym = pmi.newSym;
286 newPorts.emplace_back(idxOfInsertPoint, newPort);
287
288 assert(!port.sym ||
289 (pmi.newSym && port.sym.size() == pmi.newSym.size()));
290
291 // If want to run this pass later, need to fixup annotations.
292 if (!port.annotations.empty())
293 return mlir::emitError(port.loc)
294 << "annotations on open aggregates not handled yet";
295 } else {
296 assert(!port.sym && !pmi.newSym);
297 if (!port.annotations.empty())
298 return mlir::emitError(port.loc)
299 << "annotations found on aggregate with no HW";
300 }
301
302 // Create ports for each non-hw field.
303 for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
304 auto name = StringAttr::get(context,
305 Twine(port.name.strref()) + field.suffix);
306 auto orientation =
307 (Direction)((unsigned)port.direction ^ field.isFlip);
308 PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
309 port.loc, std::nullopt);
310 newPorts.emplace_back(idxOfInsertPoint, pi);
311 }
312 return success();
313 });
314 if (failed(result))
315 return failure();
316
317 // Insert the new ports!
318 mod.insertPorts(newPorts);
319 recordChanges(!newPorts.empty());
320
321 assert(mod->getNumRegions() == 1);
322
323 // (helper to determine/get the body block if present)
324 auto getBodyBlock = [](auto mod) {
325 auto &blocks = mod->getRegion(0).getBlocks();
326 return !blocks.empty() ? &blocks.front() : nullptr;
327 };
328
329 // Process body block.
330 // Create mapping for ports, then visit all operations within.
331 if (auto *block = getBodyBlock(mod)) {
332 // Create mappings for split ports.
333 auto result =
334 walkMappings(portMappings, /*includeErased=*/true,
335 [&](auto index, MappingInfo &pmi, auto newIndex) {
336 // Nothing to do for identity.
337 if (pmi.identity)
338 return success();
339
340 // newIndex is index of this port after insertion.
341 // This will be removed.
342 assert(portsToErase.test(newIndex));
343 auto oldPort = block->getArgument(newIndex);
344 auto newPortIndex = newIndex;
345
346 // Create mappings for split ports.
347 if (pmi.hwType)
348 hwOnlyAggMap[oldPort] =
349 block->getArgument(++newPortIndex);
350
351 for (auto &field : pmi.fields) {
352 auto ref = FieldRef(oldPort, field.fieldID);
353 auto newVal = block->getArgument(++newPortIndex);
354 nonHWValues[ref] = newVal;
355 }
356 for (auto fieldID : pmi.mapToNullInteriors) {
357 auto ref = FieldRef(oldPort, fieldID);
358 assert(!nonHWValues.count(ref));
359 nonHWValues[ref] = {};
360 }
361
362 return success();
363 });
364 if (failed(result))
365 return failure();
366
367 // Walk the module.
368 LLVM_DEBUG(llvm::dbgs().indent(4) << "body:\n");
369 if (block
370 ->walk<mlir::WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
371 return dispatchVisitor(op);
372 })
373 .wasInterrupted())
374 return failure();
375
376 assert(opsToErase.empty() || madeChanges());
377
378 // Cleanup dead operations.
379 for (auto &op : llvm::reverse(opsToErase))
380 op->erase();
381 }
382
383 // Drop dead ports.
384 mod.erasePorts(portsToErase);
385 recordChanges(portsToErase.any());
386
387 LLVM_DEBUG(refs.printStats(llvm::dbgs()));
388
389 return success();
390}
391
392LogicalResult Visitor::visitExpr(OpenSubfieldOp op) {
393 // Changes will be made.
394 recordChanges(true);
395
396 // We're indexing into an OpenBundle, which contains some non-hw elements and
397 // may contain hw elements.
398
399 // By the time this is reached, the "root" storage for the input
400 // has already been handled and mapped to its new location(s),
401 // such that the hardware-only contents are split from non-hw.
402
403 // If there is a hardware portion selected by this operation,
404 // create a "closed" subfieldop using the hardware-only new storage,
405 // and add an entry mapping our old (soon, dead) result to
406 // this new hw-only result (of the subfieldop).
407
408 // Downstream indexing operations will expect that they can
409 // still chase up through this operation, and that they will find
410 // the hw-only portion in the map.
411
412 // If this operation selects a non-hw element (not mixed),
413 // look up where that ref now lives and update all users to use that instead.
414 // (This case falls under "this selects only non-hw", which means
415 // that this operation is now dead).
416
417 // In all cases, this operation will be dead and should be removed.
418 opsToErase.push_back(op);
419
420 // Chase this to its original root.
421 // If the FieldRef for this selection has a new home,
422 // RAUW to that value and this op is dead.
423 auto resultRef = refs.getFieldRefFromValue(op.getResult());
424 auto nonHWForResult = nonHWValues.find(resultRef);
425 if (nonHWForResult != nonHWValues.end()) {
426 // If has nonHW portion, RAUW to it.
427 if (auto newResult = nonHWForResult->second) {
428 assert(op.getResult().getType() == newResult.getType());
429 assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
430 op.getResult().replaceAllUsesWith(newResult);
431 }
432 return success();
433 }
434
435 assert(hwOnlyAggMap.count(op.getInput()));
436
437 auto newInput = hwOnlyAggMap[op.getInput()];
438 assert(newInput);
439
440 auto bundleType = type_cast<BundleType>(newInput.getType());
441
442 // Recompute the "actual" index for this field, it may have changed.
443 auto fieldName = op.getFieldName();
444 auto newFieldIndex = bundleType.getElementIndex(fieldName);
445 assert(newFieldIndex.has_value());
446
447 ImplicitLocOpBuilder builder(op.getLoc(), op);
448 auto newOp = builder.create<SubfieldOp>(newInput, *newFieldIndex);
449 if (auto name = op->getAttrOfType<StringAttr>("name"))
450 newOp->setAttr("name", name);
451
452 hwOnlyAggMap[op.getResult()] = newOp;
453
454 if (type_isa<FIRRTLBaseType>(op.getType()))
455 op.getResult().replaceAllUsesWith(newOp.getResult());
456
457 return success();
458}
459
460LogicalResult Visitor::visitExpr(OpenSubindexOp op) {
461 // Changes will be made.
462 recordChanges(true);
463
464 // In all cases, this operation will be dead and should be removed.
465 opsToErase.push_back(op);
466
467 // Chase this to its original root.
468 // If the FieldRef for this selection has a new home,
469 // RAUW to that value and this op is dead.
470 auto resultRef = refs.getFieldRefFromValue(op.getResult());
471 auto nonHWForResult = nonHWValues.find(resultRef);
472 if (nonHWForResult != nonHWValues.end()) {
473 // If has nonHW portion, RAUW to it.
474 if (auto newResult = nonHWForResult->second) {
475 assert(op.getResult().getType() == newResult.getType());
476 assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
477 op.getResult().replaceAllUsesWith(newResult);
478 }
479 return success();
480 }
481
482 assert(hwOnlyAggMap.count(op.getInput()));
483
484 auto newInput = hwOnlyAggMap[op.getInput()];
485 assert(newInput);
486
487 ImplicitLocOpBuilder builder(op.getLoc(), op);
488 auto newOp = builder.create<SubindexOp>(newInput, op.getIndex());
489 if (auto name = op->getAttrOfType<StringAttr>("name"))
490 newOp->setAttr("name", name);
491
492 hwOnlyAggMap[op.getResult()] = newOp;
493
494 if (type_isa<FIRRTLBaseType>(op.getType()))
495 op.getResult().replaceAllUsesWith(newOp.getResult());
496 return success();
497}
498
499LogicalResult Visitor::visitDecl(InstanceOp op) {
500 // Rewrite ports same strategy as for modules.
501
502 SmallVector<MappingInfo, 16> portMappings;
503
504 for (auto type : op.getResultTypes()) {
505 auto pmi = mapType(type, op.getLoc());
506 if (failed(pmi))
507 return failure();
508 portMappings.push_back(*pmi);
509 }
510
511 /// Total number of types mapped to.
512 size_t countWithErased = 0;
513 for (auto &pmi : portMappings)
514 countWithErased += pmi.count(/*includeErased=*/true);
515
516 /// Ports to add.
517 SmallVector<std::pair<unsigned, PortInfo>> newPorts;
518
519 /// Ports to remove.
520 BitVector portsToErase(countWithErased);
521
522 /// Go through each port mapping, gathering information about all new ports.
523 LLVM_DEBUG({
524 llvm::dbgs().indent(6) << "- instance:\n";
525 llvm::dbgs().indent(10) << "name: " << op.getInstanceNameAttr() << "\n";
526 llvm::dbgs().indent(10) << "module: " << op.getModuleNameAttr() << "\n";
527 llvm::dbgs().indent(10) << "ports:\n";
528 });
529 auto result = walkMappings(
530 portMappings, /*includeErased=*/true,
531 [&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
532 LLVM_DEBUG({
533 llvm::dbgs().indent(12)
534 << "- name: " << op.getPortName(index) << "\n";
535 llvm::dbgs().indent(14) << "type: " << op.getType(index) << "\n";
536 llvm::dbgs().indent(14) << "mapping:\n";
537 pmi.print(llvm::dbgs(), /*indent=*/16);
538 llvm::dbgs() << "\n";
539 });
540 // Index for inserting new points next to this point.
541 // (Immediately after current port's index).
542 auto idxOfInsertPoint = index + 1;
543
544 if (pmi.identity)
545 return success();
546
547 // If not identity, mark this port for eventual removal.
548 portsToErase.set(newIndex);
549
550 auto portName = op.getPortName(index);
551 auto portDirection = op.getPortDirection(index);
552 auto loc = op.getLoc();
553
554 // Create new hw-only port, this will generally replace this port.
555 if (pmi.hwType) {
556 PortInfo hwPort(portName, pmi.hwType, portDirection,
557 /*symName=*/StringAttr{}, loc,
558 AnnotationSet(op.getPortAnnotation(index)));
559 newPorts.emplace_back(idxOfInsertPoint, hwPort);
560
561 // If want to run this pass later, need to fixup annotations.
562 if (!op.getPortAnnotation(index).empty())
563 return mlir::emitError(op.getLoc())
564 << "annotations on open aggregates not handled yet";
565 } else {
566 if (!op.getPortAnnotation(index).empty())
567 return mlir::emitError(op.getLoc())
568 << "annotations found on aggregate with no HW";
569 }
570
571 // Create ports for each non-hw field.
572 for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
573 auto name =
574 StringAttr::get(context, Twine(portName.strref()) + field.suffix);
575 auto orientation =
576 (Direction)((unsigned)portDirection ^ field.isFlip);
577 PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
578 loc, std::nullopt);
579 newPorts.emplace_back(idxOfInsertPoint, pi);
580 }
581 return success();
582 });
583 if (failed(result))
584 return failure();
585
586 // If no new ports, we're done.
587 if (newPorts.empty())
588 return success();
589
590 // Changes will be made.
591 recordChanges(true);
592
593 // Create new instance op with desired ports.
594
595 // TODO: add and erase ports without intermediate + various array attributes.
596 auto tempOp = op.cloneAndInsertPorts(newPorts);
597 opsToErase.push_back(tempOp);
598 ImplicitLocOpBuilder builder(op.getLoc(), op);
599 auto newInst = tempOp.erasePorts(builder, portsToErase);
600
601 auto mappingResult = walkMappings(
602 portMappings, /*includeErased=*/false,
603 [&](auto index, MappingInfo &pmi, auto newIndex) {
604 // Identity means index -> newIndex.
605 auto oldResult = op.getResult(index);
606 if (pmi.identity) {
607 // (Just do the RAUW here instead of tracking the mapping for this
608 // too.)
609 assert(oldResult.getType() == newInst.getType(newIndex));
610 oldResult.replaceAllUsesWith(newInst.getResult(newIndex));
611 return success();
612 }
613
614 // Create mappings for updating open aggregate users.
615 auto newPortIndex = newIndex;
616 if (pmi.hwType)
617 hwOnlyAggMap[oldResult] = newInst.getResult(newPortIndex++);
618
619 for (auto &field : pmi.fields) {
620 auto ref = FieldRef(oldResult, field.fieldID);
621 auto newVal = newInst.getResult(newPortIndex++);
622 assert(newVal.getType() == field.type);
623 nonHWValues[ref] = newVal;
624 }
625 for (auto fieldID : pmi.mapToNullInteriors) {
626 auto ref = FieldRef(oldResult, fieldID);
627 assert(!nonHWValues.count(ref));
628 nonHWValues[ref] = {};
629 }
630 return success();
631 });
632 if (failed(mappingResult))
633 return failure();
634
635 opsToErase.push_back(op);
636
637 return success();
638}
639
640LogicalResult Visitor::visitDecl(WireOp op) {
641 auto pmi = mapType(op.getResultTypes()[0], op.getLoc(), op.getInnerSymAttr());
642 if (failed(pmi))
643 return failure();
644 MappingInfo mappings = *pmi;
645
646 LLVM_DEBUG({
647 llvm::dbgs().indent(6) << "- wire:\n";
648 llvm::dbgs().indent(10) << "name: " << op.getNameAttr() << "\n";
649 llvm::dbgs().indent(10) << "type: " << op.getType(0) << "\n";
650 llvm::dbgs().indent(12) << "mapping:\n";
651 mappings.print(llvm::dbgs(), 14);
652 llvm::dbgs() << "\n";
653 });
654
655 if (mappings.identity)
656 return success();
657
658 // Changes will be made.
659 recordChanges(true);
660
661 ImplicitLocOpBuilder builder(op.getLoc(), op);
662
663 if (!op.getAnnotations().empty())
664 return mlir::emitError(op.getLoc())
665 << "annotations on open aggregates not handled yet";
666
667 // Create the new HW wire.
668 if (mappings.hwType)
669 hwOnlyAggMap[op.getResult()] =
670 builder
671 .create<WireOp>(mappings.hwType, op.getName(), op.getNameKind(),
672 op.getAnnotations(), mappings.newSym,
673 op.getForceable())
674 .getResult();
675
676 // Create the non-HW wires. Non-HW wire names are always droppable.
677 for (auto &[type, fieldID, _, suffix] : mappings.fields)
678 nonHWValues[FieldRef(op.getResult(), fieldID)] =
679 builder
680 .create<WireOp>(type,
681 builder.getStringAttr(Twine(op.getName()) + suffix),
682 NameKindEnum::DroppableName)
683 .getResult();
684
685 for (auto fieldID : mappings.mapToNullInteriors)
686 nonHWValues[FieldRef(op.getResult(), fieldID)] = {};
687
688 opsToErase.push_back(op);
689
690 return success();
691}
692
693//===----------------------------------------------------------------------===//
694// Type Conversion
695//===----------------------------------------------------------------------===//
696
697FailureOr<MappingInfo> Visitor::mapType(Type type, Location errorLoc,
698 hw::InnerSymAttr sym) {
699 MappingInfo pi{false, {}, {}, {}};
700 auto ftype = type_dyn_cast<FIRRTLType>(type);
701 // Anything that isn't an open aggregates is left alone.
702 if (!ftype || !isa<OpenBundleType, OpenVectorType>(ftype)) {
703 pi.identity = true;
704 return pi;
705 }
706
707 SmallVector<hw::InnerSymPropertiesAttr> newProps;
708
709 // NOLINTBEGIN(misc-no-recursion)
710 auto recurse = [&](auto &&f, FIRRTLType type, const Twine &suffix = "",
711 bool flip = false, uint64_t fieldID = 0,
712 uint64_t newFieldID = 0) -> FailureOr<FIRRTLBaseType> {
713 auto newType =
714 TypeSwitch<FIRRTLType, FailureOr<FIRRTLBaseType>>(type)
715 .Case<FIRRTLBaseType>([](auto base) { return base; })
716 .template Case<OpenBundleType>([&](OpenBundleType obTy)
717 -> FailureOr<FIRRTLBaseType> {
718 SmallVector<BundleType::BundleElement> hwElements;
719 uint64_t id = 0;
720 for (const auto &[index, element] :
721 llvm::enumerate(obTy.getElements())) {
722 auto base =
723 f(f, element.type, suffix + "_" + element.name.strref(),
724 flip ^ element.isFlip, fieldID + obTy.getFieldID(index),
725 newFieldID + id + 1);
726 if (failed(base))
727 return failure();
728 if (*base) {
729 hwElements.emplace_back(element.name, element.isFlip, *base);
730 id += hw::FieldIdImpl::getMaxFieldID(*base) + 1;
731 }
732 }
733
734 if (hwElements.empty()) {
735 pi.mapToNullInteriors.push_back(fieldID);
736 return FIRRTLBaseType{};
737 }
738
739 return BundleType::get(context, hwElements, obTy.isConst());
740 })
741 .template Case<OpenVectorType>([&](OpenVectorType ovTy)
742 -> FailureOr<FIRRTLBaseType> {
743 uint64_t id = 0;
744 FIRRTLBaseType convert;
745 // Walk for each index to extract each leaf separately, but expect
746 // same hw-only type for all.
747 for (auto idx : llvm::seq<size_t>(0U, ovTy.getNumElements())) {
748 auto hwElementType =
749 f(f, ovTy.getElementType(), suffix + "_" + Twine(idx), flip,
750 fieldID + ovTy.getFieldID(idx), newFieldID + id + 1);
751 if (failed(hwElementType))
752 return failure();
753 assert((!convert || convert == *hwElementType) &&
754 "expected same hw type for all elements");
755 convert = *hwElementType;
756 if (convert)
757 id += hw::FieldIdImpl::getMaxFieldID(convert) + 1;
758 }
759
760 if (!convert) {
761 pi.mapToNullInteriors.push_back(fieldID);
762 return FIRRTLBaseType{};
763 }
764
765 return FVectorType::get(convert, ovTy.getNumElements(),
766 ovTy.isConst());
767 })
768 .template Case<RefType>([&](RefType ref) {
769 auto f = NonHWField{ref, fieldID, flip, {}};
770 suffix.toVector(f.suffix);
771 pi.fields.emplace_back(std::move(f));
772 return FIRRTLBaseType{};
773 })
774 // This is identical to the RefType case above, but copied out
775 // to try to fix a bug when combining + auto w/MSVC.
776 .template Case<PropertyType>([&](PropertyType prop) {
777 auto f = NonHWField{prop, fieldID, flip, {}};
778 suffix.toVector(f.suffix);
779 pi.fields.emplace_back(std::move(f));
780 return FIRRTLBaseType{};
781 })
782 .Default([&](auto _) {
783 pi.mapToNullInteriors.push_back(fieldID);
784 return FIRRTLBaseType{};
785 });
786 if (failed(newType))
787 return failure();
788
789 // If there's a symbol on this, add it with adjusted fieldID.
790 if (sym)
791 if (auto symOnThis = sym.getSymIfExists(fieldID)) {
792 if (!*newType)
793 return mlir::emitError(errorLoc, "inner symbol ")
794 << symOnThis << " mapped to non-HW type";
795 newProps.push_back(hw::InnerSymPropertiesAttr::get(
796 context, symOnThis, newFieldID,
797 StringAttr::get(context, "public")));
798 }
799 return newType;
800 };
801
802 auto hwType = recurse(recurse, ftype);
803 if (failed(hwType))
804 return failure();
805 pi.hwType = *hwType;
806
807 assert(pi.hwType != type);
808 // NOLINTEND(misc-no-recursion)
809
810 if (sym) {
811 assert(sym.size() == newProps.size());
812
813 if (!pi.hwType && !newProps.empty())
814 return mlir::emitError(errorLoc, "inner symbol on non-HW type");
815
816 llvm::sort(newProps, [](auto &p, auto &q) {
817 return p.getFieldID() < q.getFieldID();
818 });
819 pi.newSym = hw::InnerSymAttr::get(context, newProps);
820 }
821
822 return pi;
823}
824
825//===----------------------------------------------------------------------===//
826// Pass Infrastructure
827//===----------------------------------------------------------------------===//
828
829namespace {
830struct LowerOpenAggsPass
831 : public circt::firrtl::impl::LowerOpenAggsBase<LowerOpenAggsPass> {
832 LowerOpenAggsPass() = default;
833 void runOnOperation() override;
834};
835} // end anonymous namespace
836
837// This is the main entrypoint for the lowering pass.
838void LowerOpenAggsPass::runOnOperation() {
839 LLVM_DEBUG(debugPassHeader(this) << "\n");
840 SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
841
842 LLVM_DEBUG(llvm::dbgs() << "Visiting modules:\n");
843 std::atomic<bool> madeChanges = false;
844 auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
845 Visitor visitor(&getContext());
846 auto result = visitor.visit(cast<FModuleLike>(op));
847 if (visitor.madeChanges())
848 madeChanges = true;
849 return result;
850 });
851
852 if (result.failed())
853 signalPassFailure();
854 if (!madeChanges)
855 markAllAnalysesPreserved();
856}
857
858/// This is the pass constructor.
859std::unique_ptr<mlir::Pass> circt::firrtl::createLowerOpenAggsPass() {
860 return std::make_unique<LowerOpenAggsPass>();
861}
assert(baseType &&"element must be base type")
static void dump(DIModule &module, raw_indented_ostream &os)
LogicalResult walkMappings(Range &&range, bool includeErased, llvm::function_ref< LogicalResult(size_t, MappingInfo &, size_t)> callback)
static Block * getBodyBlock(FModuleLike mod)
This class represents a reference to a specific field or element of an aggregate value.
Definition FieldRef.h:28
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
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.
ResultType visitUnhandledOp(Operation *op, ExtraArgs... args)
visitUnhandledOp is an override point for FIRRTL dialect ops that the concrete visitor didn't bother ...
Caching version of getFieldRefFromValue.
Direction
This represents the direction of a single port.
std::unique_ptr< mlir::Pass > createLowerOpenAggsPass()
This is the pass constructor.
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
ModulePort::Direction flip(ModulePort::Direction direction)
Flip a port direction.
Definition HWOps.cpp:36
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition Debug.cpp:31
Definition seq.py:1
This holds the name and type that describes the module's ports.