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