CIRCT 20.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 b.create<ConnectOp>(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 builder.create<SubfieldOp>(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 builder.create<SubindexOp>(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 = builder.create<ConnectOp>(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 = builder.create<ConnectOp>(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 processWhenOp(WhenOp whenOp, Value outerCondition);
378
379 /// Combine the connect statements from each side of the block. There are 5
380 /// cases to consider. If all are set, last connect semantics dictate that it
381 /// is actually the third case.
382 ///
383 /// Prev | Then | Else | Outcome
384 /// -----|------|------|-------
385 /// | set | | then
386 /// | | set | else
387 /// set | set | set | mux(p, then, else)
388 /// | set | set | impossible
389 /// set | set | | mux(p, then, prev)
390 /// set | | set | mux(p, prev, else)
391 ///
392 /// If the value was declared in the block, then it does not need to have been
393 /// assigned a previous value. If the value was declared before the block,
394 /// then there is an incomplete initialization error.
395 void mergeScopes(Location loc, DriverMap &thenScope, DriverMap &elseScope,
396 Value thenCondition) {
397
398 // Process all connects in the `then` block.
399 for (auto &destAndConnect : thenScope) {
400 auto dest = std::get<0>(destAndConnect);
401 auto thenConnect = std::get<1>(destAndConnect);
402
403 auto outerIt = driverMap.find(dest);
404 if (outerIt == driverMap.end()) {
405 // `dest` is set in `then` only. This indicates it was created in the
406 // `then` block, so just copy it into the outer scope.
407 driverMap[dest] = thenConnect;
408 continue;
409 }
410
411 auto elseIt = elseScope.find(dest);
412 if (elseIt != elseScope.end()) {
413 // `dest` is set in `then` and `else`. We need to combine them into and
414 // delete any previous connect.
415
416 // Create a new connect with `mux(p, then, else)`.
417 auto &elseConnect = std::get<1>(*elseIt);
418 OpBuilder connectBuilder(elseConnect);
419 auto newConnect = flattenConditionalConnections(
420 connectBuilder, loc, getDestinationValue(thenConnect),
421 thenCondition, thenConnect, elseConnect);
422
423 // Delete all old connections.
424 thenConnect->erase();
425 elseConnect->erase();
426 recordConnect(dest, newConnect);
427
428 continue;
429 }
430
431 auto &outerConnect = std::get<1>(*outerIt);
432 if (!outerConnect) {
433 if (isLastConnect(thenConnect)) {
434 // `dest` is null in the outer scope. This indicate an initialization
435 // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
436 thenConnect->erase();
437 } else {
438 assert(isStaticSingleConnect(thenConnect));
439 driverMap[dest] = thenConnect;
440 }
441 continue;
442 }
443
444 // `dest` is set in `then` and the outer scope. Create a new connect with
445 // `mux(p, then, outer)`.
446 OpBuilder connectBuilder(thenConnect);
447 auto newConnect = flattenConditionalConnections(
448 connectBuilder, loc, getDestinationValue(thenConnect), thenCondition,
449 thenConnect, outerConnect);
450
451 // Delete all old connections.
452 thenConnect->erase();
453 recordConnect(dest, newConnect);
454 }
455
456 // Process all connects in the `else` block.
457 for (auto &destAndConnect : elseScope) {
458 auto dest = std::get<0>(destAndConnect);
459 auto elseConnect = std::get<1>(destAndConnect);
460
461 // If this destination was driven in the 'then' scope, then we will have
462 // already consumed the driver from the 'else' scope, and we must skip it.
463 if (thenScope.contains(dest))
464 continue;
465
466 auto outerIt = driverMap.find(dest);
467 if (outerIt == driverMap.end()) {
468 // `dest` is set in `else` only. This indicates it was created in the
469 // `else` block, so just copy it into the outer scope.
470 driverMap[dest] = elseConnect;
471 continue;
472 }
473
474 auto &outerConnect = std::get<1>(*outerIt);
475 if (!outerConnect) {
476 if (isLastConnect(elseConnect)) {
477 // `dest` is null in the outer scope. This indicates an initialization
478 // problem: `mux(p, then, nullptr)`. Just delete the broken connect.
479 elseConnect->erase();
480 } else {
481 assert(isStaticSingleConnect(elseConnect));
482 driverMap[dest] = elseConnect;
483 }
484 continue;
485 }
486
487 // `dest` is set in the `else` and outer scope. Create a new connect with
488 // `mux(p, outer, else)`.
489 OpBuilder connectBuilder(elseConnect);
490 auto newConnect = flattenConditionalConnections(
491 connectBuilder, loc, getDestinationValue(outerConnect), thenCondition,
492 outerConnect, elseConnect);
493
494 // Delete all old connections.
495 elseConnect->erase();
496 recordConnect(dest, newConnect);
497 }
498 }
499};
500} // namespace
501
502//===----------------------------------------------------------------------===//
503// WhenOpVisitor
504//===----------------------------------------------------------------------===//
505
506/// This extends the LastConnectVisitor to handle all Simulation related
507/// constructs which do not need any processing at the module scope, but need to
508/// be processed inside of a WhenOp.
509namespace {
510class WhenOpVisitor : public LastConnectResolver<WhenOpVisitor> {
511
512public:
513 WhenOpVisitor(ScopedDriverMap &driverMap, Value condition)
514 : LastConnectResolver<WhenOpVisitor>(driverMap), condition(condition) {}
515
516 using LastConnectResolver<WhenOpVisitor>::visitExpr;
517 using LastConnectResolver<WhenOpVisitor>::visitDecl;
518 using LastConnectResolver<WhenOpVisitor>::visitStmt;
519 using LastConnectResolver<WhenOpVisitor>::visitStmtExpr;
520
521 /// Process a block, recording each declaration, and expanding all whens.
522 void process(Block &block);
523
524 /// Simulation Constructs.
525 void visitStmt(VerifAssertIntrinsicOp op);
526 void visitStmt(VerifAssumeIntrinsicOp op);
527 void visitStmt(VerifCoverIntrinsicOp op);
528 void visitStmt(AssertOp op);
529 void visitStmt(AssumeOp op);
530 void visitStmt(UnclockedAssumeIntrinsicOp op);
531 void visitStmt(CoverOp op);
532 void visitStmt(ModuleOp op);
533 void visitStmt(PrintFOp op);
534 void visitStmt(StopOp op);
535 void visitStmt(WhenOp op);
536 void visitStmt(LayerBlockOp op);
537 void visitStmt(RefForceOp op);
538 void visitStmt(RefForceInitialOp op);
539 void visitStmt(RefReleaseOp op);
540 void visitStmt(RefReleaseInitialOp op);
541 void visitStmtExpr(DPICallIntrinsicOp op);
542
543private:
544 /// And a 1-bit value with the current condition. If we are in the outer
545 /// scope, i.e. not in a WhenOp region, then there is no condition.
546 Value andWithCondition(Operation *op, Value value) {
547 // 'and' the value with the current condition.
548 return OpBuilder(op).createOrFold<AndPrimOp>(
549 condition.getLoc(), condition.getType(), condition, value);
550 }
551
552 /// Concurrent and of a property with the current condition. If we are in
553 /// the outer scope, i.e. not in a WhenOp region, then there is no condition.
554 Value ltlAndWithCondition(Operation *op, Value property) {
555 // Look through nodes.
556 while (auto nodeOp = property.getDefiningOp<NodeOp>())
557 property = nodeOp.getInput();
558
559 // Look through `ltl.clock` ops.
560 if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
561 auto input = ltlAndWithCondition(op, clockOp.getInput());
562 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
563 if (!newClockOp) {
564 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
565 newClockOp.getInputMutable().assign(input);
566 }
567 return newClockOp;
568 }
569
570 // Otherwise create a new `ltl.and` with the condition.
571 auto &newOp = createdLTLAndOps[{condition, property}];
572 if (!newOp)
573 newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
574 condition.getLoc(), property.getType(), condition, property);
575 return newOp;
576 }
577
578 /// Overlapping implication with the condition as its antecedent and a given
579 /// property as the consequent. If we are in the outer scope, i.e. not in a
580 /// WhenOp region, then there is no condition.
581 Value ltlImplicationWithCondition(Operation *op, Value property) {
582 // Look through nodes.
583 while (auto nodeOp = property.getDefiningOp<NodeOp>())
584 property = nodeOp.getInput();
585
586 // Look through `ltl.clock` ops.
587 if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
588 auto input = ltlImplicationWithCondition(op, clockOp.getInput());
589 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
590 if (!newClockOp) {
591 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
592 newClockOp.getInputMutable().assign(input);
593 }
594 return newClockOp;
595 }
596
597 // Merge condition into `ltl.implication` left-hand side.
598 if (auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
599 auto lhs = ltlAndWithCondition(op, implOp.getLhs());
600 auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
601 if (!newImplOp) {
602 auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
603 clonedOp.getLhsMutable().assign(lhs);
604 newImplOp = clonedOp;
605 }
606 return newImplOp;
607 }
608
609 // Otherwise create a new `ltl.implication` with the condition on the LHS.
610 auto &newImplOp = createdLTLImplicationOps[{condition, property}];
611 if (!newImplOp)
612 newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
613 condition.getLoc(), property.getType(), condition, property);
614 return newImplOp;
615 }
616
617private:
618 /// The current wrapping condition. If null, we are in the outer scope.
619 Value condition;
620
621 /// The `ltl.and` operations that have been created.
622 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLAndOps;
623
624 /// The `ltl.implication` operations that have been created.
625 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLImplicationOps;
626
627 /// The `ltl.clock` operations that have been created.
629 createdLTLClockOps;
630};
631} // namespace
632
633void WhenOpVisitor::process(Block &block) {
634 for (auto &op : llvm::make_early_inc_range(block)) {
635 dispatchVisitor(&op);
636 }
637}
638
639void WhenOpVisitor::visitStmt(PrintFOp op) {
640 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
641}
642
643void WhenOpVisitor::visitStmt(StopOp op) {
644 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
645}
646
647void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
648 op.getPropertyMutable().assign(
649 ltlImplicationWithCondition(op, op.getProperty()));
650}
651
652void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
653 op.getPropertyMutable().assign(
654 ltlImplicationWithCondition(op, op.getProperty()));
655}
656
657void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
658 op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
659}
660
661void WhenOpVisitor::visitStmt(AssertOp op) {
662 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
663}
664
665void WhenOpVisitor::visitStmt(AssumeOp op) {
666 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
667}
668
669void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
670 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
671}
672
673void WhenOpVisitor::visitStmt(CoverOp op) {
674 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
675}
676
677void WhenOpVisitor::visitStmt(WhenOp whenOp) {
678 processWhenOp(whenOp, condition);
679}
680
681// NOLINTNEXTLINE(misc-no-recursion)
682void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
683 process(*layerBlockOp.getBody());
684}
685
686void WhenOpVisitor::visitStmt(RefForceOp op) {
687 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
688}
689
690void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
691 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
692}
693
694void WhenOpVisitor::visitStmt(RefReleaseOp op) {
695 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
696}
697
698void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
699 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
700}
701
702void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
703 if (op.getEnable())
704 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
705 else
706 op.getEnableMutable().assign(condition);
707}
708
709/// This is a common helper that is dispatched to by the concrete visitors.
710/// This condition should be the conjunction of all surrounding WhenOp
711/// condititions.
712///
713/// This requires WhenOpVisitor to be fully defined.
714template <typename ConcreteT>
715void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
716 Value outerCondition) {
717 OpBuilder b(whenOp);
718 auto loc = whenOp.getLoc();
719 Block *parentBlock = whenOp->getBlock();
720 auto condition = whenOp.getCondition();
721 auto ui1Type = condition.getType();
722
723 // Process both sides of the WhenOp, fixing up all simulation constructs,
724 // and resolving last connect semantics in each block. This process returns
725 // the set of connects in each side of the when op.
726
727 // Process the `then` block. If we are already in a whenblock, the we need to
728 // conjoin ('and') the outer conditions.
729 Value thenCondition = whenOp.getCondition();
730 if (outerCondition)
731 thenCondition =
732 b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
733
734 auto &thenBlock = whenOp.getThenBlock();
735 driverMap.pushScope();
736 WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
737 mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
738 auto thenScope = driverMap.popScope();
739
740 // Process the `else` block.
741 DriverMap elseScope;
742 if (whenOp.hasElseRegion()) {
743 // Else condition is the complement of the then condition.
744 auto elseCondition =
745 b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
746 // Conjoin the when condition with the outer condition.
747 if (outerCondition)
748 elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
749 elseCondition);
750 auto &elseBlock = whenOp.getElseBlock();
751 driverMap.pushScope();
752 WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
753 mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
754 elseScope = driverMap.popScope();
755 }
756
757 mergeScopes(loc, thenScope, elseScope, condition);
758
759 // Delete the now empty WhenOp.
760 whenOp.erase();
761}
762
763//===----------------------------------------------------------------------===//
764// ModuleOpVisitor
765//===----------------------------------------------------------------------===//
766
767namespace {
768/// This extends the LastConnectResolver to track if anything has changed.
769class ModuleVisitor : public LastConnectResolver<ModuleVisitor> {
770public:
771 ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
772
773 using LastConnectResolver<ModuleVisitor>::visitExpr;
774 using LastConnectResolver<ModuleVisitor>::visitDecl;
775 using LastConnectResolver<ModuleVisitor>::visitStmt;
776 void visitStmt(WhenOp whenOp);
777 void visitStmt(ConnectOp connectOp);
778 void visitStmt(MatchingConnectOp connectOp);
779 void visitStmt(LayerBlockOp layerBlockOp);
780
781 bool run(FModuleLike op);
782 LogicalResult checkInitialization();
783
784private:
785 /// The outermost scope of the module body.
786 ScopedDriverMap driverMap;
787
788 /// Tracks if anything in the IR has changed.
789 bool anythingChanged = false;
790};
791} // namespace
792
793/// Run expand whens on the Module. This will emit an error for each
794/// incomplete initialization found. If an initialiazation error was detected,
795/// this will return failure and leave the IR in an inconsistent state.
796bool ModuleVisitor::run(FModuleLike op) {
797 // We only lower whens inside of fmodule ops or class ops.
798 if (!isa<FModuleOp, ClassOp>(op))
799 return anythingChanged;
800
801 for (auto &region : op->getRegions()) {
802 for (auto &block : region.getBlocks()) {
803 // Track any results (flipped arguments) of the module for init coverage.
804 for (const auto &[index, value] : llvm::enumerate(block.getArguments())) {
805 auto direction = op.getPortDirection(index);
806 auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
807 declareSinks(value, flow);
808 }
809
810 // Process the body of the module.
811 for (auto &op : llvm::make_early_inc_range(block))
812 dispatchVisitor(&op);
813 }
814 }
815
816 return anythingChanged;
817}
818
819void ModuleVisitor::visitStmt(ConnectOp op) {
820 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
821}
822
823void ModuleVisitor::visitStmt(MatchingConnectOp op) {
824 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
825}
826
827void ModuleVisitor::visitStmt(WhenOp whenOp) {
828 // If we are deleting a WhenOp something definitely changed.
829 anythingChanged = true;
830 processWhenOp(whenOp, /*outerCondition=*/{});
831}
832
833void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
834 for (auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
835 dispatchVisitor(&op);
836 }
837}
838
839/// Perform initialization checking. This uses the built up state from
840/// running on a module. Returns failure in the event of bad initialization.
841LogicalResult ModuleVisitor::checkInitialization() {
842 bool failed = false;
843 for (auto destAndConnect : driverMap.getLastScope()) {
844 // If there is valid connection to this destination, everything is good.
845 auto *connect = std::get<1>(destAndConnect);
846 if (connect)
847 continue;
848
849 // Get the op which defines the sink, and emit an error.
850 FieldRef dest = std::get<0>(destAndConnect);
851 auto loc = dest.getValue().getLoc();
852 auto *definingOp = dest.getDefiningOp();
853 if (auto mod = dyn_cast<FModuleLike>(definingOp))
854 mlir::emitError(loc) << "port \"" << getFieldName(dest).first
855 << "\" not fully initialized in \""
856 << mod.getModuleName() << "\"";
857 else
858 mlir::emitError(loc)
859 << "sink \"" << getFieldName(dest).first
860 << "\" not fully initialized in \""
861 << definingOp->getParentOfType<FModuleLike>().getModuleName() << "\"";
862 failed = true;
863 }
864 if (failed)
865 return failure();
866 return success();
867}
868
869//===----------------------------------------------------------------------===//
870// Pass Infrastructure
871//===----------------------------------------------------------------------===//
872
873namespace {
874class ExpandWhensPass
875 : public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
876 void runOnOperation() override;
877};
878} // end anonymous namespace
879
880void ExpandWhensPass::runOnOperation() {
881 ModuleVisitor visitor;
882 if (!visitor.run(getOperation()))
883 markAllAnalysesPreserved();
884 if (failed(visitor.checkInitialization()))
885 signalPassFailure();
886}
887
888std::unique_ptr<mlir::Pass> circt::firrtl::createExpandWhensPass() {
889 return std::make_unique<ExpandWhensPass>();
890}
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::unique_ptr< mlir::Pass > createExpandWhensPass()
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)