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