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"
35using namespace firrtl;
39static void mergeBlock(Block &destination, Block::iterator insertPoint,
41 destination.getOperations().splice(insertPoint, source.getOperations());
49template <
typename KeyT,
typename ValueT>
52 using StackT =
typename llvm::SmallVector<ScopeT, 3>;
56 typename ScopeT::iterator
scopeIt)
94 for (
auto i =
mapStack.size(); i > 0; --i) {
96 auto it = map.find(key);
131template <
typename ConcreteT>
140 LastConnectResolver(
ScopedDriverMap &driverMap) : driverMap(driverMap) {}
151 bool recordConnect(
FieldRef dest, Operation *connection) {
153 auto itAndInserted = driverMap.getLastScope().insert({dest, connection});
154 if (isStaticSingleConnect(connection)) {
156 assert(itAndInserted.second || !itAndInserted.first->second);
157 if (!itAndInserted.second)
158 itAndInserted.first->second = connection;
161 assert(isLastConnect(connection));
162 if (!std::get<1>(itAndInserted)) {
163 auto iterator = std::get<0>(itAndInserted);
164 auto changed =
false;
167 if (
auto *oldConnect = iterator->second) {
171 iterator->second = connection;
179 static Value getDestinationValue(Operation *op) {
180 return cast<FConnectLike>(op).getDest();
185 static Value getConnectedValue(Operation *op) {
186 return cast<FConnectLike>(op).getSrc();
190 static bool isStaticSingleConnect(Operation *op) {
191 return cast<FConnectLike>(op).hasStaticSingleConnectBehavior();
198 static bool isLastConnect(Operation *op) {
199 return cast<FConnectLike>(op).hasLastConnectBehavior();
204 void declareSinks(Value value,
Flow flow,
bool local =
false) {
205 auto type = value.getType();
209 std::function<void(Type,
Flow,
bool)> declare = [&](Type type,
Flow flow,
213 if (type_isa<DomainType>(type))
217 if (
auto classType = type_dyn_cast<ClassType>(type)) {
221 for (
auto &element : classType.getElements()) {
223 if (element.direction == Direction::Out)
224 declare(element.type, flow,
false);
226 declare(element.type,
swapFlow(flow),
false);
231 if (flow != Flow::Source)
232 driverMap[{value,
id}] =
nullptr;
235 id += classType.getMaxFieldID();
241 if (
auto bundleType = type_dyn_cast<BundleType>(type)) {
242 for (
auto &element : bundleType.getElements()) {
245 declare(element.type,
swapFlow(flow),
false);
247 declare(element.type, flow,
false);
253 if (
auto vectorType = type_dyn_cast<FVectorType>(type)) {
254 for (
unsigned i = 0; i < vectorType.getNumElements(); ++i) {
256 declare(vectorType.getElementType(), flow,
false);
262 if (
auto analogType = type_dyn_cast<AnalogType>(type))
267 if (flow != Flow::Source)
268 driverMap[{value,
id}] =
nullptr;
271 declare(type, flow, local);
276 ConnectOp flattenConditionalConnections(OpBuilder &b, Location loc,
277 Value dest, Value cond,
278 Operation *whenTrueConn,
279 Operation *whenFalseConn) {
280 assert(isLastConnect(whenTrueConn) && isLastConnect(whenFalseConn));
282 b.getFusedLoc({loc, whenTrueConn->getLoc(), whenFalseConn->getLoc()});
283 auto whenTrue = getConnectedValue(whenTrueConn);
285 isa_and_nonnull<InvalidValueOp>(whenTrue.getDefiningOp());
286 auto whenFalse = getConnectedValue(whenFalseConn);
287 auto falseIsInvalid =
288 isa_and_nonnull<InvalidValueOp>(whenFalse.getDefiningOp());
295 Value newValue = whenTrue;
296 if (trueIsInvalid == falseIsInvalid)
297 newValue = b.createOrFold<MuxPrimOp>(fusedLoc, cond, whenTrue, whenFalse);
298 else if (trueIsInvalid)
299 newValue = whenFalse;
300 return ConnectOp::create(b, loc, dest, newValue);
303 void visitDecl(WireOp op) { declareSinks(op.getResult(), Flow::Duplex); }
307 void foreachSubelement(OpBuilder &builder, Value value,
308 llvm::function_ref<
void(Value)> fn) {
310 .template Case<BundleType>([&](BundleType bundle) {
311 for (
auto i : llvm::seq(0u, (
unsigned)bundle.getNumElements())) {
313 SubfieldOp::create(builder, value.getLoc(), value, i);
314 foreachSubelement(builder, subfield, fn);
317 .
template Case<FVectorType>([&](FVectorType vector) {
318 for (
auto i : llvm::seq((
size_t)0, vector.getNumElements())) {
320 SubindexOp::create(builder, value.getLoc(), value, i);
321 foreachSubelement(builder, subindex, fn);
324 .Default([&](
auto) { fn(value); });
327 void visitDecl(RegOp op) {
330 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
331 auto fn = [&](Value value) {
332 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
335 foreachSubelement(builder, op.getResult(), fn);
338 void visitDecl(RegResetOp op) {
341 auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op));
342 auto fn = [&](Value value) {
343 auto connect = ConnectOp::create(builder, value.getLoc(), value, value);
346 foreachSubelement(builder, op.getResult(), fn);
349 template <
typename OpTy>
350 void visitInstanceDecl(OpTy op) {
353 for (
const auto &result : llvm::enumerate(op.getResults()))
357 declareSinks(result.value(), Flow::Sink);
360 void visitDecl(InstanceOp op) { visitInstanceDecl(op); }
361 void visitDecl(InstanceChoiceOp op) { visitInstanceDecl(op); }
363 void visitDecl(ObjectOp op) {
364 declareSinks(op, Flow::Source,
true);
367 void visitDecl(MemOp op) {
369 for (
auto result : op.getResults())
370 if (!isa<RefType>(result.getType()))
371 declareSinks(result, Flow::Sink);
374 void visitStmt(ConnectOp op) {
378 void visitStmt(MatchingConnectOp op) {
382 void visitStmt(RefDefineOp op) {
386 void visitStmt(PropAssignOp op) {
390 void visitStmt(DomainDefineOp op) {
394 void processWhenOp(WhenOp whenOp, Value outerCondition);
413 Value thenCondition) {
416 for (
auto &destAndConnect : thenScope) {
417 auto dest = std::get<0>(destAndConnect);
418 auto thenConnect = std::get<1>(destAndConnect);
420 auto outerIt = driverMap.find(dest);
421 if (outerIt == driverMap.end()) {
424 driverMap[dest] = thenConnect;
428 auto elseIt = elseScope.find(dest);
429 if (elseIt != elseScope.end()) {
434 auto &elseConnect = std::get<1>(*elseIt);
435 OpBuilder connectBuilder(elseConnect);
436 auto newConnect = flattenConditionalConnections(
437 connectBuilder, loc, getDestinationValue(thenConnect),
438 thenCondition, thenConnect, elseConnect);
441 thenConnect->erase();
442 elseConnect->erase();
443 recordConnect(dest, newConnect);
448 auto &outerConnect = std::get<1>(*outerIt);
450 if (isLastConnect(thenConnect)) {
453 thenConnect->erase();
455 assert(isStaticSingleConnect(thenConnect));
456 driverMap[dest] = thenConnect;
463 OpBuilder connectBuilder(thenConnect);
464 auto newConnect = flattenConditionalConnections(
465 connectBuilder, loc, getDestinationValue(thenConnect), thenCondition,
466 thenConnect, outerConnect);
469 thenConnect->erase();
470 recordConnect(dest, newConnect);
474 for (
auto &destAndConnect : elseScope) {
475 auto dest = std::get<0>(destAndConnect);
476 auto elseConnect = std::get<1>(destAndConnect);
480 if (thenScope.contains(dest))
483 auto outerIt = driverMap.find(dest);
484 if (outerIt == driverMap.end()) {
487 driverMap[dest] = elseConnect;
491 auto &outerConnect = std::get<1>(*outerIt);
493 if (isLastConnect(elseConnect)) {
496 elseConnect->erase();
498 assert(isStaticSingleConnect(elseConnect));
499 driverMap[dest] = elseConnect;
506 OpBuilder connectBuilder(elseConnect);
507 auto newConnect = flattenConditionalConnections(
508 connectBuilder, loc, getDestinationValue(outerConnect), thenCondition,
509 outerConnect, elseConnect);
512 elseConnect->erase();
513 recordConnect(dest, newConnect);
527class WhenOpVisitor :
public LastConnectResolver<WhenOpVisitor> {
531 : LastConnectResolver<WhenOpVisitor>(driverMap), condition(condition) {}
533 using LastConnectResolver<WhenOpVisitor>::visitExpr;
534 using LastConnectResolver<WhenOpVisitor>::visitDecl;
535 using LastConnectResolver<WhenOpVisitor>::visitStmt;
536 using LastConnectResolver<WhenOpVisitor>::visitStmtExpr;
539 void process(Block &block);
542 void visitStmt(VerifAssertIntrinsicOp op);
543 void visitStmt(VerifAssumeIntrinsicOp op);
544 void visitStmt(VerifCoverIntrinsicOp op);
545 void visitStmt(AssertOp op);
546 void visitStmt(AssumeOp op);
547 void visitStmt(UnclockedAssumeIntrinsicOp op);
548 void visitStmt(CoverOp op);
549 void visitStmt(ModuleOp op);
550 void visitStmt(PrintFOp op);
551 void visitStmt(FPrintFOp op);
552 void visitStmt(FFlushOp op);
553 void visitStmt(StopOp op);
554 void visitStmt(WhenOp op);
555 void visitStmt(LayerBlockOp op);
556 void visitStmt(RefForceOp op);
557 void visitStmt(RefForceInitialOp op);
558 void visitStmt(RefReleaseOp op);
559 void visitStmt(RefReleaseInitialOp op);
560 void visitStmtExpr(DPICallIntrinsicOp op);
565 Value andWithCondition(Operation *op, Value value) {
567 return OpBuilder(op).createOrFold<AndPrimOp>(
568 condition.getLoc(), condition.getType(), condition, value);
573 Value ltlAndWithCondition(Operation *op, Value property) {
575 while (
auto nodeOp = property.getDefiningOp<NodeOp>())
576 property = nodeOp.getInput();
579 if (
auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
580 auto input = ltlAndWithCondition(op, clockOp.getInput());
581 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
583 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
584 newClockOp.getInputMutable().assign(input);
590 auto &newOp = createdLTLAndOps[{condition,
property}];
592 newOp = OpBuilder(op).createOrFold<LTLAndIntrinsicOp>(
593 condition.getLoc(),
property.getType(), condition, property);
600 Value ltlImplicationWithCondition(Operation *op, Value property) {
602 while (
auto nodeOp = property.getDefiningOp<NodeOp>())
603 property = nodeOp.getInput();
606 if (
auto clockOp = property.getDefiningOp<LTLClockIntrinsicOp>()) {
607 auto input = ltlImplicationWithCondition(op, clockOp.getInput());
608 auto &newClockOp = createdLTLClockOps[{clockOp, input}];
610 newClockOp = OpBuilder(op).cloneWithoutRegions(clockOp);
611 newClockOp.getInputMutable().assign(input);
617 if (
auto implOp = property.getDefiningOp<LTLImplicationIntrinsicOp>()) {
618 auto lhs = ltlAndWithCondition(op, implOp.getLhs());
619 auto &newImplOp = createdLTLImplicationOps[{lhs, implOp.getRhs()}];
621 auto clonedOp = OpBuilder(op).cloneWithoutRegions(implOp);
622 clonedOp.getLhsMutable().assign(lhs);
623 newImplOp = clonedOp;
629 auto &newImplOp = createdLTLImplicationOps[{condition,
property}];
631 newImplOp = OpBuilder(op).createOrFold<LTLImplicationIntrinsicOp>(
632 condition.getLoc(),
property.getType(), condition, property);
652void WhenOpVisitor::process(Block &block) {
653 for (
auto &op :
llvm::make_early_inc_range(block)) {
654 dispatchVisitor(&op);
658void WhenOpVisitor::visitStmt(PrintFOp op) {
659 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
662void WhenOpVisitor::visitStmt(FPrintFOp op) {
663 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
666void WhenOpVisitor::visitStmt(FFlushOp op) {
667 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
670void WhenOpVisitor::visitStmt(StopOp op) {
671 op.getCondMutable().assign(andWithCondition(op, op.getCond()));
674void WhenOpVisitor::visitStmt(VerifAssertIntrinsicOp op) {
675 op.getPropertyMutable().assign(
676 ltlImplicationWithCondition(op, op.getProperty()));
679void WhenOpVisitor::visitStmt(VerifAssumeIntrinsicOp op) {
680 op.getPropertyMutable().assign(
681 ltlImplicationWithCondition(op, op.getProperty()));
684void WhenOpVisitor::visitStmt(VerifCoverIntrinsicOp op) {
685 op.getPropertyMutable().assign(ltlAndWithCondition(op, op.getProperty()));
688void WhenOpVisitor::visitStmt(AssertOp op) {
689 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
692void WhenOpVisitor::visitStmt(AssumeOp op) {
693 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
696void WhenOpVisitor::visitStmt(UnclockedAssumeIntrinsicOp op) {
697 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
700void WhenOpVisitor::visitStmt(CoverOp op) {
701 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
704void WhenOpVisitor::visitStmt(WhenOp whenOp) {
705 processWhenOp(whenOp, condition);
709void WhenOpVisitor::visitStmt(LayerBlockOp layerBlockOp) {
715 llvm::SaveAndRestore savedLTLAndOps(createdLTLAndOps);
716 llvm::SaveAndRestore savedLTLImplicationOps(createdLTLImplicationOps);
717 llvm::SaveAndRestore savedLTLClockOps(createdLTLClockOps);
719 process(*layerBlockOp.getBody());
722void WhenOpVisitor::visitStmt(RefForceOp op) {
723 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
726void WhenOpVisitor::visitStmt(RefForceInitialOp op) {
727 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
730void WhenOpVisitor::visitStmt(RefReleaseOp op) {
731 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
734void WhenOpVisitor::visitStmt(RefReleaseInitialOp op) {
735 op.getPredicateMutable().assign(andWithCondition(op, op.getPredicate()));
738void WhenOpVisitor::visitStmtExpr(DPICallIntrinsicOp op) {
740 op.getEnableMutable().assign(andWithCondition(op, op.getEnable()));
742 op.getEnableMutable().assign(condition);
750template <
typename ConcreteT>
751void LastConnectResolver<ConcreteT>::processWhenOp(WhenOp whenOp,
752 Value outerCondition) {
754 auto loc = whenOp.getLoc();
755 Block *parentBlock = whenOp->getBlock();
756 auto condition = whenOp.getCondition();
757 auto ui1Type = condition.getType();
765 Value thenCondition = whenOp.getCondition();
768 b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition, thenCondition);
770 auto &thenBlock = whenOp.getThenBlock();
771 driverMap.pushScope();
772 WhenOpVisitor(driverMap, thenCondition).process(thenBlock);
773 mergeBlock(*parentBlock, Block::iterator(whenOp), thenBlock);
774 auto thenScope = driverMap.popScope();
778 if (whenOp.hasElseRegion()) {
781 b.createOrFold<NotPrimOp>(loc, condition.getType(), condition);
784 elseCondition = b.createOrFold<AndPrimOp>(loc, ui1Type, outerCondition,
786 auto &elseBlock = whenOp.getElseBlock();
787 driverMap.pushScope();
788 WhenOpVisitor(driverMap, elseCondition).process(elseBlock);
789 mergeBlock(*parentBlock, Block::iterator(whenOp), elseBlock);
790 elseScope = driverMap.popScope();
793 mergeScopes(loc, thenScope, elseScope, condition);
805class ModuleVisitor :
public LastConnectResolver<ModuleVisitor> {
807 ModuleVisitor() : LastConnectResolver<ModuleVisitor>(driverMap) {}
809 using LastConnectResolver<ModuleVisitor>::visitExpr;
810 using LastConnectResolver<ModuleVisitor>::visitDecl;
811 using LastConnectResolver<ModuleVisitor>::visitStmt;
812 void visitStmt(WhenOp whenOp);
813 void visitStmt(ConnectOp connectOp);
814 void visitStmt(MatchingConnectOp connectOp);
815 void visitStmt(LayerBlockOp layerBlockOp);
817 bool run(FModuleLike op);
818 LogicalResult checkInitialization();
825 bool anythingChanged =
false;
832bool ModuleVisitor::run(FModuleLike op) {
834 if (!isa<FModuleOp, ClassOp>(op))
835 return anythingChanged;
837 for (
auto ®ion : op->getRegions()) {
838 for (
auto &block : region.getBlocks()) {
840 for (
const auto &[index, value] :
llvm::enumerate(block.getArguments())) {
841 auto direction = op.getPortDirection(index);
842 auto flow = direction == Direction::In ? Flow::Source : Flow::Sink;
843 declareSinks(value, flow);
847 for (
auto &op :
llvm::make_early_inc_range(block))
848 dispatchVisitor(&op);
852 return anythingChanged;
855void ModuleVisitor::visitStmt(ConnectOp op) {
859void ModuleVisitor::visitStmt(MatchingConnectOp op) {
863void ModuleVisitor::visitStmt(WhenOp whenOp) {
865 anythingChanged =
true;
866 processWhenOp(whenOp, {});
869void ModuleVisitor::visitStmt(LayerBlockOp layerBlockOp) {
870 for (
auto &op :
llvm::make_early_inc_range(*layerBlockOp.getBody())) {
871 dispatchVisitor(&op);
877LogicalResult ModuleVisitor::checkInitialization() {
879 for (
auto destAndConnect : driverMap.getLastScope()) {
881 auto *
connect = std::get<1>(destAndConnect);
886 FieldRef dest = std::get<0>(destAndConnect);
887 auto loc = dest.
getValue().getLoc();
889 if (
auto mod = dyn_cast<FModuleLike>(definingOp))
890 mlir::emitError(loc) <<
"port \"" <<
getFieldName(dest).first
891 <<
"\" not fully initialized in \""
892 << mod.getModuleName() <<
"\"";
896 <<
"\" not fully initialized in \""
897 << definingOp->getParentOfType<FModuleLike>().getModuleName() <<
"\"";
911 :
public circt::firrtl::impl::ExpandWhensBase<ExpandWhensPass> {
912 void runOnOperation()
override;
916void ExpandWhensPass::runOnOperation() {
917 ModuleVisitor visitor;
918 if (!visitor.run(getOperation()))
919 markAllAnalysesPreserved();
920 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.
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.
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.
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)