CIRCT 23.0.0git
Loading...
Searching...
No Matches
ExpandWhens.cpp
Go to the documentation of this file.
1//===- ExpandWhens.cpp - Expand WhenOps into muxed operations ---*- 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 ExpandWhens pass. This pass resolves last-connect
10// semantics and ensures that all values (except domain types) are connected
11// to exactly once. Domain types are exempt from initialization checking and
12// are handled by the InferDomains pass.
13//
14//===----------------------------------------------------------------------===//
15
22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/MapVector.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/Support/SaveAndRestore.h"
26
27namespace circt {
28namespace firrtl {
29#define GEN_PASS_DEF_EXPANDWHENS
30#include "circt/Dialect/FIRRTL/Passes.h.inc"
31} // namespace firrtl
32} // namespace circt
33
34using namespace circt;
35using namespace firrtl;
36
37/// Move all operations from a source block in to a destination block. Leaves
38/// the source block empty.
39static void mergeBlock(Block &destination, Block::iterator insertPoint,
40 Block &source) {
41 destination.getOperations().splice(insertPoint, source.getOperations());
42}
43
44/// This is a stack of hashtables, if lookup fails in the top-most hashtable,
45/// it will attempt to lookup in lower hashtables. This class is used instead
46/// of a ScopedHashTable so we can manually pop off a scope and keep it around.
47///
48/// This only allows inserting into the outermost scope.
49template <typename KeyT, typename ValueT>
52 using StackT = typename llvm::SmallVector<ScopeT, 3>;
53
54 struct Iterator {
55 Iterator(typename StackT::iterator stackIt,
56 typename ScopeT::iterator scopeIt)
58
59 bool operator==(const Iterator &rhs) const {
60 return stackIt == rhs.stackIt && scopeIt == rhs.scopeIt;
61 }
62
63 bool operator!=(const Iterator &rhs) const { return !(*this == rhs); }
64
65 std::pair<KeyT, ValueT> &operator*() const { return *scopeIt; }
66
68 if (scopeIt == stackIt->end())
69 scopeIt = (++stackIt)->begin();
70 else
71 ++scopeIt;
72 return *this;
73 }
74
75 typename StackT::iterator stackIt;
76 typename ScopeT::iterator scopeIt;
77 };
78
80 // We require at least one scope.
81 pushScope();
82 }
83
85
87 return Iterator(mapStack.begin(), mapStack.first().begin());
88 }
89
90 iterator end() { return Iterator(mapStack.end() - 1, mapStack.back().end()); }
91
92 iterator find(const KeyT &key) {
93 // Try to find a hashtable with the missing value.
94 for (auto i = mapStack.size(); i > 0; --i) {
95 auto &map = mapStack[i - 1];
96 auto it = map.find(key);
97 if (it != map.end())
98 return Iterator(mapStack.begin() + i - 1, it);
99 }
100 return end();
101 }
102
103 ScopeT &getLastScope() { return mapStack.back(); }
104
105 void pushScope() { mapStack.emplace_back(); }
106
108 assert(mapStack.size() > 1 && "Cannot pop the last scope");
109 return mapStack.pop_back_val();
110 }
111
112 // This class lets you insert into the top scope.
113 ValueT &operator[](const KeyT &key) { return mapStack.back()[key]; }
114
115private:
117};
118
119/// This is a determistic mapping of a FieldRef to the last operation which set
120/// a value to it.
122using DriverMap = ScopedDriverMap::ScopeT;
123
124//===----------------------------------------------------------------------===//
125// Last Connect Resolver
126//===----------------------------------------------------------------------===//
127
128namespace {
129/// This visitor visits process a block resolving last connect semantics
130/// and recursively expanding WhenOps.
131template <typename ConcreteT>
132class LastConnectResolver : public FIRRTLVisitor<ConcreteT> {
133protected:
134 /// Map of destinations and the operation which is driving a value to it in
135 /// the current scope. This is used for resolving last connect semantics, and
136 /// for retrieving the responsible connect operation.
137 ScopedDriverMap &driverMap;
138
139public:
140 LastConnectResolver(ScopedDriverMap &driverMap) : driverMap(driverMap) {}
141
142 using FIRRTLVisitor<ConcreteT>::visitExpr;
143 using FIRRTLVisitor<ConcreteT>::visitDecl;
144 using FIRRTLVisitor<ConcreteT>::visitStmt;
145
146 /// Records a connection to a destination in the current scope.
147 /// If connection has static single connect behavior, this is all.
148 /// For connections with last-connect behavior, this will delete a previous
149 /// connection to a destination if there was one.
150 /// Returns true if an old connect was erased.
151 bool recordConnect(FieldRef dest, Operation *connection) {
152 // Try to insert, if it doesn't insert, replace the previous value.
153 auto itAndInserted = driverMap.getLastScope().insert({dest, connection});
154 if (isStaticSingleConnect(connection)) {
155 // There should be no non-null driver already, Verifier checks this.
156 assert(itAndInserted.second || !itAndInserted.first->second);
157 if (!itAndInserted.second)
158 itAndInserted.first->second = connection;
159 return false;
160 }
161 assert(isLastConnect(connection));
162 if (!std::get<1>(itAndInserted)) {
163 auto iterator = std::get<0>(itAndInserted);
164 auto changed = false;
165 // Delete the old connection if it exists. Null connections are inserted
166 // on declarations.
167 if (auto *oldConnect = iterator->second) {
168 oldConnect->erase();
169 changed = true;
170 }
171 iterator->second = connection;
172 return changed;
173 }
174 return false;
175 }
176
177 /// Get the destination value from a connection. This supports any operation
178 /// which is capable of driving a value.
179 static Value getDestinationValue(Operation *op) {
180 return cast<FConnectLike>(op).getDest();
181 }
182
183 /// Get the source value from a connection. This supports any operation which
184 /// is capable of driving a value.
185 static Value getConnectedValue(Operation *op) {
186 return cast<FConnectLike>(op).getSrc();
187 }
188
189 /// Return whether the connection has static single connection behavior.
190 static bool isStaticSingleConnect(Operation *op) {
191 return cast<FConnectLike>(op).hasStaticSingleConnectBehavior();
192 }
193
194 /// Return whether the connection has last-connect behavior.
195 /// Compared to static single connect behavior, with last-connect behavior
196 /// destinations can be connected to multiple times and are connected
197 /// conditionally when connecting out from under a 'when'.
198 static bool isLastConnect(Operation *op) {
199 return cast<FConnectLike>(op).hasLastConnectBehavior();
200 }
201
202 /// For every leaf field in the sink, record that it exists and should be
203 /// initialized.
204 void declareSinks(Value value, Flow flow, bool local = false) {
205 auto type = value.getType();
206 unsigned id = 0;
207
208 // Recurse through a bundle and declare each leaf sink node.
209 std::function<void(Type, Flow, bool)> declare = [&](Type type, Flow flow,
210 bool local) {
211 // Domain types are exempt from initialization checking and will be
212 // handled by the InferDomains pass.
213 if (type_isa<DomainType>(type))
214 return;
215
216 // If this is a class type, recurse to each of the fields.
217 if (auto classType = type_dyn_cast<ClassType>(type)) {
218 if (local) {
219 // If this is a local object declaration, then we are responsible for
220 // initializing its input ports.
221 for (auto &element : classType.getElements()) {
222 id++;
223 if (element.direction == Direction::Out)
224 declare(element.type, flow, false);
225 else
226 declare(element.type, swapFlow(flow), false);
227 }
228 } else {
229 // If this is a remote object, then the object itself is potentially
230 // a sink.
231 if (flow != Flow::Source)
232 driverMap[{value, id}] = nullptr;
233 // skip over its subfields--we are not responsible for initializing
234 // the object here.
235 id += classType.getMaxFieldID();
236 }
237 return;
238 }
239
240 // If this is a bundle type, recurse to each of the fields.
241 if (auto bundleType = type_dyn_cast<BundleType>(type)) {
242 for (auto &element : bundleType.getElements()) {
243 id++;
244 if (element.isFlip)
245 declare(element.type, swapFlow(flow), false);
246 else
247 declare(element.type, flow, false);
248 }
249 return;
250 }
251
252 // If this is a vector type, recurse to each of the elements.
253 if (auto vectorType = type_dyn_cast<FVectorType>(type)) {
254 for (unsigned i = 0; i < vectorType.getNumElements(); ++i) {
255 id++;
256 declare(vectorType.getElementType(), flow, false);
257 }
258 return;
259 }
260
261 // If this is an analog type, it does not need to be tracked.
262 if (auto analogType = type_dyn_cast<AnalogType>(type))
263 return;
264
265 // If it is a leaf node with Flow::Sink or Flow::Duplex, it must be
266 // initialized.
267 if (flow != Flow::Source)
268 driverMap[{value, id}] = nullptr;
269 };
270
271 declare(type, flow, local);
272 }
273
274 /// Take two connection operations and merge them into a new connect under a
275 /// condition. Destination of both connects should be `dest`.
276 ConnectOp flattenConditionalConnections(OpBuilder &b, Location loc,
277 Value dest, Value cond,
278 Operation *whenTrueConn,
279 Operation *whenFalseConn) {
280 assert(isLastConnect(whenTrueConn) && isLastConnect(whenFalseConn));
281 auto fusedLoc =
282 b.getFusedLoc({loc, whenTrueConn->getLoc(), whenFalseConn->getLoc()});
283 auto whenTrue = getConnectedValue(whenTrueConn);
284 auto trueIsInvalid =
285 isa_and_nonnull<InvalidValueOp>(whenTrue.getDefiningOp());
286 auto whenFalse = getConnectedValue(whenFalseConn);
287 auto falseIsInvalid =
288 isa_and_nonnull<InvalidValueOp>(whenFalse.getDefiningOp());
289 // If one of the branches of the mux is an invalid value, we optimize the
290 // mux to be the non-invalid value. This optimization can only be
291 // performed while lowering when-ops into muxes, and would not be legal as
292 // a more general mux folder.
293 // mux(cond, invalid, x) -> x
294 // mux(cond, x, invalid) -> x
295 Value newValue = whenTrue;
296 if (trueIsInvalid == falseIsInvalid)
297 newValue = b.createOrFold<MuxPrimOp>(fusedLoc, cond, whenTrue, whenFalse);
298 else if (trueIsInvalid)
299 newValue = whenFalse;
300 return ConnectOp::create(b, loc, dest, newValue);
301 }
302
303 void visitDecl(WireOp op) { declareSinks(op.getResult(), Flow::Duplex); }
304
305 /// Take an aggregate value and construct ground subelements recursively.
306 /// And then apply function `fn`.
307 void foreachSubelement(OpBuilder &builder, Value value,
308 llvm::function_ref<void(Value)> fn) {
309 FIRRTLTypeSwitch<Type>(value.getType())
310 .template Case<BundleType>([&](BundleType bundle) {
311 for (auto i : llvm::seq(0u, (unsigned)bundle.getNumElements())) {
312 auto subfield =
313 SubfieldOp::create(builder, value.getLoc(), value, i);
314 foreachSubelement(builder, subfield, fn);
315 }
316 })
317 .template Case<FVectorType>([&](FVectorType vector) {
318 for (auto i : llvm::seq((size_t)0, vector.getNumElements())) {
319 auto subindex =
320 SubindexOp::create(builder, value.getLoc(), value, i);
321 foreachSubelement(builder, subindex, fn);
322 }
323 })
324 .Default([&](auto) { fn(value); });
325 }
326
327 void visitDecl(RegOp op) {
328 // Registers are initialized to themselves. If the register has an
329 // aggergate type, connect each ground type element.
330 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
331 auto fn = [&](Value value) {
332 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
333 driverMap[getFieldRefFromValue(value)] = connect;
334 };
335 foreachSubelement(builder, op.getResult(), fn);
336 }
337
338 void visitDecl(RegResetOp op) {
339 // Registers are initialized to themselves. If the register has an
340 // aggergate type, connect each ground type element.
341 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
342 auto fn = [&](Value value) {
343 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
344 driverMap[getFieldRefFromValue(value)] = connect;
345 };
346 foreachSubelement(builder, op.getResult(), fn);
347 }
348
349 template <typename OpTy>
350 void visitInstanceDecl(OpTy op) {
351 // Track any instance inputs which need to be connected to for init
352 // coverage.
353 for (const auto &result : llvm::enumerate(op.getResults()))
354 if (op.getPortDirection(result.index()) == Direction::Out)
355 declareSinks(result.value(), Flow::Source);
356 else
357 declareSinks(result.value(), Flow::Sink);
358 }
359
360 void visitDecl(InstanceOp op) { visitInstanceDecl(op); }
361 void visitDecl(InstanceChoiceOp op) { visitInstanceDecl(op); }
362
363 void visitDecl(ObjectOp op) {
364 declareSinks(op, Flow::Source, /*local=*/true);
365 }
366
367 void visitDecl(MemOp op) {
368 // Track any memory inputs which require connections.
369 for (auto result : op.getResults())
370 if (!isa<RefType>(result.getType()))
371 declareSinks(result, Flow::Sink);
372 }
373
374 void visitStmt(ConnectOp op) {
375 recordConnect(getFieldRefFromValue(op.getDest()), op);
376 }
377
378 void visitStmt(MatchingConnectOp op) {
379 recordConnect(getFieldRefFromValue(op.getDest()), op);
380 }
381
382 void visitStmt(RefDefineOp op) {
383 recordConnect(getFieldRefFromValue(op.getDest()), op);
384 }
385
386 void visitStmt(PropAssignOp op) {
387 recordConnect(getFieldRefFromValue(op.getDest()), op);
388 }
389
390 void visitStmt(DomainDefineOp op) {
391 recordConnect(getFieldRefFromValue(op.getDest()), op);
392 }
393
394 void processWhenOp(WhenOp whenOp, Value outerCondition);
395
396 /// Combine the connect statements from each side of the block. There are 5
397 /// cases to consider. If all are set, last connect semantics dictate that it
398 /// is actually the third case.
399 ///
400 /// Prev | Then | Else | Outcome
401 /// -----|------|------|-------
402 /// | set | | then
403 /// | | set | else
404 /// set | set | set | mux(p, then, else)
405 /// | set | set | impossible
406 /// set | set | | mux(p, then, prev)
407 /// set | | set | mux(p, prev, else)
408 ///
409 /// If the value was declared in the block, then it does not need to have been
410 /// assigned a previous value. If the value was declared before the block,
411 /// then there is an incomplete initialization error.
412 void mergeScopes(Location loc, DriverMap &thenScope, DriverMap &elseScope,
413 Value thenCondition) {
414
415 // Process all connects in the `then` block.
416 for (auto &destAndConnect : thenScope) {
417 auto dest = std::get<0>(destAndConnect);
418 auto thenConnect = std::get<1>(destAndConnect);
419
420 auto outerIt = driverMap.find(dest);
421 if (outerIt == driverMap.end()) {
422 // `dest` is set in `then` only. This indicates it was created in the
423 // `then` block, so just copy it into the outer scope.
424 driverMap[dest] = thenConnect;
425 continue;
426 }
427
428 auto elseIt = elseScope.find(dest);
429 if (elseIt != elseScope.end()) {
430 // `dest` is set in `then` and `else`. We need to combine them into and
431 // delete any previous connect.
432
433 // Create a new connect with `mux(p, then, else)`.
434 auto &elseConnect = std::get<1>(*elseIt);
435 OpBuilder connectBuilder(elseConnect);
436 auto newConnect = flattenConditionalConnections(
437 connectBuilder, loc, getDestinationValue(thenConnect),
438 thenCondition, thenConnect, elseConnect);
439
440 // Delete all old connections.
441 thenConnect->erase();
442 elseConnect->erase();
443 recordConnect(dest, newConnect);
444
445 continue;
446 }
447
448 auto &outerConnect = std::get<1>(*outerIt);
449 if (!outerConnect) {
450 if (isLastConnect(thenConnect)) {
451 // `dest` is null in the outer scope. This indicate an initialization
452 // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
453 thenConnect->erase();
454 } else {
455 assert(isStaticSingleConnect(thenConnect));
456 driverMap[dest] = thenConnect;
457 }
458 continue;
459 }
460
461 // `dest` is set in `then` and the outer scope. Create a new connect with
462 // `mux(p, then, outer)`.
463 OpBuilder connectBuilder(thenConnect);
464 auto newConnect = flattenConditionalConnections(
465 connectBuilder, loc, getDestinationValue(thenConnect), thenCondition,
466 thenConnect, outerConnect);
467
468 // Delete all old connections.
469 thenConnect->erase();
470 recordConnect(dest, newConnect);
471 }
472
473 // Process all connects in the `else` block.
474 for (auto &destAndConnect : elseScope) {
475 auto dest = std::get<0>(destAndConnect);
476 auto elseConnect = std::get<1>(destAndConnect);
477
478 // If this destination was driven in the 'then' scope, then we will have
479 // already consumed the driver from the 'else' scope, and we must skip it.
480 if (thenScope.contains(dest))
481 continue;
482
483 auto outerIt = driverMap.find(dest);
484 if (outerIt == driverMap.end()) {
485 // `dest` is set in `else` only. This indicates it was created in the
486 // `else` block, so just copy it into the outer scope.
487 driverMap[dest] = elseConnect;
488 continue;
489 }
490
491 auto &outerConnect = std::get<1>(*outerIt);
492 if (!outerConnect) {
493 if (isLastConnect(elseConnect)) {
494 // `dest` is null in the outer scope. This indicates an initialization
495 // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
496 elseConnect->erase();
497 } else {
498 assert(isStaticSingleConnect(elseConnect));
499 driverMap[dest] = elseConnect;
500 }
501 continue;
502 }
503
504 // `dest` is set in the `else` and outer scope. Create a new connect with
505 // `mux(p, outer, else)`.
506 OpBuilder connectBuilder(elseConnect);
507 auto newConnect = flattenConditionalConnections(
508 connectBuilder, loc, getDestinationValue(outerConnect), thenCondition,
509 outerConnect, elseConnect);
510
511 // Delete all old connections.
512 elseConnect->erase();
513 recordConnect(dest, newConnect);
514 }
515 }
516};
517} // namespace
518
519//===----------------------------------------------------------------------===//
520// WhenOpVisitor
521//===----------------------------------------------------------------------===//
522
523/// This extends the LastConnectVisitor to handle all Simulation related
524/// constructs which do not need any processing at the module scope, but need to
525/// be processed inside of a WhenOp.
526namespace {
527class WhenOpVisitor : public LastConnectResolver<WhenOpVisitor> {
528
529public:
530 WhenOpVisitor(ScopedDriverMap &driverMap, Value condition)
531 : LastConnectResolver<WhenOpVisitor>(driverMap), condition(condition) {}
532
533 using LastConnectResolver<WhenOpVisitor>::visitExpr;
534 using LastConnectResolver<WhenOpVisitor>::visitDecl;
535 using LastConnectResolver<WhenOpVisitor>::visitStmt;
536 using LastConnectResolver<WhenOpVisitor>::visitStmtExpr;
537
538 /// Process a block, recording each declaration, and expanding all whens.
539 void process(Block &block);
540
541 /// Simulation Constructs.
542 void visitStmt(VerifAssertIntrinsicOp op);
543 void visitStmt(VerifAssumeIntrinsicOp op);
544 void visitStmt(VerifCoverIntrinsicOp op);
545 void visitStmt(AssertOp op);
546 void visitStmt(AssumeOp op);
547 void visitStmt(UnclockedAssumeIntrinsicOp op);
548 void visitStmt(CoverOp op);
549 void visitStmt(ModuleOp op);
550 void visitStmt(PrintFOp op);
551 void visitStmt(FPrintFOp op);
552 void visitStmt(FFlushOp op);
553 void visitStmt(StopOp op);
554 void visitStmt(WhenOp op);
555 void visitStmt(LayerBlockOp op);
556 void visitStmt(RefForceOp op);
557 void visitStmt(RefForceInitialOp op);
558 void visitStmt(RefReleaseOp op);
559 void visitStmt(RefReleaseInitialOp op);
560 void visitStmtExpr(DPICallIntrinsicOp op);
561
562private:
563 /// And a 1-bit value with the current condition. If we are in the outer
564 /// scope, i.e. not in a WhenOp region, then there is no condition.
565 Value andWithCondition(Operation *op, Value value) {
566 // 'and' the value with the current condition.
567 return OpBuilder(op).createOrFold<AndPrimOp>(
568 condition.getLoc(), condition.getType(), condition, value);
569 }
570
571 /// Concurrent and of a property with the current condition. If we are in
572 /// the outer scope, i.e. not in a WhenOp region, then there is no condition.
573 Value ltlAndWithCondition(Operation *op, Value property) {
574 // Look through nodes.
575 while (auto nodeOp = property.getDefiningOp<NodeOp>())
576 property = nodeOp.getInput();
577
578 // Look through `ltl.clock` ops.
579 if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
580 auto input = ltlAndWithCondition(op, clockOp.getInput());
581 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
582 if (!newClockOp) {
583 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
584 newClockOp.getInputMutable().assign(input);
585 }
586 return newClockOp;
587 }
588
589 // Otherwise create a new `ltl.and` with the condition.
590 auto &newOp = createdLTLAndOps[{condition, property}];
591 if (!newOp)
592 newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
593 condition.getLoc(), property.getType(), condition, property);
594 return newOp;
595 }
596
597 /// Overlapping implication with the condition as its antecedent and a given
598 /// property as the consequent. If we are in the outer scope, i.e. not in a
599 /// WhenOp region, then there is no condition.
600 Value ltlImplicationWithCondition(Operation *op, Value property) {
601 // Look through nodes.
602 while (auto nodeOp = property.getDefiningOp<NodeOp>())
603 property = nodeOp.getInput();
604
605 // Look through `ltl.clock` ops.
606 if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
607 auto input = ltlImplicationWithCondition(op, clockOp.getInput());
608 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
609 if (!newClockOp) {
610 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
611 newClockOp.getInputMutable().assign(input);
612 }
613 return newClockOp;
614 }
615
616 // Merge condition into `ltl.implication` left-hand side.
617 if (auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
618 auto lhs = ltlAndWithCondition(op, implOp.getLhs());
619 auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
620 if (!newImplOp) {
621 auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
622 clonedOp.getLhsMutable().assign(lhs);
623 newImplOp = clonedOp;
624 }
625 return newImplOp;
626 }
627
628 // Otherwise create a new `ltl.implication` with the condition on the LHS.
629 auto &newImplOp = createdLTLImplicationOps[{condition, property}];
630 if (!newImplOp)
631 newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
632 condition.getLoc(), property.getType(), condition, property);
633 return newImplOp;
634 }
635
636private:
637 /// The current wrapping condition. If null, we are in the outer scope.
638 Value condition;
639
640 /// The `ltl.and` operations that have been created.
641 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLAndOps;
642
643 /// The `ltl.implication` operations that have been created.
644 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLImplicationOps;
645
646 /// The `ltl.clock` operations that have been created.
648 createdLTLClockOps;
649};
650} // namespace
651
652void WhenOpVisitor::process(Block &block) {
653 for (auto &op : llvm::make_early_inc_range(block)) {
654 dispatchVisitor(&op);
655 }
656}
657
658void WhenOpVisitor::visitStmt(PrintFOp op) {
659 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
660}
661
662void WhenOpVisitor::visitStmt(FPrintFOp op) {
663 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
664}
665
666void WhenOpVisitor::visitStmt(FFlushOp op) {
667 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
668}
669
670void WhenOpVisitor::visitStmt(StopOp op) {
671 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
672}
673
674void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
675 op.getPropertyMutable().assign(
676 ltlImplicationWithCondition(op, op.getProperty()));
677}
678
679void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
680 op.getPropertyMutable().assign(
681 ltlImplicationWithCondition(op, op.getProperty()));
682}
683
684void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
685 op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
686}
687
688void WhenOpVisitor::visitStmt(AssertOp op) {
689 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
690}
691
692void WhenOpVisitor::visitStmt(AssumeOp op) {
693 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
694}
695
696void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
697 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
698}
699
700void WhenOpVisitor::visitStmt(CoverOp op) {
701 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
702}
703
704void WhenOpVisitor::visitStmt(WhenOp whenOp) {
705 processWhenOp(whenOp, condition);
706}
707
708// NOLINTNEXTLINE(misc-no-recursion)
709void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
710 // Operations created inside a layerblock are not accessible in sibling
711 // layerblocks. Snapshot the LTL op caches before entering and restore them
712 // afterward so that ops created inside one layerblock are not reused by a
713 // sibling, which would cause dominance violations. Parent-scope cached ops
714 // remain available inside the layerblock since they dominate it.
715 llvm::SaveAndRestore savedLTLAndOps(createdLTLAndOps);
716 llvm::SaveAndRestore savedLTLImplicationOps(createdLTLImplicationOps);
717 llvm::SaveAndRestore savedLTLClockOps(createdLTLClockOps);
718
719 process(*layerBlockOp.getBody());
720}
721
722void WhenOpVisitor::visitStmt(RefForceOp op) {
723 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
724}
725
726void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
727 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
728}
729
730void WhenOpVisitor::visitStmt(RefReleaseOp op) {
731 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
732}
733
734void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
735 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
736}
737
738void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
739 if (op.getEnable())
740 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
741 else
742 op.getEnableMutable().assign(condition);
743}
744
745/// This is a common helper that is dispatched to by the concrete visitors.
746/// This condition should be the conjunction of all surrounding WhenOp
747/// condititions.
748///
749/// This requires WhenOpVisitor to be fully defined.
750template <typename ConcreteT>
751void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
752 Value outerCondition) {
753 OpBuilder b(whenOp);
754 auto loc = whenOp.getLoc();
755 Block *parentBlock = whenOp->getBlock();
756 auto condition = whenOp.getCondition();
757 auto ui1Type = condition.getType();
758
759 // Process both sides of the WhenOp, fixing up all simulation constructs,
760 // and resolving last connect semantics in each block. This process returns
761 // the set of connects in each side of the when op.
762
763 // Process the `then` block. If we are already in a whenblock, the we need to
764 // conjoin ('and') the outer conditions.
765 Value thenCondition = whenOp.getCondition();
766 if (outerCondition)
767 thenCondition =
768 b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
769
770 auto &thenBlock = whenOp.getThenBlock();
771 driverMap.pushScope();
772 WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
773 mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
774 auto thenScope = driverMap.popScope();
775
776 // Process the `else` block.
777 DriverMap elseScope;
778 if (whenOp.hasElseRegion()) {
779 // Else condition is the complement of the then condition.
780 auto elseCondition =
781 b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
782 // Conjoin the when condition with the outer condition.
783 if (outerCondition)
784 elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
785 elseCondition);
786 auto &elseBlock = whenOp.getElseBlock();
787 driverMap.pushScope();
788 WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
789 mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
790 elseScope = driverMap.popScope();
791 }
792
793 mergeScopes(loc, thenScope, elseScope, condition);
794
795 // Delete the now empty WhenOp.
796 whenOp.erase();
797}
798
799//===----------------------------------------------------------------------===//
800// ModuleOpVisitor
801//===----------------------------------------------------------------------===//
802
803namespace {
804/// This extends the LastConnectResolver to track if anything has changed.
805class ModuleVisitor : public LastConnectResolver<ModuleVisitor> {
806public:
807 ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
808
809 using LastConnectResolver<ModuleVisitor>::visitExpr;
810 using LastConnectResolver<ModuleVisitor>::visitDecl;
811 using LastConnectResolver<ModuleVisitor>::visitStmt;
812 void visitStmt(WhenOp whenOp);
813 void visitStmt(ConnectOp connectOp);
814 void visitStmt(MatchingConnectOp connectOp);
815 void visitStmt(LayerBlockOp layerBlockOp);
816
817 bool run(FModuleLike op);
818 LogicalResult checkInitialization();
819
820private:
821 /// The outermost scope of the module body.
822 ScopedDriverMap driverMap;
823
824 /// Tracks if anything in the IR has changed.
825 bool anythingChanged = false;
826};
827} // namespace
828
829/// Run expand whens on the Module. This will emit an error for each
830/// incomplete initialization found. If an initialiazation error was detected,
831/// this will return failure and leave the IR in an inconsistent state.
832bool ModuleVisitor::run(FModuleLike op) {
833 // We only lower whens inside of fmodule ops or class ops.
834 if (!isa<FModuleOp, ClassOp>(op))
835 return anythingChanged;
836
837 for (auto &region : op->getRegions()) {
838 for (auto &block : region.getBlocks()) {
839 // Track any results (flipped arguments) of the module for init coverage.
840 for (const auto &[index, value] : llvm::enumerate(block.getArguments())) {
841 auto direction = op.getPortDirection(index);
842 auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
843 declareSinks(value, flow);
844 }
845
846 // Process the body of the module.
847 for (auto &op : llvm::make_early_inc_range(block))
848 dispatchVisitor(&op);
849 }
850 }
851
852 return anythingChanged;
853}
854
855void ModuleVisitor::visitStmt(ConnectOp op) {
856 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
857}
858
859void ModuleVisitor::visitStmt(MatchingConnectOp op) {
860 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
861}
862
863void ModuleVisitor::visitStmt(WhenOp whenOp) {
864 // If we are deleting a WhenOp something definitely changed.
865 anythingChanged = true;
866 processWhenOp(whenOp, /*outerCondition=*/{});
867}
868
869void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
870 for (auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
871 dispatchVisitor(&op);
872 }
873}
874
875/// Perform initialization checking. This uses the built up state from
876/// running on a module. Returns failure in the event of bad initialization.
877LogicalResult ModuleVisitor::checkInitialization() {
878 bool failed = false;
879 for (auto destAndConnect : driverMap.getLastScope()) {
880 // If there is valid connection to this destination, everything is good.
881 auto *connect = std::get<1>(destAndConnect);
882 if (connect)
883 continue;
884
885 // Get the op which defines the sink, and emit an error.
886 FieldRef dest = std::get<0>(destAndConnect);
887 auto loc = dest.getValue().getLoc();
888 auto *definingOp = dest.getDefiningOp();
889 if (auto mod = dyn_cast<FModuleLike>(definingOp))
890 mlir::emitError(loc) << "port \"" << getFieldName(dest).first
891 << "\" not fully initialized in \""
892 << mod.getModuleName() << "\"";
893 else
894 mlir::emitError(loc)
895 << "sink \"" << getFieldName(dest).first
896 << "\" not fully initialized in \""
897 << definingOp->getParentOfType<FModuleLike>().getModuleName() << "\"";
898 failed = true;
899 }
900 if (failed)
901 return failure();
902 return success();
903}
904
905//===----------------------------------------------------------------------===//
906// Pass Infrastructure
907//===----------------------------------------------------------------------===//
908
909namespace {
910class ExpandWhensPass
911 : public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
912 void runOnOperation() override;
913};
914} // end anonymous namespace
915
916void ExpandWhensPass::runOnOperation() {
917 ModuleVisitor visitor;
918 if (!visitor.run(getOperation()))
919 markAllAnalysesPreserved();
920 if (failed(visitor.checkInitialization()))
921 signalPassFailure();
922}
assert(baseType &&"element must be base type")
ScopedDriverMap::ScopeT DriverMap
static void mergeBlock(Block &destination, Block::iterator insertPoint, Block &source)
Move all operations from a source block in to a destination block.
HashTableStack< FieldRef, Operation * > ScopedDriverMap
This is a determistic mapping of a FieldRef to the last operation which set a value to it.
This class represents a reference to a specific field or element of an aggregate value.
Definition FieldRef.h:28
Value getValue() const
Get the Value which created this location.
Definition FieldRef.h:39
Operation * getDefiningOp() const
Get the operation which defines this field.
Definition FieldRef.cpp:19
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
connect(destination, source)
Definition support.py:39
Flow swapFlow(Flow flow)
Get a flow's reverse.
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
StackT::iterator stackIt
bool operator!=(const Iterator &rhs) const
bool operator==(const Iterator &rhs) const
ScopeT::iterator scopeIt
std::pair< KeyT, ValueT > & operator*() const
Iterator(typename StackT::iterator stackIt, typename ScopeT::iterator scopeIt)
This is a stack of hashtables, if lookup fails in the top-most hashtable, it will attempt to lookup i...
typename llvm::MapVector< KeyT, ValueT > ScopeT
iterator begin()
iterator find(const KeyT &key)
ScopeT & getLastScope()
iterator end()
typename llvm::SmallVector< ScopeT, 3 > StackT
ValueT & operator[](const KeyT &key)