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