Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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(FPrintFOp op);
535 void visitStmt(FFlushOp op);
536 void visitStmt(StopOp op);
537 void visitStmt(WhenOp op);
538 void visitStmt(LayerBlockOp op);
539 void visitStmt(RefForceOp op);
540 void visitStmt(RefForceInitialOp op);
541 void visitStmt(RefReleaseOp op);
542 void visitStmt(RefReleaseInitialOp op);
543 void visitStmtExpr(DPICallIntrinsicOp op);
544
545private:
546 /// And a 1-bit value with the current condition. If we are in the outer
547 /// scope, i.e. not in a WhenOp region, then there is no condition.
548 Value andWithCondition(Operation *op, Value value) {
549 // 'and' the value with the current condition.
550 return OpBuilder(op).createOrFold<AndPrimOp>(
551 condition.getLoc(), condition.getType(), condition, value);
552 }
553
554 /// Concurrent and of a property with the current condition. If we are in
555 /// the outer scope, i.e. not in a WhenOp region, then there is no condition.
556 Value ltlAndWithCondition(Operation *op, Value property) {
557 // Look through nodes.
558 while (auto nodeOp = property.getDefiningOp<NodeOp>())
559 property = nodeOp.getInput();
560
561 // Look through `ltl.clock` ops.
562 if (auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
563 auto input = ltlAndWithCondition(op, clockOp.getInput());
564 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
565 if (!newClockOp) {
566 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
567 newClockOp.getInputMutable().assign(input);
568 }
569 return newClockOp;
570 }
571
572 // Otherwise create a new `ltl.and` with the condition.
573 auto &newOp = createdLTLAndOps[{condition, property}];
574 if (!newOp)
575 newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
576 condition.getLoc(), property.getType(), condition, property);
577 return newOp;
578 }
579
580 /// Overlapping implication with the condition as its antecedent and a given
581 /// property as the consequent. If we are in the outer scope, i.e. not in a
582 /// WhenOp region, then there is no condition.
583 Value ltlImplicationWithCondition(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 = ltlImplicationWithCondition(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 // Merge condition into `ltl.implication` left-hand side.
600 if (auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
601 auto lhs = ltlAndWithCondition(op, implOp.getLhs());
602 auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
603 if (!newImplOp) {
604 auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
605 clonedOp.getLhsMutable().assign(lhs);
606 newImplOp = clonedOp;
607 }
608 return newImplOp;
609 }
610
611 // Otherwise create a new `ltl.implication` with the condition on the LHS.
612 auto &newImplOp = createdLTLImplicationOps[{condition, property}];
613 if (!newImplOp)
614 newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
615 condition.getLoc(), property.getType(), condition, property);
616 return newImplOp;
617 }
618
619private:
620 /// The current wrapping condition. If null, we are in the outer scope.
621 Value condition;
622
623 /// The `ltl.and` operations that have been created.
624 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLAndOps;
625
626 /// The `ltl.implication` operations that have been created.
627 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLImplicationOps;
628
629 /// The `ltl.clock` operations that have been created.
631 createdLTLClockOps;
632};
633} // namespace
634
635void WhenOpVisitor::process(Block &block) {
636 for (auto &op : llvm::make_early_inc_range(block)) {
637 dispatchVisitor(&op);
638 }
639}
640
641void WhenOpVisitor::visitStmt(PrintFOp op) {
642 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
643}
644
645void WhenOpVisitor::visitStmt(FPrintFOp op) {
646 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
647}
648
649void WhenOpVisitor::visitStmt(FFlushOp op) {
650 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
651}
652
653void WhenOpVisitor::visitStmt(StopOp op) {
654 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
655}
656
657void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
658 op.getPropertyMutable().assign(
659 ltlImplicationWithCondition(op, op.getProperty()));
660}
661
662void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
663 op.getPropertyMutable().assign(
664 ltlImplicationWithCondition(op, op.getProperty()));
665}
666
667void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
668 op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
669}
670
671void WhenOpVisitor::visitStmt(AssertOp op) {
672 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
673}
674
675void WhenOpVisitor::visitStmt(AssumeOp op) {
676 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
677}
678
679void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
680 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
681}
682
683void WhenOpVisitor::visitStmt(CoverOp op) {
684 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
685}
686
687void WhenOpVisitor::visitStmt(WhenOp whenOp) {
688 processWhenOp(whenOp, condition);
689}
690
691// NOLINTNEXTLINE(misc-no-recursion)
692void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
693 process(*layerBlockOp.getBody());
694}
695
696void WhenOpVisitor::visitStmt(RefForceOp op) {
697 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
698}
699
700void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
701 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
702}
703
704void WhenOpVisitor::visitStmt(RefReleaseOp op) {
705 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
706}
707
708void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
709 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
710}
711
712void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
713 if (op.getEnable())
714 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
715 else
716 op.getEnableMutable().assign(condition);
717}
718
719/// This is a common helper that is dispatched to by the concrete visitors.
720/// This condition should be the conjunction of all surrounding WhenOp
721/// condititions.
722///
723/// This requires WhenOpVisitor to be fully defined.
724template <typename ConcreteT>
725void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
726 Value outerCondition) {
727 OpBuilder b(whenOp);
728 auto loc = whenOp.getLoc();
729 Block *parentBlock = whenOp->getBlock();
730 auto condition = whenOp.getCondition();
731 auto ui1Type = condition.getType();
732
733 // Process both sides of the WhenOp, fixing up all simulation constructs,
734 // and resolving last connect semantics in each block. This process returns
735 // the set of connects in each side of the when op.
736
737 // Process the `then` block. If we are already in a whenblock, the we need to
738 // conjoin ('and') the outer conditions.
739 Value thenCondition = whenOp.getCondition();
740 if (outerCondition)
741 thenCondition =
742 b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
743
744 auto &thenBlock = whenOp.getThenBlock();
745 driverMap.pushScope();
746 WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
747 mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
748 auto thenScope = driverMap.popScope();
749
750 // Process the `else` block.
751 DriverMap elseScope;
752 if (whenOp.hasElseRegion()) {
753 // Else condition is the complement of the then condition.
754 auto elseCondition =
755 b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
756 // Conjoin the when condition with the outer condition.
757 if (outerCondition)
758 elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
759 elseCondition);
760 auto &elseBlock = whenOp.getElseBlock();
761 driverMap.pushScope();
762 WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
763 mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
764 elseScope = driverMap.popScope();
765 }
766
767 mergeScopes(loc, thenScope, elseScope, condition);
768
769 // Delete the now empty WhenOp.
770 whenOp.erase();
771}
772
773//===----------------------------------------------------------------------===//
774// ModuleOpVisitor
775//===----------------------------------------------------------------------===//
776
777namespace {
778/// This extends the LastConnectResolver to track if anything has changed.
779class ModuleVisitor : public LastConnectResolver<ModuleVisitor> {
780public:
781 ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
782
783 using LastConnectResolver<ModuleVisitor>::visitExpr;
784 using LastConnectResolver<ModuleVisitor>::visitDecl;
785 using LastConnectResolver<ModuleVisitor>::visitStmt;
786 void visitStmt(WhenOp whenOp);
787 void visitStmt(ConnectOp connectOp);
788 void visitStmt(MatchingConnectOp connectOp);
789 void visitStmt(LayerBlockOp layerBlockOp);
790
791 bool run(FModuleLike op);
792 LogicalResult checkInitialization();
793
794private:
795 /// The outermost scope of the module body.
796 ScopedDriverMap driverMap;
797
798 /// Tracks if anything in the IR has changed.
799 bool anythingChanged = false;
800};
801} // namespace
802
803/// Run expand whens on the Module. This will emit an error for each
804/// incomplete initialization found. If an initialiazation error was detected,
805/// this will return failure and leave the IR in an inconsistent state.
806bool ModuleVisitor::run(FModuleLike op) {
807 // We only lower whens inside of fmodule ops or class ops.
808 if (!isa<FModuleOp, ClassOp>(op))
809 return anythingChanged;
810
811 for (auto &region : op->getRegions()) {
812 for (auto &block : region.getBlocks()) {
813 // Track any results (flipped arguments) of the module for init coverage.
814 for (const auto &[index, value] : llvm::enumerate(block.getArguments())) {
815 auto direction = op.getPortDirection(index);
816 auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
817 declareSinks(value, flow);
818 }
819
820 // Process the body of the module.
821 for (auto &op : llvm::make_early_inc_range(block))
822 dispatchVisitor(&op);
823 }
824 }
825
826 return anythingChanged;
827}
828
829void ModuleVisitor::visitStmt(ConnectOp op) {
830 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
831}
832
833void ModuleVisitor::visitStmt(MatchingConnectOp op) {
834 anythingChanged |= recordConnect(getFieldRefFromValue(op.getDest()), op);
835}
836
837void ModuleVisitor::visitStmt(WhenOp whenOp) {
838 // If we are deleting a WhenOp something definitely changed.
839 anythingChanged = true;
840 processWhenOp(whenOp, /*outerCondition=*/{});
841}
842
843void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
844 for (auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
845 dispatchVisitor(&op);
846 }
847}
848
849/// Perform initialization checking. This uses the built up state from
850/// running on a module. Returns failure in the event of bad initialization.
851LogicalResult ModuleVisitor::checkInitialization() {
852 bool failed = false;
853 for (auto destAndConnect : driverMap.getLastScope()) {
854 // If there is valid connection to this destination, everything is good.
855 auto *connect = std::get<1>(destAndConnect);
856 if (connect)
857 continue;
858
859 // Get the op which defines the sink, and emit an error.
860 FieldRef dest = std::get<0>(destAndConnect);
861 auto loc = dest.getValue().getLoc();
862 auto *definingOp = dest.getDefiningOp();
863 if (auto mod = dyn_cast<FModuleLike>(definingOp))
864 mlir::emitError(loc) << "port \"" << getFieldName(dest).first
865 << "\" not fully initialized in \""
866 << mod.getModuleName() << "\"";
867 else
868 mlir::emitError(loc)
869 << "sink \"" << getFieldName(dest).first
870 << "\" not fully initialized in \""
871 << definingOp->getParentOfType<FModuleLike>().getModuleName() << "\"";
872 failed = true;
873 }
874 if (failed)
875 return failure();
876 return success();
877}
878
879//===----------------------------------------------------------------------===//
880// Pass Infrastructure
881//===----------------------------------------------------------------------===//
882
883namespace {
884class ExpandWhensPass
885 : public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
886 void runOnOperation() override;
887};
888} // end anonymous namespace
889
890void ExpandWhensPass::runOnOperation() {
891 ModuleVisitor visitor;
892 if (!visitor.run(getOperation()))
893 markAllAnalysesPreserved();
894 if (failed(visitor.checkInitialization()))
895 signalPassFailure();
896}
897
898std::unique_ptr<mlir::Pass> circt::firrtl::createExpandWhensPass() {
899 return std::make_unique<ExpandWhensPass>();
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::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)