22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/MapVector.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/Support/SaveAndRestore.h"
29#define GEN_PASS_DEF_EXPANDWHENS
30#include "circt/Dialect/FIRRTL/Passes.h.inc"
39static void mergeBlock(Block &destination, Block::iterator insertPoint,
41 destination.getOperations().splice(insertPoint, source.getOperations());
46 while (
auto *op = value.getDefiningOp()) {
47 if (isa<InvalidValueOp>(op))
49 if (!isa<SubindexOp, SubfieldOp>(op))
51 value = op->getOperand(0);
61template <
typename KeyT,
typename ValueT>
64 using StackT =
typename llvm::SmallVector<ScopeT, 3>;
68 typename ScopeT::iterator
scopeIt)
106 for (
auto i =
mapStack.size(); i > 0; --i) {
108 auto it = map.find(key);
110 return Iterator(
mapStack.begin() + i - 1, it);
143template <
typename ConcreteT>
152 LastConnectResolver(
ScopedDriverMap &driverMap) : driverMap(driverMap) {}
163 bool recordConnect(
FieldRef dest, Operation *connection) {
165 auto itAndInserted = driverMap.
getLastScope().insert({dest, connection});
166 if (isStaticSingleConnect(connection)) {
168 assert(itAndInserted.second || !itAndInserted.first->second);
169 if (!itAndInserted.second)
170 itAndInserted.first->second = connection;
173 assert(isLastConnect(connection));
174 if (!std::get<1>(itAndInserted)) {
175 auto *iterator = std::get<0>(itAndInserted);
176 auto changed =
false;
179 if (
auto *oldConnect = iterator->second) {
183 iterator->second = connection;
191 static Value getDestinationValue(Operation *op) {
192 return cast<FConnectLike>(op).getDest();
197 static Value getConnectedValue(Operation *op) {
198 return cast<FConnectLike>(op).getSrc();
202 static bool isStaticSingleConnect(Operation *op) {
203 return cast<FConnectLike>(op).hasStaticSingleConnectBehavior();
210 static bool isLastConnect(Operation *op) {
211 return cast<FConnectLike>(op).hasLastConnectBehavior();
216 void declareSinks(Value value,
Flow flow,
bool local =
false) {
217 auto type = value.getType();
221 std::function<void(Type,
Flow,
bool)> declare = [&](Type type,
Flow flow,
233 for (
auto &element : classType.getElements()) {
235 if (element.direction == Direction::Out)
236 declare(element.type, flow,
false);
238 declare(element.type,
swapFlow(flow),
false);
243 if (flow != Flow::Source)
244 driverMap[{value,
id}] =
nullptr;
247 id += classType.getMaxFieldID();
254 for (
auto &element : bundleType.getElements()) {
257 declare(element.type,
swapFlow(flow),
false);
259 declare(element.type, flow,
false);
266 for (
unsigned i = 0; i < vectorType.getNumElements(); ++i) {
268 declare(vectorType.getElementType(), flow,
false);
279 if (flow != Flow::Source)
280 driverMap[{value,
id}] =
nullptr;
283 declare(type, flow, local);
288 ConnectOp flattenConditionalConnections(OpBuilder &b, Location loc,
289 Value dest, Value cond,
290 Operation *whenTrueConn,
291 Operation *whenFalseConn) {
292 assert(isLastConnect(whenTrueConn) && isLastConnect(whenFalseConn));
294 b.getFusedLoc({loc, whenTrueConn->getLoc(), whenFalseConn->getLoc()});
295 auto whenTrue = getConnectedValue(whenTrueConn);
297 auto whenFalse = getConnectedValue(whenFalseConn);
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);
313 void visitDecl(WireOp op) { declareSinks(op.getResult(), Flow::Duplex); }
317 void foreachSubelement(OpBuilder &builder, Value value,
318 llvm::function_ref<
void(Value)> fn) {
320 .template Case<BundleType>([&](BundleType bundle) {
321 for (
auto i : llvm::seq(0u, (
unsigned)bundle.getNumElements())) {
323 SubfieldOp::create(builder, value.getLoc(), value, i);
324 foreachSubelement(builder, subfield, fn);
327 .
template Case<FVectorType>([&](FVectorType vector) {
328 for (
auto i : llvm::seq((
size_t)0, vector.getNumElements())) {
330 SubindexOp::create(builder, value.getLoc(), value, i);
331 foreachSubelement(builder, subindex, fn);
334 .Default([&](
auto) { fn(value); });
337 void visitDecl(RegOp op) {
340 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
341 auto fn = [&](Value value) {
342 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
345 foreachSubelement(builder, op.getResult(), fn);
348 void visitDecl(RegResetOp op) {
351 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
352 auto fn = [&](Value value) {
353 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
356 foreachSubelement(builder, op.getResult(), fn);
359 template <
typename OpTy>
360 void visitInstanceDecl(OpTy op) {
363 for (
const auto &result : llvm::enumerate(op.getResults()))
364 if (op.getPortDirection(result.index()) == Direction::Out)
365 declareSinks(result.value(), Flow::Source);
367 declareSinks(result.value(), Flow::Sink);
370 void visitDecl(InstanceOp op) { visitInstanceDecl(op); }
371 void visitDecl(InstanceChoiceOp op) { visitInstanceDecl(op); }
373 void visitDecl(ObjectOp op) {
374 declareSinks(op, Flow::Source,
true);
377 void visitDecl(MemOp op) {
379 for (
auto result : op.getResults())
380 if (!isa<RefType>(result.getType()))
381 declareSinks(result, Flow::Sink);
384 void visitStmt(ConnectOp op) {
388 void visitStmt(MatchingConnectOp op) {
392 void visitStmt(RefDefineOp op) {
396 void visitStmt(PropAssignOp op) {
400 void visitStmt(DomainDefineOp op) {
404 void processWhenOp(WhenOp whenOp, Value outerCondition);
423 Value thenCondition) {
426 for (
auto &destAndConnect : thenScope) {
427 auto dest = std::get<0>(destAndConnect);
428 auto *thenConnect = std::get<1>(destAndConnect);
430 auto outerIt = driverMap.
find(dest);
431 if (outerIt == driverMap.
end()) {
434 driverMap[dest] = thenConnect;
438 auto *elseIt = elseScope.find(dest);
439 if (elseIt != elseScope.end()) {
444 auto &elseConnect = std::get<1>(*elseIt);
445 OpBuilder connectBuilder(elseConnect);
446 auto newConnect = flattenConditionalConnections(
447 connectBuilder, loc, getDestinationValue(thenConnect),
448 thenCondition, thenConnect, elseConnect);
451 thenConnect->erase();
452 elseConnect->erase();
453 recordConnect(dest, newConnect);
458 auto &outerConnect = std::get<1>(*outerIt);
460 if (isLastConnect(thenConnect)) {
463 thenConnect->erase();
465 assert(isStaticSingleConnect(thenConnect));
466 driverMap[dest] = thenConnect;
473 OpBuilder connectBuilder(thenConnect);
474 auto newConnect = flattenConditionalConnections(
475 connectBuilder, loc, getDestinationValue(thenConnect), thenCondition,
476 thenConnect, outerConnect);
479 thenConnect->erase();
480 recordConnect(dest, newConnect);
484 for (
auto &destAndConnect : elseScope) {
485 auto dest = std::get<0>(destAndConnect);
486 auto *elseConnect = std::get<1>(destAndConnect);
490 if (thenScope.contains(dest))
493 auto outerIt = driverMap.
find(dest);
494 if (outerIt == driverMap.
end()) {
497 driverMap[dest] = elseConnect;
501 auto &outerConnect = std::get<1>(*outerIt);
503 if (isLastConnect(elseConnect)) {
506 elseConnect->erase();
508 assert(isStaticSingleConnect(elseConnect));
509 driverMap[dest] = elseConnect;
516 OpBuilder connectBuilder(elseConnect);
517 auto newConnect = flattenConditionalConnections(
518 connectBuilder, loc, getDestinationValue(outerConnect), thenCondition,
519 outerConnect, elseConnect);
522 elseConnect->erase();
523 recordConnect(dest, newConnect);
537class WhenOpVisitor :
public LastConnectResolver<WhenOpVisitor> {
541 : LastConnectResolver<WhenOpVisitor>(driverMap), condition(condition) {}
543 using LastConnectResolver<WhenOpVisitor>::visitExpr;
544 using LastConnectResolver<WhenOpVisitor>::visitDecl;
545 using LastConnectResolver<WhenOpVisitor>::visitStmt;
546 using LastConnectResolver<WhenOpVisitor>::visitStmtExpr;
549 void process(Block &block);
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);
575 Value andWithCondition(Operation *op, Value value) {
577 return OpBuilder(op).createOrFold<AndPrimOp>(
578 condition.getLoc(), condition.getType(), condition, value);
583 Value ltlAndWithCondition(Operation *op, Value property) {
585 while (
auto nodeOp = property.getDefiningOp<NodeOp>())
586 property = nodeOp.getInput();
589 if (
auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
590 auto input = ltlAndWithCondition(op, clockOp.getInput());
591 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
593 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
594 newClockOp.getInputMutable().assign(input);
600 auto &newOp = createdLTLAndOps[{condition,
property}];
602 newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
603 condition.getLoc(), property.getType(), condition, property);
610 Value ltlImplicationWithCondition(Operation *op, Value property) {
612 while (
auto nodeOp = property.getDefiningOp<NodeOp>())
613 property = nodeOp.getInput();
616 if (
auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
617 auto input = ltlImplicationWithCondition(op, clockOp.getInput());
618 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
620 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
621 newClockOp.getInputMutable().assign(input);
627 if (
auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
628 auto lhs = ltlAndWithCondition(op, implOp.getLhs());
629 auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
631 auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
632 clonedOp.getLhsMutable().assign(lhs);
633 newImplOp = clonedOp;
639 auto &newImplOp = createdLTLImplicationOps[{condition,
property}];
641 newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
642 condition.getLoc(), property.getType(), condition, property);
651 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLAndOps;
654 SmallDenseMap<std::pair<Value, Value>, Value> createdLTLImplicationOps;
657 SmallDenseMap<std::pair<LTLClockIntrinsicOp, Value>, LTLClockIntrinsicOp>
662void WhenOpVisitor::process(Block &block) {
663 for (
auto &op : llvm::make_early_inc_range(block)) {
664 dispatchVisitor(&op);
668void WhenOpVisitor::visitStmt(PrintFOp op) {
669 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
672void WhenOpVisitor::visitStmt(FPrintFOp op) {
673 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
676void WhenOpVisitor::visitStmt(FFlushOp op) {
677 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
680void WhenOpVisitor::visitStmt(StopOp op) {
681 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
684void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
685 op.getPropertyMutable().assign(
686 ltlImplicationWithCondition(op, op.getProperty()));
689void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
690 op.getPropertyMutable().assign(
691 ltlImplicationWithCondition(op, op.getProperty()));
694void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
695 op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
698void WhenOpVisitor::visitStmt(AssertOp op) {
699 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
702void WhenOpVisitor::visitStmt(AssumeOp op) {
703 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
706void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
707 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
710void WhenOpVisitor::visitStmt(CoverOp op) {
711 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
714void WhenOpVisitor::visitStmt(WhenOp whenOp) {
715 processWhenOp(whenOp, condition);
719void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
725 llvm::SaveAndRestore savedLTLAndOps(createdLTLAndOps);
726 llvm::SaveAndRestore savedLTLImplicationOps(createdLTLImplicationOps);
727 llvm::SaveAndRestore savedLTLClockOps(createdLTLClockOps);
729 process(*layerBlockOp.getBody());
732void WhenOpVisitor::visitStmt(RefForceOp op) {
733 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
736void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
737 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
740void WhenOpVisitor::visitStmt(RefReleaseOp op) {
741 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
744void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
745 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
748void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
750 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
752 op.getEnableMutable().assign(condition);
760template <
typename ConcreteT>
761void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
762 Value outerCondition) {
764 auto loc = whenOp.getLoc();
765 Block *parentBlock = whenOp->getBlock();
766 auto condition = whenOp.getCondition();
767 auto ui1Type = condition.getType();
775 Value thenCondition = whenOp.getCondition();
778 b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
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();
788 if (whenOp.hasElseRegion()) {
791 b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
794 elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
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();
803 mergeScopes(loc, thenScope, elseScope, condition);
815class ModuleVisitor :
public LastConnectResolver<ModuleVisitor> {
817 ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
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);
827 bool run(FModuleLike op);
828 LogicalResult checkInitialization();
835 bool anythingChanged =
false;
842bool ModuleVisitor::run(FModuleLike op) {
844 if (!isa<FModuleOp, ClassOp>(op))
845 return anythingChanged;
847 for (
auto ®ion : op->getRegions()) {
848 for (
auto &block : region.getBlocks()) {
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);
857 for (
auto &op : llvm::make_early_inc_range(block))
858 dispatchVisitor(&op);
862 return anythingChanged;
865void ModuleVisitor::visitStmt(ConnectOp op) {
869void ModuleVisitor::visitStmt(MatchingConnectOp op) {
873void ModuleVisitor::visitStmt(WhenOp whenOp) {
875 anythingChanged =
true;
876 processWhenOp(whenOp, {});
879void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
880 for (
auto &op : llvm::make_early_inc_range(*layerBlockOp.getBody())) {
881 dispatchVisitor(&op);
887LogicalResult ModuleVisitor::checkInitialization() {
891 auto *
connect = std::get<1>(destAndConnect);
896 FieldRef dest = std::get<0>(destAndConnect);
897 auto loc = dest.
getValue().getLoc();
899 if (
auto mod = dyn_cast<FModuleLike>(definingOp))
900 mlir::emitError(loc) <<
"port \"" <<
getFieldName(dest).first
901 <<
"\" not fully initialized in \""
902 << mod.getModuleName() <<
"\"";
906 <<
"\" not fully initialized in \""
907 << definingOp->getParentOfType<FModuleLike>().getModuleName() <<
"\"";
921 :
public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
922 void runOnOperation()
override;
926void ExpandWhensPass::runOnOperation() {
927 ModuleVisitor visitor;
928 if (!visitor.run(getOperation()))
929 markAllAnalysesPreserved();
930 if (failed(visitor.checkInitialization()))
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'.
This class represents a reference to a specific field or element of an aggregate value.
Value getValue() const
Get the Value which created this location.
Operation * getDefiningOp() const
Get the operation which defines this field.
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)
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)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
bool operator!=(const Iterator &rhs) const
bool operator==(const Iterator &rhs) const
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 find(const KeyT &key)
typename llvm::SmallVector< ScopeT, 3 > StackT
ValueT & operator[](const KeyT &key)